From e5c6456d3716ba8940dea34c181febea06ea9b27 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 3 Nov 2025 14:36:57 -0800 Subject: [PATCH] Fix GH-20377: emit assignment for all final promoted properties (#20378) Previously, the assignment op line was only emitted when one of the other flags allowed for promoted properties (visibility, set visibility, or readonly) was also used, or when the property had hooks. The property was still added to the class, but the magical assignment `$this->prop = $prop` was missing. Add that assignment even when no visibility is explicitly specified, and a test to confirm the fix. --- NEWS | 2 ++ .../ctor_promotion/ctor_promotion_final.phpt | 23 +++++++++++++++++++ Zend/zend_compile.c | 5 ++-- 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/ctor_promotion/ctor_promotion_final.phpt diff --git a/NEWS b/NEWS index 2ebd0e05067..d807595f6ff 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,8 @@ PHP NEWS (ilutov) . Fixed bug GH-20194 (null offset deprecation not emitted for writes). (Girgias) + . Fixed bug GH-GH-20377 (final promoted properties without explicit visibility + not automatically assigned). (DanielEScherzer) - Opcache: . Fixed bug GH-20012 (heap buffer overflow in jit). (Arnaud) diff --git a/Zend/tests/ctor_promotion/ctor_promotion_final.phpt b/Zend/tests/ctor_promotion/ctor_promotion_final.phpt new file mode 100644 index 00000000000..abfb5b70880 --- /dev/null +++ b/Zend/tests/ctor_promotion/ctor_promotion_final.phpt @@ -0,0 +1,23 @@ +--TEST-- +GH-20377: Constructor promotion with a final property without visibility set +--FILE-- + +--EXPECTF-- +object(Demo)#%d (2) { + ["foo"]=> + string(5) "first" + ["bar"]=> + string(6) "second" +} diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0180e6e8e1c..8be1ee14f48 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7770,6 +7770,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 } } + const uint32_t promotion_flags = ZEND_ACC_PPP_MASK | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_READONLY | ZEND_ACC_FINAL; for (i = 0; i < list->children; ++i) { zend_ast *param_ast = list->child[i]; zend_ast *type_ast = param_ast->child[0]; @@ -7781,7 +7782,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 zend_string *name = zval_make_interned_string(zend_ast_get_zval(var_ast)); bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; bool is_variadic = (param_ast->attr & ZEND_PARAM_VARIADIC) != 0; - uint32_t property_flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_READONLY | ZEND_ACC_FINAL); + uint32_t property_flags = param_ast->attr & promotion_flags; bool is_promoted = property_flags || hooks_ast; CG(zend_lineno) = param_ast->lineno; @@ -8008,7 +8009,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 zend_ast *param_ast = list->child[i]; zend_ast *hooks_ast = param_ast->child[5]; bool is_ref = (param_ast->attr & ZEND_PARAM_REF) != 0; - uint32_t flags = param_ast->attr & (ZEND_ACC_PPP_MASK | ZEND_ACC_PPP_SET_MASK | ZEND_ACC_READONLY); + uint32_t flags = param_ast->attr & promotion_flags; bool is_promoted = flags || hooks_ast; if (!is_promoted) { continue;