From 2e006f862c1fbe555e38ca311a9f95e78335f97c Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Fri, 17 Jul 2026 14:20:13 +0200 Subject: [PATCH] Fix preloading FCC const exprs Preloading will try to evaluate class constants during compilation, but evaluated FCCs are objects, which can not be persisted. Apply the same fix as for enums: Fail evaluation when compiling. Preloading will also reset CG(map_ptr_last) after compilation, which results in collisions if map ptrs were allocated during compilation. Move the ZEND_MAP_PTR_NEW() to the persist phase, as a shared map ptr is not necessary before that. --- .../constexpr/gh22782.inc | 18 ++++++++++ .../constexpr/gh22782.phpt | 35 +++++++++++++++++++ Zend/zend_ast.c | 6 ++++ Zend/zend_compile.c | 1 - ext/opcache/zend_file_cache.c | 6 ++-- ext/opcache/zend_persist.c | 3 ++ 6 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/first_class_callable/constexpr/gh22782.inc create mode 100644 Zend/tests/first_class_callable/constexpr/gh22782.phpt diff --git a/Zend/tests/first_class_callable/constexpr/gh22782.inc b/Zend/tests/first_class_callable/constexpr/gh22782.inc new file mode 100644 index 000000000000..d67ba4eab14e --- /dev/null +++ b/Zend/tests/first_class_callable/constexpr/gh22782.inc @@ -0,0 +1,18 @@ + +--FILE-- +getAttributes(A::class)[0]->newInstance()->fn)('hello')); +echo "# Method attr\n"; +var_dump((new ReflectionMethod(C::class, 'f')->getAttributes(A::class)[0]->newInstance()->fn)('hello')); +echo "# Method default value\n"; +var_dump((new C()->f())('hello')); + +?> +--EXPECT-- +# Class const +int(5) +# Class attr +int(5) +# Method attr +int(5) +# Method default value +int(5) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index bb405945849e..03ce073808ad 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1054,6 +1054,12 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( case ZEND_AST_CALL: case ZEND_AST_STATIC_CALL: { + // Preloading will attempt to resolve constants but objects can't be stored in shm + // Aborting here to store the const AST instead + if (CG(in_compilation)) { + return FAILURE; + } + zend_function *fptr; zend_class_entry *called_scope = NULL; switch (ast->kind) { diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 3bda0ffadc32..d8d61ea979e4 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -11513,7 +11513,6 @@ static void zend_compile_const_expr_fcc(zend_ast **ast_ptr) if ((*args_ast)->kind != ZEND_AST_CALLABLE_CONVERT) { zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); } - ZEND_MAP_PTR_NEW(((zend_ast_fcc *)*args_ast)->fptr); switch ((*ast_ptr)->kind) { case ZEND_AST_CALL: { diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index d430f4833d3c..fef6a5a5f42b 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -1303,7 +1303,9 @@ static void zend_file_cache_unserialize_ast(zend_ast *ast, zend_ast_get_op_array(ast)->op_array = Z_PTR(z); } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *fcc = (zend_ast_fcc*)ast; - ZEND_MAP_PTR_NEW(fcc->fptr); + if (!script->corrupted) { + ZEND_MAP_PTR_NEW(fcc->fptr); + } } else if (zend_ast_is_decl(ast)) { /* Not implemented. */ ZEND_UNREACHABLE(); @@ -2109,7 +2111,7 @@ void zend_file_cache_invalidate(zend_string *full_path) if (ZCG(accel_directives).file_cache_read_only) { return; } - + char *filename; filename = zend_file_cache_get_bin_file_path(full_path); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 29b6c2f7a6b2..a4aea9fae653 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -197,6 +197,9 @@ static zend_ast *zend_persist_ast(zend_ast *ast) node = (zend_ast *) copy; } else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) { zend_ast_fcc *copy = zend_shared_memdup(ast, sizeof(zend_ast_fcc)); + if (!ZCG(current_persistent_script)->corrupted) { + ZEND_MAP_PTR_NEW(copy->fptr); + } node = (zend_ast *) copy; } else if (zend_ast_is_decl(ast)) { /* Not implemented. */