diff --git a/Sources/Fuzzilli/Base/ProgramBuilder.swift b/Sources/Fuzzilli/Base/ProgramBuilder.swift index 0df465c17..f6f3418e5 100644 --- a/Sources/Fuzzilli/Base/ProgramBuilder.swift +++ b/Sources/Fuzzilli/Base/ProgramBuilder.swift @@ -2822,6 +2822,9 @@ public class ProgramBuilder { emit(Print(), withInputs: [value]) } + public func loopNestedContinue(_ depth: Int){ + emit(LoopNestedContinue(depth), withInputs: []) + } @discardableResult public func createWasmGlobal(value: WasmGlobal, isMutable: Bool) -> Variable { diff --git a/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift b/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift index 54dc9efc8..823ec676d 100644 --- a/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift +++ b/Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift @@ -169,6 +169,7 @@ public let codeGeneratorWeights = [ "TryCatchGenerator": 5, "ThrowGenerator": 1, "BlockStatementGenerator": 1, + "LoopNestedContinueGenerator": 1, // Special generators "WellKnownPropertyLoadGenerator": 5, diff --git a/Sources/Fuzzilli/CodeGen/CodeGenerators.swift b/Sources/Fuzzilli/CodeGen/CodeGenerators.swift index fdb9bc330..a57fc2874 100644 --- a/Sources/Fuzzilli/CodeGen/CodeGenerators.swift +++ b/Sources/Fuzzilli/CodeGen/CodeGenerators.swift @@ -1866,6 +1866,10 @@ public let CodeGenerators: [CodeGenerator] = [ b.callFunction(f, withArgs: args) }, catchBody: { _ in }) }, + + CodeGenerator("LoopNestedContinueGenerator", inContext: .loop) { b in + b.loopNestedContinue(Int.random(in: 0...10)) + } ] extension Array where Element == CodeGenerator { diff --git a/Sources/Fuzzilli/FuzzIL/Instruction.swift b/Sources/Fuzzilli/FuzzIL/Instruction.swift index c3a150a29..eee900520 100644 --- a/Sources/Fuzzilli/FuzzIL/Instruction.swift +++ b/Sources/Fuzzilli/FuzzIL/Instruction.swift @@ -1030,6 +1030,8 @@ extension Instruction: ProtobufConvertible { $0.wrapSuspending = Fuzzilli_Protobuf_WrapSuspending() case .bindMethod(let op): $0.bindMethod = Fuzzilli_Protobuf_BindMethod.with { $0.methodName = op.methodName } + case .loopNestedContinue: + $0.loopNestedContinue = Fuzzilli_Protobuf_LoopNestedContinue() case .print(_): fatalError("Print operations should not be serialized") // Wasm Operations @@ -1892,6 +1894,8 @@ extension Instruction: ProtobufConvertible { op = LoadNewTarget() case .nop: op = Nop() + case .loopNestedContinue(let d): + op = LoopNestedContinue(d.depth) case .createWasmGlobal(let p): op = CreateWasmGlobal(value: convertWasmGlobal(p.wasmGlobal), isMutable: p.wasmGlobal.isMutable) case .createWasmMemory(let p): diff --git a/Sources/Fuzzilli/FuzzIL/JsOperations.swift b/Sources/Fuzzilli/FuzzIL/JsOperations.swift index 29cdfc996..65925e518 100644 --- a/Sources/Fuzzilli/FuzzIL/JsOperations.swift +++ b/Sources/Fuzzilli/FuzzIL/JsOperations.swift @@ -2463,6 +2463,17 @@ class BindMethod: JsOperation { super.init(numInputs: 1, numOutputs: 1, requiredContext: .javascript) } } +// Assuming the current nested depth is D, then depth % D is the actual level that can be continued, and a label will be generated at that level for continue +final class LoopNestedContinue: JsOperation { + override var opcode: Opcode { .loopNestedContinue(self) } + + let depth: Int + + init(_ depth: Int) { + self.depth = depth + super.init(attributes: [.isJump], requiredContext: [.javascript, .loop]) + } +} // This instruction is used to create strongly typed WasmGlobals in the JS world that can be imported by a WasmModule. diff --git a/Sources/Fuzzilli/FuzzIL/Opcodes.swift b/Sources/Fuzzilli/FuzzIL/Opcodes.swift index 8af19f434..9ebcff71b 100644 --- a/Sources/Fuzzilli/FuzzIL/Opcodes.swift +++ b/Sources/Fuzzilli/FuzzIL/Opcodes.swift @@ -202,6 +202,7 @@ enum Opcode { case endSwitch(EndSwitch) case switchBreak(SwitchBreak) case loadNewTarget(LoadNewTarget) + case loopNestedContinue(LoopNestedContinue) case print(Print) case explore(Explore) case probe(Probe) diff --git a/Sources/Fuzzilli/Lifting/FuzzILLifter.swift b/Sources/Fuzzilli/Lifting/FuzzILLifter.swift index e525d2e28..63fa36fca 100644 --- a/Sources/Fuzzilli/Lifting/FuzzILLifter.swift +++ b/Sources/Fuzzilli/Lifting/FuzzILLifter.swift @@ -753,6 +753,9 @@ public class FuzzILLifter: Lifter { case .loadNewTarget: w.emit("\(output()) <- LoadNewTarget") + case .loopNestedContinue(let op): + w.emit("LoopNestedContinue \(op.depth)") + case .beginWasmModule: w.emit("BeginWasmModule") w.increaseIndentionLevel() diff --git a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift index 773f8a976..0235548a3 100644 --- a/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift +++ b/Sources/Fuzzilli/Lifting/JavaScriptLifter.swift @@ -1150,14 +1150,17 @@ public class JavaScriptLifter: Lifter { case .beginWhileLoopBody: let COND = handleEndSingleExpressionContext(result: input(0), with: &w) + w.pushLabelStack(LabelType.loopblock) w.emitBlock("while (\(COND)) {") w.enterNewBlock() case .endWhileLoop: w.leaveCurrentBlock() w.emit("}") + w.popLabelStack(LabelType.loopblock) case .beginDoWhileLoopBody: + w.pushLabelStack(LabelType.loopblock) w.emit("do {") w.enterNewBlock() @@ -1168,6 +1171,7 @@ public class JavaScriptLifter: Lifter { case .endDoWhileLoop: let COND = handleEndSingleExpressionContext(result: input(0), with: &w) w.emitBlock("} while (\(COND))") + w.popLabelStack(LabelType.loopblock) case .beginForLoopInitializer: // While we could inline into the loop header, we probably don't want to do that as it will often lead @@ -1243,6 +1247,7 @@ public class JavaScriptLifter: Lifter { let INITIALIZER = header.initializer var CONDITION = header.condition var AFTERTHOUGHT = handleEndSingleExpressionContext(with: &w) + w.pushLabelStack(LabelType.loopblock) if !INITIALIZER.contains("\n") && !CONDITION.contains("\n") && !AFTERTHOUGHT.contains("\n") { if !CONDITION.isEmpty { CONDITION = " " + CONDITION } @@ -1262,22 +1267,26 @@ public class JavaScriptLifter: Lifter { case .endForLoop: w.leaveCurrentBlock() w.emit("}") + w.popLabelStack(LabelType.loopblock) case .beginForInLoop: let LET = w.declarationKeyword(for: instr.innerOutput) let V = w.declare(instr.innerOutput) let OBJ = input(0) + w.pushLabelStack(LabelType.loopblock) w.emit("for (\(LET) \(V) in \(OBJ)) {") w.enterNewBlock() case .endForInLoop: w.leaveCurrentBlock() w.emit("}") + w.popLabelStack(LabelType.loopblock) case .beginForOfLoop: let V = w.declare(instr.innerOutput) let LET = w.declarationKeyword(for: instr.innerOutput) let OBJ = input(0) + w.pushLabelStack(LabelType.loopblock) w.emit("for (\(LET) \(V) of \(OBJ)) {") w.enterNewBlock() @@ -1286,12 +1295,14 @@ public class JavaScriptLifter: Lifter { let PATTERN = liftArrayDestructPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement) let LET = w.varKeyword let OBJ = input(0) + w.pushLabelStack(LabelType.loopblock) w.emit("for (\(LET) [\(PATTERN)] of \(OBJ)) {") w.enterNewBlock() case .endForOfLoop: w.leaveCurrentBlock() w.emit("}") + w.popLabelStack(LabelType.loopblock) case .beginRepeatLoop(let op): let LET = w.varKeyword @@ -1302,12 +1313,14 @@ public class JavaScriptLifter: Lifter { I = "i" } let ITERATIONS = op.iterations + w.pushLabelStack(LabelType.loopblock) w.emit("for (\(LET) \(I) = 0; \(I) < \(ITERATIONS); \(I)++) {") w.enterNewBlock() case .endRepeatLoop: w.leaveCurrentBlock() w.emit("}") + w.popLabelStack(LabelType.loopblock) case .loopBreak(_), .switchBreak: @@ -1372,6 +1385,9 @@ public class JavaScriptLifter: Lifter { let VALUE = input(0) w.emit("fuzzilli('FUZZILLI_PRINT', \(VALUE));") + case .loopNestedContinue(let op): + w.liftContinueLabel(LabelType.loopblock, expDepth: op.depth) + case .createWasmGlobal(let op): let V = w.declare(instr.output) let LET = w.varKeyword @@ -1640,6 +1656,7 @@ public class JavaScriptLifter: Lifter { w.emitComment(footer) } + assert(w.labelStackDepth(LabelType.loopblock) == 0) return w.code } @@ -1846,6 +1863,11 @@ public class JavaScriptLifter: Lifter { return writer.code } + // Used for nestBreak series operations to record location information during append code, where temporary data structures such as temporaryOutputBufferStack may not necessarily be empty + var codeLength: Int { + return writer.code.count + } + // 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 @@ -1861,6 +1883,11 @@ public class JavaScriptLifter: Lifter { // See `reassign()` for more details about reassignment inlining. private var inlinedReassignments = VariableMap() + // Trace nested code block to break/continue a label + private var labelStack: [LabelType: [LabelPin]] = [ + LabelType.loopblock: [] + ] + init(analyzer: DefUseAnalyzer, version: ECMAScriptVersion, stripComments: Bool = false, includeLineNumbers: Bool = false, indent: Int = 4) { self.writer = ScriptWriter(stripComments: stripComments, includeLineNumbers: includeLineNumbers, indent: indent) self.analyzer = analyzer @@ -2236,6 +2263,66 @@ public class JavaScriptLifter: Lifter { return analyzer.numUses(of: v) <= 1 } } + + /// Records a new label pin at the current code position. + mutating func pushLabelStack(_ labelStackType: LabelType) { + labelStack[labelStackType, default: []].append( + LabelPin( + beginPos: codeLength, + hasLabel: false, + indention: writer.getCurrentIndention() + ) + ) + } + + /// Removes the most recently recorded label pin. + mutating func popLabelStack(_ labelStackType: LabelType) { + _ = labelStack[labelStackType, default: []].popLast() + } + + /// Checks whether a label has already been inserted at the specified index. + private mutating func labelExists(_ labelStack: inout [LabelPin], at index: Int) -> Bool { + return labelStack[index].hasLabel + } + + /// Updates the label stack when a label is inserted into the code. + /// + /// This method: + /// - Marks the label at the specified index as inserted. + /// - Inserts the label content at the given code position. + /// - Shifts the positions of subsequent labels accordingly. + private mutating func insertLabel(_ type: LabelType, _ index: Int, _ labelContent: String) { + var stack = labelStack[type]! + let insertPos = stack[index].beginPos + let indention = stack[index].indention + + writer.insert(insertPos, labelContent, indention) + + stack[index].hasLabel = true + let delta = labelContent.count + + for i in index+1.. Int { + return labelStack[type]!.count + } } // Helper class for formatting object literals. @@ -2268,4 +2355,15 @@ public class JavaScriptLifter: Lifter { fields[fields.count - 1] += body + "}" } } + + // Every possible position of label + struct LabelPin { + var beginPos: Int + var hasLabel: Bool + var indention: String + } + + enum LabelType { + case loopblock + } } diff --git a/Sources/Fuzzilli/Lifting/ScriptWriter.swift b/Sources/Fuzzilli/Lifting/ScriptWriter.swift index d5bf5a38c..19d65b770 100644 --- a/Sources/Fuzzilli/Lifting/ScriptWriter.swift +++ b/Sources/Fuzzilli/Lifting/ScriptWriter.swift @@ -84,4 +84,19 @@ struct ScriptWriter { assert(currentIndention.count >= indent.count) currentIndention.removeLast(indent.count) } -} + + /// Insert one or more lines of code + mutating func insert(_ pos: Int, _ content: String, _ indention: String) { + assert(!content.contains("\n")) + assert(pos >= 0 && pos <= code.count) + + var lineNumHeaderCnt = 0 + if includeLineNumbers { lineNumHeaderCnt = "\(String(format: "%3i", currentLineNumber)). ".count } + let index = code.index(code.startIndex, offsetBy: pos + indention.count + lineNumHeaderCnt) + code.insert(contentsOf: content, at: index) + } + + mutating func getCurrentIndention() -> String{ + return currentIndention + } +} \ No newline at end of file diff --git a/Sources/Fuzzilli/Mutators/OperationMutator.swift b/Sources/Fuzzilli/Mutators/OperationMutator.swift index 740d35967..f241192cd 100644 --- a/Sources/Fuzzilli/Mutators/OperationMutator.swift +++ b/Sources/Fuzzilli/Mutators/OperationMutator.swift @@ -221,6 +221,8 @@ public class OperationMutator: BaseInstructionMutator { newOp = UpdateSuperProperty(propertyName: b.randomPropertyName(), operator: chooseUniform(from: BinaryOperator.allCases)) case .beginIf(let op): newOp = BeginIf(inverted: !op.inverted) + case .loopNestedContinue(let op): + newOp = LoopNestedContinue(Int.random(in: 0...10)) case .createWasmGlobal(let op): // The type has to match for wasm, we cannot just switch types here as the rest of the wasm code will become invalid. // TODO: add nullref and funcref as types here. diff --git a/Sources/Fuzzilli/Protobuf/operations.pb.swift b/Sources/Fuzzilli/Protobuf/operations.pb.swift index 0efd2ada8..a24887941 100644 --- a/Sources/Fuzzilli/Protobuf/operations.pb.swift +++ b/Sources/Fuzzilli/Protobuf/operations.pb.swift @@ -3031,6 +3031,17 @@ public struct Fuzzilli_Protobuf_Print: Sendable { public init() {} } +public struct Fuzzilli_Protobuf_LoopNestedContinue: 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_BeginWasmModule: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the @@ -9502,6 +9513,7 @@ extension Fuzzilli_Protobuf_EndBlockStatement: SwiftProtobuf.Message, SwiftProto } } + extension Fuzzilli_Protobuf_Nop: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Nop" public static let _protobuf_nameMap = SwiftProtobuf._NameMap() @@ -9540,6 +9552,25 @@ extension Fuzzilli_Protobuf_Print: SwiftProtobuf.Message, SwiftProtobuf._Message } } +extension Fuzzilli_Protobuf_LoopNestedContinue: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LoopNestedContinue" + 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_LoopNestedContinue, rhs: Fuzzilli_Protobuf_LoopNestedContinue) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Fuzzilli_Protobuf_BeginWasmModule: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".BeginWasmModule" public static let _protobuf_nameMap = SwiftProtobuf._NameMap() diff --git a/Sources/Fuzzilli/Protobuf/operations.proto b/Sources/Fuzzilli/Protobuf/operations.proto index e9ae34bf2..3afcd258c 100644 --- a/Sources/Fuzzilli/Protobuf/operations.proto +++ b/Sources/Fuzzilli/Protobuf/operations.proto @@ -1220,6 +1220,9 @@ message WasmSelect { WasmILType type = 1; } +message LoopNestedContinue { +} + message ConstSimd128 { repeated uint32 value = 1; } diff --git a/Sources/Fuzzilli/Protobuf/program.pb.swift b/Sources/Fuzzilli/Protobuf/program.pb.swift index eeaa73cf4..b487bb643 100644 --- a/Sources/Fuzzilli/Protobuf/program.pb.swift +++ b/Sources/Fuzzilli/Protobuf/program.pb.swift @@ -2257,6 +2257,14 @@ public struct Fuzzilli_Protobuf_Instruction: Sendable { set {operation = .wasmSelect(newValue)} } + public var loopNestedContinue: Fuzzilli_Protobuf_LoopNestedContinue { + get { + if case .loopNestedContinue(let v)? = operation {return v} + return Fuzzilli_Protobuf_LoopNestedContinue() + } + set {operation = .loopNestedContinue(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_Operation: Equatable, Sendable { @@ -2536,7 +2544,7 @@ public struct Fuzzilli_Protobuf_Instruction: Sendable { case wasmSimdLoad(Fuzzilli_Protobuf_WasmSimdLoad) case wasmUnreachable(Fuzzilli_Protobuf_WasmUnreachable) case wasmSelect(Fuzzilli_Protobuf_WasmSelect) - + case loopNestedContinue(Fuzzilli_Protobuf_LoopNestedContinue) } public init() {} @@ -2862,6 +2870,7 @@ extension Fuzzilli_Protobuf_Instruction: SwiftProtobuf.Message, SwiftProtobuf._M 275: .same(proto: "wasmSimdLoad"), 276: .same(proto: "wasmUnreachable"), 277: .same(proto: "wasmSelect"), + 278: .same(proto: "loopNestedContinue"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -6454,6 +6463,19 @@ extension Fuzzilli_Protobuf_Instruction: SwiftProtobuf.Message, SwiftProtobuf._M self.operation = .wasmSelect(v) } }() + case 278: try { + var v: Fuzzilli_Protobuf_LoopNestedContinue? + var hadOneofValue = false + if let current = self.operation { + hadOneofValue = true + if case .loopNestedContinue(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.operation = .loopNestedContinue(v) + } + }() default: break } } @@ -7572,6 +7594,10 @@ extension Fuzzilli_Protobuf_Instruction: SwiftProtobuf.Message, SwiftProtobuf._M guard case .wasmSelect(let v)? = self.operation else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 277) }() + case .loopNestedContinue?: try { + guard case .loopNestedContinue(let v)? = self.operation else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 284) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) diff --git a/Sources/Fuzzilli/Protobuf/program.proto b/Sources/Fuzzilli/Protobuf/program.proto index c3bd42a18..8adcb7317 100644 --- a/Sources/Fuzzilli/Protobuf/program.proto +++ b/Sources/Fuzzilli/Protobuf/program.proto @@ -301,6 +301,7 @@ message Instruction { WasmSimdLoad wasmSimdLoad = 275; WasmUnreachable wasmUnreachable = 276; WasmSelect wasmSelect = 277; + LoopNestedContinue loopNestedContinue = 278; } } diff --git a/Tests/FuzzilliTests/LifterTest.swift b/Tests/FuzzilliTests/LifterTest.swift index 8c421c281..35b31f402 100644 --- a/Tests/FuzzilliTests/LifterTest.swift +++ b/Tests/FuzzilliTests/LifterTest.swift @@ -3095,4 +3095,36 @@ class LifterTests: XCTestCase { """ XCTAssertEqual(actual, expected) } -} + + func testLoopNestedContinueLifting(){ + let fuzzer = makeMockFuzzer() + let b = fuzzer.makeBuilder() + + b.buildRepeatLoop(n: 10) { + b.buildRepeatLoop(n: 10) { + b.buildRepeatLoop(n: 10) { + b.loopNestedContinue(2) + b.loopNestedContinue(2) + } + b.loopNestedContinue(1) + } + b.loopNestedContinue(0) + } + let program = b.finalize() + let actual = fuzzer.lifter.lift(program) + let expected = """ + label0:for (let i = 0; i < 10; i++) { + label1:for (let i = 0; i < 10; i++) { + label2:for (let i = 0; i < 10; i++) { + continue label2; + continue label2; + } + continue label1; + } + continue label0; + } + + """ + XCTAssertEqual(actual, expected) + } +} \ No newline at end of file