From b150eb35d46422d8bec833a34885b43acf488572 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 23 Oct 2025 21:30:17 +0200 Subject: [PATCH 1/2] Fix parent hook call with named args Fixes GH-20270 Closes GH-20271 --- NEWS | 1 + Zend/tests/property_hooks/gh20270.phpt | 51 ++++++++++++++++++++++++++ Zend/zend_object_handlers.c | 4 +- 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/property_hooks/gh20270.phpt diff --git a/NEWS b/NEWS index acadda0e1266d..f8f750ef6c96a 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ PHP NEWS . Fixed bug GH-19844 (Don't bail when closing resources on shutdown). (ilutov) . Fixed bug GH-20177 (Accessing overridden private property in get_object_vars() triggers assertion error). (ilutov) + . Fixed bug GH-20270 (Broken parent hook call with named arguments). (ilutov) - DOM: . Partially fixed bug GH-16317 (DOM classes do not allow diff --git a/Zend/tests/property_hooks/gh20270.phpt b/Zend/tests/property_hooks/gh20270.phpt new file mode 100644 index 0000000000000..173d21990e556 --- /dev/null +++ b/Zend/tests/property_hooks/gh20270.phpt @@ -0,0 +1,51 @@ +--TEST-- +GH-20270: Parent hook call with named arguments +--CREDITS-- +Viet Hoang Luu (@vi3tL0u1s) +--FILE-- + $custom; + } +} + +class B extends A { + public mixed $prop1 { + set { + parent::$prop1::set(value: 42); + parent::$prop1::set(unknown: 43); + } + } + public mixed $prop2 { + set { + parent::$prop2::set(custom: 42); + parent::$prop2::set(value: 43); + } + } +} + +$b = new B(); + +try { + $b->prop1 = 0; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($b->prop1); + +try { + $b->prop2 = 0; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} +var_dump($b->prop2); + +?> +--EXPECT-- +Unknown named parameter $unknown +int(42) +Unknown named parameter $value +int(42) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 67e8664f5a7ac..c113f65a7a894 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1783,7 +1783,9 @@ ZEND_API zend_function *zend_get_property_hook_trampoline( const zend_property_info *prop_info, zend_property_hook_kind kind, zend_string *prop_name) { - static const zend_arg_info arg_info[1] = {{0}}; + static const zend_internal_arg_info arg_info[2] = { + { .name = "value" } + }; zend_function *func; if (EXPECTED(EG(trampoline).common.function_name == NULL)) { func = &EG(trampoline); From 12920370e1806584a558799098075510a5ab3444 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 15 Oct 2025 21:17:43 +0200 Subject: [PATCH 2/2] Fix stale EG(opline_before_exception) pointer through eval Fixes GH-20183 Closes GH-20184 --- NEWS | 2 ++ Zend/tests/gh20183_001.phpt | 24 ++++++++++++++++++++++++ Zend/tests/gh20183_002.phpt | 34 ++++++++++++++++++++++++++++++++++ Zend/zend_generators.c | 14 +++++++++++--- Zend/zend_objects.c | 2 ++ 5 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/gh20183_001.phpt create mode 100644 Zend/tests/gh20183_002.phpt diff --git a/NEWS b/NEWS index 79b4fe2bc03b7..786a9d23fa365 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,8 @@ PHP NEWS . Fixed bug GH-19844 (Don't bail when closing resources on shutdown). (ilutov) . Fixed bug GH-20177 (Accessing overridden private property in get_object_vars() triggers assertion error). (ilutov) + . Fixed bug GH-20183 (Stale EG(opline_before_exception) pointer through eval). + (ilutov) - DOM: . Partially fixed bug GH-16317 (DOM classes do not allow diff --git a/Zend/tests/gh20183_001.phpt b/Zend/tests/gh20183_001.phpt new file mode 100644 index 0000000000000..9468bcaea5673 --- /dev/null +++ b/Zend/tests/gh20183_001.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-20183: Stale EG(opline_before_exception) pointer through eval +--CREDITS-- +Viet Hoang Luu (@vi3tL0u1s) +--FILE-- + +--EXPECTF-- +#0 %s(10): A->__destruct() + +Fatal error: Uncaught Error: Class "B" not found in %s:10 +Stack trace: +#0 {main} + thrown in %s on line 10 diff --git a/Zend/tests/gh20183_002.phpt b/Zend/tests/gh20183_002.phpt new file mode 100644 index 0000000000000..ec4d62d0960dc --- /dev/null +++ b/Zend/tests/gh20183_002.phpt @@ -0,0 +1,34 @@ +--TEST-- +GH-20183: Stale EG(opline_before_exception) pointer through eval +--CREDITS-- +Arnaud Le Blanc +--FILE-- +gen = gen(); + $this->gen->rewind(); + } +} + +B::$a = new A(); + +?> +--EXPECTF-- +#0 %s(20): gen() + +Fatal error: Uncaught Error: Class "B" not found in %s:20 +Stack trace: +#0 {main} + thrown in %s on line 20 diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index eeab16b9a1352..84b40cfdc21a3 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -317,9 +317,16 @@ static void zend_generator_dtor_storage(zend_object *object) /* {{{ */ ZEND_CALL_VAR(ex, ex->func->op_array.opcodes[try_catch->finally_end].op1.var); zend_generator_cleanup_unfinished_execution(generator, ex, try_catch->finally_op); - zend_object *old_exception = EG(exception); - const zend_op *old_opline_before_exception = EG(opline_before_exception); - EG(exception) = NULL; + + zend_object *old_exception = NULL; + const zend_op *old_opline_before_exception = NULL; + if (EG(exception)) { + EG(current_execute_data)->opline = EG(opline_before_exception); + old_exception = EG(exception); + old_opline_before_exception = EG(opline_before_exception); + EG(exception) = NULL; + } + Z_OBJ_P(fast_call) = NULL; Z_OPLINE_NUM_P(fast_call) = (uint32_t)-1; @@ -328,6 +335,7 @@ static void zend_generator_dtor_storage(zend_object *object) /* {{{ */ zend_generator_resume(generator); if (old_exception) { + EG(current_execute_data)->opline = EG(exception_op); EG(opline_before_exception) = old_opline_before_exception; if (EG(exception)) { zend_exception_set_previous(EG(exception), old_exception); diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index af4d1f265897a..30ea22c8de44f 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -164,6 +164,7 @@ ZEND_API void zend_objects_destroy_object(zend_object *object) && ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) { zend_rethrow_exception(EG(current_execute_data)); } + EG(current_execute_data)->opline = EG(opline_before_exception); old_exception = EG(exception); old_opline_before_exception = EG(opline_before_exception); EG(exception) = NULL; @@ -173,6 +174,7 @@ ZEND_API void zend_objects_destroy_object(zend_object *object) zend_call_known_instance_method_with_0_params(destructor, object, NULL); if (old_exception) { + EG(current_execute_data)->opline = EG(exception_op); EG(opline_before_exception) = old_opline_before_exception; if (EG(exception)) { zend_exception_set_previous(EG(exception), old_exception);