[php-src] PHP-8.5: Fix const expr support in preloading (#22783)

Arnaud Le Blanc via GitHub <[email protected]> Wed, 22 Jul 2026 07:45:00 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Arnaud Le Blanc (arnaud-lb)
Committer: GitHub (web-flow)
Pusher: arnaud-lb
Date: 2026-07-22T09:44:57+02:00

Commit: https://github.com/php/php-src/commit/740d1f57505f56b74b95255bc508e02c1d582493
Raw diff: https://github.com/php/php-src/commit/740d1f57505f56b74b95255bc508e02c1d582493.diff

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.

Changed paths:
  A  Zend/tests/first_class_callable/constexpr/gh22782.inc
  A  Zend/tests/first_class_callable/constexpr/gh22782.phpt
  M  NEWS
  M  Zend/zend_ast.c
  M  Zend/zend_compile.c
  M  ext/opcache/zend_file_cache.c
  M  ext/opcache/zend_persist.c


Diff:

diff --git a/NEWS b/NEWS
index 1f2b9540bfa4..0d4d5dfa1670 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 000000000000..d67ba4eab14e
--- /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 000000000000..b30820be9805
--- /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 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. */