authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-06-18 13:44:03+02:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-07-08 11:48:59+02:00
logd9c6669f72c6d6d12a6533282c07239deac0c9c7
treef9db157e55e80917a85eb744a96dbb3e67238912
parenta4254123ebb257c1c739ad5bb3c93049b51539db
signaturelock-open Commit is signed but in an unrecognized format.

grammar: introduce !ContainerDeclPrefix

Eliminate unbounded lookahead, match Parse.zig behavior

3 files changed, 97 insertions(+), 18 deletions(-)

doc/langref/grammar.peg+19-7
...@@ -39,9 +39,16 @@...@@ -39,9 +39,16 @@
39Root <- ContainerMembers skip eof39Root <- ContainerMembers skip eof
4040
41# *** Top level ***41# *** Top level ***
42ContainerMembers <- container_doc_comment? ContainerDeclaration* (ContainerField COMMA)* (ContainerField / ContainerDeclaration*)42ContainerMembers <- container_doc_comment? ContainerDecl* !ContainerDeclPrefix
43 (!ContainerDeclPrefix ContainerField COMMA)*
44 (!ContainerDeclPrefix ContainerField / ContainerDecl*)
4345
44ContainerDeclaration <- TestDecl / ComptimeDecl / doc_comment? KEYWORD_pub? Decl46ContainerDecl <- TestDecl / ComptimeDecl / doc_comment? KEYWORD_pub? Decl
47
48ContainerDeclPrefix
49 <- KEYWORD_test
50 / KEYWORD_comptime LBRACE
51 / doc_comment? KEYWORD_pub? DeclPrefix
4552
46TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block53TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block
4754
...@@ -52,6 +59,11 @@ Decl...@@ -52,6 +59,11 @@ Decl
52 / KEYWORD_extern STRINGLITERALSINGLE? FnProto SEMICOLON59 / KEYWORD_extern STRINGLITERALSINGLE? FnProto SEMICOLON
53 / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? GlobalVarDecl60 / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? GlobalVarDecl
5461
62DeclPrefix
63 <- (KEYWORD_export / KEYWORD_inline / KEYWORD_noinline)? KEYWORD_fn
64 / KEYWORD_extern STRINGLITERALSINGLE? KEYWORD_fn
65 / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? (KEYWORD_const / KEYWORD_var)
66
55FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr67FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
5668
57VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?69VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?
...@@ -165,7 +177,7 @@ SuffixExpr <- PrimaryTypeExpr SuffixOp* !SuffixOpPrefix...@@ -165,7 +177,7 @@ SuffixExpr <- PrimaryTypeExpr SuffixOp* !SuffixOpPrefix
165PrimaryTypeExpr177PrimaryTypeExpr
166 <- BUILTINIDENTIFIER FnCallArguments178 <- BUILTINIDENTIFIER FnCallArguments
167 / CHAR_LITERAL179 / CHAR_LITERAL
168 / ContainerDecl180 / ContainerType
169 / DOT IDENTIFIER181 / DOT IDENTIFIER
170 / DOT InitList182 / DOT InitList
171 / ErrorSetDecl183 / ErrorSetDecl
...@@ -181,7 +193,7 @@ PrimaryTypeExpr...@@ -181,7 +193,7 @@ PrimaryTypeExpr
181 / NUMBERLITERAL193 / NUMBERLITERAL
182 / STRINGLITERAL194 / STRINGLITERAL
183195
184ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto196ContainerType <- (KEYWORD_extern / KEYWORD_packed)? ContainerTypeAuto
185197
186ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE198ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE
187199
...@@ -387,10 +399,10 @@ ManyPtrTypeStart <- LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET...@@ -387,10 +399,10 @@ ManyPtrTypeStart <- LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET
387399
388ArrayTypeStart <- LBRACKET !ASTERISK Expr (COLON Expr)? RBRACKET400ArrayTypeStart <- LBRACKET !ASTERISK Expr (COLON Expr)? RBRACKET
389401
390# ContainerDecl specific402# ContainerType specific
391ContainerDeclAuto <- ContainerDeclType LBRACE ContainerMembers RBRACE403ContainerTypeAuto <- ContainerTypeKind LBRACE ContainerMembers RBRACE
392404
393ContainerDeclType405ContainerTypeKind
394 <- KEYWORD_struct (LPAREN Expr RPAREN)?406 <- KEYWORD_struct (LPAREN Expr RPAREN)?
395 / KEYWORD_opaque407 / KEYWORD_opaque
396 / KEYWORD_enum (LPAREN Expr RPAREN)?408 / KEYWORD_enum (LPAREN Expr RPAREN)?
lib/std/zig/parser_fuzz.zig+5
...@@ -73,6 +73,11 @@ test "return asterisk" {...@@ -73,6 +73,11 @@ test "return asterisk" {
73 try checkAgainstOracle("test{return*!0;}");73 try checkAgainstOracle("test{return*!0;}");
74}74}
7575
76// Found using AFL++
77test "fn container field" {
78 try checkAgainstOracle("fn()0");
79}
80
76fn checkAgainstOracle(source: [:0]const u8) !void {81fn checkAgainstOracle(source: [:0]const u8) !void {
77 var fba_buf: [1 << 18]u8 = undefined;82 var fba_buf: [1 << 18]u8 = undefined;
78 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);83 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);
lib/std/zig/parser_generated_oracle.zig+73-11
...@@ -25,22 +25,37 @@ const Parser = struct {...@@ -25,22 +25,37 @@ const Parser = struct {
25 return blk_0: {25 return blk_0: {
26 const pos_0 = p.i;26 const pos_0 = p.i;
27 if ((p.parsecontainer_doc_comment() or true) and blk_1: {27 if ((p.parsecontainer_doc_comment() or true) and blk_1: {
28 while (p.parseContainerDeclaration()) {}28 while (p.parseContainerDecl()) {}
29 break :blk_1 true;29 break :blk_1 true;
30 } and blk_1: {
31 const pos_1 = p.i;
32 const match_1 = p.parseContainerDeclPrefix();
33 p.i = pos_1;
34 break :blk_1 !match_1;
30 } and blk_1: {35 } and blk_1: {
31 while (blk_3: {36 while (blk_3: {
32 const pos_3 = p.i;37 const pos_3 = p.i;
33 if (p.parseContainerField() and p.parseCOMMA()) break :blk_3 true;38 if (blk_4: {
39 const pos_4 = p.i;
40 const match_4 = p.parseContainerDeclPrefix();
41 p.i = pos_4;
42 break :blk_4 !match_4;
43 } and p.parseContainerField() and p.parseCOMMA()) break :blk_3 true;
34 p.i = pos_3;44 p.i = pos_3;
35 break :blk_3 false;45 break :blk_3 false;
36 }) {}46 }) {}
37 break :blk_1 true;47 break :blk_1 true;
38 } and blk_2: {48 } and blk_2: {
39 const pos_2 = p.i;49 const pos_2 = p.i;
40 if (p.parseContainerField()) break :blk_2 true;50 if (blk_3: {
51 const pos_3 = p.i;
52 const match_3 = p.parseContainerDeclPrefix();
53 p.i = pos_3;
54 break :blk_3 !match_3;
55 } and p.parseContainerField()) break :blk_2 true;
41 p.i = pos_2;56 p.i = pos_2;
42 if (blk_3: {57 if (blk_3: {
43 while (p.parseContainerDeclaration()) {}58 while (p.parseContainerDecl()) {}
44 break :blk_3 true;59 break :blk_3 true;
45 }) break :blk_2 true;60 }) break :blk_2 true;
46 p.i = pos_2;61 p.i = pos_2;
...@@ -50,7 +65,7 @@ const Parser = struct {...@@ -50,7 +65,7 @@ const Parser = struct {
50 break :blk_0 false;65 break :blk_0 false;
51 };66 };
52 }67 }
53 pub fn parseContainerDeclaration(p: *Parser) bool {68 pub fn parseContainerDecl(p: *Parser) bool {
54 return blk_0: {69 return blk_0: {
55 const pos_0 = p.i;70 const pos_0 = p.i;
56 if (p.parseTestDecl()) break :blk_0 true;71 if (p.parseTestDecl()) break :blk_0 true;
...@@ -62,6 +77,18 @@ const Parser = struct {...@@ -62,6 +77,18 @@ const Parser = struct {
62 break :blk_0 false;77 break :blk_0 false;
63 };78 };
64 }79 }
80 pub fn parseContainerDeclPrefix(p: *Parser) bool {
81 return blk_0: {
82 const pos_0 = p.i;
83 if (p.parseKEYWORD_test()) break :blk_0 true;
84 p.i = pos_0;
85 if (p.parseKEYWORD_comptime() and p.parseLBRACE()) break :blk_0 true;
86 p.i = pos_0;
87 if ((p.parsedoc_comment() or true) and (p.parseKEYWORD_pub() or true) and p.parseDeclPrefix()) break :blk_0 true;
88 p.i = pos_0;
89 break :blk_0 false;
90 };
91 }
65 pub fn parseTestDecl(p: *Parser) bool {92 pub fn parseTestDecl(p: *Parser) bool {
66 return blk_0: {93 return blk_0: {
67 const pos_0 = p.i;94 const pos_0 = p.i;
...@@ -120,6 +147,41 @@ const Parser = struct {...@@ -120,6 +147,41 @@ const Parser = struct {
120 break :blk_0 false;147 break :blk_0 false;
121 };148 };
122 }149 }
150 pub fn parseDeclPrefix(p: *Parser) bool {
151 return blk_0: {
152 const pos_0 = p.i;
153 if ((blk_3: {
154 const pos_3 = p.i;
155 if (p.parseKEYWORD_export()) break :blk_3 true;
156 p.i = pos_3;
157 if (p.parseKEYWORD_inline()) break :blk_3 true;
158 p.i = pos_3;
159 if (p.parseKEYWORD_noinline()) break :blk_3 true;
160 p.i = pos_3;
161 break :blk_3 false;
162 } or true) and p.parseKEYWORD_fn()) break :blk_0 true;
163 p.i = pos_0;
164 if (p.parseKEYWORD_extern() and (p.parseSTRINGLITERALSINGLE() or true) and p.parseKEYWORD_fn()) break :blk_0 true;
165 p.i = pos_0;
166 if ((blk_3: {
167 const pos_3 = p.i;
168 if (p.parseKEYWORD_export()) break :blk_3 true;
169 p.i = pos_3;
170 if (p.parseKEYWORD_extern() and (p.parseSTRINGLITERALSINGLE() or true)) break :blk_3 true;
171 p.i = pos_3;
172 break :blk_3 false;
173 } or true) and (p.parseKEYWORD_threadlocal() or true) and blk_2: {
174 const pos_2 = p.i;
175 if (p.parseKEYWORD_const()) break :blk_2 true;
176 p.i = pos_2;
177 if (p.parseKEYWORD_var()) break :blk_2 true;
178 p.i = pos_2;
179 break :blk_2 false;
180 }) break :blk_0 true;
181 p.i = pos_0;
182 break :blk_0 false;
183 };
184 }
123 pub fn parseFnProto(p: *Parser) bool {185 pub fn parseFnProto(p: *Parser) bool {
124 return blk_0: {186 return blk_0: {
125 const pos_0 = p.i;187 const pos_0 = p.i;
...@@ -904,7 +966,7 @@ const Parser = struct {...@@ -904,7 +966,7 @@ const Parser = struct {
904 p.i = pos_0;966 p.i = pos_0;
905 if (p.parseCHAR_LITERAL()) break :blk_0 true;967 if (p.parseCHAR_LITERAL()) break :blk_0 true;
906 p.i = pos_0;968 p.i = pos_0;
907 if (p.parseContainerDecl()) break :blk_0 true;969 if (p.parseContainerType()) break :blk_0 true;
908 p.i = pos_0;970 p.i = pos_0;
909 if (p.parseDOT() and p.parseIDENTIFIER()) break :blk_0 true;971 if (p.parseDOT() and p.parseIDENTIFIER()) break :blk_0 true;
910 p.i = pos_0;972 p.i = pos_0;
...@@ -937,7 +999,7 @@ const Parser = struct {...@@ -937,7 +999,7 @@ const Parser = struct {
937 break :blk_0 false;999 break :blk_0 false;
938 };1000 };
939 }1001 }
940 pub fn parseContainerDecl(p: *Parser) bool {1002 pub fn parseContainerType(p: *Parser) bool {
941 return blk_0: {1003 return blk_0: {
942 const pos_0 = p.i;1004 const pos_0 = p.i;
943 if ((blk_3: {1005 if ((blk_3: {
...@@ -947,7 +1009,7 @@ const Parser = struct {...@@ -947,7 +1009,7 @@ const Parser = struct {
947 if (p.parseKEYWORD_packed()) break :blk_3 true;1009 if (p.parseKEYWORD_packed()) break :blk_3 true;
948 p.i = pos_3;1010 p.i = pos_3;
949 break :blk_3 false;1011 break :blk_3 false;
950 } or true) and p.parseContainerDeclAuto()) break :blk_0 true;1012 } or true) and p.parseContainerTypeAuto()) break :blk_0 true;
951 p.i = pos_0;1013 p.i = pos_0;
952 break :blk_0 false;1014 break :blk_0 false;
953 };1015 };
...@@ -1828,15 +1890,15 @@ const Parser = struct {...@@ -1828,15 +1890,15 @@ const Parser = struct {
1828 break :blk_0 false;1890 break :blk_0 false;
1829 };1891 };
1830 }1892 }
1831 pub fn parseContainerDeclAuto(p: *Parser) bool {1893 pub fn parseContainerTypeAuto(p: *Parser) bool {
1832 return blk_0: {1894 return blk_0: {
1833 const pos_0 = p.i;1895 const pos_0 = p.i;
1834 if (p.parseContainerDeclType() and p.parseLBRACE() and p.parseContainerMembers() and p.parseRBRACE()) break :blk_0 true;1896 if (p.parseContainerTypeKind() and p.parseLBRACE() and p.parseContainerMembers() and p.parseRBRACE()) break :blk_0 true;
1835 p.i = pos_0;1897 p.i = pos_0;
1836 break :blk_0 false;1898 break :blk_0 false;
1837 };1899 };
1838 }1900 }
1839 pub fn parseContainerDeclType(p: *Parser) bool {1901 pub fn parseContainerTypeKind(p: *Parser) bool {
1840 return blk_0: {1902 return blk_0: {
1841 const pos_0 = p.i;1903 const pos_0 = p.i;
1842 if (p.parseKEYWORD_struct() and (blk_3: {1904 if (p.parseKEYWORD_struct() and (blk_3: {