authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-06-18 12:40:06+02:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-07-08 11:48:59+02:00
loga4254123ebb257c1c739ad5bb3c93049b51539db
tree3e17e20838eba05f2e022e832dce5fc1306c27bf
parent0c7688332d10b5ae460e756c2d5a3f58ead26367
signaturelock-open Commit is signed but in an unrecognized format.

grammar: introduce !ExprPrefix

This is necessary to match the behavior of the parser. This is not a complete list of of prefixes that cause the PEG to differ in behavior from the parser, it is however difficult to determine this list by reading the grammar alone and I plan to let the AFL++ fuzzer find the missing items and fill it in while adding test cases to prevent regression.

3 files changed, 111 insertions(+), 17 deletions(-)

doc/langref/grammar.peg+8-6
......@@ -129,12 +129,12 @@ PrefixExpr <- PrefixOp* !PrefixOp PrimaryExpr
129129PrimaryExpr
130130 <- AsmExpr
131131 / IfExpr
132 / KEYWORD_break BreakLabel? Expr?
132 / KEYWORD_break BreakLabel? (Expr / !ExprPrefix)
133133 / KEYWORD_comptime Expr
134134 / KEYWORD_nosuspend Expr
135 / KEYWORD_continue BreakLabel? Expr?
135 / KEYWORD_continue BreakLabel? (Expr / !ExprPrefix)
136136 / KEYWORD_resume Expr
137 / KEYWORD_return Expr?
137 / KEYWORD_return (Expr / !ExprPrefix)
138138 / BlockLabel? LoopExpr
139139 / Block
140140 / CurlySuffixExpr
......@@ -265,7 +265,7 @@ SwitchItem <- Expr (DOT3 Expr)?
265265# For specific
266266ForArgumentsList <- ForItem (COMMA ForItem)* COMMA?
267267
268ForItem <- Expr (DOT2 Expr?)?
268ForItem <- Expr (DOT2 (Expr / !ExprPrefix) / !DOT2)
269269
270270# Operators
271271AssignOp
......@@ -363,7 +363,7 @@ PrefixTypeOpPrefix
363363 / ASTERISK
364364
365365SuffixOp
366 <- LBRACKET Expr (DOT2 Expr? (COLON Expr)?)? RBRACKET
366 <- LBRACKET Expr (DOT2 (Expr / !ExprPrefix) (COLON Expr)?)? RBRACKET
367367 / DOT IDENTIFIER
368368 / DOTASTERISK
369369 / DOTQUESTIONMARK
......@@ -412,7 +412,9 @@ AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
412412
413413ParamDeclList <- (ParamDecl COMMA)* (ParamDecl / DOT3 COMMA?)?
414414
415ExprList <- (Expr COMMA)* Expr?
415ExprList <- (Expr COMMA)* (Expr / !ExprPrefix)
416
417ExprPrefix <- ASTERISK
416418
417419# *** Tokens ***
418420
lib/std/zig/parser_fuzz.zig+5
......@@ -68,6 +68,11 @@ test "comptime fn" {
6868 try checkAgainstOracle("comptime fn()0");
6969}
7070
71// Found using AFL++
72test "return asterisk" {
73 try checkAgainstOracle("test{return*!0;}");
74}
75
7176fn checkAgainstOracle(source: [:0]const u8) !void {
7277 var fba_buf: [1 << 18]u8 = undefined;
7378 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);
lib/std/zig/parser_generated_oracle.zig+98-11
......@@ -661,17 +661,53 @@ const Parser = struct {
661661 p.i = pos_0;
662662 if (p.parseIfExpr()) break :blk_0 true;
663663 p.i = pos_0;
664 if (p.parseKEYWORD_break() and (p.parseBreakLabel() or true) and (p.parseExpr() or true)) break :blk_0 true;
664 if (p.parseKEYWORD_break() and (p.parseBreakLabel() or true) and blk_2: {
665 const pos_2 = p.i;
666 if (p.parseExpr()) break :blk_2 true;
667 p.i = pos_2;
668 if (blk_3: {
669 const pos_3 = p.i;
670 const match_3 = p.parseExprPrefix();
671 p.i = pos_3;
672 break :blk_3 !match_3;
673 }) break :blk_2 true;
674 p.i = pos_2;
675 break :blk_2 false;
676 }) break :blk_0 true;
665677 p.i = pos_0;
666678 if (p.parseKEYWORD_comptime() and p.parseExpr()) break :blk_0 true;
667679 p.i = pos_0;
668680 if (p.parseKEYWORD_nosuspend() and p.parseExpr()) break :blk_0 true;
669681 p.i = pos_0;
670 if (p.parseKEYWORD_continue() and (p.parseBreakLabel() or true) and (p.parseExpr() or true)) break :blk_0 true;
682 if (p.parseKEYWORD_continue() and (p.parseBreakLabel() or true) and blk_2: {
683 const pos_2 = p.i;
684 if (p.parseExpr()) break :blk_2 true;
685 p.i = pos_2;
686 if (blk_3: {
687 const pos_3 = p.i;
688 const match_3 = p.parseExprPrefix();
689 p.i = pos_3;
690 break :blk_3 !match_3;
691 }) break :blk_2 true;
692 p.i = pos_2;
693 break :blk_2 false;
694 }) break :blk_0 true;
671695 p.i = pos_0;
672696 if (p.parseKEYWORD_resume() and p.parseExpr()) break :blk_0 true;
673697 p.i = pos_0;
674 if (p.parseKEYWORD_return() and (p.parseExpr() or true)) break :blk_0 true;
698 if (p.parseKEYWORD_return() and blk_2: {
699 const pos_2 = p.i;
700 if (p.parseExpr()) break :blk_2 true;
701 p.i = pos_2;
702 if (blk_3: {
703 const pos_3 = p.i;
704 const match_3 = p.parseExprPrefix();
705 p.i = pos_3;
706 break :blk_3 !match_3;
707 }) break :blk_2 true;
708 p.i = pos_2;
709 break :blk_2 false;
710 }) break :blk_0 true;
675711 p.i = pos_0;
676712 if ((p.parseBlockLabel() or true) and p.parseLoopExpr()) break :blk_0 true;
677713 p.i = pos_0;
......@@ -1314,12 +1350,31 @@ const Parser = struct {
13141350 pub fn parseForItem(p: *Parser) bool {
13151351 return blk_0: {
13161352 const pos_0 = p.i;
1317 if (p.parseExpr() and (blk_3: {
1318 const pos_3 = p.i;
1319 if (p.parseDOT2() and (p.parseExpr() or true)) break :blk_3 true;
1320 p.i = pos_3;
1321 break :blk_3 false;
1322 } or true)) break :blk_0 true;
1353 if (p.parseExpr() and blk_2: {
1354 const pos_2 = p.i;
1355 if (p.parseDOT2() and blk_4: {
1356 const pos_4 = p.i;
1357 if (p.parseExpr()) break :blk_4 true;
1358 p.i = pos_4;
1359 if (blk_5: {
1360 const pos_5 = p.i;
1361 const match_5 = p.parseExprPrefix();
1362 p.i = pos_5;
1363 break :blk_5 !match_5;
1364 }) break :blk_4 true;
1365 p.i = pos_4;
1366 break :blk_4 false;
1367 }) break :blk_2 true;
1368 p.i = pos_2;
1369 if (blk_3: {
1370 const pos_3 = p.i;
1371 const match_3 = p.parseDOT2();
1372 p.i = pos_3;
1373 break :blk_3 !match_3;
1374 }) break :blk_2 true;
1375 p.i = pos_2;
1376 break :blk_2 false;
1377 }) break :blk_0 true;
13231378 p.i = pos_0;
13241379 break :blk_0 false;
13251380 };
......@@ -1662,7 +1717,19 @@ const Parser = struct {
16621717 const pos_0 = p.i;
16631718 if (p.parseLBRACKET() and p.parseExpr() and (blk_3: {
16641719 const pos_3 = p.i;
1665 if (p.parseDOT2() and (p.parseExpr() or true) and (blk_6: {
1720 if (p.parseDOT2() and blk_5: {
1721 const pos_5 = p.i;
1722 if (p.parseExpr()) break :blk_5 true;
1723 p.i = pos_5;
1724 if (blk_6: {
1725 const pos_6 = p.i;
1726 const match_6 = p.parseExprPrefix();
1727 p.i = pos_6;
1728 break :blk_6 !match_6;
1729 }) break :blk_5 true;
1730 p.i = pos_5;
1731 break :blk_5 false;
1732 } and (blk_6: {
16661733 const pos_6 = p.i;
16671734 if (p.parseCOLON() and p.parseExpr()) break :blk_6 true;
16681735 p.i = pos_6;
......@@ -1934,7 +2001,27 @@ const Parser = struct {
19342001 break :blk_3 false;
19352002 }) {}
19362003 break :blk_1 true;
1937 } and (p.parseExpr() or true)) break :blk_0 true;
2004 } and blk_2: {
2005 const pos_2 = p.i;
2006 if (p.parseExpr()) break :blk_2 true;
2007 p.i = pos_2;
2008 if (blk_3: {
2009 const pos_3 = p.i;
2010 const match_3 = p.parseExprPrefix();
2011 p.i = pos_3;
2012 break :blk_3 !match_3;
2013 }) break :blk_2 true;
2014 p.i = pos_2;
2015 break :blk_2 false;
2016 }) break :blk_0 true;
2017 p.i = pos_0;
2018 break :blk_0 false;
2019 };
2020 }
2021 pub fn parseExprPrefix(p: *Parser) bool {
2022 return blk_0: {
2023 const pos_0 = p.i;
2024 if (p.parseASTERISK()) break :blk_0 true;
19382025 p.i = pos_0;
19392026 break :blk_0 false;
19402027 };