From d43c523c48960e9ca0bf9c747e9bad8e5121edff Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 28 Mar 2026 09:41:19 +0000 Subject: [PATCH 1/4] Fix GH-21544: Dom\XMLDocument::C14N() drops namespace declarations on DOM-built documents. For programmatically built documents (e.g. createElementNS), namespaces live on node->ns without corresponding xmlns attributes. Create temporary synthetic nsDef entries so C14N can find them, complementing the existing xmlns attribute to nsDef conversion. close GH-21561 --- NEWS | 2 + ext/dom/node.c | 89 +++++++++++++++++++++------ ext/dom/tests/modern/xml/gh21544.phpt | 28 +++++++++ 3 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 ext/dom/tests/modern/xml/gh21544.phpt diff --git a/NEWS b/NEWS index b32f6d5e1ddb..b110b17bef21 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,8 @@ PHP NEWS . Fixed bug GH-21688 (segmentation fault on empty HTMLDocument). (David Carlier) . Upgrade to lexbor v2.7.0. (ndossche, ilutov) + . Fixed bug GH-21544 (Dom\XMLDocument::C14N*( drops namespace declarations + on DOM-built documents). (David Carlier, ndossche) - Iconv: . Fixed bug GH-17399 (iconv memory leak on bailout). (iliaal) diff --git a/ext/dom/node.c b/ext/dom/node.c index bbe8311147f9..505f6ee452c3 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -2103,33 +2103,72 @@ PHP_METHOD(DOMNode, lookupNamespaceURI) } /* }}} end dom_node_lookup_namespace_uri */ +/* Allocate, track and prepend a temporary nsDef entry for C14N. + * Returns the new xmlNsPtr for the caller to fill in href/prefix/_private, + * or NULL on allocation failure. */ +static xmlNsPtr dom_alloc_ns_decl(HashTable *links, xmlNodePtr node) +{ + xmlNsPtr ns = xmlMalloc(sizeof(*ns)); + if (!ns) { + return NULL; + } + + zval *zv = zend_hash_index_lookup(links, (zend_ulong) node); + if (Z_ISNULL_P(zv)) { + ZVAL_LONG(zv, 1); + } else { + Z_LVAL_P(zv)++; + } + + memset(ns, 0, sizeof(*ns)); + ns->type = XML_LOCAL_NAMESPACE; + ns->next = node->nsDef; + node->nsDef = ns; + + return ns; +} + +/* Mint a temporary nsDef entry so C14N finds namespaces that live on node->ns + * but have no matching xmlns attribute (typical for createElementNS). */ +static void dom_add_synthetic_ns_decl(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns) +{ + xmlNsPtr ns = dom_alloc_ns_decl(links, node); + if (!ns) { + return; + } + + ns->href = xmlStrdup(src_ns->href); + ns->prefix = src_ns->prefix ? xmlStrdup(src_ns->prefix) : NULL; +} + +/* Same, but for attribute namespaces, which may collide by prefix with the + * element's own ns or with a sibling attribute's ns. */ +static void dom_add_synthetic_ns_decl_for_attr(HashTable *links, xmlNodePtr node, xmlNsPtr src_ns) +{ + for (xmlNsPtr existing = node->nsDef; existing; existing = existing->next) { + if (xmlStrEqual(existing->prefix, src_ns->prefix)) { + return; + } + } + + dom_add_synthetic_ns_decl(links, node, src_ns); +} + static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node) { if (node->type == XML_ELEMENT_NODE) { for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) { if (php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) { - xmlNsPtr ns = xmlMalloc(sizeof(*ns)); + xmlNsPtr ns = dom_alloc_ns_decl(links, node); if (!ns) { return; } - zval *zv = zend_hash_index_lookup(links, (zend_ulong) node); - if (Z_ISNULL_P(zv)) { - ZVAL_LONG(zv, 1); - } else { - Z_LVAL_P(zv)++; - } - bool should_free; xmlChar *attr_value = php_libxml_attr_value(attr, &should_free); - memset(ns, 0, sizeof(*ns)); - ns->type = XML_LOCAL_NAMESPACE; ns->href = should_free ? attr_value : xmlStrdup(attr_value); ns->prefix = attr->ns->prefix ? xmlStrdup(attr->name) : NULL; - ns->next = node->nsDef; - node->nsDef = ns; - ns->_private = attr; if (attr->prev) { attr->prev->next = attr->next; @@ -2150,6 +2189,14 @@ static void dom_relink_ns_decls_element(HashTable *links, xmlNodePtr node) * can return the current namespace. */ zend_hash_index_add_new_ptr(links, (zend_ulong) node | 1, node->ns); node->ns = xmlSearchNs(node->doc, node, NULL); + } else if (node->ns) { + dom_add_synthetic_ns_decl(links, node, node->ns); + } + + for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) { + if (attr->ns && !php_dom_ns_is_fast((const xmlNode *) attr, php_dom_ns_is_xmlns_magic_token)) { + dom_add_synthetic_ns_decl_for_attr(links, node, attr->ns); + } } } } @@ -2179,13 +2226,15 @@ static void dom_unlink_ns_decls(HashTable *links) node->nsDef = ns->next; xmlAttrPtr attr = ns->_private; - if (attr->prev) { - attr->prev->next = attr; - } else { - node->properties = attr; - } - if (attr->next) { - attr->next->prev = attr; + if (attr) { + if (attr->prev) { + attr->prev->next = attr; + } else { + node->properties = attr; + } + if (attr->next) { + attr->next->prev = attr; + } } xmlFreeNs(ns); diff --git a/ext/dom/tests/modern/xml/gh21544.phpt b/ext/dom/tests/modern/xml/gh21544.phpt new file mode 100644 index 000000000000..b76742ae84a5 --- /dev/null +++ b/ext/dom/tests/modern/xml/gh21544.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-21544 (Dom\XMLDocument::C14N() drops namespace declarations on DOM-built documents) +--CREDITS-- +Toon Verwerft (veewee) +--EXTENSIONS-- +dom +--FILE-- +createElementNS("urn:envelope", "env:Root"); +$doc->appendChild($root); +$child = $doc->createElementNS("urn:child", "x:Child"); +$root->appendChild($child); + +$parsed = Dom\XMLDocument::createFromString( + '' +); + +echo "DOM-built C14N: " . $doc->C14N() . PHP_EOL; +echo "Parsed C14N: " . $parsed->C14N() . PHP_EOL; +var_dump($doc->C14N() === $parsed->C14N()); + +?> +--EXPECT-- +DOM-built C14N: +Parsed C14N: +bool(true) From da58c655198f787b1e7f100cc537561fab64d98a Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 21 Apr 2026 10:43:10 +0200 Subject: [PATCH 2/4] 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. --- Zend/zend_API.c | 4 ++-- Zend/zend_API.h | 4 +++- build/gen_stub.php | 32 +++++++++++++++++++++++--- ext/standard/basic_functions.stub.php | 5 +++- ext/standard/basic_functions_arginfo.h | 2 +- ext/standard/basic_functions_decl.h | 8 +++---- 6 files changed, 43 insertions(+), 12 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 2541486c492a..c97d9308e208 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 17f7ce3263f8..aff6a5bba220 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 db7f4d400ed7..857c6cb7087b 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 c437f0d7f6c2..0f04cc036d4d 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 1ba20c6b26cc..7ad59cfc6897 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit basic_functions.stub.php instead. - * Stub hash: a846d7e3dd1f1cebd8c6257132c97b3758067127 + * Stub hash: 85603c0acd3b8580cbff782a53c9b1453943684f * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) diff --git a/ext/standard/basic_functions_decl.h b/ext/standard/basic_functions_decl.h index ab27bb64f0c7..0dda2f894f15 100644 --- a/ext/standard/basic_functions_decl.h +++ b/ext/standard/basic_functions_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit basic_functions.stub.php instead. - * Stub hash: a846d7e3dd1f1cebd8c6257132c97b3758067127 */ + * Stub hash: 85603c0acd3b8580cbff782a53c9b1453943684f */ -#ifndef ZEND_BASIC_FUNCTIONS_DECL_a846d7e3dd1f1cebd8c6257132c97b3758067127_H -#define ZEND_BASIC_FUNCTIONS_DECL_a846d7e3dd1f1cebd8c6257132c97b3758067127_H +#ifndef ZEND_BASIC_FUNCTIONS_DECL_85603c0acd3b8580cbff782a53c9b1453943684f_H +#define ZEND_BASIC_FUNCTIONS_DECL_85603c0acd3b8580cbff782a53c9b1453943684f_H typedef enum zend_enum_SortDirection { ZEND_ENUM_SortDirection_Ascending = 1, @@ -20,4 +20,4 @@ typedef enum zend_enum_RoundingMode { ZEND_ENUM_RoundingMode_PositiveInfinity = 8, } zend_enum_RoundingMode; -#endif /* ZEND_BASIC_FUNCTIONS_DECL_a846d7e3dd1f1cebd8c6257132c97b3758067127_H */ +#endif /* ZEND_BASIC_FUNCTIONS_DECL_85603c0acd3b8580cbff782a53c9b1453943684f_H */ From 2c5dc5d8b1f9c77b706b7e48801a8acd0cdfffb1 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 21 Apr 2026 11:04:32 +0200 Subject: [PATCH 3/4] Add ZEND_ACC2_FORBID_DYN_CALLS Functions that use zend_forbid_dynamic_call() must be flagged with ZEND_ACC2_FORBID_DYN_CALLS. In stubs, this is done by using @forbid-dynamic-calls. To ensure consistency, we assert that the flag exists in zend_forbid_dynamic_call(), and we assert that flagged functions thrown after a dynamic call. Closes GH-21818 --- UPGRADING.INTERNALS | 3 ++ Zend/zend_API.h | 2 ++ Zend/zend_builtin_functions.c | 1 + Zend/zend_builtin_functions.stub.php | 8 ++++- Zend/zend_builtin_functions_arginfo.h | 26 ++++++++++++--- Zend/zend_closures.c | 1 + Zend/zend_compile.h | 5 +-- Zend/zend_execute_API.c | 1 + Zend/zend_vm_def.h | 8 +++++ Zend/zend_vm_execute.h | 44 ++++++++++++++++++++++++++ build/gen_stub.php | 8 +++++ ext/standard/basic_functions.stub.php | 1 + ext/standard/basic_functions_arginfo.h | 14 ++++++-- ext/standard/basic_functions_decl.h | 8 ++--- ext/zend_test/test.stub.php | 2 ++ ext/zend_test/test_arginfo.h | 10 +++++- ext/zend_test/test_decl.h | 8 ++--- ext/zend_test/test_legacy_arginfo.h | 10 +++++- 18 files changed, 139 insertions(+), 21 deletions(-) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 3a24fcce4772..1d53df7e4a33 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -88,6 +88,9 @@ PHP 8.6 INTERNALS UPGRADE NOTES ZEND_AST_TRAIT_METHOD_REFERENCE. . The EMPTY_SWITCH_DEFAULT_CASE() macro has been removed. Use default: ZEND_UNREACHABLE(); instead. + . Functions using zend_forbid_dynamic_call() *must* be flagged with + ZEND_ACC2_FORBID_DYN_CALLS (@forbid-dynamic-calls in stubs). In debug + builds, failing to include that flag will lead to assertion failures. ======================== 2. Build system changes diff --git a/Zend/zend_API.h b/Zend/zend_API.h index aff6a5bba220..9eaa1ec7b3c8 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -896,6 +896,8 @@ static zend_always_inline zend_result zend_forbid_dynamic_call(void) const zend_execute_data *ex = EG(current_execute_data); ZEND_ASSERT(ex != NULL && ex->func != NULL); + ZEND_ASSERT(ex->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS); + if (ZEND_CALL_INFO(ex) & ZEND_CALL_DYNAMIC) { zend_string *function_or_method_name = get_active_function_or_method_name(); zend_throw_error(NULL, "Cannot call %.*s() dynamically", diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 1fe4bcf3ee50..c19bf2779fbf 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -16,6 +16,7 @@ +----------------------------------------------------------------------+ */ +#include "php_version.h" #include "zend.h" #include "zend_API.h" #include "zend_attributes.h" diff --git a/Zend/zend_builtin_functions.stub.php b/Zend/zend_builtin_functions.stub.php index 9b2267b531eb..1d405587145d 100644 --- a/Zend/zend_builtin_functions.stub.php +++ b/Zend/zend_builtin_functions.stub.php @@ -18,11 +18,16 @@ function die(string|int $status = 0): never {} /** @refcount 1 */ function zend_version(): string {} +/** @forbid-dynamic-calls */ function func_num_args(): int {} +/** @forbid-dynamic-calls */ function func_get_arg(int $position): mixed {} -/** @return array */ +/** + * @return array + * @forbid-dynamic-calls + */ function func_get_args(): array {} function strlen(string $string): int {} @@ -156,6 +161,7 @@ function get_defined_functions(bool $exclude_disabled = true): array {} /** * @return array * @refcount 1 + * @forbid-dynamic-calls */ function get_defined_vars(): array {} diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h index cb626ff430e6..b3af43fef340 100644 --- a/Zend/zend_builtin_functions_arginfo.h +++ b/Zend/zend_builtin_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit zend_builtin_functions.stub.php instead. - * Stub hash: 9b49f527064695c812cd204d9efc63c13681d942 */ + * Stub hash: 64c61862de86d9968930893bf21b516119724064 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_clone, 0, 1, IS_OBJECT, 0) ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) @@ -316,9 +316,21 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(exit, arginfo_exit) ZEND_RAW_FENTRY("die", zif_exit, arginfo_die, 0, NULL, NULL) ZEND_FE(zend_version, arginfo_zend_version) - ZEND_FE(func_num_args, arginfo_func_num_args) - ZEND_FE(func_get_arg, arginfo_func_get_arg) - ZEND_FE(func_get_args, arginfo_func_get_args) +#if (PHP_VERSION_ID >= 80600) + ZEND_RAW_FENTRY("func_num_args", zif_func_num_args, arginfo_func_num_args, ZEND_FENTRY_FLAGS(0, ZEND_ACC2_FORBID_DYN_CALLS), NULL, NULL) +#elif (PHP_VERSION_ID >= 80400) + ZEND_RAW_FENTRY("func_num_args", zif_func_num_args, arginfo_func_num_args, 0, NULL, NULL) +#endif +#if (PHP_VERSION_ID >= 80600) + ZEND_RAW_FENTRY("func_get_arg", zif_func_get_arg, arginfo_func_get_arg, ZEND_FENTRY_FLAGS(0, ZEND_ACC2_FORBID_DYN_CALLS), NULL, NULL) +#elif (PHP_VERSION_ID >= 80400) + ZEND_RAW_FENTRY("func_get_arg", zif_func_get_arg, arginfo_func_get_arg, 0, NULL, NULL) +#endif +#if (PHP_VERSION_ID >= 80600) + ZEND_RAW_FENTRY("func_get_args", zif_func_get_args, arginfo_func_get_args, ZEND_FENTRY_FLAGS(0, ZEND_ACC2_FORBID_DYN_CALLS), NULL, NULL) +#elif (PHP_VERSION_ID >= 80400) + ZEND_RAW_FENTRY("func_get_args", zif_func_get_args, arginfo_func_get_args, 0, NULL, NULL) +#endif ZEND_FE(strlen, arginfo_strlen) ZEND_RAW_FENTRY("strcmp", zif_strcmp, arginfo_strcmp, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) ZEND_RAW_FENTRY("strncmp", zif_strncmp, arginfo_strncmp, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) @@ -358,7 +370,11 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(get_declared_traits, arginfo_get_declared_traits) ZEND_FE(get_declared_interfaces, arginfo_get_declared_interfaces) ZEND_FE(get_defined_functions, arginfo_get_defined_functions) - ZEND_FE(get_defined_vars, arginfo_get_defined_vars) +#if (PHP_VERSION_ID >= 80600) + ZEND_RAW_FENTRY("get_defined_vars", zif_get_defined_vars, arginfo_get_defined_vars, ZEND_FENTRY_FLAGS(0, ZEND_ACC2_FORBID_DYN_CALLS), NULL, NULL) +#elif (PHP_VERSION_ID >= 80400) + ZEND_RAW_FENTRY("get_defined_vars", zif_get_defined_vars, arginfo_get_defined_vars, 0, NULL, NULL) +#endif ZEND_FE(get_resource_type, arginfo_get_resource_type) ZEND_FE(get_resource_id, arginfo_get_resource_id) ZEND_FE(get_resources, arginfo_get_resources) diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 56090cddcafb..314abede4063 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -749,6 +749,7 @@ static ZEND_NAMED_FUNCTION(zend_closure_internal_handler) /* {{{ */ { zend_closure *closure = (zend_closure*)ZEND_CLOSURE_OBJECT(EX(func)); closure->orig_internal_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); + ZEND_ASSERT(!(closure->func.common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS) || EG(exception)); // Assign to EX(this) so that it is released after observer checks etc. ZEND_ADD_CALL_FLAG(execute_data, ZEND_CALL_RELEASE_THIS); Z_OBJ(EX(This)) = &closure->std; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 3b85d52c1870..8f67fee2a52b 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -412,10 +412,11 @@ typedef struct _zend_oparray_context { /* op_array uses strict mode types | | | */ #define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */ /* | | | */ -/* Function Flags 2 (fn_flags2) (unused: 0-31) | | | */ +/* Function Flags 2 (fn_flags2) (unused: 1-31) | | | */ /* ============================ | | | */ /* | | | */ -/* #define ZEND_ACC2_EXAMPLE (1 << 0) | X | | */ +/* Function forbids dynamic calls | | | */ +#define ZEND_ACC2_FORBID_DYN_CALLS (1 << 0) /* | X | | */ #define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE) #define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET) diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 5bead7034b9a..0022eb4a1df8 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1030,6 +1030,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_ } ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(fci->retval) : !Z_ISREF_P(fci->retval)); + ZEND_ASSERT(!(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif ZEND_OBSERVER_FCALL_END(call, fci->retval); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 391b82241e47..1f7e09d1be35 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4160,6 +4160,8 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret); @@ -4291,6 +4293,8 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER)) ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -4422,6 +4426,8 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -9131,6 +9137,8 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER)) ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a2e5eac491dc..d5860da23b4c 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1328,6 +1328,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif @@ -1396,6 +1398,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif @@ -1464,6 +1468,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif zend_observer_fcall_end(call, EG(exception) ? NULL : ret); @@ -1648,6 +1654,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -1764,6 +1772,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -1879,6 +1889,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -2013,6 +2025,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -2146,6 +2160,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_D ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -2276,6 +2292,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -3663,6 +3681,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALL_TRAMPOLI ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif @@ -3809,6 +3829,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CALL_TRAMPOLI ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif zend_observer_fcall_end(call, EG(exception) ? NULL : ret); @@ -54080,6 +54102,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_ICA ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif @@ -54148,6 +54172,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_ICA ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif @@ -54216,6 +54242,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_IC ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif zend_observer_fcall_end(call, EG(exception) ? NULL : ret); @@ -54400,6 +54428,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FCA ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -54516,6 +54546,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FCA ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -54631,6 +54663,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FC ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -54765,6 +54799,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FCA ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -54898,6 +54934,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FCA ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -55028,6 +55066,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DO_FC ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret)); #endif @@ -56299,6 +56339,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALL_TRAMPOLINE_SP ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif @@ -56445,6 +56487,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CALL_TRAMPOLINE_SP ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) ? Z_ISREF_P(ret) : !Z_ISREF_P(ret)); zend_verify_internal_func_info(call->func, ret); + ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) + || !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS)); } #endif zend_observer_fcall_end(call, EG(exception) ? NULL : ret); diff --git a/build/gen_stub.php b/build/gen_stub.php index 857c6cb7087b..619c4c905b60 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1304,6 +1304,7 @@ public function __construct( public ?FunctionOrMethodName $alias, private readonly bool $isDeprecated, private bool $supportsCompileTimeEval, + private bool $forbidDynamicCalls, public readonly bool $verify, public /* readonly */ array $args, public /* readonly */ ReturnInfo $return, @@ -1611,6 +1612,10 @@ private function getArginfoFlagsByPhpVersions(): VersionFlags $flags->addForVersionsAbove("ZEND_ACC_NODISCARD", PHP_85_VERSION_ID); } + if ($this->forbidDynamicCalls) { + $flags->addForVersionsAbove("ZEND_ACC2_FORBID_DYN_CALLS", PHP_86_VERSION_ID); + } + return $flags; } @@ -4797,6 +4802,7 @@ function parseFunctionLike( $alias = null; $isDeprecated = false; $supportsCompileTimeEval = false; + $forbidDynamicCalls = false; $verify = true; $docReturnType = null; $tentativeReturnType = false; @@ -4812,6 +4818,7 @@ function parseFunctionLike( $verify = !array_key_exists('no-verify', $tagMap); $tentativeReturnType = array_key_exists('tentative-return-type', $tagMap); $supportsCompileTimeEval = array_key_exists('compile-time-eval', $tagMap); + $forbidDynamicCalls = array_key_exists('forbid-dynamic-calls', $tagMap); $isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap); foreach ($tags as $tag) { @@ -4944,6 +4951,7 @@ function parseFunctionLike( $alias, $isDeprecated, $supportsCompileTimeEval, + $forbidDynamicCalls, $verify, $args, $return, diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 0f04cc036d4d..1999c9b92be1 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -1652,6 +1652,7 @@ function extract(array &$array, int $flags = EXTR_OVERWRITE, string $prefix = "" * @param array|string $var_names * @return array * @refcount 1 + * @forbid-dynamic-calls */ function compact($var_name, ...$var_names): array {} diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 7ad59cfc6897..e51a837ffa4d 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit basic_functions.stub.php instead. - * Stub hash: 85603c0acd3b8580cbff782a53c9b1453943684f + * Stub hash: 36b71aa7bbfe478a5e4af400b2822a77067efa2f * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) @@ -2944,8 +2944,16 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(array_walk_recursive, arginfo_array_walk_recursive) ZEND_RAW_FENTRY("in_array", zif_in_array, arginfo_in_array, ZEND_ACC_COMPILE_TIME_EVAL, frameless_function_infos_in_array, NULL) ZEND_RAW_FENTRY("array_search", zif_array_search, arginfo_array_search, ZEND_ACC_COMPILE_TIME_EVAL, NULL, NULL) - ZEND_FE(extract, arginfo_extract) - ZEND_FE(compact, arginfo_compact) +#if (PHP_VERSION_ID >= 80600) + ZEND_RAW_FENTRY("extract", zif_extract, arginfo_extract, ZEND_FENTRY_FLAGS(0, ZEND_ACC2_FORBID_DYN_CALLS), NULL, NULL) +#elif (PHP_VERSION_ID >= 80400) + ZEND_RAW_FENTRY("extract", zif_extract, arginfo_extract, 0, NULL, NULL) +#endif +#if (PHP_VERSION_ID >= 80600) + ZEND_RAW_FENTRY("compact", zif_compact, arginfo_compact, ZEND_FENTRY_FLAGS(0, ZEND_ACC2_FORBID_DYN_CALLS), NULL, NULL) +#elif (PHP_VERSION_ID >= 80400) + ZEND_RAW_FENTRY("compact", zif_compact, arginfo_compact, 0, NULL, NULL) +#endif ZEND_FE(array_fill, arginfo_array_fill) ZEND_FE(array_fill_keys, arginfo_array_fill_keys) ZEND_FE(range, arginfo_range) diff --git a/ext/standard/basic_functions_decl.h b/ext/standard/basic_functions_decl.h index 0dda2f894f15..b3eb25c5d988 100644 --- a/ext/standard/basic_functions_decl.h +++ b/ext/standard/basic_functions_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit basic_functions.stub.php instead. - * Stub hash: 85603c0acd3b8580cbff782a53c9b1453943684f */ + * Stub hash: 36b71aa7bbfe478a5e4af400b2822a77067efa2f */ -#ifndef ZEND_BASIC_FUNCTIONS_DECL_85603c0acd3b8580cbff782a53c9b1453943684f_H -#define ZEND_BASIC_FUNCTIONS_DECL_85603c0acd3b8580cbff782a53c9b1453943684f_H +#ifndef ZEND_BASIC_FUNCTIONS_DECL_36b71aa7bbfe478a5e4af400b2822a77067efa2f_H +#define ZEND_BASIC_FUNCTIONS_DECL_36b71aa7bbfe478a5e4af400b2822a77067efa2f_H typedef enum zend_enum_SortDirection { ZEND_ENUM_SortDirection_Ascending = 1, @@ -20,4 +20,4 @@ typedef enum zend_enum_RoundingMode { ZEND_ENUM_RoundingMode_PositiveInfinity = 8, } zend_enum_RoundingMode; -#endif /* ZEND_BASIC_FUNCTIONS_DECL_85603c0acd3b8580cbff782a53c9b1453943684f_H */ +#endif /* ZEND_BASIC_FUNCTIONS_DECL_36b71aa7bbfe478a5e4af400b2822a77067efa2f_H */ diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index c9d367d5553f..653630ed73b7 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -187,7 +187,9 @@ class ZendTestClassWithPropertyAttribute { } final class ZendTestForbidDynamicCall { + /** @forbid-dynamic-calls */ public function call(): void {} + /** @forbid-dynamic-calls */ public static function callStatic(): void {} } diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index a4da05df2ffb..adcae16cdf61 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: dcb089a336c6c3e6c685762057dcedcb393508a7 + * Stub hash: e1fb73f5a5f455a3a1eb871e670f26b671da0407 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) @@ -590,8 +590,16 @@ static const zend_function_entry class_ZendTestChildClassWithMethodWithParameter }; static const zend_function_entry class_ZendTestForbidDynamicCall_methods[] = { +#if (PHP_VERSION_ID >= 80600) + ZEND_ME(ZendTestForbidDynamicCall, call, arginfo_class_ZendTestForbidDynamicCall_call, ZEND_FENTRY_FLAGS(ZEND_ACC_PUBLIC, ZEND_ACC2_FORBID_DYN_CALLS)) +#elif (PHP_VERSION_ID >= 80000) ZEND_ME(ZendTestForbidDynamicCall, call, arginfo_class_ZendTestForbidDynamicCall_call, ZEND_ACC_PUBLIC) +#endif +#if (PHP_VERSION_ID >= 80600) + ZEND_ME(ZendTestForbidDynamicCall, callStatic, arginfo_class_ZendTestForbidDynamicCall_callStatic, ZEND_FENTRY_FLAGS(ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, ZEND_ACC2_FORBID_DYN_CALLS)) +#elif (PHP_VERSION_ID >= 80000) ZEND_ME(ZendTestForbidDynamicCall, callStatic, arginfo_class_ZendTestForbidDynamicCall_callStatic, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) +#endif ZEND_FE_END }; diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h index bc2ebaa93c3b..ba6aab902280 100644 --- a/ext/zend_test/test_decl.h +++ b/ext/zend_test/test_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: dcb089a336c6c3e6c685762057dcedcb393508a7 */ + * Stub hash: e1fb73f5a5f455a3a1eb871e670f26b671da0407 */ -#ifndef ZEND_TEST_DECL_dcb089a336c6c3e6c685762057dcedcb393508a7_H -#define ZEND_TEST_DECL_dcb089a336c6c3e6c685762057dcedcb393508a7_H +#ifndef ZEND_TEST_DECL_e1fb73f5a5f455a3a1eb871e670f26b671da0407_H +#define ZEND_TEST_DECL_e1fb73f5a5f455a3a1eb871e670f26b671da0407_H typedef enum zend_enum_ZendTestUnitEnum { ZEND_ENUM_ZendTestUnitEnum_Foo = 1, @@ -27,4 +27,4 @@ typedef enum zend_enum_ZendTestEnumWithInterface { ZEND_ENUM_ZendTestEnumWithInterface_Bar = 2, } zend_enum_ZendTestEnumWithInterface; -#endif /* ZEND_TEST_DECL_dcb089a336c6c3e6c685762057dcedcb393508a7_H */ +#endif /* ZEND_TEST_DECL_e1fb73f5a5f455a3a1eb871e670f26b671da0407_H */ diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h index b446a0f9a293..b42d524d7a8a 100644 --- a/ext/zend_test/test_legacy_arginfo.h +++ b/ext/zend_test/test_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: dcb089a336c6c3e6c685762057dcedcb393508a7 + * Stub hash: e1fb73f5a5f455a3a1eb871e670f26b671da0407 * Has decl header: yes */ ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, 0) @@ -517,8 +517,16 @@ static const zend_function_entry class_ZendTestChildClassWithMethodWithParameter }; static const zend_function_entry class_ZendTestForbidDynamicCall_methods[] = { +#if (PHP_VERSION_ID >= 80600) + ZEND_ME(ZendTestForbidDynamicCall, call, arginfo_class_ZendTestForbidDynamicCall_call, ZEND_FENTRY_FLAGS(ZEND_ACC_PUBLIC, ZEND_ACC2_FORBID_DYN_CALLS)) +#elif (PHP_VERSION_ID >= 70000) ZEND_ME(ZendTestForbidDynamicCall, call, arginfo_class_ZendTestForbidDynamicCall_call, ZEND_ACC_PUBLIC) +#endif +#if (PHP_VERSION_ID >= 80600) + ZEND_ME(ZendTestForbidDynamicCall, callStatic, arginfo_class_ZendTestForbidDynamicCall_callStatic, ZEND_FENTRY_FLAGS(ZEND_ACC_PUBLIC|ZEND_ACC_STATIC, ZEND_ACC2_FORBID_DYN_CALLS)) +#elif (PHP_VERSION_ID >= 70000) ZEND_ME(ZendTestForbidDynamicCall, callStatic, arginfo_class_ZendTestForbidDynamicCall_callStatic, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) +#endif ZEND_FE_END }; From dae7cadbe81a3a590e5f2b858475a94766db8380 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Wed, 22 Apr 2026 12:10:15 +0100 Subject: [PATCH 4/4] Update instructions for new Release Managers --- docs/release-process.md | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/docs/release-process.md b/docs/release-process.md index 2ce20355e494..7dd009676dd4 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -1087,26 +1087,19 @@ volunteers to begin the selection process for the next release managers. * php-general@lists.php.net (email php-general+subscribe@lists.php.net) * php-qa@lists.php.net (email php-qa+subscribe@lists.php.net) -4. Email systems@php.net to get setup for access to downloads.php.net, to be - added to the release-managers@php.net distribution list, and to be added to - the moderators for php-announce@lists.php.net so you are able to moderate - your release announcements. - - Provide the following information in a single email: - - - An SSH public key, preferably a new unique one for PHP systems and - projects. - - Read [Machine Access](https://wiki.php.net/systems#machine_access) to set - up access to downloads.php.net through jump hosts, and provide a - `.google_authenticator` file for 2FA. - - Your @php.net email address to use for the release-managers@php.net - distribution list and php-announce@lists.php.net moderator address. This - should preferably not forward to a Gmail address. - - Your GitHub account name, so that your membership to the release managers - group may be approved. - - A system admin will then contact you to go through with steps 5 through 8 of - [2FA setup instructions](https://wiki.php.net/systems#fa_setup_instructions). +4. File a [ticket in the infrastructure](https://github.com/php/infrastructure/issues/new?template=request-release-manager-access.yml) + project and provide an SSH key, your @php.net email address, your GitHub + account name, and your preferred system account name. Preferrably they're + all the same! + +5. Read [Logging into Servers](https://github.com/php/infrastructure/blob/main/docs/ServerAccess.rst#logging-into-servers) to set up + access to downloads.php.net through jump hosts with 2FA. + + Then [create a Google Authenticator file](https://github.com/php/infrastructure/blob/main/docs/ServerAccess.rst#creating-google-authenticator-files), + and provide the `.google_authenticator` file that this created, as + attachment to an email to systems@php.net. In this email you should also + provide a link to the ticket in the infrastructure project that you have + created in the previous step > 💬 **Hint** \ > To send email from your @php.net address, you will need to use a custom @@ -1114,7 +1107,7 @@ volunteers to begin the selection process for the next release managers. > "[Send emails from a different address or alias][]." -5. Create a [GPG key][] for your @php.net address. +6. Create a [GPG key][] for your @php.net address. > 💡 **Tip** \ > If you're new to GPG, follow GitHub's instructions for @@ -1179,7 +1172,7 @@ volunteers to begin the selection process for the next release managers. git push ``` -6. Make sure you have the following repositories cloned locally: +7. Make sure you have the following repositories cloned locally: * https://github.com/php/php-src * https://github.com/php/web-php