Skip to content

Commit

Permalink
feature/forawaitof
Browse files Browse the repository at this point in the history
  • Loading branch information
p0-tato committed Feb 3, 2025
1 parent f31876f commit fb092ce
Show file tree
Hide file tree
Showing 22 changed files with 1,110 additions and 556 deletions.
13 changes: 13 additions & 0 deletions Sources/Fuzzilli/Base/ProgramBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2748,6 +2748,19 @@ public class ProgramBuilder {
emit(EndForLoop())
}

public func buildForAwaitOfLoop(_ obj: Variable, _ body: (Variable) -> ()) {
let i = emit(BeginForAwaitOfLoop(), withInputs: [obj]).innerOutput
body(i)
emit(EndForAwaitOfLoop())
}

public func buildForAwaitOfLoop(_ obj: Variable, selecting indices: [Int64], hasRestElement: Bool = false, _ body: ([Variable]) -> ()) {
let instr = emit(BeginForAwaitOfLoopWithDestruct(indices: indices, hasRestElement: hasRestElement), withInputs: [obj])
body(Array(instr.innerOutputs))
emit(EndForAwaitOfLoop())
}


public func buildForInLoop(_ obj: Variable, _ body: (Variable) -> ()) {
let i = emit(BeginForInLoop(), withInputs: [obj]).innerOutput
body(i)
Expand Down
2 changes: 2 additions & 0 deletions Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ public let codeGeneratorWeights = [
"DoWhileLoopGenerator": 15,
"SimpleForLoopGenerator": 10,
"ComplexForLoopGenerator": 10,
"ForAwaitOfLoopGenerator": 10,
"ForAwaitOfWithDestructLoopGenerator": 3,
"ForInLoopGenerator": 10,
"ForOfLoopGenerator": 10,
"ForOfWithDestructLoopGenerator": 3,
Expand Down
27 changes: 27 additions & 0 deletions Sources/Fuzzilli/CodeGen/CodeGenerators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,33 @@ public let CodeGenerators: [CodeGenerator] = [
}
},

RecursiveCodeGenerator("ForAwaitOfLoopGenerator", inContext: .asyncFunction, inputs: .preferred(.iterable)) { b, obj in
assert(b.context.contains(.asyncFunction))

b.buildForAwaitOfLoop(obj) { _ in
b.buildRecursive()
}
},

RecursiveCodeGenerator("ForAwaitOfWithDestructLoopGenerator", inContext: .asyncFunction, inputs: .preferred(.iterable)) { b, obj in
assert(b.context.contains(.asyncFunction))

var indices: [Int64] = []
for idx in 0..<Int64.random(in: 1..<5) {
withProbability(0.8) {
indices.append(idx)
}
}

if indices.isEmpty {
indices = [0]
}

b.buildForOfLoop(obj, selecting: indices, hasRestElement: probability(0.2)) { _ in
b.buildRecursive()
}
},

RecursiveCodeGenerator("ForInLoopGenerator", inputs: .preferred(.object())) { b, obj in
b.buildForInLoop(obj) { _ in
b.buildRecursive()
Expand Down
21 changes: 21 additions & 0 deletions Sources/Fuzzilli/Compiler/Compiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,27 @@ public class JavaScriptCompiler {

emit(EndForLoop())

case .forAwaitOfLoop(let forAwaitOfLoop):
let initializer = forAwaitOfLoop.left;

if !contextAnalyzer.context.contains(.asyncFunction) {
throw CompilerError.invalidNodeError("`for await...of` is currently only supported in async functions")
}

guard !initializer.hasValue else {
throw CompilerError.invalidNodeError("Expected no initial value for the variable declared in a for-await-of loop")
}

let obj = try compileExpression(forAwaitOfLoop.right)

let loopVar = emit(BeginForAwaitOfLoop(), withInputs: [obj]).innerOutput
try enterNewScope {
map(initializer.name, to: loopVar)
try compileBody(forAwaitOfLoop.body)
}

emit(EndForAwaitOfLoop())

case .forInLoop(let forInLoop):
let initializer = forInLoop.left;
guard !initializer.hasValue else {
Expand Down
18 changes: 15 additions & 3 deletions Sources/Fuzzilli/Compiler/Parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,18 @@ function parse(script, proto) {
forLoop.body = visitStatement(node.body);
return makeStatement('ForLoop', forLoop);
}
case 'ForAwaitOfStatement': {
assert(node.left.type === 'VaraibleDeclaration', "Expected variable declaration as init part of a for-await-of loop, found " + node.left.type)
assert(node.left.declarations.length === 1, "Expected exactly one variable declaration in the init part of a for-await-of loop");
let decl = node.left.declarations[0];
let forAwaitOfLoop = {};
let initDecl = { name: decl.id.name };
assert(decl.init == null, "Expected no initial value for the varaible declared as part of a for-await-of loop")
forAwaitOfLoop.left = make('VariableDeclarator', initDecl);
forAwaitOfLoop.right = visitExpression(node.right);
forAwaitOfLoop.body = visitStatement(node.body);
return makeStatement('ForAwaitOfLoop', forAwaitOfLoop);
}
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");
Expand All @@ -294,12 +306,12 @@ function parse(script, proto) {
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");
assert(node.left.type === 'VariableDeclaration', "Expected variable declaration as init part of a for-of loop, found " + node.left.type);
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 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")
assert(decl.init == null, "Expected no initial value for the variable declared as part of a for-of loop")
forOfLoop.left = make('VariableDeclarator', initDecl);
forOfLoop.right = visitExpression(node.right);
forOfLoop.body = visitStatement(node.body);
Expand Down
15 changes: 15 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,15 @@ extension Instruction: ProtobufConvertible {
$0.beginForLoopBody = Fuzzilli_Protobuf_BeginForLoopBody()
case .endForLoop:
$0.endForLoop = Fuzzilli_Protobuf_EndForLoop()
case .beginForAwaitOfLoop:
$0.beginForAwaitOfLoop = Fuzzilli_Protobuf_BeginForAwaitOfLoop()
case .beginForAwaitOfLoopWithDestruct(let op):
$0.beginForAwaitOfLoopWithDestruct = Fuzzilli_Protobuf_BeginForAwaitOfLoopWithDestruct.with {
$0.indices = op.indices.map({ Int32($0) })
$0.hasRestElement_p = op.hasRestElement
}
case .endForAwaitOfLoop:
$0.endForAwaitOfLoop = Fuzzilli_Protobuf_EndForAwaitOfLoop()
case .beginForInLoop:
$0.beginForInLoop = Fuzzilli_Protobuf_BeginForInLoop()
case .endForInLoop:
Expand Down Expand Up @@ -1852,6 +1861,12 @@ extension Instruction: ProtobufConvertible {
op = BeginForLoopBody(numLoopVariables: inouts.count)
case .endForLoop:
op = EndForLoop()
case .beginForAwaitOfLoop:
op = BeginForAwaitOfLoop()
case .beginForAwaitOfLoopWithDestruct(let p):
op = BeginForAwaitOfLoopWithDestruct(indices: p.indices.map({ Int64($0) }), hasRestElement: p.hasRestElement_p)
case .endForAwaitOfLoop:
op = EndForAwaitOfLoop()
case .beginForInLoop:
op = BeginForInLoop()
case .endForInLoop:
Expand Down
11 changes: 11 additions & 0 deletions Sources/Fuzzilli/FuzzIL/JSTyper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ public struct JSTyper: Analyzer {
case .endForLoop:
state.endGroupOfConditionallyExecutingBlocks(typeChanges: &typeChanges)
case .beginWhileLoopBody,
.beginForAwaitOfLoop,
.beginForAwaitOfLoopWithDestruct,
.beginForInLoop,
.beginForOfLoop,
.beginForOfLoopWithDestruct,
Expand All @@ -409,6 +411,7 @@ public struct JSTyper: Analyzer {
// Push a new state tracking the types inside the loop
state.enterConditionallyExecutingBlock(typeChanges: &typeChanges)
case .endWhileLoop,
.endForAwaitOfLoop,
.endForInLoop,
.endForOfLoop,
.endRepeatLoop,
Expand Down Expand Up @@ -926,6 +929,14 @@ public struct JSTyper: Analyzer {
assert(inputTypes.count == instr.numInnerOutputs)
zip(instr.innerOutputs, inputTypes).forEach({ set($0, $1) })

case .beginForAwaitOfLoop:
set(instr.innerOutput, .string)

case .beginForAwaitOfLoopWithDestruct:
for v in instr.innerOutputs {
set(v, .anything)
}

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

Expand Down
31 changes: 31 additions & 0 deletions Sources/Fuzzilli/FuzzIL/JsOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2140,6 +2140,37 @@ final class EndForLoop: JsOperation {
}
}

final class BeginForAwaitOfLoop: JsOperation {
override var opcode: Opcode { .beginForAwaitOfLoop(self) }

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

final class BeginForAwaitOfLoopWithDestruct: JsOperation {
override var opcode: Opcode { .beginForAwaitOfLoopWithDestruct(self) }

let indices: [Int64]
let hasRestElement: Bool

init(indices: [Int64], hasRestElement: Bool) {
assert(indices.count >= 1)
self.indices = indices
self.hasRestElement = hasRestElement
super.init(numInputs: 1, numInnerOutputs: indices.count, attributes: [.isBlockStart, .propagatesSurroundingContext], contextOpened: [.javascript, .loop, .asyncFunction])
}
}

final class EndForAwaitOfLoop: JsOperation {
override var opcode: Opcode { .endForAwaitOfLoop(self) }

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


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

Expand Down
3 changes: 3 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Opcodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ enum Opcode {
case beginForLoopAfterthought(BeginForLoopAfterthought)
case beginForLoopBody(BeginForLoopBody)
case endForLoop(EndForLoop)
case beginForAwaitOfLoop(BeginForAwaitOfLoop)
case beginForAwaitOfLoopWithDestruct(BeginForAwaitOfLoopWithDestruct)
case endForAwaitOfLoop(EndForAwaitOfLoop)
case beginForInLoop(BeginForInLoop)
case endForInLoop(EndForInLoop)
case beginForOfLoop(BeginForOfLoop)
Expand Down
3 changes: 3 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Semantics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ extension Operation {
return endOp is BeginForLoopBody
case .beginForLoopBody:
return endOp is EndForLoop
case .beginForAwaitOfLoop,
.beginForAwaitOfLoopWithDestruct:
return endOp is EndForAwaitOfLoop
case .beginForInLoop:
return endOp is EndForInLoop
case .beginForOfLoop,
Expand Down
13 changes: 13 additions & 0 deletions Sources/Fuzzilli/Lifting/FuzzILLifter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,19 @@ public class FuzzILLifter: Lifter {
w.decreaseIndentionLevel()
w.emit("EndForLoop")

case .beginForAwaitOfLoop:
w.emit("BeginForAwaitOfLooop \(input(0)) -> \(innerOutput())")
w.increaseIndentionLevel()

case .beginForAwaitOfLoopWithDestruct(let op):
let outputs = instr.innerOutputs.map(lift)
w.emit("BeginForAwaitOfLoopWithDestruct \(input(0)) -> [\(liftArrayDestructPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement))]")
w.increaseIndentionLevel()

case .endForAwaitOfLoop:
w.decreaseIndentionLevel()
w.emit("EndForAwaitOfLoop")

case .beginForInLoop:
w.emit("BeginForInLoop \(input(0)) -> \(innerOutput())")
w.increaseIndentionLevel()
Expand Down
19 changes: 19 additions & 0 deletions Sources/Fuzzilli/Lifting/JavaScriptLifter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,25 @@ public class JavaScriptLifter: Lifter {
w.leaveCurrentBlock()
w.emit("}")

case .beginForAwaitOfLoop:
let V = w.declare(instr.innerOutput)
let LET = w.declarationKeyword(for: instr.innerOutput)
let OBJ = input(0)
w.emit("for await (\(LET) \(V) of \(OBJ)) {")
w.enterNewBlock()

case .beginForAwaitOfLoopWithDestruct(let op):
let outputs = w.declareAll(instr.innerOutputs)
let PATTERN = liftArrayDestructPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement)
let LET = w.varKeyword
let OBJ = input(0)
w.emit("for await (\(LET) [\(PATTERN)] of \(OBJ)) {")
w.enterNewBlock()

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

case .beginForInLoop:
let LET = w.declarationKeyword(for: instr.innerOutput)
let V = w.declare(instr.innerOutput)
Expand Down
2 changes: 2 additions & 0 deletions Sources/Fuzzilli/Minimization/BlockReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ struct BlockReducer: Reducer {
case .beginWhileLoopHeader,
.beginDoWhileLoopBody,
.beginForLoopInitializer,
.beginForAwaitOfLoop,
.beginForAwaitOfLoopWithDestruct,
.beginForInLoop,
.beginForOfLoop,
.beginForOfLoopWithDestruct,
Expand Down
8 changes: 5 additions & 3 deletions Sources/Fuzzilli/Minimization/LoopSimplifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ struct LoopSimplifier: Reducer {
tryReplaceDoWhileLoopWithRepeatLoop(group, with: helper)
case .beginRepeatLoop:
tryReduceRepeatLoopIterationCount(group, with: helper)
case .beginForInLoop,
.beginForOfLoop,
.beginForOfLoopWithDestruct:
case .beginForAwaitOfLoop,
.beginForAwaitOfLoopWithDestruct,
.beginForInLoop,
.beginForOfLoop,
.beginForOfLoopWithDestruct:
// These loops are (usually) guaranteed to terminate, and should probably anyway not be replaced by repeat-loops.
break
default:
Expand Down
3 changes: 3 additions & 0 deletions Sources/Fuzzilli/Mutators/OperationMutator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,9 @@ public class OperationMutator: BaseInstructionMutator {
.beginForLoopAfterthought(_),
.beginForLoopBody(_),
.endForLoop(_),
.beginForAwaitOfLoop(_),
.beginForAwaitOfLoopWithDestruct(_),
.endForAwaitOfLoop(_),
.beginForInLoop(_),
.endForInLoop(_),
.beginForOfLoop(_),
Expand Down
Loading

0 comments on commit fb092ce

Please sign in to comment.