From fbf0d5f6829f0989781d1eea94c1310e3897c0d2 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 7 Nov 2026 17:35:54 +0000 Subject: [PATCH 1/3] zend_test: add missing opcache enable INI setting --- ext/zend_test/tests/optimizer_register_pass.phpt | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/zend_test/tests/optimizer_register_pass.phpt b/ext/zend_test/tests/optimizer_register_pass.phpt index 50499344c470..907ec7f7f2df 100644 --- a/ext/zend_test/tests/optimizer_register_pass.phpt +++ b/ext/zend_test/tests/optimizer_register_pass.phpt @@ -13,6 +13,7 @@ if (getenv("SKIP_PRELOAD")) { } ?> --INI-- +opcache.enable=1 opcache.enable_cli=1 opcache.file_cache= opcache.file_cache_only=0 From 7bdec34bb8c0e81c480304609c7857c8f7207127 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 7 Nov 2026 17:36:35 +0000 Subject: [PATCH 2/3] zend_test: add additional tests with zend_object_init_with_constructor() --- ...nstructor_classes_without_constructor.phpt | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/ext/zend_test/tests/zend_object_init_with_constructor_classes_without_constructor.phpt b/ext/zend_test/tests/zend_object_init_with_constructor_classes_without_constructor.phpt index 1adaaafaa195..9d113fa6084f 100644 --- a/ext/zend_test/tests/zend_object_init_with_constructor_classes_without_constructor.phpt +++ b/ext/zend_test/tests/zend_object_init_with_constructor_classes_without_constructor.phpt @@ -98,6 +98,83 @@ try { echo $e::class, ': ', $e->getMessage(), PHP_EOL; } +echo "\n#### Splatting arrays with string key ####\n"; +$a = ['unknown' => "hello"]; +echo "Userland class:\n"; +echo "Using new:\n"; +try { + $o = new TestUserWithoutConstructor(...$a); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +echo "Using zend_object_init_with_constructor():\n"; +try { + $o = zend_object_init_with_constructor("TestUserWithoutConstructor", ...$a); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +echo "Internal class:\n"; +echo "Using new:\n"; +try { + $o = new _ZendTestMagicCall(...$a); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +echo "Using zend_object_init_with_constructor():\n"; +try { + $o = zend_object_init_with_constructor("_ZendTestMagicCall", ...$a); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + + +echo "\n#### Splatting arrays with int key ####\n"; +$a = [1 => "hello"]; +echo "Userland class:\n"; +echo "Using new:\n"; +try { + $o = new TestUserWithoutConstructor(...$a); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +echo "Using zend_object_init_with_constructor():\n"; +try { + $o = zend_object_init_with_constructor("TestUserWithoutConstructor", ...$a); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +echo "Internal class:\n"; +echo "Using new:\n"; +try { + $o = new _ZendTestMagicCall(...$a); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +echo "Using zend_object_init_with_constructor():\n"; +try { + $o = zend_object_init_with_constructor("_ZendTestMagicCall", ...$a); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + ?> --EXPECT-- #### Passing no args #### @@ -147,3 +224,33 @@ Using new: Error: Unknown named parameter $unknown_param Using zend_object_init_with_constructor(): Error: Unknown named parameter $unknown_param + +#### Splatting arrays with string key #### +Userland class: +Using new: +Error: Unknown named parameter $unknown +Using zend_object_init_with_constructor(): +Error: Unknown named parameter $unknown +Internal class: +Using new: +Error: Unknown named parameter $unknown +Using zend_object_init_with_constructor(): +Error: Unknown named parameter $unknown + +#### Splatting arrays with int key #### +Userland class: +Using new: +object(TestUserWithoutConstructor)#2 (0) { +} +Destructor for TestUserWithoutConstructor +Using zend_object_init_with_constructor(): +object(TestUserWithoutConstructor)#2 (0) { +} +Destructor for TestUserWithoutConstructor +Internal class: +Using new: +object(_ZendTestMagicCall)#2 (0) { +} +Using zend_object_init_with_constructor(): +object(_ZendTestMagicCall)#2 (0) { +} From 6b4ded7514e3944e9a2eb85cb1a326e82db46ff1 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 7 Nov 2026 18:06:12 +0000 Subject: [PATCH 3/3] Zend: fix object_init_with_constructor() with positional args in named_params --- Zend/zend_API.c | 26 +-- ext/zend_test/test.c | 22 +++ ext/zend_test/test.stub.php | 1 + ext/zend_test/test_arginfo.h | 9 +- .../zend_object_init_with_constructor.phpt | 1 - ...nd_object_init_with_constructor_array.phpt | 152 ++++++++++++++++++ 6 files changed, 197 insertions(+), 14 deletions(-) create mode 100644 ext/zend_test/tests/zend_object_init_with_constructor_array.phpt diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 8b7fa4c73b34..d8ea2c5ed553 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1890,19 +1890,21 @@ ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *c * However, if there are named arguments (and it is not empty), * an Error must be thrown to be consistent with new ClassName() */ if (UNEXPECTED(named_params != NULL && zend_hash_num_elements(named_params) != 0)) { - /* Throw standard Error */ - zend_string *arg_name = NULL; - zend_hash_get_current_key(named_params, &arg_name, /* num_index */ NULL); - ZEND_ASSERT(arg_name != NULL); - zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(arg_name)); - /* Do not call destructor, free object, and set arg to IS_UNDEF */ - zend_object_store_ctor_failed(obj); - zval_ptr_dtor(arg); - ZVAL_UNDEF(arg); - return FAILURE; - } else { - return SUCCESS; + /* As named_arguments may contain positional args we need to check there is actually a string key */ + ZEND_HASH_FOREACH_STR_KEY(named_params, zend_string *arg_name) { + if (arg_name == NULL) { + continue; + } + /* Throw standard Error */ + zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(arg_name)); + /* Do not call destructor, free object, and set arg to IS_UNDEF */ + zend_object_store_ctor_failed(obj); + zval_ptr_dtor(arg); + ZVAL_UNDEF(arg); + return FAILURE; + } ZEND_HASH_FOREACH_END(); } + return SUCCESS; } /* A constructor should not return a value, however if an exception is thrown * zend_call_known_function() will set the retval to IS_UNDEF */ diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 854dd2979f75..4bd96976a965 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -566,6 +566,28 @@ static ZEND_FUNCTION(zend_object_init_with_constructor) ZVAL_COPY_VALUE(return_value, &obj); } + +/* Instantiate a class and run the constructor via object_init_with_constructor */ +static ZEND_FUNCTION(zend_object_init_with_constructor_array) +{ + zend_class_entry *ce = NULL; + HashTable *args; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_CLASS(ce) + Z_PARAM_ARRAY_HT(args) + ZEND_PARSE_PARAMETERS_END(); + + zval obj; + /* We don't use return_value directly to check for memory leaks of the API on failure */ + zend_result status = object_init_with_constructor(&obj, ce, 0, NULL, args); + if (status == FAILURE) { + RETURN_THROWS(); + } + ZEND_ASSERT(!EG(exception)); + ZVAL_COPY_VALUE(return_value, &obj); +} + static ZEND_FUNCTION(zend_get_unit_enum) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 36d1be17a177..785390dd70cc 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -287,6 +287,7 @@ function zend_get_current_func_name(): string {} function zend_call_method(object|string $obj_or_class, string $method, mixed $arg1 = UNKNOWN, mixed $arg2 = UNKNOWN): mixed {} function zend_object_init_with_constructor(string $class, mixed ...$args): mixed {} + function zend_object_init_with_constructor_array(string $class, array $args): mixed {} function zend_test_zend_ini_parse_quantity(string $str): int {} function zend_test_zend_ini_parse_uquantity(string $str): int {} diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index e2a388659ecd..45545edf5db8 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: f25b9c451734f903ba385f9fbf05fb198ecd83af */ + * Stub hash: 20856900222b20ff71d57bf575ad7034ffc34bf4 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -119,6 +119,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_object_init_with_constructo ZEND_ARG_VARIADIC_TYPE_INFO(0, args, IS_MIXED, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_object_init_with_constructor_array, 0, 2, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, class, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, args, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_zend_ini_parse_quantity, 0, 1, IS_LONG, 0) ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -319,6 +324,7 @@ static ZEND_FUNCTION(zend_test_attribute_with_named_argument); static ZEND_FUNCTION(zend_get_current_func_name); static ZEND_FUNCTION(zend_call_method); static ZEND_FUNCTION(zend_object_init_with_constructor); +static ZEND_FUNCTION(zend_object_init_with_constructor_array); static ZEND_FUNCTION(zend_test_zend_ini_parse_quantity); static ZEND_FUNCTION(zend_test_zend_ini_parse_uquantity); static ZEND_FUNCTION(zend_test_zend_ini_str); @@ -434,6 +440,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_get_current_func_name, arginfo_zend_get_current_func_name) ZEND_FE(zend_call_method, arginfo_zend_call_method) ZEND_FE(zend_object_init_with_constructor, arginfo_zend_object_init_with_constructor) + ZEND_FE(zend_object_init_with_constructor_array, arginfo_zend_object_init_with_constructor_array) ZEND_FE(zend_test_zend_ini_parse_quantity, arginfo_zend_test_zend_ini_parse_quantity) ZEND_FE(zend_test_zend_ini_parse_uquantity, arginfo_zend_test_zend_ini_parse_uquantity) ZEND_FE(zend_test_zend_ini_str, arginfo_zend_test_zend_ini_str) diff --git a/ext/zend_test/tests/zend_object_init_with_constructor.phpt b/ext/zend_test/tests/zend_object_init_with_constructor.phpt index 65b111447f0b..1f1402180bce 100644 --- a/ext/zend_test/tests/zend_object_init_with_constructor.phpt +++ b/ext/zend_test/tests/zend_object_init_with_constructor.phpt @@ -2,7 +2,6 @@ Zend: Test object_init_with_constructor() API --EXTENSIONS-- zend_test -sysvmsg --FILE-- 'str']; +$argsNamedExtraUnknown = [5, 'string_param' => 'str', 'unknown' => 'unknown']; + +echo "Testing param passing\n"; +echo "Test TestUserWithConstructorArgs:\n"; +$o = zend_object_init_with_constructor_array("TestUserWithConstructorArgs", $args); +var_dump($o); +unset($o); + +$o = zend_object_init_with_constructor_array("TestUserWithConstructorArgs", $argsNamed); +var_dump($o); +unset($o); + +try { + $o = zend_object_init_with_constructor_array("TestUserWithConstructorArgs", $argsNamedExtraUnknown); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +echo "Test TestUserWithConstructorNoParams:\n"; +try { + $o = zend_object_init_with_constructor_array("TestUserWithConstructorNoParams", $args); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + $o = zend_object_init_with_constructor_array("TestUserWithConstructorNoParams", $argsNamed); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + $o = zend_object_init_with_constructor_array("TestUserWithConstructorNoParams", $argsNamedExtraUnknown); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +echo "Test TestUserWithoutConstructor:\n"; +$o = zend_object_init_with_constructor_array("TestUserWithoutConstructor", $args); +var_dump($o); +unset($o); + +try { + $o = zend_object_init_with_constructor_array("TestUserWithoutConstructor", $argsNamed); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + $o = zend_object_init_with_constructor_array("TestUserWithoutConstructor", $argsNamedExtraUnknown); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +echo "Test _ZendTestMagicCall (internal class no ctor):\n"; +try { + $o = zend_object_init_with_constructor_array("_ZendTestMagicCall", $args); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + $o = zend_object_init_with_constructor_array("_ZendTestMagicCall", $argsNamed); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +try { + $o = zend_object_init_with_constructor_array("_ZendTestMagicCall", $argsNamedExtraUnknown); + var_dump($o); + unset($o); +} catch (\Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Testing param passing +Test TestUserWithConstructorArgs: +object(TestUserWithConstructorArgs)#1 (0) { +} +Destructor for TestUserWithConstructorArgs +object(TestUserWithConstructorArgs)#1 (0) { +} +Destructor for TestUserWithConstructorArgs +Error: Unknown named parameter $unknown +Test TestUserWithConstructorNoParams: +object(TestUserWithConstructorNoParams)#1 (0) { +} +Destructor for TestUserWithConstructorNoParams +Error: Unknown named parameter $string_param +Error: Unknown named parameter $string_param +Test TestUserWithoutConstructor: +object(TestUserWithoutConstructor)#3 (0) { +} +Destructor for TestUserWithoutConstructor +Error: Unknown named parameter $string_param +Error: Unknown named parameter $string_param +Test _ZendTestMagicCall (internal class no ctor): +object(_ZendTestMagicCall)#2 (0) { +} +Error: Unknown named parameter $string_param +Error: Unknown named parameter $string_param