Discarding PM_TOKEN_HEREDOC_START in hash pattern key recovery strands PM_LEX_HEREDOC on the lex mode stack
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 72/100
Línea de trabajo
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.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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).
- Lenguaje dominante
- C
- Estrellas
- 1k
- Forks
- 202
- Merge medio
- 5 h 55 min
- PR fusionados (30 d)
- 85
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de ruby/prism
-
Prism::Translation::Parser raises NoMethodError for a method with a &nil parameter and a bodyPosiblemente ocupada @bbatsov la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
Los mantenedores suelen responder en 1 día
-
Heredoc and outer-context recovery at recursion depth / stack budget limit causes location underflowAbierto
Dificultad 4/5 3-5 días Aptitud para principiantes 43/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 45/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 1/100
Los mantenedores suelen responder en 1 día
Todos los issues de ruby/prism
Issues similares
-
Memory leak: "Add to Playlist" (`CMD_EVENT_ADD_TO_PLAYLIST`) never frees the playlist it loadsAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
Los mantenedores suelen responder en 1 día
-
Signed/unsigned mix in the EOCD backward scan rejects valid archives with a 65535-byte commentAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
OpenXiangShan/NEMU#1287 ·
Los mantenedores suelen responder en 1 día
-
area: recompiler area: runtime kind: bug preview-effort: small priority: low
Dificultad 2/5 1-3 horas Aptitud para principiantes 74/100
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día