Commit c0e7d498c54 for php.net

commit c0e7d498c543c11329615b2c71d18556825c46e2
Author: David Carlier <devnexen@gmail.com>
Date:   Sat Aug 22 14:46:28 2026 +0100

    ext/zlib: deflate_init() assertion failure on uninitialised typed properties

    Fix GH-22142

    An object passed as options exposes uninitialised typed properties as
    IS_UNDEF slots, which zval_try_get_long() rejected through an unreachable
    branch. Those slots are now skipped, matching get_object_vars().

    Close GH-23409

diff --git a/NEWS b/NEWS
index 887ff5b3401..92e4b175d59 100644
--- a/NEWS
+++ b/NEWS
@@ -83,6 +83,10 @@ PHP                                                                        NEWS
   . Fixed bug GH-17787 (ZipArchive stream stops reading early when the archive
     is freed while the stream is still open). (Eyüp Can Akman)

+- Zlib:
+  . Fixed bug GH-22142 (Assertion failure in deflate_init() when an option
+    object has uninitialised typed properties). (David Carlier)
+
 13 Aug 2026, PHP 8.6.0beta1

 - Core:
diff --git a/ext/zlib/tests/gh22142.phpt b/ext/zlib/tests/gh22142.phpt
new file mode 100644
index 00000000000..d41fab7e331
--- /dev/null
+++ b/ext/zlib/tests/gh22142.phpt
@@ -0,0 +1,50 @@
+--TEST--
+GH-22142 (Assertion failure in zendi_try_get_long() on IS_UNDEF)
+--CREDITS--
+JIANG Yuancheng
+--EXTENSIONS--
+zlib
+--INI--
+error_reporting=E_ALL & ~E_DEPRECATED
+--FILE--
+<?php
+
+class DeflateOptions {
+    public int $level;
+    public int $memory;
+    public int $window;
+    public int $strategy;
+    public string $dictionary;
+}
+
+class InflateOptions {
+    public int $window;
+    public string $dictionary;
+}
+
+class BadDeflateOptions {
+    public int $level = 42;
+    public int $memory;
+}
+
+$deflate = new DeflateOptions();
+var_dump(deflate_init(ZLIB_ENCODING_DEFLATE, $deflate) instanceof DeflateContext);
+var_dump(deflate_init(ZLIB_ENCODING_DEFLATE, get_object_vars($deflate)) instanceof DeflateContext);
+
+$inflate = new InflateOptions();
+var_dump(inflate_init(ZLIB_ENCODING_DEFLATE, $inflate) instanceof InflateContext);
+var_dump(inflate_init(ZLIB_ENCODING_DEFLATE, get_object_vars($inflate)) instanceof InflateContext);
+
+try {
+    deflate_init(ZLIB_ENCODING_DEFLATE, new BadDeflateOptions());
+} catch (ValueError $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+ValueError: deflate_init(): "level" option must be between -1 and 9
diff --git a/ext/zlib/tests/gh22142_inflate.phpt b/ext/zlib/tests/gh22142_inflate.phpt
deleted file mode 100644
index 853099e4911..00000000000
--- a/ext/zlib/tests/gh22142_inflate.phpt
+++ /dev/null
@@ -1,21 +0,0 @@
---TEST--
-GH-22142 (Assertion failure in zendi_try_get_long() on IS_UNDEF)
---EXTENSIONS--
-zlib
---FILE--
-<?php
-
-class Options {
-    public int $window;
-}
-
-try {
-    inflate_init(ZLIB_ENCODING_DEFLATE, new Options());
-} catch (TypeError $e) {
-    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
-}
-
-?>
---EXPECTF--
-Deprecated: inflate_init(): Passing an object for argument #2 $option to inflate_init() is deprecated, call get_object_vars() first instead in %s on line %d
-TypeError: inflate_init(): Argument #2 ($options) the value for option "window" must be of type int, null given
diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c
index 44ab23233ac..f88350081fe 100644
--- a/ext/zlib/zlib.c
+++ b/ext/zlib/zlib.c
@@ -779,11 +779,26 @@ PHP_ZLIB_DECODE_FUNC(gzdecode, PHP_ZLIB_ENCODING_GZIP);
 PHP_ZLIB_DECODE_FUNC(gzuncompress, PHP_ZLIB_ENCODING_DEFLATE);
 /* }}} */

+ZEND_ATTRIBUTE_NONNULL static zval *zlib_find_option(HashTable *options, const char *name, size_t name_len)
+{
+	zval *option = zend_hash_str_find(options, name, name_len);
+
+	if (!option) {
+		return NULL;
+	}
+
+	ZVAL_DEINDIRECT(option);
+
+	if (UNEXPECTED(Z_TYPE_P(option) == IS_UNDEF)) {
+		return NULL;
+	}
+	return option;
+}
+
 static bool zlib_create_dictionary_string(HashTable *options, char **dict, size_t *dictlen) {
 	zval *option_buffer;

-	if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("dictionary"))) != NULL) {
-		ZVAL_DEINDIRECT(option_buffer);
+	if (options && (option_buffer = zlib_find_option(options, ZEND_STRL("dictionary"))) != NULL) {
 		ZVAL_DEREF(option_buffer);
 		switch (Z_TYPE_P(option_buffer)) {
 			case IS_STRING: {
@@ -853,14 +868,12 @@ static bool zlib_create_dictionary_string(HashTable *options, char **dict, size_
 ZEND_ATTRIBUTE_NONNULL static bool zlib_get_long_option(HashTable *options, const char *option_name, size_t option_name_len, zend_long *value)
 {
 	bool failed = false;
-	zval *option_buffer = zend_hash_str_find(options, option_name, option_name_len);
+	zval *option_buffer = zlib_find_option(options, option_name, option_name_len);

 	if (!option_buffer) {
 		return true;
 	}

-	/* The |H ZPP specifier may leave HashTable entries wrapped in IS_INDIRECT. */
-	ZVAL_DEINDIRECT(option_buffer);
 	*value = zval_try_get_long(option_buffer, &failed);
 	if (UNEXPECTED(failed)) {
 		zend_argument_type_error(