Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/loop without var decl #474

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
22 changes: 17 additions & 5 deletions Sources/Fuzzilli/Base/ProgramBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2493,19 +2493,31 @@ public class ProgramBuilder {
emit(EndForLoop())
}

public func buildForInLoop(_ obj: Variable, _ body: (Variable) -> ()) {
let i = emit(BeginForInLoop(), withInputs: [obj]).innerOutput
public func buildPlainForInLoop(_ obj: Variable, _ body: (Variable) -> ()) {
let i = emit(BeginPlainForInLoop(), withInputs: [obj]).innerOutput
body(i)
emit(EndForInLoop())
}

public func buildForOfLoop(_ obj: Variable, _ body: (Variable) -> ()) {
let i = emit(BeginForOfLoop(), withInputs: [obj]).innerOutput
public func buildForInLoopWithReassignment(_ obj: Variable, _ existingVar: Variable, _ body: () -> ()) {
emit(BeginForInLoopWithReassignment(), withInputs: [obj, existingVar])
body()
emit(EndForInLoop())
}

public func buildPlainForOfLoop(_ obj: Variable, _ body: (Variable) -> ()) {
let i = emit(BeginPlainForOfLoop(), withInputs: [obj]).innerOutput
body(i)
emit(EndForOfLoop())
}

public func buildForOfLoop(_ obj: Variable, selecting indices: [Int64], hasRestElement: Bool = false, _ body: ([Variable]) -> ()) {
public func buildForOfLoopWithReassignment(_ obj: Variable, _ existingVar: Variable, _ body: () -> ()) {
emit(BeginForOfLoopWithReassignment(), withInputs: [obj, existingVar])
body()
emit(EndForOfLoop())
}

public func buildForOfLoopWithDestruct(_ obj: Variable, selecting indices: [Int64], hasRestElement: Bool = false, _ body: ([Variable]) -> ()) {
let instr = emit(BeginForOfLoopWithDestruct(indices: indices, hasRestElement: hasRestElement), withInputs: [obj])
body(Array(instr.innerOutputs))
emit(EndForOfLoop())
Expand Down
6 changes: 4 additions & 2 deletions Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,10 @@ public let codeGeneratorWeights = [
"DoWhileLoopGenerator": 15,
"SimpleForLoopGenerator": 10,
"ComplexForLoopGenerator": 10,
"ForInLoopGenerator": 10,
"ForOfLoopGenerator": 10,
"PlainForInLoopGenerator": 8,
"ForInLoopWithReassignmentGenerator": 2,
"PlainForOfLoopGenerator": 8,
"ForOfLoopWithReassignmentGenerator": 2,
"ForOfWithDestructLoopGenerator": 3,
"RepeatLoopGenerator": 10,
"SwitchCaseBreakGenerator": 5,
Expand Down
25 changes: 20 additions & 5 deletions Sources/Fuzzilli/CodeGen/CodeGenerators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1370,14 +1370,29 @@ public let CodeGenerators: [CodeGenerator] = [
}
},

RecursiveCodeGenerator("ForInLoopGenerator", inputs: .preferred(.object())) { b, obj in
b.buildForInLoop(obj) { _ in
RecursiveCodeGenerator("PlainForInLoopGenerator", inputs: .preferred(.object())) { b, obj in
b.buildPlainForInLoop(obj) { _ in
b.buildRecursive()
}
},

RecursiveCodeGenerator("ForOfLoopGenerator", inputs: .preferred(.iterable)) { b, obj in
b.buildForOfLoop(obj) { _ in
RecursiveCodeGenerator("ForInLoopWithReassignmentGenerator", inputs: .preferred(.object())) { b, obj in
// use a pre-declared variable as the iterator variable (i.e., reassign it)
let existing = b.randomVariable()
b.buildForInLoopWithReassignment(obj, existing) {
b.buildRecursive()
}
},

RecursiveCodeGenerator("PlainForOfLoopGenerator", inputs: .preferred(.iterable)) { b, obj in
b.buildPlainForOfLoop(obj) { _ in
b.buildRecursive()
}
},

RecursiveCodeGenerator("ForOfLoopWithReassignmentGenerator", inputs: .preferred(.iterable)) { b, obj in
let existing = b.randomVariable()
b.buildForOfLoopWithReassignment(obj, existing) {
b.buildRecursive()
}
},
Expand All @@ -1394,7 +1409,7 @@ public let CodeGenerators: [CodeGenerator] = [
indices = [0]
}

b.buildForOfLoop(obj, selecting: indices, hasRestElement: probability(0.2)) { _ in
b.buildForOfLoopWithDestruct(obj, selecting: indices, hasRestElement: probability(0.2)) { _ in
b.buildRecursive()
}
},
Expand Down
77 changes: 55 additions & 22 deletions Sources/Fuzzilli/Compiler/Compiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -412,36 +412,69 @@ public class JavaScriptCompiler {
}

case .forInLoop(let forInLoop):
let initializer = forInLoop.left;
guard !initializer.hasValue else {
throw CompilerError.invalidNodeError("Expected no initial value for the variable declared in a for-in loop")
}

let initializer = forInLoop.left!
let obj = try compileExpression(forInLoop.right)
// Processing a for-in or for-of loop requires an iterator, which is typically declared in the function header.
// Alternatively, an existing variable can be used, resulting in an identifier instead of a variable declarator.
// If the identifier is not previously declared, it is implicitly created as a global variable.

switch initializer {
case .variableDeclarator(let variableDeclarator):
guard !variableDeclarator.hasValue else {
throw CompilerError.invalidNodeError("Expected no initial value for the variable declared in a for-in loop")
}
let loopVar = emit(BeginPlainForInLoop(), withInputs: [obj]).innerOutput
try enterNewScope {
map(variableDeclarator.name, to: loopVar)
try compileBody(forInLoop.body)
}
emit(EndForInLoop())

let loopVar = emit(BeginForInLoop(), withInputs: [obj]).innerOutput
try enterNewScope {
map(initializer.name, to: loopVar)
try compileBody(forInLoop.body)
case .identifier(let identifier):
let loopVar: Variable
if let existingVar = lookupIdentifier(identifier.name) {
loopVar = existingVar
} else {
loopVar = emit(LoadNamedVariable(identifier.name)).output
map(identifier.name, to: loopVar)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to do this here but not in the case above (line 436)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the case starting in line 438 is used for global variables and the case in line 436 for non-global variables. In both cases, the compiler just has an identfier as input. For global variables, lookupIdentifer will return nil because the identifier is not associted with a FuzzIL variable. Either because the global Variable has not been set but also when it has actually been set before, i.e. with StoreNamedVariable (see example "a" and "b")

Illustration

for (a of ["new a"]) {} // line 469. lookupIdentifier didn't return FuzzIL Variable because a is used for the first time here
output("value of a: " + a);

b = "old b";
for (b of ["new b"]) {} // line 469. lookupIdentifier didn't return FuzzIL Variable because the b identifier was not mapped to a FuzzIL variable
output("value of b: " + b);

var c = "old c";
for (c of ["new c"]) {} // line 467 because lookupIdentifier returned the FuzzIL Variable that the previous line mapped the identifier 'c' to
output("value of c: " + c);

for (var d of ["new d"]) {} // line 457. lhs of for loop header is not an identfier...
output("value of d: " + d);

}
emit(BeginForInLoopWithReassignment(), withInputs: [obj, loopVar])
try enterNewScope {
try compileBody(forInLoop.body)
}
emit(EndForInLoop())
}

emit(EndForInLoop())

case .forOfLoop(let forOfLoop):
let initializer = forOfLoop.left;
guard !initializer.hasValue else {
throw CompilerError.invalidNodeError("Expected no initial value for the variable declared in a for-of loop")
}

let initializer = forOfLoop.left!
let obj = try compileExpression(forOfLoop.right)

let loopVar = emit(BeginForOfLoop(), withInputs: [obj]).innerOutput
try enterNewScope {
map(initializer.name, to: loopVar)
try compileBody(forOfLoop.body)
}
switch initializer {
case .variableDeclarator(let variableDeclarator):
guard !variableDeclarator.hasValue else {
throw CompilerError.invalidNodeError("Expected no initial value for the variable declared in a for-of loop")
}
let loopVar = emit(BeginPlainForOfLoop(), withInputs: [obj]).innerOutput
try enterNewScope {
map(variableDeclarator.name, to: loopVar)
try compileBody(forOfLoop.body)
}
emit(EndForOfLoop())

emit(EndForOfLoop())
case .identifier(let identifier):
let loopVar: Variable
if let existingVar = lookupIdentifier(identifier.name) {
loopVar = existingVar
} else {
loopVar = emit(LoadNamedVariable(identifier.name)).output
map(identifier.name, to: loopVar)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

}
emit(BeginForOfLoopWithReassignment(), withInputs: [obj, loopVar])
try enterNewScope {
try compileBody(forOfLoop.body)
}
emit(EndForOfLoop())
}

case .breakStatement:
// If we're in both .loop and .switch context, then the loop must be the most recent context
Expand Down
34 changes: 22 additions & 12 deletions Sources/Fuzzilli/Compiler/Parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,25 +267,35 @@ function parse(script, proto) {
return makeStatement('ForLoop', forLoop);
}
case 'ForInStatement': {
assert(node.left.type === 'VariableDeclaration', "Expected variable declaration as init part of a for-in loop, found " + node.left.type);
assert(node.left.declarations.length === 1, "Expected exactly one variable declaration in the init part of a for-in loop");
let decl = node.left.declarations[0];
let forInLoop = {};
let initDecl = { name: decl.id.name };
assert(decl.init == null, "Expected no initial value for the variable declared as part of a for-in loop")
forInLoop.left = make('VariableDeclarator', initDecl);
if (node.left.type === 'VariableDeclaration') {
assert(node.left.declarations.length === 1, "Expected exactly one variable declaration in the init part of a for-in loop");
let decl = node.left.declarations[0];
let initDecl = { name: decl.id.name };
assert(decl.init == null, "Expected no initial value for the variable declared as part of a for-in loop")
forInLoop.variableDeclarator = make('VariableDeclarator', initDecl);
} else if (node.left.type === 'Identifier') {
forOfLoop.identifier = make('Identifier', { name: node.left.name });
} else {
throw "Unsupported left side of for-in loop: " + node.left.type;
}
forInLoop.right = visitExpression(node.right);
forInLoop.body = visitStatement(node.body);
return makeStatement('ForInLoop', forInLoop);
}
case 'ForOfStatement': {
assert(node.left.type === 'VariableDeclaration', "Expected variable declaration as init part of a for-in loop, found " + node.left.type);
assert(node.left.declarations.length === 1, "Expected exactly one variable declaration in the init part of a for-in loop");
let decl = node.left.declarations[0];
let forOfLoop = {};
let initDecl = { name: decl.id.name };
assert(decl.init == null, "Expected no initial value for the variable declared as part of a for-in loop")
forOfLoop.left = make('VariableDeclarator', initDecl);
if (node.left.type === 'VariableDeclaration') {
assert(node.left.declarations.length === 1, "Expected exactly one variable declaration in the init part of a for-of loop");
let decl = node.left.declarations[0];
let initDecl = { name: decl.id.name };
assert(decl.init == null, "Expected no initial value for the variable declared as part of a for-of loop")
forOfLoop.variableDeclarator = make('VariableDeclarator', initDecl);
} else if (node.left.type === 'Identifier') {
forOfLoop.identifier = make('Identifier', { name: node.left.name });
} else {
throw "Unsupported left side of for-of loop: " + node.left.type;
}
forOfLoop.right = visitExpression(node.right);
forOfLoop.body = visitStatement(node.body);
return makeStatement('ForOfLoop', forOfLoop);
Expand Down
29 changes: 20 additions & 9 deletions Sources/Fuzzilli/FuzzIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -792,12 +792,16 @@ extension Instruction: ProtobufConvertible {
$0.beginForLoopBody = Fuzzilli_Protobuf_BeginForLoopBody()
case .endForLoop:
$0.endForLoop = Fuzzilli_Protobuf_EndForLoop()
case .beginForInLoop:
$0.beginForInLoop = Fuzzilli_Protobuf_BeginForInLoop()
case .beginPlainForInLoop:
$0.beginPlainForInLoop = Fuzzilli_Protobuf_BeginPlainForInLoop()
case .beginForInLoopWithReassignment:
$0.beginForInLoopWithReassignment = Fuzzilli_Protobuf_BeginForInLoopWithReassignment()
case .endForInLoop:
$0.endForInLoop = Fuzzilli_Protobuf_EndForInLoop()
case .beginForOfLoop:
$0.beginForOfLoop = Fuzzilli_Protobuf_BeginForOfLoop()
case .beginPlainForOfLoop:
$0.beginPlainForOfLoop = Fuzzilli_Protobuf_BeginPlainForOfLoop()
case .beginForOfLoopWithReassignment:
$0.beginForOfLoopWithReassignment = Fuzzilli_Protobuf_BeginForOfLoopWithReassignment()
case .beginForOfLoopWithDestruct(let op):
$0.beginForOfLoopWithDestruct = Fuzzilli_Protobuf_BeginForOfLoopWithDestruct.with {
$0.indices = op.indices.map({ Int32($0) })
Expand Down Expand Up @@ -1216,14 +1220,21 @@ extension Instruction: ProtobufConvertible {
op = BeginForLoopBody(numLoopVariables: inouts.count)
case .endForLoop:
op = EndForLoop()
case .beginForInLoop:
op = BeginForInLoop()
case .beginPlainForInLoop:
op = BeginPlainForInLoop()
case .beginForInLoopWithReassignment:
op = BeginForInLoopWithReassignment()
case .endForInLoop:
op = EndForInLoop()
case .beginForOfLoop:
op = BeginForOfLoop()
case .beginPlainForOfLoop:
op = BeginPlainForOfLoop()
case .beginForOfLoopWithReassignment:
op = BeginForOfLoopWithReassignment()
case .beginForOfLoopWithDestruct(let p):
op = BeginForOfLoopWithDestruct(indices: p.indices.map({ Int64($0) }), hasRestElement: p.hasRestElement_p)
op = BeginForOfLoopWithDestruct(
indices: p.indices.map({ Int64($0) }),
hasRestElement: p.hasRestElement_p
)
case .endForOfLoop:
op = EndForOfLoop()
case .beginRepeatLoop(let p):
Expand Down
16 changes: 12 additions & 4 deletions Sources/Fuzzilli/FuzzIL/JSTyper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,10 @@ public struct JSTyper: Analyzer {
case .endForLoop:
state.endGroupOfConditionallyExecutingBlocks(typeChanges: &typeChanges)
case .beginWhileLoopBody,
.beginForInLoop,
.beginForOfLoop,
.beginPlainForInLoop,
.beginForInLoopWithReassignment,
.beginPlainForOfLoop,
.beginForOfLoopWithReassignment,
.beginForOfLoopWithDestruct,
.beginRepeatLoop,
.beginCodeString:
Expand Down Expand Up @@ -797,12 +799,18 @@ public struct JSTyper: Analyzer {
assert(inputTypes.count == instr.numInnerOutputs)
zip(instr.innerOutputs, inputTypes).forEach({ set($0, $1) })

case .beginForInLoop:
case .beginPlainForInLoop:
set(instr.innerOutput, .string)

case .beginForOfLoop:
case .beginForInLoopWithReassignment:
set(instr.input(1), .string)

case .beginPlainForOfLoop:
set(instr.innerOutput, .anything)

case .beginForOfLoopWithReassignment:
set(instr.input(1), .anything)

case .beginForOfLoopWithDestruct:
for v in instr.innerOutputs {
set(v, .anything)
Expand Down
26 changes: 19 additions & 7 deletions Sources/Fuzzilli/FuzzIL/JsOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1974,30 +1974,42 @@ final class EndForLoop: JsOperation {
}
}

final class BeginForInLoop: JsOperation {
override var opcode: Opcode { .beginForInLoop(self) }

final class BeginPlainForInLoop: JsOperation {
override var opcode: Opcode { .beginPlainForInLoop(self) }
init() {
super.init(numInputs: 1, numInnerOutputs: 1, attributes: [.isBlockStart, .propagatesSurroundingContext], contextOpened: [.javascript, .loop])
}
}

final class BeginForInLoopWithReassignment: JsOperation {
override var opcode: Opcode { .beginForInLoopWithReassignment(self) }
init() {
super.init(numInputs: 2, numInnerOutputs: 0, attributes: [.isBlockStart, .propagatesSurroundingContext], contextOpened: [.javascript, .loop])
}
}

final class EndForInLoop: JsOperation {
override var opcode: Opcode { .endForInLoop(self) }

init() {
super.init(attributes: .isBlockEnd)
}
}

final class BeginForOfLoop: JsOperation {
override var opcode: Opcode { .beginForOfLoop(self) }

// TODO: Support even more types of for loops, e.g.: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of#examples
final class BeginPlainForOfLoop: JsOperation {
override var opcode: Opcode { .beginPlainForOfLoop(self) }
init() {
super.init(numInputs: 1, numInnerOutputs: 1, attributes: [.isBlockStart, .propagatesSurroundingContext], contextOpened: [.javascript, .loop])
}
}

final class BeginForOfLoopWithReassignment: JsOperation {
override var opcode: Opcode { .beginForOfLoopWithReassignment(self) }
init() {
super.init(numInputs: 2, numInnerOutputs: 0, attributes: [.isBlockStart, .propagatesSurroundingContext], contextOpened: [.javascript, .loop])
}
}

final class BeginForOfLoopWithDestruct: JsOperation {
override var opcode: Opcode { .beginForOfLoopWithDestruct(self) }

Expand Down
6 changes: 4 additions & 2 deletions Sources/Fuzzilli/FuzzIL/Opcodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,11 @@ enum Opcode {
case beginForLoopAfterthought(BeginForLoopAfterthought)
case beginForLoopBody(BeginForLoopBody)
case endForLoop(EndForLoop)
case beginForInLoop(BeginForInLoop)
case beginPlainForInLoop(BeginPlainForInLoop)
case beginForInLoopWithReassignment(BeginForInLoopWithReassignment)
case endForInLoop(EndForInLoop)
case beginForOfLoop(BeginForOfLoop)
case beginPlainForOfLoop(BeginPlainForOfLoop)
case beginForOfLoopWithReassignment(BeginForOfLoopWithReassignment)
case beginForOfLoopWithDestruct(BeginForOfLoopWithDestruct)
case endForOfLoop(EndForOfLoop)
case beginRepeatLoop(BeginRepeatLoop)
Expand Down
Loading
Loading