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...@@ -129,12 +129,12 @@ PrefixExpr <- PrefixOp* !PrefixOp PrimaryExpr
129PrimaryExpr129PrimaryExpr
130 <- AsmExpr130 <- AsmExpr
131 / IfExpr131 / IfExpr
132 / KEYWORD_break BreakLabel? Expr?132 / KEYWORD_break BreakLabel? (Expr / !ExprPrefix)
133 / KEYWORD_comptime Expr133 / KEYWORD_comptime Expr
134 / KEYWORD_nosuspend Expr134 / KEYWORD_nosuspend Expr
135 / KEYWORD_continue BreakLabel? Expr?135 / KEYWORD_continue BreakLabel? (Expr / !ExprPrefix)
136 / KEYWORD_resume Expr136 / KEYWORD_resume Expr
137 / KEYWORD_return Expr?137 / KEYWORD_return (Expr / !ExprPrefix)
138 / BlockLabel? LoopExpr138 / BlockLabel? LoopExpr
139 / Block139 / Block
140 / CurlySuffixExpr140 / CurlySuffixExpr
...@@ -265,7 +265,7 @@ SwitchItem <- Expr (DOT3 Expr)?...@@ -265,7 +265,7 @@ SwitchItem <- Expr (DOT3 Expr)?
265# For specific265# For specific
266ForArgumentsList <- ForItem (COMMA ForItem)* COMMA?266ForArgumentsList <- ForItem (COMMA ForItem)* COMMA?
267267
268ForItem <- Expr (DOT2 Expr?)?268ForItem <- Expr (DOT2 (Expr / !ExprPrefix) / !DOT2)
269269
270# Operators270# Operators
271AssignOp271AssignOp
...@@ -363,7 +363,7 @@ PrefixTypeOpPrefix...@@ -363,7 +363,7 @@ PrefixTypeOpPrefix
363 / ASTERISK363 / ASTERISK
364364
365SuffixOp365SuffixOp
366 <- LBRACKET Expr (DOT2 Expr? (COLON Expr)?)? RBRACKET366 <- LBRACKET Expr (DOT2 (Expr / !ExprPrefix) (COLON Expr)?)? RBRACKET
367 / DOT IDENTIFIER367 / DOT IDENTIFIER
368 / DOTASTERISK368 / DOTASTERISK
369 / DOTQUESTIONMARK369 / DOTQUESTIONMARK
...@@ -412,7 +412,9 @@ AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?...@@ -412,7 +412,9 @@ AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
412412
413ParamDeclList <- (ParamDecl COMMA)* (ParamDecl / DOT3 COMMA?)?413ParamDeclList <- (ParamDecl COMMA)* (ParamDecl / DOT3 COMMA?)?
414414
415ExprList <- (Expr COMMA)* Expr?415ExprList <- (Expr COMMA)* (Expr / !ExprPrefix)
416
417ExprPrefix <- ASTERISK
416418
417# *** Tokens ***419# *** Tokens ***
418420
lib/std/zig/parser_fuzz.zig+5
...@@ -68,6 +68,11 @@ test "comptime fn" {...@@ -68,6 +68,11 @@ test "comptime fn" {
68 try checkAgainstOracle("comptime fn()0");68 try checkAgainstOracle("comptime fn()0");
69}69}
7070
71// Found using AFL++
72test "return asterisk" {
73 try checkAgainstOracle("test{return*!0;}");
74}
75
71fn checkAgainstOracle(source: [:0]const u8) !void {76fn checkAgainstOracle(source: [:0]const u8) !void {
72 var fba_buf: [1 << 18]u8 = undefined;77 var fba_buf: [1 << 18]u8 = undefined;
73 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);78 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);
lib/std/zig/parser_generated_oracle.zig+98-11
...@@ -661,17 +661,53 @@ const Parser = struct {...@@ -661,17 +661,53 @@ const Parser = struct {
661 p.i = pos_0;661 p.i = pos_0;
662 if (p.parseIfExpr()) break :blk_0 true;662 if (p.parseIfExpr()) break :blk_0 true;
663 p.i = pos_0;663 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;
665 p.i = pos_0;677 p.i = pos_0;
666 if (p.parseKEYWORD_comptime() and p.parseExpr()) break :blk_0 true;678 if (p.parseKEYWORD_comptime() and p.parseExpr()) break :blk_0 true;
667 p.i = pos_0;679 p.i = pos_0;
668 if (p.parseKEYWORD_nosuspend() and p.parseExpr()) break :blk_0 true;680 if (p.parseKEYWORD_nosuspend() and p.parseExpr()) break :blk_0 true;
669 p.i = pos_0;681 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;
671 p.i = pos_0;695 p.i = pos_0;
672 if (p.parseKEYWORD_resume() and p.parseExpr()) break :blk_0 true;696 if (p.parseKEYWORD_resume() and p.parseExpr()) break :blk_0 true;
673 p.i = pos_0;697 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;
675 p.i = pos_0;711 p.i = pos_0;
676 if ((p.parseBlockLabel() or true) and p.parseLoopExpr()) break :blk_0 true;712 if ((p.parseBlockLabel() or true) and p.parseLoopExpr()) break :blk_0 true;
677 p.i = pos_0;713 p.i = pos_0;
...@@ -1314,12 +1350,31 @@ const Parser = struct {...@@ -1314,12 +1350,31 @@ const Parser = struct {
1314 pub fn parseForItem(p: *Parser) bool {1350 pub fn parseForItem(p: *Parser) bool {
1315 return blk_0: {1351 return blk_0: {
1316 const pos_0 = p.i;1352 const pos_0 = p.i;
1317 if (p.parseExpr() and (blk_3: {1353 if (p.parseExpr() and blk_2: {
1318 const pos_3 = p.i;1354 const pos_2 = p.i;
1319 if (p.parseDOT2() and (p.parseExpr() or true)) break :blk_3 true;1355 if (p.parseDOT2() and blk_4: {
1320 p.i = pos_3;1356 const pos_4 = p.i;
1321 break :blk_3 false;1357 if (p.parseExpr()) break :blk_4 true;
1322 } or true)) break :blk_0 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;
1323 p.i = pos_0;1378 p.i = pos_0;
1324 break :blk_0 false;1379 break :blk_0 false;
1325 };1380 };
...@@ -1662,7 +1717,19 @@ const Parser = struct {...@@ -1662,7 +1717,19 @@ const Parser = struct {
1662 const pos_0 = p.i;1717 const pos_0 = p.i;
1663 if (p.parseLBRACKET() and p.parseExpr() and (blk_3: {1718 if (p.parseLBRACKET() and p.parseExpr() and (blk_3: {
1664 const pos_3 = p.i;1719 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: {
1666 const pos_6 = p.i;1733 const pos_6 = p.i;
1667 if (p.parseCOLON() and p.parseExpr()) break :blk_6 true;1734 if (p.parseCOLON() and p.parseExpr()) break :blk_6 true;
1668 p.i = pos_6;1735 p.i = pos_6;
...@@ -1934,7 +2001,27 @@ const Parser = struct {...@@ -1934,7 +2001,27 @@ const Parser = struct {
1934 break :blk_3 false;2001 break :blk_3 false;
1935 }) {}2002 }) {}
1936 break :blk_1 true;2003 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;
1938 p.i = pos_0;2025 p.i = pos_0;
1939 break :blk_0 false;2026 break :blk_0 false;
1940 };2027 };