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
71 changes: 49 additions & 22 deletions Sources/Fuzzilli/Compiler/Compiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -412,36 +412,63 @@ 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(BeginForInLoop(), 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):
guard let loopVar = lookupIdentifier(identifier.name) else {
// TODO instead of throwing an error, we should create a global property with the identifier name
throw CompilerError.unsupportedFeatureError("Identifier '\(identifier.name)' not found for for-in loop.")
}
emit(BeginForInLoop(usesPredeclaredIterator: true), 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(BeginForOfLoop(), withInputs: [obj]).innerOutput
try enterNewScope {
map(variableDeclarator.name, to: loopVar)
try compileBody(forOfLoop.body)
}
emit(EndForOfLoop())

emit(EndForOfLoop())
case .identifier(let identifier):
guard let loopVar = lookupIdentifier(identifier.name) else {
// TODO instead of throwing an error, we should create a global property with the identifier name
throw CompilerError.unsupportedFeatureError("Identifier '\(identifier.name)' not found for for-of loop.")
}
emit(BeginForOfLoop(usesPredeclaredIterator: true), 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
26 changes: 14 additions & 12 deletions Sources/Fuzzilli/Compiler/Parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,25 +267,27 @@ 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 forInLoop.identifier = make('Identifier', { name: node.left.name });
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 forOfLoop.identifier = make('Identifier', { name: node.left.name });
forOfLoop.right = visitExpression(node.right);
forOfLoop.body = visitStatement(node.body);
return makeStatement('ForOfLoop', forOfLoop);
Expand Down
20 changes: 12 additions & 8 deletions Sources/Fuzzilli/FuzzIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -790,12 +790,16 @@ extension Instruction: ProtobufConvertible {
$0.beginForLoopBody = Fuzzilli_Protobuf_BeginForLoopBody()
case .endForLoop:
$0.endForLoop = Fuzzilli_Protobuf_EndForLoop()
case .beginForInLoop:
$0.beginForInLoop = Fuzzilli_Protobuf_BeginForInLoop()
case .beginForInLoop(let op):
$0.beginForInLoop = Fuzzilli_Protobuf_BeginForInLoop.with { protobufOp in
protobufOp.usesPredeclaredIterator = op.usesPredeclaredIterator
}
case .endForInLoop:
$0.endForInLoop = Fuzzilli_Protobuf_EndForInLoop()
case .beginForOfLoop:
$0.beginForOfLoop = Fuzzilli_Protobuf_BeginForOfLoop()
case .beginForOfLoop(let op):
$0.beginForOfLoop = Fuzzilli_Protobuf_BeginForOfLoop.with { protobufOp in
protobufOp.usesPredeclaredIterator = op.usesPredeclaredIterator
}
case .beginForOfLoopWithDestruct(let op):
$0.beginForOfLoopWithDestruct = Fuzzilli_Protobuf_BeginForOfLoopWithDestruct.with {
$0.indices = op.indices.map({ Int32($0) })
Expand Down Expand Up @@ -1212,12 +1216,12 @@ extension Instruction: ProtobufConvertible {
op = BeginForLoopBody(numLoopVariables: inouts.count)
case .endForLoop:
op = EndForLoop()
case .beginForInLoop:
op = BeginForInLoop()
case .beginForInLoop(let p):
op = BeginForInLoop(usesPredeclaredIterator: p.usesPredeclaredIterator)
case .endForInLoop:
op = EndForInLoop()
case .beginForOfLoop:
op = BeginForOfLoop()
case .beginForOfLoop(let p):
op = BeginForOfLoop(usesPredeclaredIterator: p.usesPredeclaredIterator)
case .beginForOfLoopWithDestruct(let p):
op = BeginForOfLoopWithDestruct(indices: p.indices.map({ Int64($0) }), hasRestElement: p.hasRestElement_p)
case .endForOfLoop:
Expand Down
12 changes: 10 additions & 2 deletions Sources/Fuzzilli/FuzzIL/JSTyper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -794,10 +794,18 @@ public struct JSTyper: Analyzer {
zip(instr.innerOutputs, inputTypes).forEach({ set($0, $1) })

case .beginForInLoop:
set(instr.innerOutput, .string)
if instr.numInputs == 2 {
set(instr.input(1), .string) // Iterator is declared beforehand
} else {
set(instr.innerOutput, .string) // Iterator is declared in the function header
}

case .beginForOfLoop:
set(instr.innerOutput, .anything)
if instr.numInputs == 2 {
set(instr.input(1), .anything)
} else {
set(instr.innerOutput, .anything)
}

case .beginForOfLoopWithDestruct:
for v in instr.innerOutputs {
Expand Down
25 changes: 21 additions & 4 deletions Sources/Fuzzilli/FuzzIL/JsOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1967,8 +1967,16 @@ final class EndForLoop: JsOperation {
final class BeginForInLoop: JsOperation {
override var opcode: Opcode { .beginForInLoop(self) }

init() {
super.init(numInputs: 1, numInnerOutputs: 1, attributes: [.isBlockStart, .propagatesSurroundingContext], contextOpened: [.javascript, .loop])
let usesPredeclaredIterator: Bool

init(usesPredeclaredIterator: Bool = false) {
self.usesPredeclaredIterator = usesPredeclaredIterator
super.init(
numInputs: usesPredeclaredIterator ? 2 : 1,
numInnerOutputs: usesPredeclaredIterator ? 0 : 1,
attributes: [.isBlockStart, .propagatesSurroundingContext],
contextOpened: [.javascript, .loop]
)
}
}

Expand All @@ -1983,8 +1991,17 @@ final class EndForInLoop: JsOperation {
final class BeginForOfLoop: JsOperation {
override var opcode: Opcode { .beginForOfLoop(self) }

init() {
super.init(numInputs: 1, numInnerOutputs: 1, attributes: [.isBlockStart, .propagatesSurroundingContext], contextOpened: [.javascript, .loop])

let usesPredeclaredIterator: Bool

init(usesPredeclaredIterator: Bool = false) {
self.usesPredeclaredIterator = usesPredeclaredIterator
super.init(
numInputs: usesPredeclaredIterator ? 2 : 1,
numInnerOutputs: usesPredeclaredIterator ? 0 : 1,
attributes: [.isBlockStart, .propagatesSurroundingContext],
contextOpened: [.javascript, .loop]
)
}
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Semantics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ extension Operation {
case .destructArrayAndReassign,
.destructObjectAndReassign:
return inputIdx != 0
case .beginForInLoop(let op):
return op.usesPredeclaredIterator && inputIdx == 1
case .beginForOfLoop(let op):
return op.usesPredeclaredIterator && inputIdx == 1
default:
return false
}
Expand Down
12 changes: 10 additions & 2 deletions Sources/Fuzzilli/Lifting/FuzzILLifter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -667,15 +667,23 @@ public class FuzzILLifter: Lifter {
w.emit("EndForLoop")

case .beginForInLoop:
w.emit("BeginForInLoop \(input(0)) -> \(innerOutput())")
if instr.numInputs == 2 {
w.emit("BeginForInLoop \(input(0)) -> \(input(1))") // Iterator is declared beforehand
} else {
w.emit("BeginForInLoop \(input(0)) -> \(innerOutput())") // Iterator is declared in the function header
}
w.increaseIndentionLevel()

case .endForInLoop:
w.decreaseIndentionLevel()
w.emit("EndForInLoop")

case .beginForOfLoop:
w.emit("BeginForOfLoop \(input(0)) -> \(innerOutput())")
if instr.numInputs == 2 {
w.emit("BeginForOfLoop \(input(0)) -> \(input(1))")
} else {
w.emit("BeginForOfLoop \(input(0)) -> \(innerOutput())")
}
w.increaseIndentionLevel()

case .beginForOfLoopWithDestruct(let op):
Expand Down
24 changes: 18 additions & 6 deletions Sources/Fuzzilli/Lifting/JavaScriptLifter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1169,21 +1169,33 @@ public class JavaScriptLifter: Lifter {
w.emit("}")

case .beginForInLoop:
let LET = w.declarationKeyword(for: instr.innerOutput)
let V = w.declare(instr.innerOutput)
let OBJ = input(0)
w.emit("for (\(LET) \(V) in \(OBJ)) {")
let LET: String = ""
TobiasWienand marked this conversation as resolved.
Show resolved Hide resolved
if instr.numInputs == 2 { // Iterator is declared elsewhere
let V = input(1)
w.emit("for (\(LET) \(V) in \(OBJ)) {")
} else { // Iterator is declared in the function header
let V = w.declare(instr.innerOutput)
let LET = w.declarationKeyword(for: instr.innerOutput)
w.emit("for (\(LET) \(V) in \(OBJ)) {")
}
w.enterNewBlock()

case .endForInLoop:
w.leaveCurrentBlock()
w.emit("}")

case .beginForOfLoop:
let V = w.declare(instr.innerOutput)
let LET = w.declarationKeyword(for: instr.innerOutput)
let OBJ = input(0)
w.emit("for (\(LET) \(V) of \(OBJ)) {")
let LET: String = ""
TobiasWienand marked this conversation as resolved.
Show resolved Hide resolved
if instr.numInputs == 2 {
let V = input(1)
w.emit("for (\(LET) \(V) of \(OBJ)) {")
} else {
let V = w.declare(instr.innerOutput)
let LET = w.declarationKeyword(for: instr.innerOutput)
w.emit("for (\(LET) \(V) of \(OBJ)) {")
}
w.enterNewBlock()

case .beginForOfLoopWithDestruct(let op):
Expand Down
Loading
Loading