From 61ec9558ef396e7560979834425ecd7bcc7a5199 Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Thu, 5 Nov 2026 22:54:15 +0100 Subject: [PATCH] Fix AST export of static member names that do not lex as identifiers --- NEWS | 2 + .../ast_export_unlexable_static_member.phpt | 55 +++++++++++++++++++ Zend/zend_ast.c | 47 ++++++++++------ 3 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 Zend/tests/ast/ast_export_unlexable_static_member.phpt diff --git a/NEWS b/NEWS index 9ae8c7add120..cbf28b639f9b 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,8 @@ PHP NEWS hooked subclass). (Lazizbek Ergashev) . Fixed bug GH-25299 (Exception string code is lost after unserialize()). (Lazizbek Ergashev, David Carlier) + . Fixed AST export of static member names that do not lex as identifiers. + (Jorg Sowa) - Curl: . Fixed curl_reset() retaining CURLOPT_PRIVATE after reset. (lacatoire) diff --git a/Zend/tests/ast/ast_export_unlexable_static_member.phpt b/Zend/tests/ast/ast_export_unlexable_static_member.phpt new file mode 100644 index 000000000000..dacb8ddcaefe --- /dev/null +++ b/Zend/tests/ast/ast_export_unlexable_static_member.phpt @@ -0,0 +1,55 @@ +--TEST-- +AST export of static member names that do not lex as identifiers +--EXTENSIONS-- +zend_test +--FILE-- +__halt_compiler(); +$o->public(set); +C::$__halt_compiler; +PHP; + +$export = zend_test_compile_to_ast($code); +echo $export; +eval("return;\n$export"); + +?> +--EXPECT-- +C::{'__halt_compiler'}; +C::{'__HALT_compiler'}; +$o::{'__halt_compiler'}; +C::{'__halt_compiler'}(); +C::{'__HALT_compiler'}(); +$o::{'__halt_compiler'}(); +C::{'a b'}(); +C::{''}(); +C::{'public'}(set); +C::{'protected'}(set); +C::{'private'}(set); +C::{'PUBLIC'}(SET); +$o::{'public'}(set); +C::{'public'}(set); +C::{'public'}(); +C::static(set); +$o->__halt_compiler(); +$o->public(set); +C::$__halt_compiler; diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 7d6038a194de..41522a4c96dd 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1429,22 +1429,11 @@ static ZEND_COLD bool zend_ast_var_needs_braces(char ch) return ch == '[' || zend_ast_valid_var_char(ch); } -static ZEND_COLD void zend_ast_export_class_const_name(smart_str *str, zend_ast *ast, int indent) +/* T_HALT_COMPILER is the only keyword the grammar rejects as an identifier. */ +static ZEND_COLD bool zend_ast_valid_identifier(const zend_string *name) { - if (ast->kind == ZEND_AST_ZVAL) { - const zval *zv = zend_ast_get_zval(ast); - - /* A bare "class" would turn the dynamic fetch into a class name fetch. */ - if (Z_TYPE_P(zv) == IS_STRING - && zend_ast_valid_var_name(Z_STRVAL_P(zv), Z_STRLEN_P(zv)) - && !zend_string_equals_ci(Z_STR_P(zv), ZSTR_KNOWN(ZEND_STR_CLASS))) { - smart_str_append(str, Z_STR_P(zv)); - return; - } - } - smart_str_appendc(str, '{'); - zend_ast_export_ex(str, ast, 0, indent); - smart_str_appendc(str, '}'); + return zend_ast_valid_var_name(ZSTR_VAL(name), ZSTR_LEN(name)) + && !zend_string_equals_literal_ci(name, "__halt_compiler"); } static ZEND_COLD void zend_ast_export_var(smart_str *str, zend_ast *ast, int priority, int indent) @@ -1465,6 +1454,30 @@ static ZEND_COLD void zend_ast_export_var(smart_str *str, zend_ast *ast, int pri smart_str_appendc(str, '}'); } +static ZEND_COLD void zend_ast_export_static_member_name(smart_str *str, zend_ast *ast, bool is_call, int indent) +{ + if (ast->kind == ZEND_AST_ZVAL && Z_TYPE_P(zend_ast_get_zval(ast)) == IS_STRING) { + const zend_string *name = zend_ast_get_str(ast); + /* A bare "class" fetch is a class name fetch, and "public(set)" and its siblings lex as one token. */ + bool needs_braces = is_call + ? zend_string_equals_literal_ci(name, "public") + || zend_string_equals_literal_ci(name, "protected") + || zend_string_equals_literal_ci(name, "private") + : zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_CLASS)); + + if (zend_ast_valid_identifier(name) && !needs_braces) { + smart_str_append(str, name); + return; + } + } else if (is_call) { + zend_ast_export_var(str, ast, 0, indent); + return; + } + smart_str_appendc(str, '{'); + zend_ast_export_ex(str, ast, 0, indent); + smart_str_appendc(str, '}'); +} + static ZEND_COLD void zend_ast_export_list(smart_str *str, zend_ast_list *list, bool separator, int priority, int indent) { uint32_t i = 0; @@ -2255,7 +2268,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio case ZEND_AST_CLASS_CONST: zend_ast_export_ns_name(str, ast->child[0], 0, indent); smart_str_appends(str, "::"); - zend_ast_export_class_const_name(str, ast->child[1], indent); + zend_ast_export_static_member_name(str, ast->child[1], false, indent); break; case ZEND_AST_CLASS_NAME: if (ast->child[0] == NULL) { @@ -2580,7 +2593,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio case ZEND_AST_STATIC_CALL: zend_ast_export_ns_name(str, ast->child[0], 0, indent); smart_str_appends(str, "::"); - zend_ast_export_var(str, ast->child[1], 0, indent); + zend_ast_export_static_member_name(str, ast->child[1], true, indent); smart_str_appendc(str, '('); zend_ast_export_ex(str, ast->child[2], 0, indent); smart_str_appendc(str, ')');