Discarding PM_TOKEN_HEREDOC_START in hash pattern key recovery strands PM_LEX_HEREDOC on the lex mode stack
維護者通常 1 天內回覆
還沒有人認領這個 Issue。
評估
研究方向
Start in src/prism.c at parse_pattern_primitive, specifically the PM_TOKEN_BRACE_LEFT_HASH recovery branch, and trace how PM_TOKEN_HEREDOC_START is handled compared with parse_heredoc. Reproduce with ./bin/parse -e $'x in {<<0\n#{class' and inspect the parser tests for pattern recovery and heredocs. Done means this input no longer strands a lex mode or produces a node with an underflowed location length, while retaining appropriate parse errors.
由索引模型根據 Issue 內容生成。
描述
Fuzzer find on bdbd482dc.
Simulation at https://stevenjohnstone.github.io/prism-loupe/?version=v2.1.0&code=eCBpbiB7PDwwCiN7Y2xhc3M%3D.
In parse_pattern_primitive (case PM_TOKEN_BRACE_LEFT_HASH:), when the parser encounters an invalid token where a hash pattern key is expected, the default: error-recovery branch calls parser_lex(parser) to discard parser->current.
When parser->current is PM_TOKEN_HEREDOC_START (e.g., x in {<<0), lex_heredoc_start has already pushed PM_LEX_HEREDOC onto parser->lex_modes. Discarding PM_TOKEN_HEREDOC_START with a raw parser_lex(parser) instead of leaving it to be parsed via parse_heredoc leaves PM_LEX_HEREDOC stranded on parser->lex_modes while the parser unwinds back to outer statement parsing. When the stranded PM_LEX_HEREDOC later reaches EOF inside an outer node on line 2, it rewinds parser->next_start back to line 1 mid-node, causing start > end and underflowing uint32_t location.length.
Reproduction
✗ ./bin/parse -e $'x in {<<0\n#{class'
Errors:
[#<Prism::ParseError @type=:pattern_hash_key @message="unexpected heredoc beginning; expected a key in the hash pattern" @location=#<Prism::Location @start_offset=6 @length=3 start_line=1> @level=:syntax>,
#<Prism::ParseError @type=:pattern_hash_key_label @message="expected a label as the key in the hash pattern" @location=#<Prism::Location @start_offset=6 @length=3 start_line=1> @level=:syntax>,
#<Prism::ParseError @type=:pattern_term_brace @message="expected a `}` to close the pattern expression" @location=#<Prism::Location @start_offset=5 @length=1 start_line=1> @level=:syntax>,
#<Prism::ParseError @type=:expect_eol_after_statement @message="unexpected '\#{', expecting end-of-input" @location=#<Prism::Location @start_offset=10 @length=2 start_line=2> @level=:syntax>,
#<Prism::ParseError @type=:unexpected_token_ignore @message="unexpected '\#{', ignoring it" @location=#<Prism::Location @start_offset=10 @length=2 start_line=2> @level=:syntax>,
#<Prism::ParseError @type=:heredoc_term @message="unterminated heredoc; can't find string \"0\" anywhere before EOF" @location=#<Prism::Location @start_offset=8 @length=1 start_line=1> @level=:syntax>,
#<Prism::ParseError @type=:class_name @message="unexpected constant path after `class`; class/module name must be CONSTANT" @location=#<Prism::Location @start_offset=12 @length=5 start_line=2> @level=:syntax>,
#<Prism::ParseError @type=:class_name @message="unexpected constant path after `class`; class/module name must be CONSTANT" @location=#<Prism::Location @start_offset=12 @length=5 start_line=2> @level=:syntax>,
#<Prism::ParseError @type=:unexpected_token_ignore @message="unexpected heredoc ending, ignoring it" @location=#<Prism::Location @start_offset=17 @length=0 start_line=2> @level=:syntax>,
#<Prism::ParseError @type=:unexpected_token_close_context @message="unexpected end-of-input, assuming it is closing the parent top level context" @location=#<Prism::Location @start_offset=17 @length=0 start_line=2> @level=:syntax>,
#<Prism::ParseError @type=:class_term @message="expected an `end` to close the `class` statement" @location=#<Prism::Location @start_offset=12 @length=5 start_line=2> @level=:syntax>,
#<Prism::ParseError @type=:class_name @message="unexpected constant path after `class`; class/module name must be CONSTANT" @location=#<Prism::Location @start_offset=12 @length=5 start_line=2> @level=:syntax>]
AST:
@ ProgramNode (location: (1,0)-(2,0))
├── flags: ∅
├── locals: []
└── statements:
@ StatementsNode (location: (1,0)-(2,0))
├── flags: ∅
└── body: (length: 3)
├── @ MatchPredicateNode (location: (1,0)-(1,9))
│ ├── flags: newline
│ ├── value:
│ │ @ CallNode (location: (1,0)-(1,1))
│ │ ├── flags: variable_call, ignore_visibility
│ │ ├── receiver: ∅
│ │ ├── call_operator_loc: ∅
│ │ ├── name: :x
│ │ ├── message_loc: (1,0)-(1,1) = "x"
│ │ ├── opening_loc: ∅
│ │ ├── arguments: ∅
│ │ ├── closing_loc: ∅
│ │ ├── equal_loc: ∅
│ │ └── block: ∅
│ ├── pattern:
│ │ @ HashPatternNode (location: (1,5)-(1,9))
│ │ ├── flags: ∅
│ │ ├── constant: ∅
│ │ ├── elements: (length: 1)
│ │ │ └── @ AssocNode (location: (1,6)-(1,9))
│ │ │ ├── flags: ∅
│ │ │ ├── key:
│ │ │ │ @ ErrorRecoveryNode (location: (1,6)-(1,9))
│ │ │ │ ├── flags: ∅
│ │ │ │ └── unexpected: ∅
│ │ │ ├── value:
│ │ │ │ @ ErrorRecoveryNode (location: (1,6)-(1,9))
│ │ │ │ ├── flags: ∅
│ │ │ │ └── unexpected: ∅
│ │ │ └── operator_loc: ∅
│ │ ├── rest: ∅
│ │ ├── opening_loc: (1,5)-(1,6) = "{"
│ │ └── closing_loc: ∅
│ └── keyword_loc: (1,2)-(1,4) = "in"
├── @ ErrorRecoveryNode (location: (1,9)-(1,9))
│ ├── flags: newline
│ └── unexpected: ∅
└── @ ClassNode (location: (2,2)-(2,4294967296))
├── flags: newline
├── locals: []
├── class_keyword_loc: (2,2)-(2,7) = "class"
├── constant_path:
│ @ ErrorRecoveryNode (location: (2,2)-(2,7))
│ ├── flags: ∅
│ └── unexpected: ∅
├── inheritance_operator_loc: ∅
├── superclass: ∅
├── body:
│ @ StatementsNode (location: (1,9)-(2,4))
│ ├── flags: ∅
│ └── body: (length: 2)
│ ├── @ ErrorRecoveryNode (location: (2,2)-(2,7))
│ │ ├── flags: newline
│ │ └── unexpected: ∅
│ └── @ ErrorRecoveryNode (location: (1,9)-(2,0))
│ ├── flags: newline
│ └── unexpected: ∅
├── end_keyword_loc: (2,0)-(2,0) = ""
└── name: :class
Byte offsets in "x in {<<0\n\#{class" (17 bytes):
x in {:0..6((1,0)-(1,6))<<0:6..9((1,6)-(1,9))\n:9..10((1,9)-(2,0))#{:10..12((2,0)-(2,2))class:12..17((2,2)-(2,7))
In Prism.parse("x in {<<0\n\#{class"), the ClassNode on line 2 starts at byte 12 (class at (2,2)) and ends at byte 10 ((2,0), at the end of the rewound \n 9..10), wrapping uint32_t location.length (U32(10 - 12)) to 4294967294 (0xFFFFFFFE, printed as (2,2)-(2,4294967296) by ./bin/parse).
Root Cause
- On line 1 (
x in {<<0\n),inbegins a pattern matching predicate (parse_pattern), and{begins a hash pattern (case PM_TOKEN_BRACE_LEFT_HASH:inparse_pattern_primitive). - Inside
{, the next token is<<0(PM_TOKEN_HEREDOC_START, offsets6..9). Whenparser_lexlexes<<0, it pushesPM_LEX_HEREDOContoparser->lex_modes(savinglex_mode.as.heredoc.next_start = 9, pointing at\non line 1) and setsparser->next_start = 10(#{classon line 2). - A heredoc is not a valid hash pattern key. In
parse_pattern_primitive(src/prism.c), thedefault:branch currently does:
default: {
PM_PARSER_ERR_TOKEN_FORMAT(parser, &parser->current, PM_ERR_PATTERN_HASH_KEY, pm_token_str(parser->current.type));
parser_lex(parser);
first_node = UP(pm_error_recovery_node_create(parser, PM_TOKEN_START(parser, &parser->previous), PM_TOKEN_LENGTH(&parser->previous)));
break;
}
- Calling
parser_lex(parser)discardsPM_TOKEN_HEREDOC_STARTwithout callingparse_heredoc, and immediately lexes the first token of the heredoc body on line 2 (#{,PM_TOKEN_EMBEXPR_BEGIN[10..12], which pushesPM_LEX_EMBEXPRon top of the strandedPM_LEX_HEREDOCwithoutparse_string_partever pushingPM_CONTEXT_EMBEXPRontoparser->contexts). parse_pattern_hashrecordsPM_ERR_PATTERN_HASH_KEY_LABEL,expect1_openingrecords a missing}(PM_ERR_PATTERN_TERM_BRACE), andMatchPredicateNodereturns to the top-levelparse_statementsloop withparser->current = PM_TOKEN_EMBEXPR_BEGIN[10..12].- The top-level
parse_statementsloop skips the unexpected#{token viaparser_lex(parser)(advancing toclassat12..17) and entersparse_classwhilePM_LEX_EMBEXPRand the strandedPM_LEX_HEREDOCare still onparser->lex_modes. - Consuming
class(12..17) hits EOF at byte17, poppingPM_LEX_EMBEXPRand running the strandedPM_LEX_HEREDOCat EOF, which emitsPM_TOKEN_HEREDOC_END[17..17]and setsparser->next_start = 9(\non line 1) andparser->heredoc_end = 17. - Inside
parse_class,parse_statements(parser, PM_CONTEXT_CLASS, ...)skipsPM_TOKEN_HEREDOC_END, rewinds to lex and acceptPM_TOKEN_NEWLINE[9..10](flushingnext_start = 17back toEOF[17..17]), setsparser->recovering = trueonEOF, and synthesizes a missingendtoken atparser->previous.end = 10((2,0)), givingClassNodestart = 12andend = 10(U32(10 - 12) = 4294967294).
- 主要語言
- C
- 星號
- 1k
- 分支
- 202
- 平均合併
- 5 小時 55 分鐘
- 30 天內合併 PR
- 85
環境準備
- 沒有 Dockerfile 或 Docker Compose 檔案
- 沒有 Pull Request 範本
- 閱讀貢獻指南
從這裡開始
- 先讀完整個 Issue,再讀專案的貢獻指南。
- 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
- Fork 儲存庫,在一個分支上完成修改。
- 送出 Pull Request,並在描述裡引用這個 Issue 編號。
ruby/prism 的其他 Issue
-
Prism::Translation::Parser raises NoMethodError for a method with a &nil parameter and a body可能已有人在做 @bbatsov 今天認領。 未關閉
難度 2/5 1-3 小時 新手友好度 82/100
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 74/100
維護者通常 1 天內回覆
-
Heredoc and outer-context recovery at recursion depth / stack budget limit causes location underflow未關閉
難度 4/5 3-5 天 新手友好度 43/100
維護者通常 1 天內回覆
-
難度 4/5 3-5 天 新手友好度 45/100
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 1/100
維護者通常 1 天內回覆
相似的 Issue
-
runtime: lf_f128_mod is documented as fmodq (truncating) but implements floor-based modulo; unused未關閉internal correctness low priority rt_lib
難度 2/5 1-3 小時 新手友好度 72/100
維護者通常 1 天內回覆
-
bug
難度 2/5 1-3 小時 新手友好度 70/100
SanderMertens/flecs#2395 ·
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 76/100
維護者通常 1 天內回覆
-
難度 2/5 1-3 小時 新手友好度 78/100
-
難度 2/5 1-3 小時 新手友好度 78/100
OpenXiangShan/NEMU#1287 ·
維護者通常 1 天內回覆