authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-07-06 12:01:46+02:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-07-08 11:49:01+02:00
log87d695e4d910e337d611a2a92b01c805e1588c8d
treed0114d70bf058725fa1917ba0ef0dea4e1803e78
parente054442e2371b0176c14ff740d78607d078ca9fb
signaturelock-open Commit is signed but in an unrecognized format.

parser: allow fully disabling recovery

The previous attempt to disable recovery was incomplete, consider for example the recovery done when parsing an init list with a missing comma .{a, b c} The parser will currently add a warning about the missing comma and continue parsing, which means that the fuzzer can hit stack overflows .{a, b (((((((((((((((((((((((((((((((((((((((((((((((((((((((... To fix this in general, make the warn functions return ParseError when recovery is disabled.

1 files changed, 33 insertions(+), 13 deletions(-)

lib/std/zig/Parse.zig+33-13
......@@ -122,7 +122,7 @@ fn addExtra(p: *Parse, extra: anytype) Allocator.Error!ExtraIndex {
122122 return result;
123123}
124124
125fn warnExpected(p: *Parse, expected_token: Token.Tag) error{OutOfMemory}!void {
125fn warnExpected(p: *Parse, expected_token: Token.Tag) Error!void {
126126 @branchHint(.cold);
127127 try p.warnMsg(.{
128128 .tag = .expected_token,
......@@ -131,12 +131,12 @@ fn warnExpected(p: *Parse, expected_token: Token.Tag) error{OutOfMemory}!void {
131131 });
132132}
133133
134fn warn(p: *Parse, error_tag: AstError.Tag) error{OutOfMemory}!void {
134fn warn(p: *Parse, error_tag: AstError.Tag) Error!void {
135135 @branchHint(.cold);
136136 try p.warnMsg(.{ .tag = error_tag, .token = p.tok_i });
137137}
138138
139fn warnMsg(p: *Parse, msg: Ast.Error) error{OutOfMemory}!void {
139fn warnMsg(p: *Parse, msg: Ast.Error) Error!void {
140140 @branchHint(.cold);
141141 switch (msg.tag) {
142142 .expected_semi_after_decl,
......@@ -175,11 +175,13 @@ fn warnMsg(p: *Parse, msg: Ast.Error) error{OutOfMemory}!void {
175175 var copy = msg;
176176 copy.token_is_prev = true;
177177 copy.token -= 1;
178 return p.errors.append(p.gpa, copy);
178 try p.errors.append(p.gpa, copy);
179 } else {
180 try p.errors.append(p.gpa, msg);
179181 },
180 else => {},
182 else => try p.errors.append(p.gpa, msg),
181183 }
182 try p.errors.append(p.gpa, msg);
184 if (!p.recover) return error.ParseError;
183185}
184186
185187fn fail(p: *Parse, tag: Ast.Error.Tag) error{ ParseError, OutOfMemory } {
......@@ -203,17 +205,29 @@ fn failMsg(p: *Parse, msg: Ast.Error) error{ ParseError, OutOfMemory } {
203205}
204206
205207/// Root <- skip ContainerMembers eof
206pub fn parseRoot(p: *Parse) !void {
208pub fn parseRoot(p: *Parse) Allocator.Error!void {
207209 // Root node must be index 0.
208210 p.nodes.appendAssumeCapacity(.{
209211 .tag = .root,
210212 .main_token = 0,
211213 .data = undefined,
212214 });
213 const root_members = try p.parseContainerMembers();
215 const root_members = p.parseContainerMembers() catch |err| switch (err) {
216 error.OutOfMemory => |e| return e,
217 error.ParseError => {
218 assert(p.errors.items.len > 0);
219 return;
220 },
221 };
214222 const root_decls = try root_members.toSpan(p);
215223 if (p.tokenTag(p.tok_i) != .eof) {
216 try p.warnExpected(.eof);
224 p.warnExpected(.eof) catch |err| switch (err) {
225 error.OutOfMemory => |e| return e,
226 error.ParseError => {
227 assert(p.errors.items.len > 0);
228 return;
229 },
230 };
217231 }
218232 p.nodes.items(.data)[0] = .{ .extra_range = root_decls };
219233}
......@@ -221,7 +235,7 @@ pub fn parseRoot(p: *Parse) !void {
221235/// Parse in ZON mode. Subset of the language.
222236/// TODO: set a flag in Parse struct, and honor that flag
223237/// by emitting compilation errors when non-zon nodes are encountered.
224pub fn parseZon(p: *Parse) !void {
238pub fn parseZon(p: *Parse) Allocator.Error!void {
225239 // We must use index 0 so that 0 can be used as null elsewhere.
226240 p.nodes.appendAssumeCapacity(.{
227241 .tag = .root,
......@@ -236,7 +250,13 @@ pub fn parseZon(p: *Parse) !void {
236250 else => |e| return e,
237251 };
238252 if (p.tokenTag(p.tok_i) != .eof) {
239 try p.warnExpected(.eof);
253 p.warnExpected(.eof) catch |err| switch (err) {
254 error.OutOfMemory => |e| return e,
255 error.ParseError => {
256 assert(p.errors.items.len > 0);
257 return;
258 },
259 };
240260 }
241261 p.nodes.items(.data)[0] = .{ .node = node_index };
242262}
......@@ -246,7 +266,7 @@ pub fn parseZon(p: *Parse) !void {
246266/// ContainerDeclaration <- TestDecl / ComptimeDecl / doc_comment? KEYWORD_pub? Decl
247267///
248268/// ComptimeDecl <- KEYWORD_comptime Block
249fn parseContainerMembers(p: *Parse) Allocator.Error!Members {
269fn parseContainerMembers(p: *Parse) Error!Members {
250270 const scratch_top = p.scratch.items.len;
251271 defer p.scratch.shrinkRetainingCapacity(scratch_top);
252272
......@@ -3582,7 +3602,7 @@ fn expectFor(p: *Parse, comptime bodyParseFn: fn (p: *Parse) Error!Node.Index) !
35823602}
35833603
35843604/// Skips over doc comment tokens. Returns the first one, if any.
3585fn eatDocComments(p: *Parse) Allocator.Error!?TokenIndex {
3605fn eatDocComments(p: *Parse) Error!?TokenIndex {
35863606 if (p.eatToken(.doc_comment)) |tok| {
35873607 var first_line = tok;
35883608 if (tok > 0 and tokensOnSameLine(p, tok - 1, tok)) {