From d45de6d4bd0052fa0187b0ea4e6c274fded7bdc0 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Tue, 7 Jul 2026 12:10:33 +0200 Subject: [PATCH] grammar: fix inconsistency with parser Consider the new test case (found by AFL++): test{for(0)|t|0,const w=0;} Currently the grammar backtracks after failing to parse this with the ForStatement rule and instead finds that the VarAssignStatement rule is a match. This behavior is not LL(k) and inconsistent with the parser. Add the required negative lookahead to eliminate this case and similar cases. --- doc/langref/grammar.peg | 10 ++++- lib/std/zig/Parse.zig | 10 ++++- lib/std/zig/parser_fuzz.zig | 5 +++ lib/std/zig/parser_generated_oracle.zig | 57 ++++++++++++++++--------- 4 files changed, 57 insertions(+), 25 deletions(-) diff --git a/doc/langref/grammar.peg b/doc/langref/grammar.peg index 17c25427dc8f3a4b438f11d8fc5e923540b06c74..dd0b92e9237f7946658c785b9e4e035049805535 100644 --- a/doc/langref/grammar.peg +++ b/doc/langref/grammar.peg @@ -77,7 +77,7 @@ BlockStatement <- Statement / KEYWORD_defer BlockExprStatement / KEYWORD_errdefer BlockExprStatement - / !KEYWORD_nosuspend (KEYWORD_comptime !BlockExprPrefix)? VarAssignStatement + / !StatementPrefix KEYWORD_comptime? VarAssignStatement Statement <- IfStatement @@ -85,7 +85,13 @@ Statement / KEYWORD_nosuspend BlockExprStatement / KEYWORD_comptime BlockExpr / KEYWORD_suspend BlockExprStatement - / (KEYWORD_comptime !BlockExprPrefix)? AssignExpr SEMICOLON + / !StatementPrefix KEYWORD_comptime? AssignExpr SEMICOLON + +StatementPrefix + <- KEYWORD_if + / BlockLabel? (LBRACE / KEYWORD_inline? (KEYWORD_for / KEYWORD_while) / KEYWORD_switch) + / KEYWORD_nosuspend + / KEYWORD_comptime BlockExprPrefix IfStatement <- IfPrefix BlockExpr (KEYWORD_else Payload? Statement / !KEYWORD_else) diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index bb75a31af95eda7cbd138f4b7ffe509fafc45d15..3d9883269d261d9437805d4b74e9a7b36d78b92a 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -956,7 +956,7 @@ fn expectContainerField(p: *Parse) !Node.Index { /// <- Statement /// / KEYWORD_defer BlockExprStatement /// / KEYWORD_errdefer BlockExprStatement -/// / (KEYWORD_comptime !BlockExprPrefix)? VarAssignStatement +/// / !StatementPrefix KEYWORD_comptime? VarAssignStatement /// /// Statement /// <- IfStatement @@ -964,7 +964,13 @@ fn expectContainerField(p: *Parse) !Node.Index { /// / KEYWORD_nosuspend BlockExprStatement /// / KEYWORD_comptime BlockExpr /// / KEYWORD_suspend BlockExprStatement -/// / (KEYWORD_comptime !BlockExprPrefix)? AssignExpr SEMICOLON +/// / !StatementPrefix KEYWORD_comptime? AssignExpr SEMICOLON +/// +/// StatementPrefix +/// <- KEYWORD_if +/// / BlockLabel? (LBRACE / KEYWORD_inline? (KEYWORD_for / KEYWORD_while) / KEYWORD_switch) +/// / KEYWORD_nosuspend +/// / KEYWORD_comptime BlockExprPrefix fn expectStatement(p: *Parse, is_block_level: bool) Error!Node.Index { if (p.eatToken(.keyword_comptime)) |comptime_token| { const opt_block_expr = try p.parseBlockExpr(); diff --git a/lib/std/zig/parser_fuzz.zig b/lib/std/zig/parser_fuzz.zig index 7cbf4f895c1c940dfceaa0b1e8a93df5c1e9db74..298b1dea8536a095567fb6931659c3ca3c038ba1 100644 --- a/lib/std/zig/parser_fuzz.zig +++ b/lib/std/zig/parser_fuzz.zig @@ -153,6 +153,11 @@ test "resume block chained compare ops" { try checkAgainstOracle("test{resume{0 > 0;} > 0 > 0;}"); } +// Found using AFL++ +test "for multiassign" { + try checkAgainstOracle("test{for(0)|t|0,const w=0;}"); +} + fn checkAgainstOracle(source: [:0]const u8) !void { var fba_buf: [1 << 18]u8 = undefined; var fba: std.heap.FixedBufferAllocator = .init(&fba_buf); diff --git a/lib/std/zig/parser_generated_oracle.zig b/lib/std/zig/parser_generated_oracle.zig index 15252098a1839c445e16c05db6a3ff144e1b5668..73525a9d21be6895fee477a54e71c39a147d3f52 100644 --- a/lib/std/zig/parser_generated_oracle.zig +++ b/lib/std/zig/parser_generated_oracle.zig @@ -270,20 +270,10 @@ const Parser = struct { p.i = pos_0; if (blk_1: { const pos_1 = p.i; - const match_1 = try p.parseKEYWORD_nosuspend(); + const match_1 = try p.parseStatementPrefix(); p.i = pos_1; break :blk_1 !match_1; - } and (blk_3: { - const pos_3 = p.i; - if (try p.parseKEYWORD_comptime() and blk_4: { - const pos_4 = p.i; - const match_4 = try p.parseBlockExprPrefix(); - p.i = pos_4; - break :blk_4 !match_4; - }) break :blk_3 true; - p.i = pos_3; - break :blk_3 false; - } or true) and try p.parseVarAssignStatement()) break :blk_0 true; + } and (try p.parseKEYWORD_comptime() or true) and try p.parseVarAssignStatement()) break :blk_0 true; p.i = pos_0; break :blk_0 false; }; @@ -301,17 +291,42 @@ const Parser = struct { p.i = pos_0; if (try p.parseKEYWORD_suspend() and try p.parseBlockExprStatement()) break :blk_0 true; p.i = pos_0; - if ((blk_3: { - const pos_3 = p.i; - if (try p.parseKEYWORD_comptime() and blk_4: { + if (blk_1: { + const pos_1 = p.i; + const match_1 = try p.parseStatementPrefix(); + p.i = pos_1; + break :blk_1 !match_1; + } and (try p.parseKEYWORD_comptime() or true) and try p.parseAssignExpr() and try p.parseSEMICOLON()) break :blk_0 true; + p.i = pos_0; + break :blk_0 false; + }; + } + pub fn parseStatementPrefix(p: *Parser) Error!bool { + return blk_0: { + const pos_0 = p.i; + if (try p.parseKEYWORD_if()) break :blk_0 true; + p.i = pos_0; + if ((try p.parseBlockLabel() or true) and blk_2: { + const pos_2 = p.i; + if (try p.parseLBRACE()) break :blk_2 true; + p.i = pos_2; + if ((try p.parseKEYWORD_inline() or true) and blk_4: { const pos_4 = p.i; - const match_4 = try p.parseBlockExprPrefix(); + if (try p.parseKEYWORD_for()) break :blk_4 true; p.i = pos_4; - break :blk_4 !match_4; - }) break :blk_3 true; - p.i = pos_3; - break :blk_3 false; - } or true) and try p.parseAssignExpr() and try p.parseSEMICOLON()) break :blk_0 true; + if (try p.parseKEYWORD_while()) break :blk_4 true; + p.i = pos_4; + break :blk_4 false; + }) break :blk_2 true; + p.i = pos_2; + if (try p.parseKEYWORD_switch()) break :blk_2 true; + p.i = pos_2; + break :blk_2 false; + }) break :blk_0 true; + p.i = pos_0; + if (try p.parseKEYWORD_nosuspend()) break :blk_0 true; + p.i = pos_0; + if (try p.parseKEYWORD_comptime() and try p.parseBlockExprPrefix()) break :blk_0 true; p.i = pos_0; break :blk_0 false; }; -- 2.54.0