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
1414 / KEYWORD_extern STRINGLITERALSINGLE? FnProto SEMICOLON
1515 / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? GlobalVarDecl
1616
17FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr !ExprSuffix
17FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
1818
1919VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?
2020
......@@ -93,25 +93,25 @@ PrefixExpr <- PrefixOp* PrimaryExpr
9393PrimaryExpr
9494 <- AsmExpr
9595 / IfExpr
96 / KEYWORD_break BreakLabel? (Expr !ExprSuffix / !SinglePtrTypeStart)
97 / KEYWORD_comptime Expr !ExprSuffix
98 / KEYWORD_nosuspend Expr !ExprSuffix
99 / KEYWORD_continue BreakLabel? (Expr !ExprSuffix / !SinglePtrTypeStart)
100 / KEYWORD_resume Expr !ExprSuffix
101 / KEYWORD_return (Expr !ExprSuffix / !SinglePtrTypeStart)
96 / KEYWORD_break BreakLabel? Expr?
97 / KEYWORD_comptime Expr
98 / KEYWORD_nosuspend Expr
99 / KEYWORD_continue BreakLabel? Expr?
100 / KEYWORD_resume Expr
101 / KEYWORD_return Expr?
102102 / BlockLabel? LoopExpr
103103 / Block
104104 / CurlySuffixExpr
105105
106IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix
106IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?
107107
108108Block <- LBRACE BlockStatement* RBRACE
109109
110110LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)
111111
112ForExpr <- ForPrefix Expr (KEYWORD_else Expr / !KEYWORD_else) !ExprSuffix
112ForExpr <- ForPrefix Expr (KEYWORD_else Expr / !KEYWORD_else)
113113
114WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix
114WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?
115115
116116CurlySuffixExpr <- TypeExpr InitList?
117117
......@@ -139,7 +139,7 @@ PrimaryTypeExpr
139139 / LabeledTypeExpr
140140 / IDENTIFIER
141141 / IfTypeExpr
142 / KEYWORD_comptime TypeExpr !ExprSuffix
142 / KEYWORD_comptime TypeExpr
143143 / KEYWORD_error DOT IDENTIFIER
144144 / KEYWORD_anyframe
145145 / KEYWORD_unreachable
......@@ -152,7 +152,7 @@ ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
152152
153153GroupedExpr <- LPAREN Expr RPAREN
154154
155IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix
155IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
156156
157157LabeledTypeExpr
158158 <- BlockLabel Block
......@@ -161,9 +161,9 @@ LabeledTypeExpr
161161
162162LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)
163163
164ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr / !KEYWORD_else) !ExprSuffix
164ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr / !KEYWORD_else)
165165
166WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix
166WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?
167167
168168SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE
169169
......@@ -329,23 +329,6 @@ SuffixOp
329329
330330FnCallArguments <- 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
349332# Ptr specific
350333SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET
351334
lib/std/zig/parser_fuzz.zig+5
......@@ -57,6 +57,11 @@ test "extra capture in for loop" {
5757 try checkAgainstOracle("for(0)|t,r|0");
5858}
5959
60// Found using AFL++
61test "expression nesting" {
62 try checkAgainstOracle("test{*comptime 0 == 0;}");
63}
64
6065fn checkAgainstOracle(source: [:0]const u8) !void {
6166 var fba_buf: [1 << 18]u8 = undefined;
6267 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);
lib/std/zig/parser_generated_oracle.zig+12-156
......@@ -123,12 +123,7 @@ const Parser = struct {
123123 pub fn parseFnProto(p: *Parser) bool {
124124 return blk_0: {
125125 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: {
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;
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;
132127 p.i = pos_0;
133128 break :blk_0 false;
134129 };
......@@ -571,83 +566,17 @@ const Parser = struct {
571566 p.i = pos_0;
572567 if (p.parseIfExpr()) break :blk_0 true;
573568 p.i = pos_0;
574 if (p.parseKEYWORD_break() and (p.parseBreakLabel() or true) and blk_2: {
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;
569 if (p.parseKEYWORD_break() and (p.parseBreakLabel() or true) and (p.parseExpr() or true)) break :blk_0 true;
592570 p.i = pos_0;
593 if (p.parseKEYWORD_comptime() and p.parseExpr() and blk_1: {
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;
571 if (p.parseKEYWORD_comptime() and p.parseExpr()) break :blk_0 true;
599572 p.i = pos_0;
600 if (p.parseKEYWORD_nosuspend() and p.parseExpr() and blk_1: {
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;
573 if (p.parseKEYWORD_nosuspend() and p.parseExpr()) break :blk_0 true;
606574 p.i = pos_0;
607 if (p.parseKEYWORD_continue() and (p.parseBreakLabel() or true) and blk_2: {
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;
575 if (p.parseKEYWORD_continue() and (p.parseBreakLabel() or true) and (p.parseExpr() or true)) break :blk_0 true;
625576 p.i = pos_0;
626 if (p.parseKEYWORD_resume() and p.parseExpr() and blk_1: {
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;
577 if (p.parseKEYWORD_resume() and p.parseExpr()) break :blk_0 true;
632578 p.i = pos_0;
633 if (p.parseKEYWORD_return() and blk_2: {
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;
579 if (p.parseKEYWORD_return() and (p.parseExpr() or true)) break :blk_0 true;
651580 p.i = pos_0;
652581 if ((p.parseBlockLabel() or true) and p.parseLoopExpr()) break :blk_0 true;
653582 p.i = pos_0;
......@@ -666,12 +595,7 @@ const Parser = struct {
666595 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseExpr()) break :blk_3 true;
667596 p.i = pos_3;
668597 break :blk_3 false;
669 } or true) and blk_1: {
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;
598 } or true)) break :blk_0 true;
675599 p.i = pos_0;
676600 break :blk_0 false;
677601 };
......@@ -717,11 +641,6 @@ const Parser = struct {
717641 }) break :blk_2 true;
718642 p.i = pos_2;
719643 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;
725644 }) break :blk_0 true;
726645 p.i = pos_0;
727646 break :blk_0 false;
......@@ -735,12 +654,7 @@ const Parser = struct {
735654 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseExpr()) break :blk_3 true;
736655 p.i = pos_3;
737656 break :blk_3 false;
738 } or true) and blk_1: {
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;
657 } or true)) break :blk_0 true;
744658 p.i = pos_0;
745659 break :blk_0 false;
746660 };
......@@ -848,12 +762,7 @@ const Parser = struct {
848762 p.i = pos_0;
849763 if (p.parseIfTypeExpr()) break :blk_0 true;
850764 p.i = pos_0;
851 if (p.parseKEYWORD_comptime() and p.parseTypeExpr() and blk_1: {
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;
765 if (p.parseKEYWORD_comptime() and p.parseTypeExpr()) break :blk_0 true;
857766 p.i = pos_0;
858767 if (p.parseKEYWORD_error() and p.parseDOT() and p.parseIDENTIFIER()) break :blk_0 true;
859768 p.i = pos_0;
......@@ -907,12 +816,7 @@ const Parser = struct {
907816 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseTypeExpr()) break :blk_3 true;
908817 p.i = pos_3;
909818 break :blk_3 false;
910 } or true) and blk_1: {
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;
819 } or true)) break :blk_0 true;
916820 p.i = pos_0;
917821 break :blk_0 false;
918822 };
......@@ -959,11 +863,6 @@ const Parser = struct {
959863 }) break :blk_2 true;
960864 p.i = pos_2;
961865 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;
967866 }) break :blk_0 true;
968867 p.i = pos_0;
969868 break :blk_0 false;
......@@ -977,12 +876,7 @@ const Parser = struct {
977876 if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseTypeExpr()) break :blk_3 true;
978877 p.i = pos_3;
979878 break :blk_3 false;
980 } or true) and blk_1: {
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;
879 } or true)) break :blk_0 true;
986880 p.i = pos_0;
987881 break :blk_0 false;
988882 };
......@@ -1643,44 +1537,6 @@ const Parser = struct {
16431537 break :blk_0 false;
16441538 };
16451539 }
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 }
16841540 pub fn parseSliceTypeStart(p: *Parser) bool {
16851541 return blk_0: {
16861542 const pos_0 = p.i;