diff --git a/Zend/tests/gh10709.phpt b/Zend/tests/gh10709.phpt new file mode 100644 index 0000000000000..f394e1a7882db --- /dev/null +++ b/Zend/tests/gh10709.phpt @@ -0,0 +1,21 @@ +--TEST-- +GH-10709: Recursive class constant evaluation +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +string(2) "AB" diff --git a/Zend/tests/gh10709_2.phpt b/Zend/tests/gh10709_2.phpt new file mode 100644 index 0000000000000..723fa29cc94bc --- /dev/null +++ b/Zend/tests/gh10709_2.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-10709: Recursive class constant evaluation +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +object(B)#2 (1) { + ["prop"]=> + string(1) "A" +} +object(B)#2 (1) { + ["prop"]=> + string(1) "A" +} diff --git a/Zend/tests/gh10709_3.phpt b/Zend/tests/gh10709_3.phpt new file mode 100644 index 0000000000000..151b84a19e0cc --- /dev/null +++ b/Zend/tests/gh10709_3.phpt @@ -0,0 +1,42 @@ +--TEST-- +GH-10709: Recursive class constant evaluation with outer call failing +--FILE-- + +--EXPECTF-- +object(B)#3 (1) { + ["prop"]=> + string(2) "AS" +} + +Fatal error: Uncaught Exception: Thrown from S in %s:%d +Stack trace: +#0 %s(%d): S->__toString() +#1 {main} + thrown in %s on line %d diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 3ef2dec1036c2..14ec0a9b589e3 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -680,9 +680,23 @@ ZEND_API zend_result ZEND_FASTCALL zval_update_constant_ex(zval *p, zend_class_e } else { zval tmp; + // Increase the refcount during zend_ast_evaluate to avoid releasing the ast too early + // on nested calls to zval_update_constant_ex which can happen when retriggering ast + // evaluation during autoloading. + zend_ast_ref *ast_ref = Z_AST_P(p); + bool ast_is_refcounted = !(GC_FLAGS(ast_ref) & GC_IMMUTABLE); + if (ast_is_refcounted) { + GC_ADDREF(ast_ref); + } if (UNEXPECTED(zend_ast_evaluate(&tmp, ast, scope) != SUCCESS)) { + if (ast_is_refcounted && !GC_DELREF(ast_ref)) { + rc_dtor_func((zend_refcounted *)ast_ref); + } return FAILURE; } + if (ast_is_refcounted && !GC_DELREF(ast_ref)) { + rc_dtor_func((zend_refcounted *)ast_ref); + } zval_ptr_dtor_nogc(p); ZVAL_COPY_VALUE(p, &tmp); } diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 2b9f33a67be74..0b240ae37ddc5 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -3035,9 +3035,23 @@ static zend_result ZEND_FASTCALL zval_jit_update_constant_ex(zval *p, zend_class } else { zval tmp; + // Increase the refcount during zend_ast_evaluate to avoid releasing the ast too early + // on nested calls to zval_update_constant_ex which can happen when retriggering ast + // evaluation during autoloading. + zend_ast_ref *ast_ref = Z_AST_P(p); + bool ast_is_refcounted = !(GC_FLAGS(ast_ref) & GC_IMMUTABLE); + if (ast_is_refcounted) { + GC_ADDREF(ast_ref); + } if (UNEXPECTED(zend_ast_evaluate(&tmp, ast, scope) != SUCCESS)) { + if (ast_is_refcounted && !GC_DELREF(ast_ref)) { + rc_dtor_func((zend_refcounted *)ast_ref); + } return FAILURE; } + if (ast_is_refcounted && !GC_DELREF(ast_ref)) { + rc_dtor_func((zend_refcounted *)ast_ref); + } zval_ptr_dtor_nogc(p); ZVAL_COPY_VALUE(p, &tmp); } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 6ca8c46892ae0..49dfea34e33e8 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -248,6 +248,7 @@ static void zend_persist_zval(zval *z) zend_persist_ast(GC_AST(old_ref)); Z_TYPE_FLAGS_P(z) = 0; GC_SET_REFCOUNT(Z_COUNTED_P(z), 1); + GC_ADD_FLAGS(Z_COUNTED_P(z), GC_IMMUTABLE); efree(old_ref); } break;