From 6de65700557edab956a67f88010d2307c0340f20 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Tue, 8 Sep 2026 12:09:57 -0400 Subject: [PATCH] Fix GH-23552: keyword class constant name not retagged to T_STRING The tokenizer found the token a parser feedback applies to by scanning the collected token list backwards for a matching token text. Typed class constants defer reducing the constant name past the initializer scan, so for `const NEW = Bar::NEW;` the declaration's feedback re-found the initializer's token and left the declared name tagged T_NEW. Match the ident's source offset instead, accumulated as tokens are emitted. Text matching also inverted `const NEW = NEW Bar();`, retagging the NEW operator rather than the declaration, and its search could not fail, so the store is now guarded rather than asserted. Fixes GH-23552 --- NEWS | 5 +++ ext/tokenizer/tests/gh23552.phpt | 65 ++++++++++++++++++++++++++++++++ ext/tokenizer/tokenizer.c | 44 +++++++++++++-------- 3 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 ext/tokenizer/tests/gh23552.phpt diff --git a/NEWS b/NEWS index ade78480b215..1c427a7e7451 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,11 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.27 +- Tokenizer: + . Fixed GH-23552 (token_get_all() does not retag a reserved keyword used as a + class constant name when the same keyword appears in the initializer). + (Ilia Alshanetsky) + 24 Sep 2026, PHP 8.4.26 diff --git a/ext/tokenizer/tests/gh23552.phpt b/ext/tokenizer/tests/gh23552.phpt new file mode 100644 index 000000000000..d87dfae11a56 --- /dev/null +++ b/ext/tokenizer/tests/gh23552.phpt @@ -0,0 +1,65 @@ +--TEST-- +GH-23552 (keyword class constant name not retagged to T_STRING) +--EXTENSIONS-- +tokenizer +--FILE-- +text), ['new', 'print'], true)) { + echo ' PhpToken: ', token_name($token->id), ' [', $token->text, '] at ', $token->pos, "\n"; + } + } +} + +try { + token_get_all('getMessage(), "\n"; +} +?> +--EXPECT-- +tokens, token, (unsigned char *) text, length, line, ctx->token_class, NULL); + ctx->offset += length; break; case ON_FEEDBACK: { HashTable *tokens_ht = Z_ARRVAL_P(ctx->tokens); + size_t target = (unsigned char *) text - LANG_SCNG(yy_start); + size_t offset = ctx->offset; zval *token_zv, *id_zv = NULL; ZEND_HASH_REVERSE_FOREACH_VAL(tokens_ht, token_zv) { - id_zv = extract_token_id_to_replace(token_zv, text, length); - if (id_zv) { + zval *candidate_id; + zend_string *candidate_text; + size_t candidate_length = + token_source_length(token_zv, &candidate_id, &candidate_text); + if (offset < candidate_length || offset - candidate_length < target) { + break; + } + offset -= candidate_length; + if (offset == target) { + ZEND_ASSERT(ZSTR_LEN(candidate_text) == length + && !memcmp(ZSTR_VAL(candidate_text), text, length)); + id_zv = candidate_id; break; } } ZEND_HASH_FOREACH_END(); - ZEND_ASSERT(id_zv); - ZVAL_LONG(id_zv, token); + if (id_zv) { + ZVAL_LONG(id_zv, token); + } break; } case ON_STOP: @@ -445,6 +457,7 @@ static void on_event( add_token(ctx->tokens, T_INLINE_HTML, LANG_SCNG(yy_cursor), LANG_SCNG(yy_limit) - LANG_SCNG(yy_cursor), CG(zend_lineno), ctx->token_class, NULL); + ctx->offset += LANG_SCNG(yy_limit) - LANG_SCNG(yy_cursor); } break; } @@ -471,6 +484,7 @@ static bool tokenize_parse( ctx.tokens = &token_stream; ctx.token_class = token_class; + ctx.offset = 0; CG(ast) = NULL; CG(ast_arena) = zend_arena_create(1024 * 32);