| author | |
| committer | |
| log | 296b39c32324d536b13fe92fef42a7cca3487d35 |
| tree | 0378d4cd33edd870e183bc79ce759a23b0952dba |
| parent | 664062a5f633913906fe5c4b7ecd672b51b5cdd9 |
| signature |
AFL++ is able to identify "hangs" as well as crashes and has found
several small inputs that take multiple seconds for the generated parser
to parse. This is because our PEG is not yet LL(k) despite recognizing
the same language as our handwritten LL(k) recursive descent parser.
Consider the PEG semantics for the following non-terminal:
ParamDeclList <- (ParamDecl COMMA)* (ParamDecl / DOT3 COMMA?)?
Here, the PEG checks twice if the last ParamDecl matches when
there is no trailing comma. Now, consider that the ParamDecl may
recursively contain a nested ParamDeclList, which in turn has ParamDecls
that contain further nested ParamDeclLists. It should be clear that this
results in worst-case exponential runtime.
This can be avoided by defining ParamDeclList using recursion instead of
the * operator.
This same pattern likely needs to be applied to other instances of this
pattern in the grammar.
The main motivation for fixing this is to improve fuzzer performance.
Additionally, using recursion rather than the * operator makes it easier
to translate from a PEG to a CFG, which I'm quite confident we will want
to do for the language specification eventually. A CFG would also allow
for smith-based fuzz testing to complement the oracle-based approach.2 files changed, 27 insertions(+), 23 deletions(-)
doc/langref/grammar.peg+6-2| ... | @@ -64,7 +64,7 @@ DeclPrefix | ... | @@ -64,7 +64,7 @@ DeclPrefix |
| 64 | / KEYWORD_extern STRINGLITERALSINGLE? KEYWORD_fn | 64 | / KEYWORD_extern STRINGLITERALSINGLE? KEYWORD_fn |
| 65 | / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? (KEYWORD_const / KEYWORD_var) | 65 | / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? (KEYWORD_const / KEYWORD_var) |
| 66 | 66 | ||
| 67 | FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr | 67 | FnProto <- KEYWORD_fn IDENTIFIER? ParamDeclList ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr |
| 68 | 68 | ||
| 69 | VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? | 69 | VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? |
| 70 | 70 | ||
| ... | @@ -448,7 +448,11 @@ AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem? | ... | @@ -448,7 +448,11 @@ AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem? |
| 448 | 448 | ||
| 449 | AsmInputList <- (AsmInputItem COMMA)* AsmInputItem? | 449 | AsmInputList <- (AsmInputItem COMMA)* AsmInputItem? |
| 450 | 450 | ||
| 451 | ParamDeclList <- (ParamDecl COMMA)* (ParamDecl / DOT3 COMMA?)? | 451 | ParamDeclList <- LPAREN ParamDeclListRest |
| 452 | ParamDeclListRest | ||
| 453 | <- RPAREN | ||
| 454 | / DOT3 COMMA? RPAREN | ||
| 455 | / ParamDecl (COMMA ParamDeclListRest / RPAREN) | ||
| 452 | 456 | ||
| 453 | ExprList <- (Expr COMMA)* (Expr / !ExprPrefix) | 457 | ExprList <- (Expr COMMA)* (Expr / !ExprPrefix) |
| 454 | 458 |
lib/std/zig/parser_generated_oracle.zig+21-21| ... | @@ -203,7 +203,7 @@ const Parser = struct { | ... | @@ -203,7 +203,7 @@ const Parser = struct { |
| 203 | pub fn parseFnProto(p: *Parser) Error!bool { | 203 | pub fn parseFnProto(p: *Parser) Error!bool { |
| 204 | return blk_0: { | 204 | return blk_0: { |
| 205 | const pos_0 = p.i; | 205 | const pos_0 = p.i; |
| 206 | if (try p.parseKEYWORD_fn() and (try p.parseIDENTIFIER() or true) and try p.parseLPAREN() and try p.parseParamDeclList() and try p.parseRPAREN() and (try p.parseByteAlign() or true) and (try p.parseAddrSpace() or true) and (try p.parseLinkSection() or true) and (try p.parseCallConv() or true) and (try p.parseEXCLAMATIONMARK() or true) and try p.parseTypeExpr()) break :blk_0 true; | 206 | if (try p.parseKEYWORD_fn() and (try p.parseIDENTIFIER() or true) and try p.parseParamDeclList() and (try p.parseByteAlign() or true) and (try p.parseAddrSpace() or true) and (try p.parseLinkSection() or true) and (try p.parseCallConv() or true) and (try p.parseEXCLAMATIONMARK() or true) and try p.parseTypeExpr()) break :blk_0 true; |
| 207 | p.i = pos_0; | 207 | p.i = pos_0; |
| 208 | break :blk_0 false; | 208 | break :blk_0 false; |
| 209 | }; | 209 | }; |
| ... | @@ -2286,26 +2286,26 @@ const Parser = struct { | ... | @@ -2286,26 +2286,26 @@ const Parser = struct { |
| 2286 | pub fn parseParamDeclList(p: *Parser) Error!bool { | 2286 | pub fn parseParamDeclList(p: *Parser) Error!bool { |
| 2287 | return blk_0: { | 2287 | return blk_0: { |
| 2288 | const pos_0 = p.i; | 2288 | const pos_0 = p.i; |
| 2289 | if (blk_1: { | 2289 | if (try p.parseLPAREN() and try p.parseParamDeclListRest()) break :blk_0 true; |
| 2290 | var i_1: usize = 0; | 2290 | p.i = pos_0; |
| 2291 | while (blk_3: { | 2291 | break :blk_0 false; |
| 2292 | const pos_3 = p.i; | 2292 | }; |
| 2293 | if (try p.parseParamDecl() and try p.parseCOMMA()) break :blk_3 true; | 2293 | } |
| 2294 | p.i = pos_3; | 2294 | pub fn parseParamDeclListRest(p: *Parser) Error!bool { |
| 2295 | break :blk_3 false; | 2295 | return blk_0: { |
| 2296 | }) { | 2296 | const pos_0 = p.i; |
| 2297 | if (i_1 > max_depth) return error.MaxDepth; | 2297 | if (try p.parseRPAREN()) break :blk_0 true; |
| 2298 | i_1 += 1; | 2298 | p.i = pos_0; |
| 2299 | } | 2299 | if (try p.parseDOT3() and (try p.parseCOMMA() or true) and try p.parseRPAREN()) break :blk_0 true; |
| 2300 | break :blk_1 true; | 2300 | p.i = pos_0; |
| 2301 | } and (blk_3: { | 2301 | if (try p.parseParamDecl() and blk_2: { |
| 2302 | const pos_3 = p.i; | 2302 | const pos_2 = p.i; |
| 2303 | if (try p.parseParamDecl()) break :blk_3 true; | 2303 | if (try p.parseCOMMA() and try p.parseParamDeclListRest()) break :blk_2 true; |
| 2304 | p.i = pos_3; | 2304 | p.i = pos_2; |
| 2305 | if (try p.parseDOT3() and (try p.parseCOMMA() or true)) break :blk_3 true; | 2305 | if (try p.parseRPAREN()) break :blk_2 true; |
| 2306 | p.i = pos_3; | 2306 | p.i = pos_2; |
| 2307 | break :blk_3 false; | 2307 | break :blk_2 false; |
| 2308 | } or true)) break :blk_0 true; | 2308 | }) break :blk_0 true; |
| 2309 | p.i = pos_0; | 2309 | p.i = pos_0; |
| 2310 | break :blk_0 false; | 2310 | break :blk_0 false; |
| 2311 | }; | 2311 | }; |