diff --git a/Sources/Fuzzilli/Base/ProgramBuilder.swift b/Sources/Fuzzilli/Base/ProgramBuilder.swift index 9b8b380e7..8701d15b3 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 loopBreakNested(_ depth: Int){ + emit(LoopBreakNested(depth), withInputs: []) + } + + public func loopContinueNested(_ depth: Int){ + emit(LoopContinueNested(depth), withInputs: []) + } + 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..f1b890da5 100644 --- a/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift +++ b/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift @@ -164,6 +164,8 @@ public let codeGeneratorWeights = [ "RepeatLoopGenerator": 10, "SwitchCaseBreakGenerator": 5, "LoopBreakGenerator": 5, + "LoopLabelBreakGenerator": 3, + "LoopLabelContinueGenerator": 3, "ContinueGenerator": 5, "TryCatchGenerator": 5, "ThrowGenerator": 1, diff --git a/Sources/Fuzzilli/CodeGen/CodeGenerators.swift b/Sources/Fuzzilli/CodeGen/CodeGenerators.swift index 1d0167493..fc48e50bf 100644 --- a/Sources/Fuzzilli/CodeGen/CodeGenerators.swift +++ b/Sources/Fuzzilli/CodeGen/CodeGenerators.swift @@ -1415,6 +1415,14 @@ public let CodeGenerators: [CodeGenerator] = [ b.loopContinue() }, + CodeGenerator("LoopLabelBreakGenerator", inContext: .loop) { b in + b.loopBreakNested(Int.random(in: 0...10)) + }, + + CodeGenerator("LoopLabelContinueGenerator", inContext: .loop) { b in + b.loopContinueNested(Int.random(in: 0...10)) + }, + 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..faa596b81 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 .loopBreakNested: + $0.loopBreakNested = Fuzzilli_Protobuf_LoopBreakNested() + case .loopContinueNested: + $0.loopContinueNested = Fuzzilli_Protobuf_LoopContinueNested() case .beginTry: $0.beginTry = Fuzzilli_Protobuf_BeginTry() case .beginCatch: @@ -1234,6 +1238,10 @@ extension Instruction: ProtobufConvertible { op = LoopBreak() case .loopContinue: op = LoopContinue() + case .loopBreakNested(let d): + op = LoopBreakNested(d.depth) + case .loopContinueNested(let d): + op = LoopContinueNested(d.depth) case .beginTry: op = BeginTry() case .beginCatch: diff --git a/Sources/Fuzzilli/FuzzIL/JsOperations.swift b/Sources/Fuzzilli/FuzzIL/JsOperations.swift index 5905a9e11..722879c0e 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -2064,6 +2064,26 @@ final class LoopContinue: JsOperation { } } +final class LoopBreakNested: JsOperation { + override var opcode: Opcode { .loopBreakNested(self) } + + let depth: Int + init(_ depth: Int) { + self.depth = depth + super.init(attributes: [.isJump], requiredContext: [.javascript, .loop]) + } +} + +final class LoopContinueNested: JsOperation { + override var opcode: Opcode { .loopContinueNested(self) } + + let depth: Int + init(_ depth: Int) { + self.depth = depth + super.init(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..674ab727b 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 loopBreakNested(LoopBreakNested) + case loopContinueNested(LoopContinueNested) 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..3024fff20 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 .loopBreakNested: + w.emit("LoopBreakNested") + + case .loopContinueNested: + w.emit("LoopContinueNested") + case .beginTry: w.emit("BeginTry") w.increaseIndentionLevel() diff --git a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift index b7de0815e..736f3b223 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,32 @@ public class JavaScriptLifter: Lifter { case .loopContinue: w.emit("continue;") + case .loopBreakNested(let op): + let expectedDepth = op.depth + 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 .loopContinueNested(let op): + let expectedDepth = op.depth + 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 +1567,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 +1593,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 +1831,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/Mutators/OperationMutator.swift b/Sources/Fuzzilli/Mutators/OperationMutator.swift index 0550b4cf4..e5710d855 100644 --- a/Sources/Fuzzilli/Mutators/OperationMutator.swift +++ b/Sources/Fuzzilli/Mutators/OperationMutator.swift @@ -227,6 +227,10 @@ public class OperationMutator: BaseInstructionMutator { newOp = UpdateSuperProperty(propertyName: b.randomPropertyName(), operator: chooseUniform(from: BinaryOperator.allCases)) case .beginIf(let op): newOp = BeginIf(inverted: !op.inverted) + case .loopBreakNested(let op): + newOp = LoopBreakNested(Int.random(in: 0...10)) + case .loopContinueNested(let op): + newOp = LoopContinueNested(Int.random(in: 0...10)) default: fatalError("Unhandled Operation: \(type(of: instr.op))") } diff --git a/Sources/Fuzzilli/Protobuf/operations.pb.swift b/Sources/Fuzzilli/Protobuf/operations.pb.swift index 8a0cc83b0..85b029eaf 100644 --- a/Sources/Fuzzilli/Protobuf/operations.pb.swift +++ b/Sources/Fuzzilli/Protobuf/operations.pb.swift @@ -2377,6 +2377,30 @@ public struct Fuzzilli_Protobuf_LoopContinue: Sendable { public init() {} } +public struct Fuzzilli_Protobuf_LoopBreakNested: 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 depth: Int = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct Fuzzilli_Protobuf_LoopContinueNested: 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 depth: Int = 0 + + 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 +7366,44 @@ extension Fuzzilli_Protobuf_LoopContinue: SwiftProtobuf.Message, SwiftProtobuf._ } } +extension Fuzzilli_Protobuf_LoopBreakNested: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LoopBreakNested" + 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_LoopBreakNested, rhs: Fuzzilli_Protobuf_LoopBreakNested) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Fuzzilli_Protobuf_LoopContinueNested: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LoopContinueNested" + 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_LoopContinueNested, rhs: Fuzzilli_Protobuf_LoopContinueNested) -> 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..d6cdf8e4b 100644 --- a/Sources/Fuzzilli/Protobuf/operations.proto +++ b/Sources/Fuzzilli/Protobuf/operations.proto @@ -714,6 +714,12 @@ message LoopBreak { message LoopContinue { } +message LoopBreakNested { +} + +message LoopContinueNested { +} + message BeginTry { } diff --git a/Sources/Fuzzilli/Protobuf/program.pb.swift b/Sources/Fuzzilli/Protobuf/program.pb.swift index e03b56af8..50f7a8362 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 loopBreakNested: Fuzzilli_Protobuf_LoopBreakNested { + get { + if case .loopBreakNested(let v)? = operation {return v} + return Fuzzilli_Protobuf_LoopBreakNested() + } + set {operation = .loopBreakNested(newValue)} + } + + public var loopContinueNested: Fuzzilli_Protobuf_LoopContinueNested { + get { + if case .loopContinueNested(let v)? = operation {return v} + return Fuzzilli_Protobuf_LoopContinueNested() + } + set {operation = .loopContinueNested(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 loopBreakNested(Fuzzilli_Protobuf_LoopBreakNested) + case loopContinueNested(Fuzzilli_Protobuf_LoopContinueNested) 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: "loopBreakNested"), + 182: .same(proto: "loopContinueNested"), ] 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_LoopBreakNested? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .loopBreakNested(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .loopBreakNested(v) + } + }() + case 182: try { + var v: Fuzzilli_Protobuf_LoopContinueNested? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .loopContinueNested(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .loopContinueNested(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 .loopBreakNested?: try { + guard case .loopBreakNested(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 181) + }() + case .loopContinueNested?: try { + guard case .loopContinueNested(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..a695369d1 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; + LoopBreakNested loopBreakNested = 181; + LoopContinueNested loopContinueNested = 182; } } diff --git a/Tests/FuzzilliTests/LifterTest.swift b/Tests/FuzzilliTests/LifterTest.swift index 8371f9f16..94c4c5508 100644 --- a/Tests/FuzzilliTests/LifterTest.swift +++ b/Tests/FuzzilliTests/LifterTest.swift @@ -2889,4 +2889,287 @@ class LifterTests: XCTestCase { """ XCTAssertEqual(actual, expected) } + + func testForLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let d1 = 1 + let d2 = 2 + let d3 = 3 + b.buildForLoop() { + b.buildForLoop() { + b.buildForLoop() { + b.loopBreakNested(d2) + } + b.loopContinueNested(d1) + } + b.loopBreakNested(d3) + } + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + label0: + for (;;) { + label1: + for (;;) { + label2: + for (;;) { + break label2; + } + continue label1; + } + break label0; + } + + """ + XCTAssertEqual(actual, expected) + + } + + func testWhileLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let a1 = b.loadInt(0) + + let d1 = 1 + let d2 = 2 + let d3 = 3 + + b.buildWhileLoop({ b.compare(a1, with: b.loadInt(100), using: .lessThan) }) { + b.buildWhileLoop({ b.compare(a1, with: b.loadInt(100), using: .lessThan) }) { + b.buildWhileLoop({ b.compare(a1, with: b.loadInt(100), using: .lessThan) }) { + b.loopBreakNested(d1) + } + b.loopContinueNested(d2) + } + b.loopBreakNested(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 label1; + } + continue label0; + } + break label0; + } + + """ + XCTAssertEqual(actual, expected) + + } + + func testDoWhileLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let a1 = b.loadInt(0) + + let d1 = 1 + let d2 = 2 + let d3 = 3 + b.buildDoWhileLoop(do: { + b.buildDoWhileLoop(do: { + b.buildDoWhileLoop(do: { + b.loopContinueNested(d3) + }, while: { b.compare(a1, with: b.loadInt(100), using: .lessThan) }) + b.loopBreakNested(d1) + }, while: { b.compare(a1, with: b.loadInt(100), using: .lessThan) }) + b.loopBreakNested(d3) + }, while: { b.compare(a1, with: b.loadInt(100), using: .lessThan) }) + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + label0: + do { + label1: + do { + do { + continue label0; + } while (0 < 100) + break label1; + } while (0 < 100) + break label0; + } while (0 < 100) + + """ + XCTAssertEqual(actual, expected) + } + + + func testForInLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let a1 = b.loadInt(0) + + let d1 = 1 + let d2 = 2 + let d3 = 3 + let v1 = b.createObject(with: ["a": a1]) + b.buildForInLoop(v1) { v2 in + b.buildForInLoop(v1) { v2 in + b.buildForInLoop(v1) { v2 in + b.loopBreakNested(d1) + } + b.loopBreakNested(d3) + } + b.loopContinueNested(d1) + } + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + const o1 = { + "a": 0, + }; + label0: + for (const v2 in o1) { + label1: + for (const v3 in o1) { + for (const v4 in o1) { + break label1; + } + break label1; + } + 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 = 1 + let d2 = 2 + b.buildForOfLoop(v1) { v2 in + b.buildForOfLoop(v1) { v2 in + b.buildForOfLoop(v1) { v2 in + b.loopContinueNested(d1) + } + b.loopBreakNested(d2) + } + } + + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + + let expected = """ + const v1 = [NaN,NaN,NaN]; + label0: + for (const v2 of v1) { + label1: + for (const v3 of v1) { + for (const v4 of v1) { + continue 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 = 1 + let d2 = 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.loopContinueNested(d1) + } + b.loopBreakNested(d2) + } + } + + 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 [v16,,...v17] of v15) { + label1: + for (let [v18,,...v19] of v15) { + for (let [v20,,...v21] of v15) { + continue label1; + } + break label0; + } + } + + """ + XCTAssertEqual(actual, expected) + + } + + func testRepeatLoop(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + let d1 = 1 + let d2 = 2 + + b.buildRepeatLoop(n: 10) { d1 + b.buildRepeatLoop(n: 10) { d1 + b.buildRepeatLoop(n: 10) { d1 + b.loopContinueNested(d1) + } + b.loopBreakNested(d2) + } + } + 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 label1; + } + break label0; + } + } + + """ + XCTAssertEqual(actual, expected) + + } }