From 0253a34412633e636046eb7ee06b5c6a90a7bb17 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 4 Aug 2026 10:19:56 -0400 Subject: [PATCH] Ensure symbol encoding is not leaked across alias --- src/prism.c | 35 +++++++++++---------- test/prism/encoding/string_encoding_test.rb | 21 +++++++++++++ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/prism.c b/src/prism.c index ec5a90752b..2161121e5c 100644 --- a/src/prism.c +++ b/src/prism.c @@ -6766,6 +6766,21 @@ pm_symbol_node_create_current_string(pm_parser_t *parser, const pm_token_t *open return node; } +/** + * Allocate and initialize a new SymbolNode node from a bare name. This covers + * every symbol whose contents are shared straight from the source: method + * names, keywords, constants, identifiers, instance/class/global variables, and + * numbered or back references. None of them hold escapes, so the encoding is + * determined entirely by those contents. + */ +static pm_symbol_node_t * +pm_symbol_node_bare_create(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *value) { + pm_string_t unescaped; + pm_string_shared_init(&unescaped, value->start, value->end); + + return pm_symbol_node_create_unescaped(parser, opening, value, NULL, &unescaped, parse_symbol_encoding(parser, NULL, value, &unescaped, false)); +} + /** * Allocate and initialize a new SymbolNode node from a label. */ @@ -6779,7 +6794,7 @@ pm_symbol_node_label_create(pm_parser_t *parser, const pm_token_t *token) { assert((label.end - label.start) >= 0); pm_string_shared_init(&node->unescaped, label.start, label.end); - pm_node_flag_set(UP(node), parse_symbol_encoding(parser, parser->explicit_encoding, &label, &node->unescaped, false)); + pm_node_flag_set(UP(node), parse_symbol_encoding(parser, NULL, &label, &node->unescaped, false)); return node; } @@ -16240,11 +16255,7 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s break; } - pm_symbol_node_t *symbol = pm_symbol_node_create(parser, &opening, &parser->previous, NULL); - pm_string_shared_init(&symbol->unescaped, parser->previous.start, parser->previous.end); - pm_node_flag_set(UP(symbol), parse_symbol_encoding(parser, parser->explicit_encoding, &parser->previous, &symbol->unescaped, false)); - - return UP(symbol); + return UP(pm_symbol_node_bare_create(parser, &opening, &parser->previous)); } if (lex_mode->as.string.interpolation) { @@ -16361,11 +16372,7 @@ parse_undef_argument(pm_parser_t *parser, uint16_t depth) { case PM_TOKEN_METHOD_NAME: { parser_lex(parser); - pm_symbol_node_t *symbol = pm_symbol_node_create(parser, NULL, &parser->previous, NULL); - pm_string_shared_init(&symbol->unescaped, parser->previous.start, parser->previous.end); - pm_node_flag_set(UP(symbol), parse_symbol_encoding(parser, parser->explicit_encoding, &parser->previous, &symbol->unescaped, false)); - - return UP(symbol); + return UP(pm_symbol_node_bare_create(parser, NULL, &parser->previous)); } case PM_TOKEN_SYMBOL_BEGIN: { pm_lex_mode_t lex_mode = *parser->lex_modes.current; @@ -16397,11 +16404,7 @@ parse_alias_argument(pm_parser_t *parser, bool first, uint16_t depth) { if (first) lex_state_set(parser, PM_LEX_STATE_FNAME | PM_LEX_STATE_FITEM); parser_lex(parser); - pm_symbol_node_t *symbol = pm_symbol_node_create(parser, NULL, &parser->previous, NULL); - pm_string_shared_init(&symbol->unescaped, parser->previous.start, parser->previous.end); - pm_node_flag_set(UP(symbol), parse_symbol_encoding(parser, parser->explicit_encoding, &parser->previous, &symbol->unescaped, false)); - - return UP(symbol); + return UP(pm_symbol_node_bare_create(parser, NULL, &parser->previous)); } case PM_TOKEN_SYMBOL_BEGIN: { pm_lex_mode_t lex_mode = *parser->lex_modes.current; diff --git a/test/prism/encoding/string_encoding_test.rb b/test/prism/encoding/string_encoding_test.rb index bc563b0893..9dd57f949d 100644 --- a/test/prism/encoding/string_encoding_test.rb +++ b/test/prism/encoding/string_encoding_test.rb @@ -123,6 +123,27 @@ def test_encodings_are_not_leaked_across_hash_elements end end + def test_encodings_are_not_leaked_into_undef_and_alias + { + ["ascii-8bit", "undef :\"\\u00E9\", foo"] => [Encoding::UTF_8, Encoding::US_ASCII], + ["ascii-8bit", "undef foo, :\"\\u00E9\""] => [Encoding::US_ASCII, Encoding::UTF_8], + ["ascii-8bit", "alias :\"\\u00E9\" foo"] => [Encoding::UTF_8, Encoding::US_ASCII], + ["ascii-8bit", "alias foo :\"\\u00E9\""] => [Encoding::US_ASCII, Encoding::UTF_8], + # A US-ASCII source is the only one that forces a symbol to binary, so + # it is the only place the binary branch can leak into the bare name. + ["us-ascii", "undef :\"\\xE9\", foo"] => [Encoding::ASCII_8BIT, Encoding::US_ASCII], + ["us-ascii", "alias :\"\\xE9\" foo"] => [Encoding::ASCII_8BIT, Encoding::US_ASCII] + }.each do |(encoding, expression), expected| + result = Prism.parse("# encoding: #{encoding}\n#{expression}") + assert_predicate result, :success?, "failed to parse: #{expression}" + + node = result.statement + names = node.is_a?(UndefNode) ? node.names : [node.new_name, node.old_name] + + assert_equal expected, names.map { |name| binary_node_encoding(name) }, "#{encoding} #{expression}" + end + end + private def binary_node_encoding(node)