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. */