authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-06-15 18:32:35+02:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-07-08 11:48:58+02:00
log99babb4ae44688c85b786f742d8cf54379819e5e
treeb15e7874b188aae01f7669a1b77a26869c6f5933
parentad0a294ae23252ce5e1494a93e4d8b160b6cd4df
signaturelock-open Commit is signed but in an unrecognized format.

grammar: remove unbounded lookahead

This unbounded lookahead is causing discrepancies between the Parse.zig implementation and the PEG grammar. Also, unbounded lookahead should never have been added to the PEG grammar in the first place. It was added in 785fb1be111186525bf288fa3460945404f676eb which demonstrated insufficient understanding of PEG semantics through many redundant additions. Whatever problem this unbounded lookahead attempted to solve needs to be solved differently in any case. This commit adds one test case found by AFL++ that fails before this commit and now passes.

3 files changed, 31 insertions(+), 187 deletions(-)

doc/langref/grammar.peg+14-31
...@@ -14,7 +14,7 @@ Decl...@@ -14,7 +14,7 @@ Decl
14 / KEYWORD_extern STRINGLITERALSINGLE? FnProto SEMICOLON14 / KEYWORD_extern STRINGLITERALSINGLE? FnProto SEMICOLON
15 / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? GlobalVarDecl15 / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? GlobalVarDecl
1616
17FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr !ExprSuffix17FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
1818
19VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?19VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?
2020
...@@ -93,25 +93,25 @@ PrefixExpr <- PrefixOp* PrimaryExpr...@@ -93,25 +93,25 @@ PrefixExpr <- PrefixOp* PrimaryExpr
93PrimaryExpr93PrimaryExpr
94 <- AsmExpr94 <- AsmExpr
95 / IfExpr95 / IfExpr
96 / KEYWORD_break BreakLabel? (Expr !ExprSuffix / !SinglePtrTypeStart)96 / KEYWORD_break BreakLabel? Expr?
97 / KEYWORD_comptime Expr !ExprSuffix97 / KEYWORD_comptime Expr
98 / KEYWORD_nosuspend Expr !ExprSuffix98 / KEYWORD_nosuspend Expr
99 / KEYWORD_continue BreakLabel? (Expr !ExprSuffix / !SinglePtrTypeStart)99 / KEYWORD_continue BreakLabel? Expr?
100 / KEYWORD_resume Expr !ExprSuffix100 / KEYWORD_resume Expr
101 / KEYWORD_return (Expr !ExprSuffix / !SinglePtrTypeStart)101 / KEYWORD_return Expr?
102 / BlockLabel? LoopExpr102 / BlockLabel? LoopExpr
103 / Block103 / Block
104 / CurlySuffixExpr104 / CurlySuffixExpr
105105
106IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix106IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
107107
108Block <- LBRACE BlockStatement* RBRACE108Block <- LBRACE BlockStatement* RBRACE
109109
110LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)110LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)
111111
112ForExpr <- ForPrefix Expr (KEYWORD_else Expr / !KEYWORD_else) !ExprSuffix112ForExpr <- ForPrefix Expr (KEYWORD_else Expr / !KEYWORD_else)
113113
114WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix114WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
115115
116CurlySuffixExpr <- TypeExpr InitList?116CurlySuffixExpr <- TypeExpr InitList?
117117
...@@ -139,7 +139,7 @@ PrimaryTypeExpr...@@ -139,7 +139,7 @@ PrimaryTypeExpr
139 / LabeledTypeExpr139 / LabeledTypeExpr
140 / IDENTIFIER140 / IDENTIFIER
141 / IfTypeExpr141 / IfTypeExpr
142 / KEYWORD_comptime TypeExpr !ExprSuffix142 / KEYWORD_comptime TypeExpr
143 / KEYWORD_error DOT IDENTIFIER143 / KEYWORD_error DOT IDENTIFIER
144 / KEYWORD_anyframe144 / KEYWORD_anyframe
145 / KEYWORD_unreachable145 / KEYWORD_unreachable
...@@ -152,7 +152,7 @@ ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE...@@ -152,7 +152,7 @@ ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
152152
153GroupedExpr <- LPAREN Expr RPAREN153GroupedExpr <- LPAREN Expr RPAREN
154154
155IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix155IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
156156
157LabeledTypeExpr157LabeledTypeExpr
158 <- BlockLabel Block158 <- BlockLabel Block
...@@ -161,9 +161,9 @@ LabeledTypeExpr...@@ -161,9 +161,9 @@ LabeledTypeExpr
161161
162LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)162LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
163163
164ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr / !KEYWORD_else) !ExprSuffix164ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr / !KEYWORD_else)
165165
166WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix166WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
167167
168SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE168SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
169169
...@@ -329,23 +329,6 @@ SuffixOp...@@ -329,23 +329,6 @@ SuffixOp
329329
330FnCallArguments <- LPAREN ExprList RPAREN330FnCallArguments <- LPAREN ExprList RPAREN
331331
332ExprSuffix
333 <- KEYWORD_or
334 / KEYWORD_and
335 / CompareOp
336 / BitwiseOp
337 / BitShiftOp
338 / AdditionOp
339 / MultiplyOp
340 / EXCLAMATIONMARK
341 / SuffixOp
342 / FnCallArguments
343
344LabelableExpr
345 <- Block
346 / SwitchExpr
347 / LoopExpr
348
349# Ptr specific332# Ptr specific
350SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET333SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET
351334
lib/std/zig/parser_fuzz.zig+5
...@@ -57,6 +57,11 @@ test "extra capture in for loop" {...@@ -57,6 +57,11 @@ test "extra capture in for loop" {
57 try checkAgainstOracle("for(0)|t,r|0");57 try checkAgainstOracle("for(0)|t,r|0");
58}58}
5959
60// Found using AFL++
61test "expression nesting" {
62 try checkAgainstOracle("test{*comptime 0 == 0;}");
63}
64
60fn checkAgainstOracle(source: [:0]const u8) !void {65fn checkAgainstOracle(source: [:0]const u8) !void {
61 var fba_buf: [1 << 18]u8 = undefined;66 var fba_buf: [1 << 18]u8 = undefined;
62 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);67 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);
lib/std/zig/parser_generated_oracle.zig+12-156
...@@ -123,12 +123,7 @@ const Parser = struct {...@@ -123,12 +123,7 @@ const Parser = struct {
123 pub fn parseFnProto(p: *Parser) bool {123 pub fn parseFnProto(p: *Parser) bool {
124 return blk_0: {124 return blk_0: {
125 const pos_0 = p.i;125 const pos_0 = p.i;
126 if (p.parseKEYWORD_fn() and (p.parseIDENTIFIER() or true) and p.parseLPAREN() and p.parseParamDeclList() and p.parseRPAREN() and (p.parseByteAlign() or true) and (p.parseAddrSpace() or true) and (p.parseLinkSection() or true) and (p.parseCallConv() or true) and (p.parseEXCLAMATIONMARK() or true) and p.parseTypeExpr() and blk_1: {126 if (p.parseKEYWORD_fn() and (p.parseIDENTIFIER() or true) and p.parseLPAREN() and p.parseParamDeclList() and p.parseRPAREN() and (p.parseByteAlign() or true) and (p.parseAddrSpace() or true) and (p.parseLinkSection() or true) and (p.parseCallConv() or true) and (p.parseEXCLAMATIONMARK() or true) and p.parseTypeExpr()) break :blk_0 true;
127 const pos_1 = p.i;
128 const match_1 = p.parseExprSuffix();
129 p.i = pos_1;
130 break :blk_1 !match_1;
131 }) break :blk_0 true;
132 p.i = pos_0;127 p.i = pos_0;
133 break :blk_0 false;128 break :blk_0 false;
134 };129 };
...@@ -571,83 +566,17 @@ const Parser = struct {...@@ -571,83 +566,17 @@ const Parser = struct {
571 p.i = pos_0;566 p.i = pos_0;
572 if (p.parseIfExpr()) break :blk_0 true;567 if (p.parseIfExpr()) break :blk_0 true;
573 p.i = pos_0;568 p.i = pos_0;
574 if (p.parseKEYWORD_break() and (p.parseBreakLabel() or true) and blk_2: {569 if (p.parseKEYWORD_break() and (p.parseBreakLabel() or true) and (p.parseExpr() or true)) break :blk_0 true;
575 const pos_2 = p.i;
576 if (p.parseExpr() and blk_3: {
577 const pos_3 = p.i;
578 const match_3 = p.parseExprSuffix();
579 p.i = pos_3;
580 break :blk_3 !match_3;
581 }) break :blk_2 true;
582 p.i = pos_2;
583 if (blk_3: {
584 const pos_3 = p.i;
585 const match_3 = p.parseSinglePtrTypeStart();
586 p.i = pos_3;
587 break :blk_3 !match_3;
588 }) break :blk_2 true;
589 p.i = pos_2;
590 break :blk_2 false;
591 }) break :blk_0 true;
592 p.i = pos_0;570 p.i = pos_0;
593 if (p.parseKEYWORD_comptime() and p.parseExpr() and blk_1: {571 if (p.parseKEYWORD_comptime() and p.parseExpr()) break :blk_0 true;
594 const pos_1 = p.i;
595 const match_1 = p.parseExprSuffix();
596 p.i = pos_1;
597 break :blk_1 !match_1;
598 }) break :blk_0 true;
599 p.i = pos_0;572 p.i = pos_0;
600 if (p.parseKEYWORD_nosuspend() and p.parseExpr() and blk_1: {573 if (p.parseKEYWORD_nosuspend() and p.parseExpr()) break :blk_0 true;
601 const pos_1 = p.i;
602 const match_1 = p.parseExprSuffix();
603 p.i = pos_1;
604 break :blk_1 !match_1;
605 }) break :blk_0 true;
606 p.i = pos_0;574 p.i = pos_0;
607 if (p.parseKEYWORD_continue() and (p.parseBreakLabel() or true) and blk_2: {575 if (p.parseKEYWORD_continue() and (p.parseBreakLabel() or true) and (p.parseExpr() or true)) break :blk_0 true;
608 const pos_2 = p.i;
609 if (p.parseExpr() and blk_3: {
610 const pos_3 = p.i;
611 const match_3 = p.parseExprSuffix();
612 p.i = pos_3;
613 break :blk_3 !match_3;
614 }) break :blk_2 true;
615 p.i = pos_2;
616 if (blk_3: {
617 const pos_3 = p.i;
618 const match_3 = p.parseSinglePtrTypeStart();
619 p.i = pos_3;
620 break :blk_3 !match_3;
621 }) break :blk_2 true;
622 p.i = pos_2;
623 break :blk_2 false;
624 }) break :blk_0 true;
625 p.i = pos_0;576 p.i = pos_0;
626 if (p.parseKEYWORD_resume() and p.parseExpr() and blk_1: {577 if (p.parseKEYWORD_resume() and p.parseExpr()) break :blk_0 true;
627 const pos_1 = p.i;
628 const match_1 = p.parseExprSuffix();
629 p.i = pos_1;
630 break :blk_1 !match_1;
631 }) break :blk_0 true;
632 p.i = pos_0;578 p.i = pos_0;
633 if (p.parseKEYWORD_return() and blk_2: {579 if (p.parseKEYWORD_return() and (p.parseExpr() or true)) break :blk_0 true;
634 const pos_2 = p.i;
635 if (p.parseExpr() and blk_3: {
636 const pos_3 = p.i;
637 const match_3 = p.parseExprSuffix();
638 p.i = pos_3;
639 break :blk_3 !match_3;
640 }) break :blk_2 true;
641 p.i = pos_2;
642 if (blk_3: {
643 const pos_3 = p.i;
644 const match_3 = p.parseSinglePtrTypeStart();
645 p.i = pos_3;
646 break :blk_3 !match_3;
647 }) break :blk_2 true;
648 p.i = pos_2;
649 break :blk_2 false;
650 }) break :blk_0 true;
651 p.i = pos_0;580 p.i = pos_0;
652 if ((p.parseBlockLabel() or true) and p.parseLoopExpr()) break :blk_0 true;581 if ((p.parseBlockLabel() or true) and p.parseLoopExpr()) break :blk_0 true;
653 p.i = pos_0;582 p.i = pos_0;
...@@ -666,12 +595,7 @@ const Parser = struct {...@@ -666,12 +595,7 @@ const Parser = struct {
666 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseExpr()) break :blk_3 true;595 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseExpr()) break :blk_3 true;
667 p.i = pos_3;596 p.i = pos_3;
668 break :blk_3 false;597 break :blk_3 false;
669 } or true) and blk_1: {598 } or true)) break :blk_0 true;
670 const pos_1 = p.i;
671 const match_1 = p.parseExprSuffix();
672 p.i = pos_1;
673 break :blk_1 !match_1;
674 }) break :blk_0 true;
675 p.i = pos_0;599 p.i = pos_0;
676 break :blk_0 false;600 break :blk_0 false;
677 };601 };
...@@ -717,11 +641,6 @@ const Parser = struct {...@@ -717,11 +641,6 @@ const Parser = struct {
717 }) break :blk_2 true;641 }) break :blk_2 true;
718 p.i = pos_2;642 p.i = pos_2;
719 break :blk_2 false;643 break :blk_2 false;
720 } and blk_1: {
721 const pos_1 = p.i;
722 const match_1 = p.parseExprSuffix();
723 p.i = pos_1;
724 break :blk_1 !match_1;
725 }) break :blk_0 true;644 }) break :blk_0 true;
726 p.i = pos_0;645 p.i = pos_0;
727 break :blk_0 false;646 break :blk_0 false;
...@@ -735,12 +654,7 @@ const Parser = struct {...@@ -735,12 +654,7 @@ const Parser = struct {
735 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseExpr()) break :blk_3 true;654 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseExpr()) break :blk_3 true;
736 p.i = pos_3;655 p.i = pos_3;
737 break :blk_3 false;656 break :blk_3 false;
738 } or true) and blk_1: {657 } or true)) break :blk_0 true;
739 const pos_1 = p.i;
740 const match_1 = p.parseExprSuffix();
741 p.i = pos_1;
742 break :blk_1 !match_1;
743 }) break :blk_0 true;
744 p.i = pos_0;658 p.i = pos_0;
745 break :blk_0 false;659 break :blk_0 false;
746 };660 };
...@@ -848,12 +762,7 @@ const Parser = struct {...@@ -848,12 +762,7 @@ const Parser = struct {
848 p.i = pos_0;762 p.i = pos_0;
849 if (p.parseIfTypeExpr()) break :blk_0 true;763 if (p.parseIfTypeExpr()) break :blk_0 true;
850 p.i = pos_0;764 p.i = pos_0;
851 if (p.parseKEYWORD_comptime() and p.parseTypeExpr() and blk_1: {765 if (p.parseKEYWORD_comptime() and p.parseTypeExpr()) break :blk_0 true;
852 const pos_1 = p.i;
853 const match_1 = p.parseExprSuffix();
854 p.i = pos_1;
855 break :blk_1 !match_1;
856 }) break :blk_0 true;
857 p.i = pos_0;766 p.i = pos_0;
858 if (p.parseKEYWORD_error() and p.parseDOT() and p.parseIDENTIFIER()) break :blk_0 true;767 if (p.parseKEYWORD_error() and p.parseDOT() and p.parseIDENTIFIER()) break :blk_0 true;
859 p.i = pos_0;768 p.i = pos_0;
...@@ -907,12 +816,7 @@ const Parser = struct {...@@ -907,12 +816,7 @@ const Parser = struct {
907 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseTypeExpr()) break :blk_3 true;816 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseTypeExpr()) break :blk_3 true;
908 p.i = pos_3;817 p.i = pos_3;
909 break :blk_3 false;818 break :blk_3 false;
910 } or true) and blk_1: {819 } or true)) break :blk_0 true;
911 const pos_1 = p.i;
912 const match_1 = p.parseExprSuffix();
913 p.i = pos_1;
914 break :blk_1 !match_1;
915 }) break :blk_0 true;
916 p.i = pos_0;820 p.i = pos_0;
917 break :blk_0 false;821 break :blk_0 false;
918 };822 };
...@@ -959,11 +863,6 @@ const Parser = struct {...@@ -959,11 +863,6 @@ const Parser = struct {
959 }) break :blk_2 true;863 }) break :blk_2 true;
960 p.i = pos_2;864 p.i = pos_2;
961 break :blk_2 false;865 break :blk_2 false;
962 } and blk_1: {
963 const pos_1 = p.i;
964 const match_1 = p.parseExprSuffix();
965 p.i = pos_1;
966 break :blk_1 !match_1;
967 }) break :blk_0 true;866 }) break :blk_0 true;
968 p.i = pos_0;867 p.i = pos_0;
969 break :blk_0 false;868 break :blk_0 false;
...@@ -977,12 +876,7 @@ const Parser = struct {...@@ -977,12 +876,7 @@ const Parser = struct {
977 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseTypeExpr()) break :blk_3 true;876 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseTypeExpr()) break :blk_3 true;
978 p.i = pos_3;877 p.i = pos_3;
979 break :blk_3 false;878 break :blk_3 false;
980 } or true) and blk_1: {879 } or true)) break :blk_0 true;
981 const pos_1 = p.i;
982 const match_1 = p.parseExprSuffix();
983 p.i = pos_1;
984 break :blk_1 !match_1;
985 }) break :blk_0 true;
986 p.i = pos_0;880 p.i = pos_0;
987 break :blk_0 false;881 break :blk_0 false;
988 };882 };
...@@ -1643,44 +1537,6 @@ const Parser = struct {...@@ -1643,44 +1537,6 @@ const Parser = struct {
1643 break :blk_0 false;1537 break :blk_0 false;
1644 };1538 };
1645 }1539 }
1646 pub fn parseExprSuffix(p: *Parser) bool {
1647 return blk_0: {
1648 const pos_0 = p.i;
1649 if (p.parseKEYWORD_or()) break :blk_0 true;
1650 p.i = pos_0;
1651 if (p.parseKEYWORD_and()) break :blk_0 true;
1652 p.i = pos_0;
1653 if (p.parseCompareOp()) break :blk_0 true;
1654 p.i = pos_0;
1655 if (p.parseBitwiseOp()) break :blk_0 true;
1656 p.i = pos_0;
1657 if (p.parseBitShiftOp()) break :blk_0 true;
1658 p.i = pos_0;
1659 if (p.parseAdditionOp()) break :blk_0 true;
1660 p.i = pos_0;
1661 if (p.parseMultiplyOp()) break :blk_0 true;
1662 p.i = pos_0;
1663 if (p.parseEXCLAMATIONMARK()) break :blk_0 true;
1664 p.i = pos_0;
1665 if (p.parseSuffixOp()) break :blk_0 true;
1666 p.i = pos_0;
1667 if (p.parseFnCallArguments()) break :blk_0 true;
1668 p.i = pos_0;
1669 break :blk_0 false;
1670 };
1671 }
1672 pub fn parseLabelableExpr(p: *Parser) bool {
1673 return blk_0: {
1674 const pos_0 = p.i;
1675 if (p.parseBlock()) break :blk_0 true;
1676 p.i = pos_0;
1677 if (p.parseSwitchExpr()) break :blk_0 true;
1678 p.i = pos_0;
1679 if (p.parseLoopExpr()) break :blk_0 true;
1680 p.i = pos_0;
1681 break :blk_0 false;
1682 };
1683 }
1684 pub fn parseSliceTypeStart(p: *Parser) bool {1540 pub fn parseSliceTypeStart(p: *Parser) bool {
1685 return blk_0: {1541 return blk_0: {
1686 const pos_0 = p.i;1542 const pos_0 = p.i;