Skip to content

Commit 17edc6b

Browse files
committed
feature/loopNestedContinue
1 parent f31876f commit 17edc6b

16 files changed

+260
-5
lines changed

Sources/Fuzzilli/Base/ProgramBuilder.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2822,6 +2822,10 @@ public class ProgramBuilder {
28222822
emit(Print(), withInputs: [value])
28232823
}
28242824

2825+
public func loopNestedContinue(_ depth: Int){
2826+
emit(LoopNestedContinue(depth), withInputs: [])
2827+
}
2828+
28252829

28262830
@discardableResult
28272831
public func createWasmGlobal(value: WasmGlobal, isMutable: Bool) -> Variable {

Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ public let codeGeneratorWeights = [
169169
"TryCatchGenerator": 5,
170170
"ThrowGenerator": 1,
171171
"BlockStatementGenerator": 1,
172+
"LoopNestedContinueGenerator": 1,
172173

173174
// Special generators
174175
"WellKnownPropertyLoadGenerator": 5,

Sources/Fuzzilli/CodeGen/CodeGenerators.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public let CodeGenerators: [CodeGenerator] = [
5454
ValueGenerator("StringGenerator") { b, n in
5555
for _ in 0..<n {
5656
b.loadString(b.randomString())
57-
}
57+
}
5858
},
5959

6060
ValueGenerator("BooleanGenerator") { b, n in
@@ -1866,6 +1866,10 @@ public let CodeGenerators: [CodeGenerator] = [
18661866
b.callFunction(f, withArgs: args)
18671867
}, catchBody: { _ in })
18681868
},
1869+
1870+
CodeGenerator("LoopNestedContinueGenerator", inContext: .loop) { b in
1871+
b.loopNestedContinue(Int.random(in: 0...10))
1872+
}
18691873
]
18701874

