Commit da58c655198 for php.net

commit da58c655198f787b1e7f100cc537561fab64d98a
Author: Arnaud Le Blanc <arnaud.lb@gmail.com>
Date:   Tue Apr 21 10:43:10 2026 +0200

    gen_stub: Support fn_flags2 flags

    Change zend_function_entry.flags to a uint64_t to that both ZEND_ACC_ and
    ZEND_ACC2_ flags can be represented.

    Introduce ZEND_FENTRY_FLAGS(flags, flags2) to pass ZEND_ACC2_ flags to
    ZEND_RAW_FENTRY(), ZEND_FENTRY().

    Source-level backwards compatibility is maintained, as passing raw ZEND_ACC_
    flags to ZEND_RAW_FENTRY(), ZEND_FENTRY() still works.

diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index 2541486c492..c97d9308e20 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -3045,7 +3045,6 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
 		internal_function->prop_info = NULL;
 		internal_function->attributes = NULL;
 		internal_function->frameless_function_infos = ptr->frameless_function_infos;
-		internal_function->fn_flags2 = 0;
 		if (EG(active)) { // at run-time: this ought to only happen if registered with dl() or somehow temporarily at runtime
 			ZEND_MAP_PTR_INIT(internal_function->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size()));
 		} else {
@@ -3055,7 +3054,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
 			ZEND_MAP_PTR_INIT(internal_function->run_time_cache, NULL);
 #endif
 		}
-		if (ptr->flags) {
+		if (ptr->flags & UINT32_MAX) {
 			if (!(ptr->flags & ZEND_ACC_PPP_MASK)) {
 				if (ptr->flags != ZEND_ACC_DEPRECATED && scope) {
 					zend_error(error_type, "Invalid access level for %s::%s() - access must be exactly one of public, protected or private", ZSTR_VAL(scope->name), ptr->fname);
@@ -3067,6 +3066,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
 		} else {
 			internal_function->fn_flags = ZEND_ACC_PUBLIC;
 		}
+		internal_function->fn_flags2 = ptr->flags >> 32;

 		if (ptr->arg_info) {
 			zend_internal_function_info *info = (zend_internal_function_info*)ptr->arg_info;
diff --git a/Zend/zend_API.h b/Zend/zend_API.h
index 17f7ce3263f..aff6a5bba22 100644
--- a/Zend/zend_API.h
+++ b/Zend/zend_API.h
@@ -36,7 +36,7 @@ typedef struct _zend_function_entry {
 	zif_handler handler;
 	const struct _zend_internal_arg_info *arg_info;
 	uint32_t num_args;
-	uint32_t flags;
+	uint64_t flags;
 	const zend_frameless_function_info *frameless_function_infos;
 	const char *doc_comment;
 } zend_function_entry;
@@ -74,6 +74,8 @@ typedef struct _zend_fcall_info_cache {
 #define ZEND_FUNCTION(name)				ZEND_NAMED_FUNCTION(zif_##name)
 #define ZEND_METHOD(classname, name)	ZEND_NAMED_FUNCTION(zim_##classname##_##name)

+#define ZEND_FENTRY_FLAGS(flags, flags2) (((uint64_t)flags) | ((uint64_t)flags2 << 32))
+
 #define ZEND_FENTRY(zend_name, name, arg_info, flags)	{ #zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags, NULL, NULL },

 #define ZEND_RAW_FENTRY(zend_name, name, arg_info, flags, frameless_function_infos, doc_comment)   { zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags, frameless_function_infos, doc_comment },
diff --git a/build/gen_stub.php b/build/gen_stub.php
index db7f4d400ed..857c6cb7087 100755
--- a/build/gen_stub.php
+++ b/build/gen_stub.php
@@ -1184,6 +1184,32 @@ public function isEmpty(): bool {
         );
     }

+    /**
+     * If we have ZEND_ACC2_ flags, represent as 'ZEND_FENTRY_FLAGS(flags1, flags2)'.
+     * Otherwise, represent as just 'flags1' (backwards compatible).
+     */
+    private function formatFlags(array $flags): string {
+        $flags1 = [];
+        $flags2 = [];
+        foreach ($flags as $flag) {
+            if (str_starts_with($flag, 'ZEND_ACC2_')) {
+                $flags2[] = $flag;
+            } else {
+                $flags1[] = $flag;
+            }
+        }
+
+        if ($flags2 !== []) {
+            return sprintf(
+                'ZEND_FENTRY_FLAGS(%s, %s)',
+                $flags1 === [] ? 0 : implode("|", $flags1),
+                implode("|", $flags2),
+            );
+        }
+
+        return implode("|", $flags1);
+    }
+
     public function generateVersionDependentFlagCode(
         string $codeTemplate,
         ?int $phpVersionIdMinimumCompatibility,
@@ -1199,7 +1225,7 @@ public function generateVersionDependentFlagCode(
             if (empty($flagsByPhpVersions[$currentPhpVersion])) {
                 return '';
             }
-            return sprintf($codeTemplate, implode("|", $flagsByPhpVersions[$currentPhpVersion]));
+            return sprintf($codeTemplate, $this->formatFlags($flagsByPhpVersions[$currentPhpVersion]));
         }

         ksort($flagsByPhpVersions);
@@ -1240,7 +1266,7 @@ public function generateVersionDependentFlagCode(
             reset($flagsByPhpVersions);
             $firstVersion = key($flagsByPhpVersions);
             if ($firstVersion === $phpVersionIdMinimumCompatibility) {
-                return sprintf($codeTemplate, implode("|", reset($flagsByPhpVersions)));
+                return sprintf($codeTemplate, $this->formatFlags(reset($flagsByPhpVersions)));
             }
         }

@@ -1253,7 +1279,7 @@ public function generateVersionDependentFlagCode(

             $code .= "$if (PHP_VERSION_ID >= $version)\n";

-            $code .= sprintf($codeTemplate, implode("|", $versionFlags));
+            $code .= sprintf($codeTemplate, $this->formatFlags($versionFlags));
             $code .= $endif;

             $i++;
diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php
index c437f0d7f6c..0f04cc036d4 100644
--- a/ext/standard/basic_functions.stub.php
+++ b/ext/standard/basic_functions.stub.php
@@ -1641,7 +1641,10 @@ function in_array(mixed $needle, array $haystack, bool $strict = false): bool {}
  */
 function array_search(mixed $needle, array $haystack, bool $strict = false): int|string|false {}

-/** @prefer-ref $array */
+/**
+ * @prefer-ref $array
+ * @forbid-dynamic-calls
+ */
 function extract(array &$array, int $flags = EXTR_OVERWRITE, string $prefix = ""): int {}

 /**
diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h
index 1ba20c6b26c..7ad59cfc689 100644
Binary files a/ext/standard/basic_functions_arginfo.h and b/ext/standard/basic_functions_arginfo.h differ
diff --git a/ext/standard/basic_functions_decl.h b/ext/standard/basic_functions_decl.h
index ab27bb64f0c..0dda2f894f1 100644
Binary files a/ext/standard/basic_functions_decl.h and b/ext/standard/basic_functions_decl.h differ