mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-10-29 03:06:10 +08:00
grammar: fix ParamDeclList worst-case exponential time
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.
This commit is contained in:
2 files changed
+27
-23
No files matched your search
@@ -64,7 +64,7 @@ DeclPrefix
|
||||
/ KEYWORD_extern STRINGLITERALSINGLE? KEYWORD_fn
|
||||
/ (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? (KEYWORD_const / KEYWORD_var)
|
||||
|
||||
FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
|
||||
FnProto <- KEYWORD_fn IDENTIFIER? ParamDeclList ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
|
||||
|
||||
VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?
|
||||
|
||||
@@ -448,7 +448,11 @@ AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?
|
||||
|
||||
AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?
|
||||
|
||||
ParamDeclList <- (ParamDecl COMMA)* (ParamDecl / DOT3 COMMA?)?
|
||||
ParamDeclList <- LPAREN ParamDeclListRest
|
||||
ParamDeclListRest
|
||||
<- RPAREN
|
||||
/ DOT3 COMMA? RPAREN
|
||||
/ ParamDecl (COMMA ParamDeclListRest / RPAREN)
|
||||
|
||||
ExprList <- (Expr COMMA)* (Expr / !ExprPrefix)
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ const Parser = struct {
|
||||
pub fn parseFnProto(p: *Parser) Error!bool {
|
||||
return blk_0: {
|
||||
const pos_0 = p.i;
|
||||
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;
|
||||
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;
|
||||
p.i = pos_0;
|
||||
break :blk_0 false;
|
||||
};
|
||||
@@ -2286,26 +2286,26 @@ const Parser = struct {
|
||||
pub fn parseParamDeclList(p: *Parser) Error!bool {
|
||||
return blk_0: {
|
||||
const pos_0 = p.i;
|
||||
if (blk_1: {
|
||||
var i_1: usize = 0;
|
||||
while (blk_3: {
|
||||
const pos_3 = p.i;
|
||||
if (try p.parseParamDecl() and try p.parseCOMMA()) break :blk_3 true;
|
||||
p.i = pos_3;
|
||||
break :blk_3 false;
|
||||
}) {
|
||||
if (i_1 > max_depth) return error.MaxDepth;
|
||||
i_1 += 1;
|
||||
}
|
||||
break :blk_1 true;
|
||||
} and (blk_3: {
|
||||
const pos_3 = p.i;
|
||||
if (try p.parseParamDecl()) break :blk_3 true;
|
||||
p.i = pos_3;
|
||||
if (try p.parseDOT3() and (try p.parseCOMMA() or true)) break :blk_3 true;
|
||||
p.i = pos_3;
|
||||
break :blk_3 false;
|
||||
} or true)) break :blk_0 true;
|
||||
if (try p.parseLPAREN() and try p.parseParamDeclListRest()) break :blk_0 true;
|
||||
p.i = pos_0;
|
||||
break :blk_0 false;
|
||||
};
|
||||
}
|
||||
pub fn parseParamDeclListRest(p: *Parser) Error!bool {
|
||||
return blk_0: {
|
||||
const pos_0 = p.i;
|
||||
if (try p.parseRPAREN()) break :blk_0 true;
|
||||
p.i = pos_0;
|
||||
if (try p.parseDOT3() and (try p.parseCOMMA() or true) and try p.parseRPAREN()) break :blk_0 true;
|
||||
p.i = pos_0;
|
||||
if (try p.parseParamDecl() and blk_2: {
|
||||
const pos_2 = p.i;
|
||||
if (try p.parseCOMMA() and try p.parseParamDeclListRest()) break :blk_2 true;
|
||||
p.i = pos_2;
|
||||
if (try p.parseRPAREN()) break :blk_2 true;
|
||||
p.i = pos_2;
|
||||
break :blk_2 false;
|
||||
}) break :blk_0 true;
|
||||
p.i = pos_0;
|
||||
break :blk_0 false;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user