From e21ced91e30d8bc943ebe0a1f1f6d3bc6821740e Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 15 Jun 2026 17:09:59 +0200 Subject: [PATCH] std: move for loop capture count errors to AstGen Currently Parse.zig gives a parse error when the number of for loop inputs does not match the number of for loop captures. This property is however both overly complex to specify in the formal grammar and not necessary to make further parsing possible. This eliminates yet another discrepancy between the formal grammar and Parse.zig implementation that has been discovered through fuzzing with AFL++. --- lib/std/zig/Ast.zig | 8 -------- lib/std/zig/AstGen.zig | 7 +++++++ lib/std/zig/Parse.zig | 14 +------------- lib/std/zig/parser_fuzz.zig | 5 +++++ 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 372823324ee4205c31b15cf145d4c2b9b055fe42..260f62abbb0871a1332978640b1b675a9c2156e9 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -559,12 +559,6 @@ pub fn renderError(tree: Ast, parse_error: Error, w: *Writer) Writer.Error!void .var_const_decl => { return w.writeAll("use 'var' or 'const' to declare variable"); }, - .extra_for_capture => { - return w.writeAll("extra capture in for loop"); - }, - .for_input_not_captured => { - return w.writeAll("for input is not captured"); - }, .invalid_byte => { const tok_slice = tree.source[tree.tokens.items(.start)[parse_error.token]..]; @@ -2889,8 +2883,6 @@ pub const Error = struct { expected_var_const, wrong_equal_var_decl, var_const_decl, - extra_for_capture, - for_input_not_captured, zig_style_container, previous_field, diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 4adf7d0232804afe4738add9bf7a5307e2f1cdd3..e41536f7d456fb0ccf8397742558971d0e5ce7d4 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -6708,6 +6708,9 @@ fn forExpr( for (for_full.ast.inputs, indexables, lens) |input, *indexable_ref, *len_refs| { const capture_is_ref = tree.tokenTag(capture_token) == .asterisk; const ident_tok = capture_token + @intFromBool(capture_is_ref); + if (tree.tokenTag(ident_tok) != .identifier) { + return astgen.failNode(input, "for input is not captured", .{}); + } const is_discard = mem.eql(u8, tree.tokenSlice(ident_tok), "_"); if (is_discard and capture_is_ref) { @@ -6750,6 +6753,10 @@ fn forExpr( len_refs.* = .{ indexable, .none }; } } + // There may or may not be a trailing comma after the final capture + if (tree.tokenTag(capture_token) != .pipe and tree.tokenTag(capture_token - 1) != .pipe) { + return astgen.failTok(capture_token, "extra capture in for loop", .{}); + } } if (!any_len_checks) { diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index d4c6015131d4c612633e3b87f8da61f0faeaf6a4..0978bad73823deb2a0a47dc68a1064cd9923e352 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -2106,16 +2106,9 @@ fn forPrefix(p: *Parse) Error!usize { return inputs; }; - var warned_excess = false; - var captures: u32 = 0; while (true) { _ = p.eatToken(.asterisk); - const identifier = try p.expectToken(.identifier); - captures += 1; - if (captures > inputs and !warned_excess) { - try p.warnMsg(.{ .tag = .extra_for_capture, .token = identifier }); - warned_excess = true; - } + _ = try p.expectToken(.identifier); switch (p.tokenTag(p.tok_i)) { .comma => p.tok_i += 1, .pipe => { @@ -2128,11 +2121,6 @@ fn forPrefix(p: *Parse) Error!usize { if (p.eatToken(.pipe)) |_| break; } - if (captures < inputs) { - const index = p.scratch.items.len - captures; - const input = p.nodeMainToken(p.scratch.items[index]); - try p.warnMsg(.{ .tag = .for_input_not_captured, .token = input }); - } return inputs; } diff --git a/lib/std/zig/parser_fuzz.zig b/lib/std/zig/parser_fuzz.zig index dcf20ccb69d3337a8a39b6dc946872d784850c9c..62e6d18e264842c474870bd6a8d60bf5992193cf 100644 --- a/lib/std/zig/parser_fuzz.zig +++ b/lib/std/zig/parser_fuzz.zig @@ -52,6 +52,11 @@ test "newline required before doc comment not at start of file" { try checkAgainstOracle("///\ntest {}"); } +// Found using AFL++ +test "extra capture in for loop" { + try checkAgainstOracle("for(0)|t,r|0"); +} + fn checkAgainstOracle(source: [:0]const u8) !void { var fba_buf: [1 << 18]u8 = undefined; var fba: std.heap.FixedBufferAllocator = .init(&fba_buf); -- 2.54.0