18711875
extension Array where Element == CodeGenerator {

Sources/Fuzzilli/FuzzIL/Instruction.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,8 @@ extension Instruction: ProtobufConvertible {
10301030
$0.wrapSuspending = Fuzzilli_Protobuf_WrapSuspending()
10311031
case .bindMethod(let op):
10321032
$0.bindMethod = Fuzzilli_Protobuf_BindMethod.with { $0.methodName = op.methodName }
1033+
case .loopNestedContinue:
1034+
$0.loopNestedContinue = Fuzzilli_Protobuf_LoopNestedContinue()
10331035
case .print(_):
10341036
fatalError("Print operations should not be serialized")
10351037
// Wasm Operations
@@ -1892,6 +1894,8 @@ extension Instruction: ProtobufConvertible {
18921894
op = LoadNewTarget()
18931895
case .nop:
18941896
op = Nop()
1897+
case .loopNestedContinue(let d):
1898+
op = LoopNestedContinue(d.depth)
18951899
case .createWasmGlobal(let p):
18961900
op = CreateWasmGlobal(value: convertWasmGlobal(p.wasmGlobal), isMutable: p.wasmGlobal.isMutable)
18971901
case .createWasmMemory(let p):

Sources/Fuzzilli/FuzzIL/JsOperations.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2256,7 +2256,7 @@ final class BeginFinally: JsOperation {
22562256

22572257
final class EndTryCatchFinally: JsOperation {
22582258
override var opcode: Opcode { .endTryCatchFinally(self) }
2259-
2259+
22602260
init() {
22612261
super.init(attributes: [.isBlockEnd])
22622262
}
@@ -2464,6 +2464,17 @@ class BindMethod: JsOperation {
24642464
}
24652465
}
24662466

2467+
final class LoopNestedContinue: JsOperation {
2468+
override var opcode: Opcode { .loopNestedContinue(self) }
2469+
2470+
let depth: Int
2471+
2472+
init(_ depth: Int) {
2473+
self.depth = depth
2474+
super.init(attributes: [.isJump], requiredContext: [.javascript, .loop])
2475+
}
2476+
}
2477+
24672478

24682479
// This instruction is used to create strongly typed WasmGlobals in the JS world that can be imported by a WasmModule.
24692480
class CreateWasmGlobal: JsOperation {

Sources/Fuzzilli/FuzzIL/Opcodes.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ enum Opcode {
202202
case endSwitch(EndSwitch)
203203
case switchBreak(SwitchBreak)
204204
case loadNewTarget(LoadNewTarget)
205+
case loopNestedContinue(LoopNestedContinue)
205206
case print(Print)
206207
case explore(Explore)
207208
case probe(Probe)

Sources/Fuzzilli/Lifting/FuzzILLifter.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,9 @@ public class FuzzILLifter: Lifter {
753753
case .loadNewTarget:
754754
w.emit("\(output()) <- LoadNewTarget")
755755

756+
case .loopNestedContinue(let op):
757+
w.emit("LoopNestedContinue \(op.depth)")
758+
756759
case .beginWasmModule:
757760
w.emit("BeginWasmModule")
758761
w.increaseIndentionLevel()

Sources/Fuzzilli/Lifting/JavaScriptLifter.swift

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ public class JavaScriptLifter: Lifter {
101101

102102
var w = JavaScriptWriter(analyzer: analyzer, version: version, stripComments: !options.contains(.includeComments), includeLineNumbers: options.contains(.includeLineNumbers))
103103

104+
var loopLabelStack = LabelStack(type: .loopblock)
105+
104106
var wasmCodeStarts: Int? = nil
105107

106108
if options.contains(.includeComments), let header = program.comments.at(.header) {
@@ -1150,14 +1152,19 @@ public class JavaScriptLifter: Lifter {
11501152

11511153
case .beginWhileLoopBody:
11521154
let COND = handleEndSingleExpressionContext(result: input(0), with: &w)
1155+
loopLabelStack.push(currentCodeLength: w.code.count)
11531156
w.emitBlock("while (\(COND)) {")
11541157
w.enterNewBlock()
11551158

1159+
11561160
case .endWhileLoop:
11571161
w.leaveCurrentBlock()
11581162
w.emit("}")
1163+
loopLabelStack.pop()
1164+
11591165

11601166
case .beginDoWhileLoopBody:
1167+
loopLabelStack.push(currentCodeLength: w.code.count)
11611168
w.emit("do {")
11621169
w.enterNewBlock()
11631170

@@ -1168,6 +1175,7 @@ public class JavaScriptLifter: Lifter {
11681175
case .endDoWhileLoop:
11691176
let COND = handleEndSingleExpressionContext(result: input(0), with: &w)
11701177
w.emitBlock("} while (\(COND))")
1178+
loopLabelStack.pop()
11711179

11721180
case .beginForLoopInitializer:
11731181
// While we could inline into the loop header, we probably don't want to do that as it will often lead
@@ -1243,6 +1251,7 @@ public class JavaScriptLifter: Lifter {
12431251
let INITIALIZER = header.initializer
12441252
var CONDITION = header.condition
12451253
var AFTERTHOUGHT = handleEndSingleExpressionContext(with: &w)
1254+
loopLabelStack.push(currentCodeLength: w.code.count)
12461255

12471256
if !INITIALIZER.contains("\n") && !CONDITION.contains("\n") && !AFTERTHOUGHT.contains("\n") {
12481257
if !CONDITION.isEmpty { CONDITION = " " + CONDITION }
@@ -1262,36 +1271,48 @@ public class JavaScriptLifter: Lifter {
12621271
case .endForLoop:
12631272
w.leaveCurrentBlock()
12641273
w.emit("}")
1274+
loopLabelStack.pop()
1275+
12651276

12661277
case .beginForInLoop:
12671278
let LET = w.declarationKeyword(for: instr.innerOutput)
12681279
let V = w.declare(instr.innerOutput)
12691280
let OBJ = input(0)
1281+
loopLabelStack.push(currentCodeLength: w.code.count)
12701282
w.emit("for (\(LET) \(V) in \(OBJ)) {")
12711283
w.enterNewBlock()
12721284

1285+
12731286
case .endForInLoop:
12741287
w.leaveCurrentBlock()
12751288
w.emit("}")
1289+
loopLabelStack.pop()
1290+
12761291

12771292
case .beginForOfLoop:
12781293
let V = w.declare(instr.innerOutput)
12791294
let LET = w.declarationKeyword(for: instr.innerOutput)
12801295
let OBJ = input(0)
1296+
loopLabelStack.push(currentCodeLength: w.code.count)
12811297
w.emit("for (\(LET) \(V) of \(OBJ)) {")
12821298
w.enterNewBlock()
12831299

1300+
12841301
case .beginForOfLoopWithDestruct(let op):
12851302
let outputs = w.declareAll(instr.innerOutputs)
12861303
let PATTERN = liftArrayDestructPattern(indices: op.indices, outputs: outputs, hasRestElement: op.hasRestElement)
12871304
let LET = w.varKeyword
12881305
let OBJ = input(0)
1306+
loopLabelStack.push(currentCodeLength: w.code.count)
12891307
w.emit("for (\(LET) [\(PATTERN)] of \(OBJ)) {")
12901308
w.enterNewBlock()
12911309

1310+
12921311
case .endForOfLoop:
12931312
w.leaveCurrentBlock()
12941313
w.emit("}")
1314+
loopLabelStack.pop()
1315+
12951316

12961317
case .beginRepeatLoop(let op):
12971318
let LET = w.varKeyword
@@ -1302,12 +1323,16 @@ public class JavaScriptLifter: Lifter {
13021323
I = "i"
13031324
}
13041325
let ITERATIONS = op.iterations
1326+
loopLabelStack.push(currentCodeLength: w.code.count)
13051327
w.emit("for (\(LET) \(I) = 0; \(I) < \(ITERATIONS); \(I)++) {")
13061328
w.enterNewBlock()
13071329

1330+
13081331
case .endRepeatLoop:
13091332
w.leaveCurrentBlock()
13101333
w.emit("}")
1334+
loopLabelStack.pop()
1335+
13111336

13121337
case .loopBreak(_),
13131338
.switchBreak:
@@ -1361,6 +1386,7 @@ public class JavaScriptLifter: Lifter {
13611386
w.emit("{")
13621387
w.enterNewBlock()
13631388

1389+
13641390
case .endBlockStatement:
13651391
w.leaveCurrentBlock()
13661392
w.emit("}")
@@ -1372,6 +1398,11 @@ public class JavaScriptLifter: Lifter {
13721398
let VALUE = input(0)
13731399
w.emit("fuzzilli('FUZZILLI_PRINT', \(VALUE));")
13741400

1401+
case .loopNestedContinue(let op):
1402+
w.withScriptWriter { writer in
1403+
loopLabelStack.translateContinueLabel(w: &writer, expDepth: op.depth)
1404+
}
1405+
13751406
case .createWasmGlobal(let op):
13761407
let V = w.declare(instr.output)
13771408
let LET = w.varKeyword
@@ -1818,6 +1849,7 @@ public class JavaScriptLifter: Lifter {
18181849
}
18191850
}
18201851

1852+
18211853
/// A wrapper around a ScriptWriter. It's main responsibility is expression inlining.
18221854
///
18231855
/// Expression inlining roughly works as follows:
@@ -2236,6 +2268,10 @@ public class JavaScriptLifter: Lifter {
22362268
return analyzer.numUses(of: v) <= 1
22372269
}
22382270
}
2271+
2272+
mutating func withScriptWriter(_ body: (inout ScriptWriter) -> Void) {
2273+
body(&writer)
2274+
}
22392275
}
22402276

22412277
// Helper class for formatting object literals.
@@ -2268,4 +2304,89 @@ public class JavaScriptLifter: Lifter {
22682304
fields[fields.count - 1] += body + "}"
22692305
}
22702306
}
2307+
2308+
// Every possible position of label
2309+
struct LabelPin {
2310+
var beginPos: Int
2311+
var hasLabel: Bool
2312+
}
2313+
2314+
enum LabelType {
2315+
case loopblock
2316+
case ifblock
2317+
case switchblock
2318+
case tryblock
2319+
case codeBlock
2320+
case withblock
2321+
}
2322+
2323+
/// A structure that manages label positions within a specific control flow block (e.g., loop, if, switch, etc.).
2324+
public struct LabelStack {
2325+
let type: LabelType
2326+
2327+
private var stack: [LabelPin] = []
2328+
2329+
init(type: LabelType) {
2330+
self.type = type
2331+
}
2332+
2333+
/// Records a new label pin at the current code position.
2334+
mutating func push(currentCodeLength: Int) {
2335+
stack.append(LabelPin(beginPos: currentCodeLength, hasLabel: false))
2336+
}
2337+
2338+
/// Removes the most recently recorded label pin.
2339+
mutating func pop() {
2340+
_ = stack.popLast()
2341+
}
2342+
2343+
/// Checks whether a label has already been inserted at the specified index.
2344+
func labelExists(at index: Int) -> Bool {
2345+
return stack[index].hasLabel
2346+
}
2347+
2348+
/// Updates the label stack when a label is inserted into the code.
2349+
///
2350+
/// This method:
2351+
/// - Marks the label at the specified index as inserted.
2352+
/// - Inserts the label content at the given code position.
2353+
/// - Shifts the positions of subsequent labels accordingly.
2354+
mutating func insertLabel(writer: inout ScriptWriter, at index: Int, labelContent: String) {
2355+
guard index < stack.count else { return }
2356+
2357+
let insertPos = stack[index].beginPos
2358+
writer.insert(insertPos, labelContent)
2359+
2360+
stack[index].hasLabel = true
2361+
2362+
let delta = labelContent.count
2363+
for i in index+1..<stack.count {
2364+
stack[i].beginPos += delta
2365+
}
2366+
}
2367+
2368+
mutating func translateBreakLabel(w: inout ScriptWriter, expDepth: Int){
2369+
let d = expDepth % stack.count
2370+
let pre = String(repeating: " ", count: 4 * d)
2371+
let s = pre + "label" + String(d) + ":\n"
2372+
if(!stack[d].hasLabel){
2373+
insertLabel(writer: &w, at: d, labelContent: s)
2374+
}
2375+
w.emit("break " + "label" + String(d) + ";")
2376+
}
2377+
2378+
mutating func translateContinueLabel(w: inout ScriptWriter, expDepth: Int){
2379+
let d = expDepth % stack.count
2380+
let pre = String(repeating: " ", count: 4 * d)
2381+
let s = pre + "label" + String(d) + ":\n"
2382+
if(!stack[d].hasLabel){
2383+
insertLabel(writer: &w, at: d, labelContent: s)
2384+
}
2385+
w.emit("continue " + "label" + String(d) + ";")
2386+
}
2387+
2388+
mutating func depth() -> Int{
2389+
return stack.count
2390+
}
2391+
}
22712392
}

Sources/Fuzzilli/Lifting/ScriptWriter.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,12 @@ struct ScriptWriter {
8484
assert(currentIndention.count >= indent.count)
8585
currentIndention.removeLast(indent.count)
8686
}
87+
88+
mutating func insert(_ pos: Int, _ content: String){
89+
if code.index(code.startIndex, offsetBy: pos, limitedBy: code.endIndex) != nil {
90+
let index = code.index(code.startIndex, offsetBy: pos)
91+
code.insert(contentsOf: content, at: index)
92+
currentLineNumber += 1
93+
}
94+
}
8795
}

Sources/Fuzzilli/Mutators/OperationMutator.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ public class OperationMutator: BaseInstructionMutator {
221221
newOp = UpdateSuperProperty(propertyName: b.randomPropertyName(), operator: chooseUniform(from: BinaryOperator.allCases))
222222
case .beginIf(let op):
223223
newOp = BeginIf(inverted: !op.inverted)
224+
case .loopNestedContinue(let op):
225+
newOp = LoopNestedContinue(Int.random(in: 0...10))
224226
case .createWasmGlobal(let op):
225227
// The type has to match for wasm, we cannot just switch types here as the rest of the wasm code will become invalid.
226228
// TODO: add nullref and funcref as types here.

0 commit comments

Comments
 (0)