From 99babb4ae44688c85b786f742d8cf54379819e5e Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 15 Jun 2026 18:32:35 +0200 Subject: [PATCH] grammar: remove unbounded lookahead This unbounded lookahead is causing discrepancies between the Parse.zig implementation and the PEG grammar. Also, unbounded lookahead should never have been added to the PEG grammar in the first place. It was added in 785fb1be111186525bf288fa3460945404f676eb which demonstrated insufficient understanding of PEG semantics through many redundant additions. Whatever problem this unbounded lookahead attempted to solve needs to be solved differently in any case. This commit adds one test case found by AFL++ that fails before this commit and now passes. --- doc/langref/grammar.peg | 45 ++----- lib/std/zig/parser_fuzz.zig | 5 + lib/std/zig/parser_generated_oracle.zig | 168 ++---------------------- 3 files changed, 31 insertions(+), 187 deletions(-) diff --git a/doc/langref/grammar.peg b/doc/langref/grammar.peg index a778dc7e60bb75695df06efe73f9608c222baf35..b061d3eb5084d630ddd34c18cc49fa033f1ff995 100644 --- a/doc/langref/grammar.peg +++ b/doc/langref/grammar.peg @@ -14,7 +14,7 @@ Decl / KEYWORD_extern STRINGLITERALSINGLE? FnProto SEMICOLON / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? GlobalVarDecl -FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr !ExprSuffix +FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? @@ -93,25 +93,25 @@ PrefixExpr <- PrefixOp* PrimaryExpr PrimaryExpr <- AsmExpr / IfExpr - / KEYWORD_break BreakLabel? (Expr !ExprSuffix / !SinglePtrTypeStart) - / KEYWORD_comptime Expr !ExprSuffix - / KEYWORD_nosuspend Expr !ExprSuffix - / KEYWORD_continue BreakLabel? (Expr !ExprSuffix / !SinglePtrTypeStart) - / KEYWORD_resume Expr !ExprSuffix - / KEYWORD_return (Expr !ExprSuffix / !SinglePtrTypeStart) + / KEYWORD_break BreakLabel? Expr? + / KEYWORD_comptime Expr + / KEYWORD_nosuspend Expr + / KEYWORD_continue BreakLabel? Expr? + / KEYWORD_resume Expr + / KEYWORD_return Expr? / BlockLabel? LoopExpr / Block / CurlySuffixExpr -IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix +IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)? Block <- LBRACE BlockStatement* RBRACE LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr) -ForExpr <- ForPrefix Expr (KEYWORD_else Expr / !KEYWORD_else) !ExprSuffix +ForExpr <- ForPrefix Expr (KEYWORD_else Expr / !KEYWORD_else) -WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)? !ExprSuffix +WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)? CurlySuffixExpr <- TypeExpr InitList? @@ -139,7 +139,7 @@ PrimaryTypeExpr / LabeledTypeExpr / IDENTIFIER / IfTypeExpr - / KEYWORD_comptime TypeExpr !ExprSuffix + / KEYWORD_comptime TypeExpr / KEYWORD_error DOT IDENTIFIER / KEYWORD_anyframe / KEYWORD_unreachable @@ -152,7 +152,7 @@ ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE GroupedExpr <- LPAREN Expr RPAREN -IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix +IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? LabeledTypeExpr <- BlockLabel Block @@ -161,9 +161,9 @@ LabeledTypeExpr LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr) -ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr / !KEYWORD_else) !ExprSuffix +ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr / !KEYWORD_else) -WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? !ExprSuffix +WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)? SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE @@ -329,23 +329,6 @@ SuffixOp FnCallArguments <- LPAREN ExprList RPAREN -ExprSuffix - <- KEYWORD_or - / KEYWORD_and - / CompareOp - / BitwiseOp - / BitShiftOp - / AdditionOp - / MultiplyOp - / EXCLAMATIONMARK - / SuffixOp - / FnCallArguments - -LabelableExpr - <- Block - / SwitchExpr - / LoopExpr - # Ptr specific SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET diff --git a/lib/std/zig/parser_fuzz.zig b/lib/std/zig/parser_fuzz.zig index 62e6d18e264842c474870bd6a8d60bf5992193cf..8adb5f225c004414a464f1cd75eba163bbf527b8 100644 --- a/lib/std/zig/parser_fuzz.zig +++ b/lib/std/zig/parser_fuzz.zig @@ -57,6 +57,11 @@ test "extra capture in for loop" { try checkAgainstOracle("for(0)|t,r|0"); } +// Found using AFL++ +test "expression nesting" { + try checkAgainstOracle("test{*comptime 0 == 0;}"); +} + fn checkAgainstOracle(source: [:0]const u8) !void { var fba_buf: [1 << 18]u8 = undefined; var fba: std.heap.FixedBufferAllocator = .init(&fba_buf); diff --git a/lib/std/zig/parser_generated_oracle.zig b/lib/std/zig/parser_generated_oracle.zig index 675d5231269c557ace96022f11abf26cda1884a0..0e3d078372a87e7569f3335dd45fb8ae5a4dd22b 100644 --- a/lib/std/zig/parser_generated_oracle.zig +++ b/lib/std/zig/parser_generated_oracle.zig @@ -123,12 +123,7 @@ const Parser = struct { pub fn parseFnProto(p: *Parser) bool { return blk_0: { const pos_0 = p.i; - if (p.parseKEYWORD_fn() and (p.parseIDENTIFIER() or true) and p.parseLPAREN() and p.parseParamDeclList() and p.parseRPAREN() and (p.parseByteAlign() or true) and (p.parseAddrSpace() or true) and (p.parseLinkSection() or true) and (p.parseCallConv() or true) and (p.parseEXCLAMATIONMARK() or true) and p.parseTypeExpr() and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; - }) break :blk_0 true; + if (p.parseKEYWORD_fn() and (p.parseIDENTIFIER() or true) and p.parseLPAREN() and p.parseParamDeclList() and p.parseRPAREN() and (p.parseByteAlign() or true) and (p.parseAddrSpace() or true) and (p.parseLinkSection() or true) and (p.parseCallConv() or true) and (p.parseEXCLAMATIONMARK() or true) and p.parseTypeExpr()) break :blk_0 true; p.i = pos_0; break :blk_0 false; }; @@ -571,83 +566,17 @@ const Parser = struct { p.i = pos_0; if (p.parseIfExpr()) break :blk_0 true; p.i = pos_0; - if (p.parseKEYWORD_break() and (p.parseBreakLabel() or true) and blk_2: { - const pos_2 = p.i; - if (p.parseExpr() and blk_3: { - const pos_3 = p.i; - const match_3 = p.parseExprSuffix(); - p.i = pos_3; - break :blk_3 !match_3; - }) break :blk_2 true; - p.i = pos_2; - if (blk_3: { - const pos_3 = p.i; - const match_3 = p.parseSinglePtrTypeStart(); - p.i = pos_3; - break :blk_3 !match_3; - }) break :blk_2 true; - p.i = pos_2; - break :blk_2 false; - }) break :blk_0 true; + if (p.parseKEYWORD_break() and (p.parseBreakLabel() or true) and (p.parseExpr() or true)) break :blk_0 true; p.i = pos_0; - if (p.parseKEYWORD_comptime() and p.parseExpr() and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; - }) break :blk_0 true; + if (p.parseKEYWORD_comptime() and p.parseExpr()) break :blk_0 true; p.i = pos_0; - if (p.parseKEYWORD_nosuspend() and p.parseExpr() and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; - }) break :blk_0 true; + if (p.parseKEYWORD_nosuspend() and p.parseExpr()) break :blk_0 true; p.i = pos_0; - if (p.parseKEYWORD_continue() and (p.parseBreakLabel() or true) and blk_2: { - const pos_2 = p.i; - if (p.parseExpr() and blk_3: { - const pos_3 = p.i; - const match_3 = p.parseExprSuffix(); - p.i = pos_3; - break :blk_3 !match_3; - }) break :blk_2 true; - p.i = pos_2; - if (blk_3: { - const pos_3 = p.i; - const match_3 = p.parseSinglePtrTypeStart(); - p.i = pos_3; - break :blk_3 !match_3; - }) break :blk_2 true; - p.i = pos_2; - break :blk_2 false; - }) break :blk_0 true; + if (p.parseKEYWORD_continue() and (p.parseBreakLabel() or true) and (p.parseExpr() or true)) break :blk_0 true; p.i = pos_0; - if (p.parseKEYWORD_resume() and p.parseExpr() and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; - }) break :blk_0 true; + if (p.parseKEYWORD_resume() and p.parseExpr()) break :blk_0 true; p.i = pos_0; - if (p.parseKEYWORD_return() and blk_2: { - const pos_2 = p.i; - if (p.parseExpr() and blk_3: { - const pos_3 = p.i; - const match_3 = p.parseExprSuffix(); - p.i = pos_3; - break :blk_3 !match_3; - }) break :blk_2 true; - p.i = pos_2; - if (blk_3: { - const pos_3 = p.i; - const match_3 = p.parseSinglePtrTypeStart(); - p.i = pos_3; - break :blk_3 !match_3; - }) break :blk_2 true; - p.i = pos_2; - break :blk_2 false; - }) break :blk_0 true; + if (p.parseKEYWORD_return() and (p.parseExpr() or true)) break :blk_0 true; p.i = pos_0; if ((p.parseBlockLabel() or true) and p.parseLoopExpr()) break :blk_0 true; p.i = pos_0; @@ -666,12 +595,7 @@ const Parser = struct { if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseExpr()) break :blk_3 true; p.i = pos_3; break :blk_3 false; - } or true) and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; - }) break :blk_0 true; + } or true)) break :blk_0 true; p.i = pos_0; break :blk_0 false; }; @@ -717,11 +641,6 @@ const Parser = struct { }) break :blk_2 true; p.i = pos_2; break :blk_2 false; - } and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; }) break :blk_0 true; p.i = pos_0; break :blk_0 false; @@ -735,12 +654,7 @@ const Parser = struct { if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseExpr()) break :blk_3 true; p.i = pos_3; break :blk_3 false; - } or true) and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; - }) break :blk_0 true; + } or true)) break :blk_0 true; p.i = pos_0; break :blk_0 false; }; @@ -848,12 +762,7 @@ const Parser = struct { p.i = pos_0; if (p.parseIfTypeExpr()) break :blk_0 true; p.i = pos_0; - if (p.parseKEYWORD_comptime() and p.parseTypeExpr() and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; - }) break :blk_0 true; + if (p.parseKEYWORD_comptime() and p.parseTypeExpr()) break :blk_0 true; p.i = pos_0; if (p.parseKEYWORD_error() and p.parseDOT() and p.parseIDENTIFIER()) break :blk_0 true; p.i = pos_0; @@ -907,12 +816,7 @@ const Parser = struct { if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseTypeExpr()) break :blk_3 true; p.i = pos_3; break :blk_3 false; - } or true) and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; - }) break :blk_0 true; + } or true)) break :blk_0 true; p.i = pos_0; break :blk_0 false; }; @@ -959,11 +863,6 @@ const Parser = struct { }) break :blk_2 true; p.i = pos_2; break :blk_2 false; - } and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; }) break :blk_0 true; p.i = pos_0; break :blk_0 false; @@ -977,12 +876,7 @@ const Parser = struct { if (p.parseKEYWORD_else() and (p.parsePayload() or true) and p.parseTypeExpr()) break :blk_3 true; p.i = pos_3; break :blk_3 false; - } or true) and blk_1: { - const pos_1 = p.i; - const match_1 = p.parseExprSuffix(); - p.i = pos_1; - break :blk_1 !match_1; - }) break :blk_0 true; + } or true)) break :blk_0 true; p.i = pos_0; break :blk_0 false; }; @@ -1643,44 +1537,6 @@ const Parser = struct { break :blk_0 false; }; } - pub fn parseExprSuffix(p: *Parser) bool { - return blk_0: { - const pos_0 = p.i; - if (p.parseKEYWORD_or()) break :blk_0 true; - p.i = pos_0; - if (p.parseKEYWORD_and()) break :blk_0 true; - p.i = pos_0; - if (p.parseCompareOp()) break :blk_0 true; - p.i = pos_0; - if (p.parseBitwiseOp()) break :blk_0 true; - p.i = pos_0; - if (p.parseBitShiftOp()) break :blk_0 true; - p.i = pos_0; - if (p.parseAdditionOp()) break :blk_0 true; - p.i = pos_0; - if (p.parseMultiplyOp()) break :blk_0 true; - p.i = pos_0; - if (p.parseEXCLAMATIONMARK()) break :blk_0 true; - p.i = pos_0; - if (p.parseSuffixOp()) break :blk_0 true; - p.i = pos_0; - if (p.parseFnCallArguments()) break :blk_0 true; - p.i = pos_0; - break :blk_0 false; - }; - } - pub fn parseLabelableExpr(p: *Parser) bool { - return blk_0: { - const pos_0 = p.i; - if (p.parseBlock()) break :blk_0 true; - p.i = pos_0; - if (p.parseSwitchExpr()) break :blk_0 true; - p.i = pos_0; - if (p.parseLoopExpr()) break :blk_0 true; - p.i = pos_0; - break :blk_0 false; - }; - } pub fn parseSliceTypeStart(p: *Parser) bool { return blk_0: { const pos_0 = p.i; -- 2.54.0