diff --git a/Sources/Fuzzilli/Base/ProgramBuilder.swift b/Sources/Fuzzilli/Base/ProgramBuilder.swift index 9b8b380e7..60fc84b4e 100644 --- a/Sources/Fuzzilli/Base/ProgramBuilder.swift +++ b/Sources/Fuzzilli/Base/ProgramBuilder.swift @@ -2531,6 +2531,14 @@ public class ProgramBuilder { emit(LoopContinue(), withInputs: []) } + public func breakNested(_ depth: Variable){ + emit(BreakNested(), withInputs: [depth]) + } + + public func continueNested(_ depth: Variable){ + emit(ContinueNested(), withInputs: [depth]) + } + public func buildTryCatchFinally(tryBody: () -> (), catchBody: ((Variable) -> ())? = nil, finallyBody: (() -> ())? = nil) { assert(catchBody != nil || finallyBody != nil, "Must have either a Catch or a Finally block (or both)") emit(BeginTry()) diff --git a/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift b/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift index 4c8d809cc..679a982eb 100644 --- a/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift +++ b/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift @@ -194,4 +194,6 @@ public let codeGeneratorWeights = [ "ApiMethodCallGenerator": 15, "ApiFunctionCallGenerator": 15, "VoidGenerator": 1, + "LoopLabelBreakGenerator": 3, + "ContinueLabelGenerator": 3 ] diff --git a/Sources/Fuzzilli/CodeGen/CodeGenerators.swift b/Sources/Fuzzilli/CodeGen/CodeGenerators.swift index 1d0167493..c249b274e 100644 --- a/Sources/Fuzzilli/CodeGen/CodeGenerators.swift +++ b/Sources/Fuzzilli/CodeGen/CodeGenerators.swift @@ -1415,6 +1415,15 @@ public let CodeGenerators: [CodeGenerator] = [ b.loopContinue() }, + CodeGenerator("LoopLabelBreakGenerator", inContext: .loop) { b in + b.breakNested(b.loadInt(Int64.random(in: 0...100))) + }, + + CodeGenerator("ContinueLabelGenerator", inContext: .loop) { b in + assert(b.context.contains(.loop)) + b.continueNested(b.loadInt(Int64.random(in: 0...100))) + }, + RecursiveCodeGenerator("TryCatchGenerator") { b in // Build either try-catch-finally, try-catch, or try-finally withEqualProbability({ diff --git a/Sources/Fuzzilli/FuzzIL/Instruction.swift b/Sources/Fuzzilli/FuzzIL/Instruction.swift index 4ba5c2792..f474f8d15 100644 --- a/Sources/Fuzzilli/FuzzIL/Instruction.swift +++ b/Sources/Fuzzilli/FuzzIL/Instruction.swift @@ -816,6 +816,10 @@ extension Instruction: ProtobufConvertible { $0.loopBreak = Fuzzilli_Protobuf_LoopBreak() case .loopContinue: $0.loopContinue = Fuzzilli_Protobuf_LoopContinue() + case .breakNested: + $0.breakNested = Fuzzilli_Protobuf_BreakNested() + case .continueNested: + $0.continueNested = Fuzzilli_Protobuf_ContinueNested() case .beginTry: $0.beginTry = Fuzzilli_Protobuf_BeginTry() case .beginCatch: @@ -1234,6 +1238,10 @@ extension Instruction: ProtobufConvertible { op = LoopBreak() case .loopContinue: op = LoopContinue() + case .breakNested: + op = BreakNested() + case .continueNested: + op = ContinueNested() case .beginTry: op = BeginTry() case .beginCatch: diff --git a/Sources/Fuzzilli/FuzzIL/JsOperations.swift b/Sources/Fuzzilli/FuzzIL/JsOperations.swift index 5905a9e11..8ae867db2 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -2064,6 +2064,22 @@ final class LoopContinue: JsOperation { } } +final class BreakNested: JsOperation { + override var opcode: Opcode { .breakNested(self) } + + init() { + super.init(numInputs: 1, attributes: [.isJump], requiredContext: [.javascript, .loop]) + } +} + +final class ContinueNested: JsOperation { + override var opcode: Opcode { .continueNested(self) } + + init() { + super.init(numInputs: 1, attributes: [.isJump], requiredContext: [.javascript, .loop]) + } +} + final class BeginTry: JsOperation { override var opcode: Opcode { .beginTry(self) } diff --git a/Sources/Fuzzilli/FuzzIL/Opcodes.swift b/Sources/Fuzzilli/FuzzIL/Opcodes.swift index 8f376ea09..b85559296 100644 --- a/Sources/Fuzzilli/FuzzIL/Opcodes.swift +++ b/Sources/Fuzzilli/FuzzIL/Opcodes.swift @@ -186,6 +186,8 @@ enum Opcode { case endRepeatLoop(EndRepeatLoop) case loopBreak(LoopBreak) case loopContinue(LoopContinue) + case breakNested(BreakNested) + case continueNested(ContinueNested) case beginTry(BeginTry) case beginCatch(BeginCatch) case beginFinally(BeginFinally) diff --git a/Sources/Fuzzilli/Lifting/FuzzILLifter.swift b/Sources/Fuzzilli/Lifting/FuzzILLifter.swift index 8bfe3507c..68ddcf61b 100644 --- a/Sources/Fuzzilli/Lifting/FuzzILLifter.swift +++ b/Sources/Fuzzilli/Lifting/FuzzILLifter.swift @@ -709,6 +709,12 @@ public class FuzzILLifter: Lifter { case .loopContinue: w.emit("Continue") + case .breakNested: + w.emit("BreakNested") + + case .continueNested: + w.emit("ContinueNested") + case .beginTry: w.emit("BeginTry") w.increaseIndentionLevel() diff --git a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift index b7de0815e..fd42967a1 100644 --- a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift +++ b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift @@ -43,6 +43,8 @@ public class JavaScriptLifter: Lifter { } private var forLoopHeaderStack = Stack() + private var actualLoopDepth = 0 + public init(prefix: String = "", suffix: String = "", ecmaVersion: ECMAScriptVersion) { @@ -1059,14 +1061,20 @@ public class JavaScriptLifter: Lifter { case .beginWhileLoopBody: let COND = handleEndSingleExpressionContext(result: input(0), with: &w) + actualLoopDepth += 1 + w.recordLoopPos() w.emitBlock("while (\(COND)) {") w.enterNewBlock() case .endWhileLoop: w.leaveCurrentBlock() w.emit("}") + actualLoopDepth -= 1 + w.popLoopPos() case .beginDoWhileLoopBody: + actualLoopDepth += 1 + w.recordLoopPos() w.emit("do {") w.enterNewBlock() @@ -1077,6 +1085,8 @@ public class JavaScriptLifter: Lifter { case .endDoWhileLoop: let COND = handleEndSingleExpressionContext(result: input(0), with: &w) w.emitBlock("} while (\(COND))") + actualLoopDepth -= 1 + w.popLoopPos() case .beginForLoopInitializer: // While we could inline into the loop header, we probably don't want to do that as it will often lead @@ -1152,7 +1162,8 @@ public class JavaScriptLifter: Lifter { let INITIALIZER = header.initializer var CONDITION = header.condition var AFTERTHOUGHT = handleEndSingleExpressionContext(with: &w) - + actualLoopDepth += 1 + w.recordLoopPos() if !INITIALIZER.contains("\n") && !CONDITION.contains("\n") && !AFTERTHOUGHT.contains("\n") { if !CONDITION.isEmpty { CONDITION = " " + CONDITION } if !AFTERTHOUGHT.isEmpty { AFTERTHOUGHT = " " + AFTERTHOUGHT } @@ -1171,22 +1182,30 @@ public class JavaScriptLifter: Lifter { case .endForLoop: w.leaveCurrentBlock() w.emit("}") + actualLoopDepth -= 1 + w.popLoopPos() case .beginForInLoop: let LET = w.declarationKeyword(for: instr.innerOutput) let V = w.declare(instr.innerOutput) let OBJ = input(0) + actualLoopDepth += 1 + w.recordLoopPos() w.emit("for (\(LET) \(V) in \(OBJ)) {") w.enterNewBlock() case .endForInLoop: w.leaveCurrentBlock() w.emit("}") + actualLoopDepth -= 1 + w.popLoopPos() case .beginForOfLoop: let V = w.declare(instr.innerOutput) let LET = w.declarationKeyword(for: instr.innerOutput) let OBJ = input(0) + actualLoopDepth += 1 + w.recordLoopPos() w.emit("for (\(LET) \(V) of \(OBJ)) {") w.enterNewBlock() @@ -1195,12 +1214,16 @@ public class JavaScriptLifter: Lifter { let PATTERN = liftArrayDestructPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement) let LET = w.varKeyword let OBJ = input(0) + actualLoopDepth += 1 + w.recordLoopPos() w.emit("for (\(LET) [\(PATTERN)] of \(OBJ)) {") w.enterNewBlock() case .endForOfLoop: w.leaveCurrentBlock() w.emit("}") + actualLoopDepth -= 1 + w.popLoopPos() case .beginRepeatLoop(let op): let LET = w.varKeyword @@ -1211,12 +1234,16 @@ public class JavaScriptLifter: Lifter { I = "i" } let ITERATIONS = op.iterations + actualLoopDepth += 1 + w.recordLoopPos() w.emit("for (\(LET) \(I) = 0; \(I) < \(ITERATIONS); \(I)++) {") w.enterNewBlock() case .endRepeatLoop: w.leaveCurrentBlock() w.emit("}") + actualLoopDepth -= 1 + w.popLoopPos() case .loopBreak(_), .switchBreak: @@ -1225,6 +1252,34 @@ public class JavaScriptLifter: Lifter { case .loopContinue: w.emit("continue;") + case .breakNested(_): + let input = input(0) + let expectedDepth = Int(input.text) ?? 0 + let d = expectedDepth % actualLoopDepth + let pos = w.getLoopPos(d) + let pre = String(repeating: " ", count: 4 * d) + let s = pre + "label" + String(d) + ":\n" + if(!w.getLabelExist(d)){ + w.insertLabel(pos, s) + w.setLabelExist(d) + w.updateLoopPos(d + 1, s.length) + } + w.emit("break " + "label" + String(d) + ";") + + case .continueNested: + let input = input(0) + let expectedDepth = Int(input.text) ?? 0 + let d = expectedDepth % actualLoopDepth + let pos = w.getLoopPos(d) + let pre = String(repeating: " ", count: 4 * d) + let s = pre + "label" + String(d) + ":\n" + if(!w.getLabelExist(d)){ + w.insertLabel(pos, s) + w.setLabelExist(d) + w.updateLoopPos(d + 1, s.length) + } + w.emit("continue " + "label" + String(d) + ";") + case .beginTry: w.emit("try {") w.enterNewBlock() @@ -1514,6 +1569,12 @@ public class JavaScriptLifter: Lifter { return writer.code } + struct LoopPosInfo { + var loopBeginPos: Int + var exist: Bool + } + private var loopPos: [LoopPosInfo] + // Maps each FuzzIL variable to its JavaScript expression. // The expression for a FuzzIL variable can generally either be // * an identifier like "v42" if the FuzzIL variable is mapped to a JavaScript variable OR @@ -1534,6 +1595,7 @@ public class JavaScriptLifter: Lifter { self.analyzer = analyzer self.varKeyword = version == .es6 ? "let" : "var" self.constKeyword = version == .es6 ? "const" : "var" + self.loopPos = [] } /// Assign a JavaScript expression to a FuzzIL variable. @@ -1771,6 +1833,43 @@ public class JavaScriptLifter: Lifter { writer.emit(line) } + mutating func recordLoopPos(){ + loopPos.append(LoopPosInfo(loopBeginPos: code.count, exist: false )) + } + + mutating func popLoopPos(){ + loopPos.popLast() + } + + mutating func getLoopPos(_ idx: Int) -> Int{ + return loopPos[idx].loopBeginPos + } + + // if we insert one label into code, then the after record index move the same length + mutating func updateLoopPos(_ startIndex: Int, _ len: Int){ + if(startIndex <= loopPos.count - 1){ + for i in startIndex...loopPos.count - 1 { + loopPos[i].loopBeginPos += len + } + } + } + + mutating func getLabelExist(_ idx: Int) -> Bool{ + return loopPos[idx].exist + } + + mutating func setLabelExist(_ idx: Int){ + loopPos[idx].exist = true + } + + mutating func clearLoopPos(){ + loopPos = [] + } + + mutating func insertLabel(_ pos: Int, _ content: String){ + writer.insert(pos, content) + } + /// Emit a (potentially multi-line) comment. mutating func emitComment(_ comment: String) { writer.emitComment(comment) diff --git a/Sources/Fuzzilli/Lifting/ScriptWriter.swift b/Sources/Fuzzilli/Lifting/ScriptWriter.swift index d5bf5a38c..df76cc4f5 100644 --- a/Sources/Fuzzilli/Lifting/ScriptWriter.swift +++ b/Sources/Fuzzilli/Lifting/ScriptWriter.swift @@ -84,4 +84,12 @@ struct ScriptWriter { assert(currentIndention.count >= indent.count) currentIndention.removeLast(indent.count) } + + mutating func insert(_ pos: Int, _ content: String){ + if code.index(code.startIndex, offsetBy: pos, limitedBy: code.endIndex) != nil { + let index = code.index(code.startIndex, offsetBy: pos) + code.insert(contentsOf: content, at: index) + currentLineNumber += 1 + } + } } diff --git a/Sources/Fuzzilli/Protobuf/operations.pb.swift b/Sources/Fuzzilli/Protobuf/operations.pb.swift index 8a0cc83b0..1e9a1258c 100644 --- a/Sources/Fuzzilli/Protobuf/operations.pb.swift +++ b/Sources/Fuzzilli/Protobuf/operations.pb.swift @@ -2377,6 +2377,26 @@ public struct Fuzzilli_Protobuf_LoopContinue: Sendable { public init() {} } +public struct Fuzzilli_Protobuf_BreakNested: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Fuzzilli_Protobuf_ContinueNested: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + public struct Fuzzilli_Protobuf_BeginTry: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -7342,6 +7362,44 @@ extension Fuzzilli_Protobuf_LoopContinue: SwiftProtobuf.Message, SwiftProtobuf._ } } +extension Fuzzilli_Protobuf_BreakNested: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".BreakNested" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + public mutating func decodeMessage(decoder: inout D) throws { + while let _ = try decoder.nextFieldNumber() { + } + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_BreakNested, rhs: Fuzzilli_Protobuf_BreakNested) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_ContinueNested: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ContinueNested" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap() + + public mutating func decodeMessage(decoder: inout D) throws { + while let _ = try decoder.nextFieldNumber() { + } + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Fuzzilli_Protobuf_ContinueNested, rhs: Fuzzilli_Protobuf_ContinueNested) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Fuzzilli_Protobuf_BeginTry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".BeginTry" public static let _protobuf_nameMap = SwiftProtobuf._NameMap() diff --git a/Sources/Fuzzilli/Protobuf/operations.proto b/Sources/Fuzzilli/Protobuf/operations.proto index 292ae8ce4..2e95fefab 100644 --- a/Sources/Fuzzilli/Protobuf/operations.proto +++ b/Sources/Fuzzilli/Protobuf/operations.proto @@ -714,6 +714,12 @@ message LoopBreak { message LoopContinue { } +message BreakNested { +} + +message ContinueNested { +} + message BeginTry { } diff --git a/Sources/Fuzzilli/Protobuf/program.pb.swift b/Sources/Fuzzilli/Protobuf/program.pb.swift index e03b56af8..4760c4219 100644 --- a/Sources/Fuzzilli/Protobuf/program.pb.swift +++ b/Sources/Fuzzilli/Protobuf/program.pb.swift @@ -1321,6 +1321,22 @@ public struct Fuzzilli_Protobuf_Instruction: Sendable { set {operation = .loopContinue(newValue)} } + public var breakNested: Fuzzilli_Protobuf_BreakNested { + get { + if case .breakNested(let v)? = operation {return v} + return Fuzzilli_Protobuf_BreakNested() + } + set {operation = .breakNested(newValue)} + } + + public var continueNested: Fuzzilli_Protobuf_ContinueNested { + get { + if case .continueNested(let v)? = operation {return v} + return Fuzzilli_Protobuf_ContinueNested() + } + set {operation = .continueNested(newValue)} + } + public var beginTry: Fuzzilli_Protobuf_BeginTry { get { if case .beginTry(let v)? = operation {return v} @@ -1643,6 +1659,8 @@ public struct Fuzzilli_Protobuf_Instruction: Sendable { case endRepeatLoop(Fuzzilli_Protobuf_EndRepeatLoop) case loopBreak(Fuzzilli_Protobuf_LoopBreak) case loopContinue(Fuzzilli_Protobuf_LoopContinue) + case breakNested(Fuzzilli_Protobuf_BreakNested) + case continueNested(Fuzzilli_Protobuf_ContinueNested) case beginTry(Fuzzilli_Protobuf_BeginTry) case beginCatch(Fuzzilli_Protobuf_BeginCatch) case beginFinally(Fuzzilli_Protobuf_BeginFinally) @@ -1892,6 +1910,8 @@ extension Fuzzilli_Protobuf_Instruction: SwiftProtobuf.Message, SwiftProtobuf._M 178: .same(proto: "explore"), 179: .same(proto: "probe"), 180: .same(proto: "fixup"), + 181: .same(proto: "breakNested"), + 182: .same(proto: "continueNested"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -4223,6 +4243,32 @@ extension Fuzzilli_Protobuf_Instruction: SwiftProtobuf.Message, SwiftProtobuf._M self.operation = .fixup(v) } }() + case 181: try { + var v: Fuzzilli_Protobuf_BreakNested? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .breakNested(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .breakNested(v) + } + }() + case 182: try { + var v: Fuzzilli_Protobuf_ContinueNested? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .continueNested(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .continueNested(v) + } + }() default: break } } @@ -4953,6 +4999,14 @@ extension Fuzzilli_Protobuf_Instruction: SwiftProtobuf.Message, SwiftProtobuf._M guard case .fixup(let v)? = self.operation else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 180) }() + case .breakNested?: try { + guard case .breakNested(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 181) + }() + case .continueNested?: try { + guard case .continueNested(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 182) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) diff --git a/Sources/Fuzzilli/Protobuf/program.proto b/Sources/Fuzzilli/Protobuf/program.proto index 9a5af6fee..f3fb8c4df 100644 --- a/Sources/Fuzzilli/Protobuf/program.proto +++ b/Sources/Fuzzilli/Protobuf/program.proto @@ -204,6 +204,8 @@ message Instruction { Explore explore = 178; Probe probe = 179; Fixup fixup = 180; + BreakNested breakNested = 181; + ContinueNested continueNested = 182; } } diff --git a/Tests/FuzzilliTests/LifterTest.swift b/Tests/FuzzilliTests/LifterTest.swift index 8371f9f16..1154ba5d7 100644 --- a/Tests/FuzzilliTests/LifterTest.swift +++ b/Tests/FuzzilliTests/LifterTest.swift @@ -2889,4 +2889,288 @@ class LifterTests: XCTestCase { """ XCTAssertEqual(actual, expected) } + + func testForLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let d1 = b.loadInt(0) + let d2 = b.loadInt(1) + let d3 = b.loadInt(2) + b.buildForLoop() { + b.buildForLoop() { + b.buildForLoop() { + b.breakNested(d1) + } + b.continueNested(d2) + } + b.breakNested(d3) + } + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + label0: + for (;;) { + label1: + for (;;) { + for (;;) { + break label0; + } + continue label1; + } + break label0; + } + + """ + XCTAssertEqual(actual, expected) + + } + + func testWhileLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let d1 = b.loadInt(0) + let d2 = b.loadInt(1) + let d3 = b.loadInt(2) + + b.buildWhileLoop({ b.compare(d1, with: b.loadInt(100), using: .lessThan) }) { + b.buildWhileLoop({ b.compare(d1, with: b.loadInt(100), using: .lessThan) }) { + b.buildWhileLoop({ b.compare(d1, with: b.loadInt(100), using: .lessThan) }) { + b.breakNested(d1) + } + b.continueNested(d2) + } + b.breakNested(d3) + } + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + label0: + while (0 < 100) { + label1: + while (0 < 100) { + while (0 < 100) { + break label0; + } + continue label1; + } + break label0; + } + + """ + XCTAssertEqual(actual, expected) + + } + + func testDoWhileLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let d1 = b.loadInt(0) + let d2 = b.loadInt(1) + let d3 = b.loadInt(2) + b.buildDoWhileLoop(do: { + b.buildDoWhileLoop(do: { + b.buildDoWhileLoop(do: { + b.continueNested(d3) + }, while: { b.compare(d1, with: b.loadInt(100), using: .lessThan) }) + b.breakNested(d1) + }, while: { b.compare(d1, with: b.loadInt(100), using: .lessThan) }) + b.breakNested(d3) + }, while: { b.compare(d1, with: b.loadInt(100), using: .lessThan) }) + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + label0: + do { + do { + label2: + do { + continue label2; + } while (0 < 100) + break label0; + } while (0 < 100) + break label0; + } while (0 < 100) + + """ + XCTAssertEqual(actual, expected) + } + + + func testForInLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let d1 = b.loadInt(0) + let d2 = b.loadInt(1) + let d3 = b.loadInt(2) + let v1 = b.createObject(with: ["a": d1]) + b.buildForInLoop(v1) { v2 in + b.buildForInLoop(v1) { v2 in + b.buildForInLoop(v1) { v2 in + b.breakNested(d1) + } + b.breakNested(d3) + } + b.continueNested(d1) + } + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + const o3 = { + "a": 0, + }; + label0: + for (const v4 in o3) { + for (const v5 in o3) { + for (const v6 in o3) { + break label0; + } + break label0; + } + continue label0; + } + + """ + XCTAssertEqual(actual, expected) + + } + + + func testForOfLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let n1 = b.loadFloat(Double.nan) + let v1 = b.createArray(with: [n1, n1, n1]) + + let d1 = b.loadInt(0) + let d2 = b.loadInt(1) + let d3 = b.loadInt(2) + b.buildForOfLoop(v1) { v2 in + b.buildForOfLoop(v1) { v2 in + b.buildForOfLoop(v1) { v2 in + b.continueNested(d1) + } + b.breakNested(d2) + } + b.breakNested(d3) + } + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + const v1 = [NaN,NaN,NaN]; + label0: + for (const v5 of v1) { + label1: + for (const v6 of v1) { + for (const v7 of v1) { + continue label0; + } + break label1; + } + break label0; + } + + """ + XCTAssertEqual(actual, expected) + + } + + func testForOfLoopWithDestruct(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let a1 = b.createArray(with: [b.loadInt(10), b.loadInt(11), b.loadInt(12), b.loadInt(13), b.loadInt(14)]) + let a2 = b.createArray(with: [b.loadInt(20), b.loadInt(21), b.loadInt(22), b.loadInt(23)]) + let a3 = b.createArray(with: [b.loadInt(30), b.loadInt(31), b.loadInt(32)]) + let a4 = b.createArray(with: [a1, a2, a3]) + + let d1 = b.loadInt(0) + let d2 = b.loadInt(1) + let d3 = b.loadInt(2) + + b.buildForOfLoop(a4, selecting: [0,2], hasRestElement: true) { args in + + b.buildForOfLoop(a4, selecting: [0,2], hasRestElement: true) { args in + b.buildForOfLoop(a4, selecting: [0,2], hasRestElement: true) { args in + b.continueNested(d1) + } + b.breakNested(d2) + } + b.breakNested(d3) + } + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + const v15 = [[10,11,12,13,14],[20,21,22,23],[30,31,32]]; + label0: + for (let [v19,,...v20] of v15) { + label1: + for (let [v21,,...v22] of v15) { + for (let [v23,,...v24] of v15) { + continue label0; + } + break label1; + } + break label0; + } + + """ + XCTAssertEqual(actual, expected) + + } + + func testRepeatLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let d1 = b.loadInt(0) + let d2 = b.loadInt(1) + let d3 = b.loadInt(2) + + b.buildRepeatLoop(n: 10) { d1 + b.buildRepeatLoop(n: 10) { d1 + b.buildRepeatLoop(n: 10) { d1 + b.continueNested(d1) + } + b.breakNested(d2) + } + b.breakNested(d3) + } + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + print(actual) + + let expected = """ + label0: + for (let i = 0; i < 10; i++) { + label1: + for (let i = 0; i < 10; i++) { + for (let i = 0; i < 10; i++) { + continue label0; + } + break label1; + } + break label0; + } + + """ + XCTAssertEqual(actual, expected) + + } }