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 {...@@ -122,7 +122,7 @@ fn addExtra(p: *Parse, extra: anytype) Allocator.Error!ExtraIndex {
122 return result;122 return result;
123}123}
124124
125fn warnExpected(p: *Parse, expected_token: Token.Tag) error{OutOfMemory}!void {125fn warnExpected(p: *Parse, expected_token: Token.Tag) Error!void {
126 @branchHint(.cold);126 @branchHint(.cold);
127 try p.warnMsg(.{127 try p.warnMsg(.{
128 .tag = .expected_token,128 .tag = .expected_token,
...@@ -131,12 +131,12 @@ fn warnExpected(p: *Parse, expected_token: Token.Tag) error{OutOfMemory}!void {...@@ -131,12 +131,12 @@ fn warnExpected(p: *Parse, expected_token: Token.Tag) error{OutOfMemory}!void {
131 });131 });
132}132}
133133
134fn warn(p: *Parse, error_tag: AstError.Tag) error{OutOfMemory}!void {134fn warn(p: *Parse, error_tag: AstError.Tag) Error!void {
135 @branchHint(.cold);135 @branchHint(.cold);
136 try p.warnMsg(.{ .tag = error_tag, .token = p.tok_i });136 try p.warnMsg(.{ .tag = error_tag, .token = p.tok_i });
137}137}
138138
139fn warnMsg(p: *Parse, msg: Ast.Error) error{OutOfMemory}!void {139fn warnMsg(p: *Parse, msg: Ast.Error) Error!void {
140 @branchHint(.cold);140 @branchHint(.cold);
141 switch (msg.tag) {141 switch (msg.tag) {
142 .expected_semi_after_decl,142 .expected_semi_after_decl,
...@@ -175,11 +175,13 @@ fn warnMsg(p: *Parse, msg: Ast.Error) error{OutOfMemory}!void {...@@ -175,11 +175,13 @@ fn warnMsg(p: *Parse, msg: Ast.Error) error{OutOfMemory}!void {
175 var copy = msg;175 var copy = msg;
176 copy.token_is_prev = true;176 copy.token_is_prev = true;
177 copy.token -= 1;177 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);
179 },181 },
180 else => {},182 else => try p.errors.append(p.gpa, msg),
181 }183 }
182 try p.errors.append(p.gpa, msg);184 if (!p.recover) return error.ParseError;
183}185}
184186
185fn fail(p: *Parse, tag: Ast.Error.Tag) error{ ParseError, OutOfMemory } {187fn fail(p: *Parse, tag: Ast.Error.Tag) error{ ParseError, OutOfMemory } {
...@@ -203,17 +205,29 @@ fn failMsg(p: *Parse, msg: Ast.Error) error{ ParseError, OutOfMemory } {...@@ -203,17 +205,29 @@ fn failMsg(p: *Parse, msg: Ast.Error) error{ ParseError, OutOfMemory } {
203}205}
204206
205/// Root <- skip ContainerMembers eof207/// Root <- skip ContainerMembers eof
206pub fn parseRoot(p: *Parse) !void {208pub fn parseRoot(p: *Parse) Allocator.Error!void {
207 // Root node must be index 0.209 // Root node must be index 0.
208 p.nodes.appendAssumeCapacity(.{210 p.nodes.appendAssumeCapacity(.{
209 .tag = .root,211 .tag = .root,
210 .main_token = 0,212 .main_token = 0,
211 .data = undefined,213 .data = undefined,
212 });214 });
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 };
214 const root_decls = try root_members.toSpan(p);222 const root_decls = try root_members.toSpan(p);
215 if (p.tokenTag(p.tok_i) != .eof) {223 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 };
217 }231 }
218 p.nodes.items(.data)[0] = .{ .extra_range = root_decls };232 p.nodes.items(.data)[0] = .{ .extra_range = root_decls };
219}233}
...@@ -221,7 +235,7 @@ pub fn parseRoot(p: *Parse) !void {...@@ -221,7 +235,7 @@ pub fn parseRoot(p: *Parse) !void {
221/// Parse in ZON mode. Subset of the language.235/// Parse in ZON mode. Subset of the language.
222/// TODO: set a flag in Parse struct, and honor that flag236/// TODO: set a flag in Parse struct, and honor that flag
223/// by emitting compilation errors when non-zon nodes are encountered.237/// by emitting compilation errors when non-zon nodes are encountered.
224pub fn parseZon(p: *Parse) !void {238pub fn parseZon(p: *Parse) Allocator.Error!void {
225 // We must use index 0 so that 0 can be used as null elsewhere.239 // We must use index 0 so that 0 can be used as null elsewhere.
226 p.nodes.appendAssumeCapacity(.{240 p.nodes.appendAssumeCapacity(.{
227 .tag = .root,241 .tag = .root,
...@@ -236,7 +250,13 @@ pub fn parseZon(p: *Parse) !void {...@@ -236,7 +250,13 @@ pub fn parseZon(p: *Parse) !void {
236 else => |e| return e,250 else => |e| return e,
237 };251 };
238 if (p.tokenTag(p.tok_i) != .eof) {252 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 };
240 }260 }
241 p.nodes.items(.data)[0] = .{ .node = node_index };261 p.nodes.items(.data)[0] = .{ .node = node_index };
242}262}
...@@ -246,7 +266,7 @@ pub fn parseZon(p: *Parse) !void {...@@ -246,7 +266,7 @@ pub fn parseZon(p: *Parse) !void {
246/// ContainerDeclaration <- TestDecl / ComptimeDecl / doc_comment? KEYWORD_pub? Decl266/// ContainerDeclaration <- TestDecl / ComptimeDecl / doc_comment? KEYWORD_pub? Decl
247///267///
248/// ComptimeDecl <- KEYWORD_comptime Block268/// ComptimeDecl <- KEYWORD_comptime Block
249fn parseContainerMembers(p: *Parse) Allocator.Error!Members {269fn parseContainerMembers(p: *Parse) Error!Members {
250 const scratch_top = p.scratch.items.len;270 const scratch_top = p.scratch.items.len;
251 defer p.scratch.shrinkRetainingCapacity(scratch_top);271 defer p.scratch.shrinkRetainingCapacity(scratch_top);
252272
...@@ -3582,7 +3602,7 @@ fn expectFor(p: *Parse, comptime bodyParseFn: fn (p: *Parse) Error!Node.Index) !...@@ -3582,7 +3602,7 @@ fn expectFor(p: *Parse, comptime bodyParseFn: fn (p: *Parse) Error!Node.Index) !
3582}3602}
35833603
3584/// Skips over doc comment tokens. Returns the first one, if any.3604/// Skips over doc comment tokens. Returns the first one, if any.
3585fn eatDocComments(p: *Parse) Allocator.Error!?TokenIndex {3605fn eatDocComments(p: *Parse) Error!?TokenIndex {
3586 if (p.eatToken(.doc_comment)) |tok| {3606 if (p.eatToken(.doc_comment)) |tok| {
3587 var first_line = tok;3607 var first_line = tok;
3588 if (tok > 0 and tokensOnSameLine(p, tok - 1, tok)) {3608 if (tok > 0 and tokensOnSameLine(p, tok - 1, tok)) {