Skip to content

Commit

Permalink
Improve handling of block bodies in parser
Browse files Browse the repository at this point in the history
We now have a common helper function (visitBody) to process the body of
various statements instead of repeating the same code fragment.
Similarly, we now also have a common visitParameters function. Finally,
this change also adds a test to make sure that we don't introduce
additional block statements due to the way statement bodies are
represented in the AST. See the comment in the test for more details.

Change-Id: Id494669c02ec36132daf7327bf2654fc21e13e0e
Reviewed-on: https://chrome-internal-review.googlesource.com/c/v8/fuzzilli/+/7934187
Reviewed-by: Carl Smith <[email protected]>
Commit-Queue: Samuel Groß <[email protected]>
  • Loading branch information
Samuel Groß authored and V8-internal LUCI CQ committed Jan 7, 2025
1 parent 50d2fcc commit 48cf047
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 30 deletions.
2 changes: 1 addition & 1 deletion Sources/Fuzzilli/Compiler/Compiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,7 @@ public class JavaScriptCompiler {
guard let body = arrowFunction.body else { throw CompilerError.invalidNodeError("missing body in arrow function") }
switch body {
case .block(let block):
try compileStatement(block)
try compileBody(block)
case .expression(let expr):
let result = try compileExpression(expr)
emit(Return(hasReturnValue: true), withInputs: [result])
Expand Down
68 changes: 39 additions & 29 deletions Sources/Fuzzilli/Compiler/Parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ function tryReadFile(path) {

// Parse the given JavaScript script and return an AST compatible with Fuzzilli's protobuf-based AST format.
function parse(script, proto) {
let ast = Parser.parse(script, { plugins: ["v8intrinsic"] });
let ast = Parser.parse(script, { plugins: ["v8intrinsic"] });

function assertNoError(err) {
if (err) throw err;
}
Expand Down Expand Up @@ -77,6 +77,20 @@ function parse(script, proto) {
return make('Parameter', { name: param.name });
}

function visitParameters(params) {
return params.map(visitParameter)
}

// Processes the body of a block statement node and returns a list of statements.
function visitBody(node) {
assert(node.type === 'BlockStatement', "Expected block statement, found " + node.type);
let statements = [];
for (let stmt of node.body) {
statements.push(visitStatement(stmt));
}
return statements;
}

function visitVariableDeclaration(node) {
let kind;
if (node.kind === "var") {
Expand All @@ -102,22 +116,18 @@ function parse(script, proto) {
return { kind, declarations };
}


function visitStatement(node) {
switch (node.type) {
case 'EmptyStatement': {
return makeStatement('EmptyStatement', {});
}
case 'BlockStatement': {
let body = [];
for (let stmt of node.body) {
body.push(visitStatement(stmt));
}
return makeStatement('BlockStatement', {body});
let body = visitBody(node);
return makeStatement('BlockStatement', { body });
}
case 'ExpressionStatement': {
let expr = visitExpression(node.expression);
return makeStatement('ExpressionStatement', {expression: expr});
let expression = visitExpression(node.expression);
return makeStatement('ExpressionStatement', { expression });
}
case 'VariableDeclaration': {
return makeStatement('VariableDeclaration', visitVariableDeclaration(node));
Expand All @@ -133,9 +143,9 @@ function parse(script, proto) {
} else if (node.async) {
type = 2; //"ASYNC";
}
let parameters = node.params.map(visitParameter);
let parameters = visitParameters(node.params);
assert(node.body.type === 'BlockStatement', "Expected block statement as function declaration body, found " + node.body.type);
let body = node.body.body.map(visitStatement);
let body = visitBody(node.body);
return makeStatement('FunctionDeclaration', { name, type, parameters, body });
}
case 'ClassDeclaration': {
Expand Down Expand Up @@ -182,29 +192,29 @@ function parse(script, proto) {
assert(name === 'constructor', "Expected name to be exactly 'constructor'");
assert(!isStatic, "Expected isStatic to be false");

let parameters = method.params.map(visitParameter);
let body = method.body.body.map(visitStatement);
let parameters = visitParameters(method.params);
let body = visitBody(method.body);
field.ctor = make('ClassConstructor', { parameters, body });
} else if (method.kind === 'method') {
assert(method.body.type === 'BlockStatement', "Expected method.body.type to be exactly 'BlockStatement'");

let parameters = method.params.map(visitParameter);
let body = method.body.body.map(visitStatement);
let parameters = visitParameters(method.params);
let body = visitBody(method.body);
field.method = make('ClassMethod', { name, isStatic, parameters, body });
} else if (method.kind === 'get') {
assert(method.params.length === 0, "Expected method.params.length to be exactly 0");
assert(!method.generator && !method.async, "Expected both conditions to hold: !method.generator and !method.async");
assert(method.body.type === 'BlockStatement', "Expected method.body.type to be exactly 'BlockStatement'");

let body = method.body.body.map(visitStatement);
let body = visitBody(method.body);
field.getter = make('ClassGetter', { name, isStatic, body });
} else if (method.kind === 'set') {
assert(method.params.length === 1, "Expected method.params.length to be exactly 1");
assert(!method.generator && !method.async, "Expected both conditions to hold: !method.generator and !method.async");
assert(method.body.type === 'BlockStatement', "Expected method.body.type to be exactly 'BlockStatement'");

let parameter = visitParameter(method.params[0]);
let body = method.body.body.map(visitStatement);
let body = visitBody(method.body);
field.setter = make('ClassSetter', { name, isStatic, parameter, body });
} else {
throw "Unknown method kind: " + method.kind;
Expand Down Expand Up @@ -299,7 +309,7 @@ function parse(script, proto) {
case 'TryStatement': {
assert(node.block.type === 'BlockStatement', "Expected block statement as body of a try block");
let tryStatement = {}
tryStatement.body = node.block.body.map(visitStatement);
tryStatement.body = visitBody(node.block);
assert(node.handler !== null || node.finalizer !== null, "TryStatements require either a handler or a finalizer (or both)")
if (node.handler !== null) {
assert(node.handler.type === 'CatchClause', "Expected catch clause as try handler");
Expand All @@ -308,13 +318,13 @@ function parse(script, proto) {
if (node.handler.param !== null) {
catchClause.parameter = visitParameter(node.handler.param);
}
catchClause.body = node.handler.body.body.map(visitStatement);
catchClause.body = visitBody(node.handler.body);
tryStatement.catch = make('CatchClause', catchClause);
}
if (node.finalizer !== null) {
assert(node.finalizer.type === 'BlockStatement', "Expected block statement as body of finally block");
let finallyClause = {};
finallyClause.body = node.finalizer.body.map(visitStatement);
finallyClause.body = visitBody(node.finalizer);
tryStatement.finally = make('FinallyClause', finallyClause);
}
return makeStatement('TryStatement', tryStatement);
Expand Down Expand Up @@ -442,23 +452,23 @@ function parse(script, proto) {
} else if (method.async) {
out.type = 2; //"ASYNC";
}
out.parameters = method.params.map(visitParameter);
out.body = method.body.body.map(visitStatement);
out.parameters = visitParameters(method.params);
out.body = visitBody(method.body);
field.method = make('ObjectMethod', out);
} else if (method.kind === 'get') {
assert(method.params.length === 0, "Expected method.params.length to be exactly 0");
assert(!method.generator && !method.async, "Expected both conditions to hold: !method.generator and !method.async");
assert(method.body.type === 'BlockStatement', "Expected method.body.type to be exactly 'BlockStatement'");

out.body = method.body.body.map(visitStatement);
out.body = visitBody(method.body);
field.getter = make('ObjectGetter', out);
} else if (method.kind === 'set') {
assert(method.params.length === 1, "Expected method.params.length to be exactly 1");
assert(!method.generator && !method.async, "Expected both conditions to hold: !method.generator and !method.async");
assert(method.body.type === 'BlockStatement', "Expected method.body.type to be exactly 'BlockStatement'");

out.parameter = visitParameter(method.params[0]);
out.body = method.body.body.map(visitStatement);
out.body = visitBody(method.body);
field.setter = make('ObjectSetter', out);
} else {
throw "Unknown method kind: " + method.kind;
Expand Down Expand Up @@ -489,9 +499,9 @@ function parse(script, proto) {
} else if (node.async) {
type = 2; //"ASYNC";
}
let parameters = node.params.map(visitParameter);
let parameters = visitParameters(node.params);
assert(node.body.type === 'BlockStatement', "Expected block statement as function expression body, found " + node.body.type);
let body = node.body.body.map(visitStatement);
let body = visitBody(node.body);
return makeExpression('FunctionExpression', { type, parameters, body });
}
case 'ArrowFunctionExpression': {
Expand All @@ -501,7 +511,7 @@ function parse(script, proto) {
if (node.async) {
type = 2; //"ASYNC";
}
let parameters = node.params.map(visitParameter);
let parameters = visitParameters(node.params);
let out = { type, parameters };
if (node.body.type === 'BlockStatement') {
out.block = visitStatement(node.body);
Expand Down Expand Up @@ -621,7 +631,7 @@ protobuf.load(astProtobufDefinitionPath, function(err, root) {

// Uncomment this to print the AST to stdout (will be very verbose).
//console.log(JSON.stringify(ast, null, 2));

const AST = root.lookupType('compiler.protobuf.AST');
let buffer = AST.encode(ast).finish();

Expand Down
64 changes: 64 additions & 0 deletions Tests/FuzzilliTests/CompilerTests/no_added_blocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
if (typeof output === 'undefined') output = console.log;

// This tests makes sure that we don't create additional block statements during compilation.
// For example, a typical AST for an if statement (ignoring the condition) would look like this:
//
// IfStatement
// |
// BlockStatement
// / | \
// Foo Bar Baz
//
// In that case, we want to generate the following IL code:
//
// BeginIf
// Foo
// Bar
// Baz
// EndIf
//
// And not
//
// BeginIf
// BeginBlock
// Foo
// Bar
// Baz
// EndBlock
// EndIf
//
function test() {
function f1() {}
function* f2() {}
async function f3() {}
let f4 = () => {};
{}
if (true) {}
else {}
for (let i = 0; i < 1; i++) {}
for (let p of {}) {}
for (let p in {}) {}
while (false) {}
do {} while (false);
try {} catch (e) {} finally {}
with ({}) {}
let o = {
m() {},
get a() {},
set a(v) {}
};
class C {
constructor() {}
m() {}
get a() {}
set a(v) {}
static n() {}
static get b() {}
static set b(v) {}
static {}
}
}

let source = test.toString();
let num_braces = source.split('{').length - 1;
output(num_braces);

0 comments on commit 48cf047

Please sign in to comment.