diff --git a/Zend/tests/gh10709.phpt b/Zend/tests/gh10709.phpt new file mode 100644 index 00000000000..f394e1a7882 --- /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 00000000000..723fa29cc94 --- /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 00000000000..1b0ccb5f8f2 --- /dev/null +++ b/Zend/tests/gh10709_3.phpt @@ -0,0 +1,43 @@ +--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): [constant expression]() +#1 %s(%d): S->__toString() +#2 {main} + thrown in %s on line %d diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index cf1be1b5ad1..9b10db265da 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -698,7 +698,19 @@ ZEND_API zend_result ZEND_FASTCALL zval_update_constant_with_ctx(zval *p, zend_c zval tmp; bool short_circuited; - if (UNEXPECTED(zend_ast_evaluate_ex(&tmp, ast, scope, &short_circuited, ctx) != SUCCESS)) { + // 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); + } + zend_result result = zend_ast_evaluate_ex(&tmp, ast, scope, &short_circuited, ctx) != SUCCESS; + if (ast_is_refcounted && !GC_DELREF(ast_ref)) { + rc_dtor_func((zend_refcounted *)ast_ref); + } + if (UNEXPECTED(result != SUCCESS)) { return FAILURE; } zval_ptr_dtor_nogc(p); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 9698d584e80..cca90bb41e9 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -261,6 +261,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; diff --git a/run-tests.php b/run-tests.php index 1f21b5073f3..5101f7845d9 100755 --- a/run-tests.php +++ b/run-tests.php @@ -675,6 +675,7 @@ function main(): void if (!$phpdbg) { $phpdbg = get_binary($php, 'phpdbg', 'sapi/phpdbg/phpdbg'); } + $phpdbg = null; putenv("TEST_PHP_EXECUTABLE=$php"); $environment['TEST_PHP_EXECUTABLE'] = $php;