Skip to content

feature/loopNestedContinue #512

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Sources/Fuzzilli/Base/ProgramBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ public let codeGeneratorWeights = [
"TryCatchGenerator": 5,
"ThrowGenerator": 1,
"BlockStatementGenerator": 1,
"LoopNestedContinueGenerator": 1,

// Special generators
"WellKnownPropertyLoadGenerator": 5,
Expand Down
4 changes: 4 additions & 0 deletions Sources/Fuzzilli/CodeGen/CodeGenerators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
11 changes: 11 additions & 0 deletions Sources/Fuzzilli/FuzzIL/JsOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe here would be a good place for a short comment describing how this operation works, in particular how depth is interpreted (e.g. that it wraps around if there are fewer open loops)

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.
Expand Down
1 change: 1 addition & 0 deletions Sources/Fuzzilli/FuzzIL/Opcodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions Sources/Fuzzilli/Lifting/FuzzILLifter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
98 changes: 98 additions & 0 deletions Sources/Fuzzilli/Lifting/JavaScriptLifter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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
Expand Down Expand Up @@ -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 }
Expand All @@ -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()

Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1640,6 +1656,7 @@ public class JavaScriptLifter: Lifter {
w.emitComment(footer)
}

assert(w.labelStackDepth(LabelType.loopblock) == 0)
return w.code
}

Expand Down Expand Up @@ -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
Expand All @@ -1861,6 +1883,11 @@ public class JavaScriptLifter: Lifter {
// See `reassign()` for more details about reassignment inlining.
private var inlinedReassignments = VariableMap<Expression>()

// 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
Expand Down Expand Up @@ -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..<stack.count {
stack[i].beginPos += delta
}

labelStack[type] = stack
}

mutating func liftContinueLabel(_ type: LabelType, expDepth: Int) {
let stack = labelStack[type]!
let d = expDepth % stack.count
let labelName = "label\(d):"

if !stack[d].hasLabel {
insertLabel(type, d, labelName)
}

emit("continue label\(d);")
}

mutating func labelStackDepth(_ type: LabelType) -> Int {
return labelStack[type]!.count
}
}

// Helper class for formatting object literals.
Expand Down Expand Up @@ -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
}
}
17 changes: 16 additions & 1 deletion Sources/Fuzzilli/Lifting/ScriptWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
2 changes: 2 additions & 0 deletions Sources/Fuzzilli/Mutators/OperationMutator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 31 additions & 0 deletions Sources/Fuzzilli/Protobuf/operations.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
while let _ = try decoder.nextFieldNumber() {
}
}

public func traverse<V: SwiftProtobuf.Visitor>(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()
Expand Down
3 changes: 3 additions & 0 deletions Sources/Fuzzilli/Protobuf/operations.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,9 @@ message WasmSelect {
WasmILType type = 1;
}

message LoopNestedContinue {
}

message ConstSimd128 {
repeated uint32 value = 1;
}
Expand Down
Loading