diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index abd0170803f330cc2e0a51e86a07c718a783326d..f2af6c9f173e280b3ad7135431ed7251b313cc7a 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -138,7 +138,7 @@ pub fn deinit(tree: *Ast, gpa: Allocator) void { tree.* = undefined; } -pub const Mode = enum { zig, zon }; +pub const Mode = enum { zig, zon, zig_no_recover }; /// Result should be freed with tree.deinit() when there are /// no more references to any of the tokens or nodes. @@ -180,6 +180,7 @@ pub fn parseTokens( .extra_data = .empty, .scratch = .empty, .tok_i = 0, + .recover = true, }; defer parser.errors.deinit(gpa); defer parser.nodes.deinit(gpa); @@ -193,6 +194,10 @@ pub fn parseTokens( switch (mode) { .zig => try parser.parseRoot(), + .zig_no_recover => { + parser.recover = false; + try parser.parseRoot(); + }, .zon => try parser.parseZon(), } diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index 9a4f800851cd767e5c8c082257b42c8a138ca8c4..ad767a41ead1a338681ccf9472b550f2fdea8441 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -23,6 +23,7 @@ errors: std.ArrayList(AstError), nodes: Ast.NodeList, extra_data: std.ArrayList(u32), scratch: std.ArrayList(Node.Index), +recover: bool, fn tokenTag(p: *const Parse, token_index: TokenIndex) Token.Tag { return p.tokens.items(.tag)[token_index]; @@ -497,6 +498,12 @@ fn parseContainerMembers(p: *Parse) Allocator.Error!Members { /// Attempts to find next container member by searching for certain tokens fn findNextContainerMember(p: *Parse) void { + if (!p.recover) { + while (p.tokenTag(p.tok_i) != .eof) { + p.tok_i += 1; + } + return; + } var level: u32 = 0; while (true) { const tok = p.nextToken(); @@ -554,6 +561,12 @@ fn findNextContainerMember(p: *Parse) void { /// Attempts to find the next statement by searching for a semicolon fn findNextStmt(p: *Parse) void { + if (!p.recover) { + while (p.tokenTag(p.tok_i) != .eof) { + p.tok_i += 1; + } + return; + } var level: u32 = 0; while (true) { const tok = p.nextToken(); @@ -3615,7 +3628,7 @@ fn expectSemicolon(p: *Parse, error_tag: AstError.Tag, recoverable: bool) Error! return; } try p.warn(error_tag); - if (!recoverable) return error.ParseError; + if (!recoverable or !p.recover) return error.ParseError; } fn nextToken(p: *Parse) TokenIndex { diff --git a/lib/std/zig/parser_fuzz.zig b/lib/std/zig/parser_fuzz.zig index cb17a588bbbc4891863301412e978bfd8d937fca..6250d1bb837030f658100b71a96a7a05cb94e970 100644 --- a/lib/std/zig/parser_fuzz.zig +++ b/lib/std/zig/parser_fuzz.zig @@ -123,10 +123,17 @@ fn checkAgainstOracle(source: [:0]const u8) !void { var fba_buf: [1 << 18]u8 = undefined; var fba: std.heap.FixedBufferAllocator = .init(&fba_buf); - const ast = try std.zig.Ast.parse(fba.allocator(), source, .zig); - const expected = try oracle.parse(source); + // It is important to disable recovery for fuzz testing. + // Consider the case where there is a parse error right at the beginning of the file, + // followed by a valid declaration with a million nested parens. The oracle will not + // skip this input due to the max depth being exceeded since the oracle hits a parse + // error right away and does no recovery. However, std.zig.Ast.parse() does recovery + // by default and will hit a stack overflow rather than returning after the parser error. + // Stack overflows are not interesting and we do not want the fuzzer to be able to find them. + const ast = try std.zig.Ast.parse(fba.allocator(), source, .zig_no_recover); + errdefer logBadSource(source, ast); try std.testing.expectEqual(expected, ast.errors.len == 0); }