Commit 740d1f57505 for php.net
commit 740d1f57505f56b74b95255bc508e02c1d582493
Author: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com>
Date: Wed Jul 22 09:44:57 2026 +0200
Fix const expr support in preloading (#22783)
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.
Fixes GH-22782.
diff --git a/NEWS b/NEWS
index 1f2b9540bfa..0d4d5dfa167 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,9 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.5.10
+- Core:
+ . Fixed bug GH-22782 (Const expr FCC crashes under preloading). (Arnaud)
+
- Date:
. Fixed leak on double DatePeriod::__construct() call. (ilutov)
@@ -12,6 +15,8 @@ PHP NEWS
- Opcache:
. Fixed GH-22693 (DT_TEXTREL in JIT-generated TLS access on x86_64).
(David Carlier)
+ . Fixed bug GH-22763 (JIT fails to clear ZREG_TYPE_ONLY after setting reg).
+ (Arnaud)
- Sockets:
. Fixed socket_set_option() validation error messages for UDP_SEGMENT and
@@ -126,7 +131,7 @@ PHP NEWS
PHP 8.3 and PHP 8.5). (jorgsowa)
- SPL:
- . Fix class_parents for classes with leading slash in non-autoload mode.
+ . Fix class_parents for classes with leading slash in non-autoload mode.
(jorgsowa)
. Ignore leading back-slash in class_parents(), class_implements(), and
class_uses(). (jorgsowa)
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 00000000000..d67ba4eab14
--- /dev/null
+++ b/Zend/tests/first_class_callable/constexpr/gh22782.inc
@@ -0,0 +1,18 @@
+<?php
+
+#[Attribute]
+class A {
+ function __construct(public mixed $fn) {}
+}
+
+#[A(strlen(...))]
+class C {
+ const CC = strlen(...);
+
+ #[A(strlen(...))]
+ function f($arg = strlen(...)) {
+ return $arg;
+ }
+}
+
+const CC = strlen(...);
diff --git a/Zend/tests/first_class_callable/constexpr/gh22782.phpt b/Zend/tests/first_class_callable/constexpr/gh22782.phpt
new file mode 100644
index 00000000000..b30820be980
--- /dev/null
+++ b/Zend/tests/first_class_callable/constexpr/gh22782.phpt
@@ -0,0 +1,35 @@
+--TEST--
+GH-22782: FCC preloading
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.preload={PWD}/gh22782.inc
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
+?>
+--FILE--
+<?php
+
+echo "# Class const\n";
+var_dump((C::CC)('hello'));
+echo "# Class attr\n";
+var_dump((new ReflectionClass(C::class)->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 bb405945849..03ce073808a 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 3bda0ffadc3..d8d61ea979e 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 d430f4833d3..fef6a5a5f42 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 29b6c2f7a6b..a4aea9fae65 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. */