Skip to content
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

feature/label statement #467

Open
wants to merge 3 commits 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
38 changes: 38 additions & 0 deletions Sources/Fuzzilli/Base/ProgramBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,36 @@ public class ProgramBuilder {
}
}


public var loopSta: [Variable]

public func pushLabel(_ variable: Variable) {
loopSta.append(variable)
}

public func popLabel() {
loopSta.removeLast()
}

public func randomLabel() -> Variable {
if let randomElement = loopSta.randomElement() {
return randomElement
}
return loadString("ErrorLabel")
}

public func isLoopStaNotEmpty() -> Bool { loopSta.count > 0 }

public func clearLoopSta() {
if loopSta.count > 0 { loopSta.removeAll() }
}

/// Constructs a new program builder for the given fuzzer.
init(for fuzzer: Fuzzer, parent: Program?) {
self.fuzzer = fuzzer
self.jsTyper = JSTyper(for: fuzzer.environment)
self.parent = parent
self.loopSta = []
}

/// Resets this builder.
Expand All @@ -168,6 +193,7 @@ public class ProgramBuilder {
jsTyper.reset()
activeObjectLiterals.removeAll()
activeClassDefinitions.removeAll()
loopSta.removeAll()
}

/// Finalizes and returns the constructed program, then resets this builder so it can be reused for building another program.
Expand Down Expand Up @@ -2518,6 +2544,18 @@ public class ProgramBuilder {
emit(EndRepeatLoop())
}

public func loadLabel(_ value: Variable) {
emit(LoadLabel(), withInputs: [value])
}

public func loopLabelBreak(_ value: Variable) {
emit(LoopLabelBreak(), withInputs: [value])
}

public func loopLabelContinue(_ value: Variable) {
emit(LoopLabelContinue(), withInputs: [value])
}

public func loopBreak() {
emit(LoopBreak())
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/Fuzzilli/CodeGen/CodeGeneratorWeights.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ public let codeGeneratorWeights = [
"SwitchCaseBreakGenerator": 5,
"LoopBreakGenerator": 5,
"ContinueGenerator": 5,
"LoopLabelBreakGenerator": 5,
"LoopLabelContinueGenerator": 5,
"TryCatchGenerator": 5,
"ThrowGenerator": 1,
"BlockStatementGenerator": 1,
Expand Down
33 changes: 32 additions & 1 deletion Sources/Fuzzilli/CodeGen/CodeGenerators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1326,22 +1326,28 @@ public let CodeGenerators: [CodeGenerator] = [

RecursiveCodeGenerator("WhileLoopGenerator") { b in
let loopVar = b.loadInt(0)
let make = randomloadLabel(b)
b.buildWhileLoop({ b.compare(loopVar, with: b.loadInt(Int64.random(in: 0...10)), using: .lessThan) }) {
b.buildRecursive()
popLabelIfMake(b, make)
b.unary(.PostInc, loopVar)
}
},

RecursiveCodeGenerator("DoWhileLoopGenerator") { b in
let loopVar = b.loadInt(0)
let make = randomloadLabel(b)
b.buildDoWhileLoop(do: {
b.buildRecursive()
popLabelIfMake(b, make)
b.unary(.PostInc, loopVar)
}, while: { b.compare(loopVar, with: b.loadInt(Int64.random(in: 0...10)), using: .lessThan) })
},

RecursiveCodeGenerator("SimpleForLoopGenerator") { b in
let make = randomloadLabel(b)
b.buildForLoop(i: { b.loadInt(0) }, { i in b.compare(i, with: b.loadInt(Int64.random(in: 0...10)), using: .lessThan) }, { i in b.unary(.PostInc, i) }) { _ in
popLabelIfMake(b, make)
b.buildRecursive()
}
},
Expand All @@ -1350,27 +1356,35 @@ public let CodeGenerators: [CodeGenerator] = [
if probability(0.5) {
// Generate a for-loop without any loop variables.
let counter = b.loadInt(10)
let make = randomloadLabel(b)
b.buildForLoop({}, { b.unary(.PostDec, counter) }) {
b.buildRecursive()
popLabelIfMake(b, make)
}
} else {
// Generate a for-loop with two loop variables.
// TODO could also generate loops with even more loop variables?
let make = randomloadLabel(b)
b.buildForLoop({ return [b.loadInt(0), b.loadInt(10)] }, { vs in b.compare(vs[0], with: vs[1], using: .lessThan) }, { vs in b.unary(.PostInc, vs[0]); b.unary(.PostDec, vs[1]) }) { _ in
b.buildRecursive()
popLabelIfMake(b, make)
}
}
},

RecursiveCodeGenerator("ForInLoopGenerator", inputs: .preferred(.object())) { b, obj in
let make = randomloadLabel(b)
b.buildForInLoop(obj) { _ in
b.buildRecursive()
popLabelIfMake(b, make)
}
},

RecursiveCodeGenerator("ForOfLoopGenerator", inputs: .preferred(.iterable)) { b, obj in
let make = randomloadLabel(b)
b.buildForOfLoop(obj) { _ in
b.buildRecursive()
popLabelIfMake(b, make)
}
},

Expand All @@ -1385,16 +1399,19 @@ public let CodeGenerators: [CodeGenerator] = [
if indices.isEmpty {
indices = [0]
}

let make = randomloadLabel(b)
b.buildForOfLoop(obj, selecting: indices, hasRestElement: probability(0.2)) { _ in
b.buildRecursive()
popLabelIfMake(b, make)
}
},

RecursiveCodeGenerator("RepeatLoopGenerator") { b in
let numIterations = Int.random(in: 2...100)
let make = randomloadLabel(b)
b.buildRepeatLoop(n: numIterations) { _ in
b.buildRecursive()
popLabelIfMake(b, make)
}
},

Expand All @@ -1407,6 +1424,20 @@ public let CodeGenerators: [CodeGenerator] = [
b.loopContinue()
},

CodeGenerator("LoopLabelBreakGenerator", inContext: .loop) { b in
assert(b.context.contains(.loop))
if b.isLoopStaNotEmpty() {
b.loopLabelBreak(b.randomLabel())
}
},

CodeGenerator("LoopLabelContinueGenerator", inContext: .loop) { b in
assert(b.context.contains(.loop))
if b.isLoopStaNotEmpty() {
b.loopLabelContinue(b.randomLabel())
}
},

RecursiveCodeGenerator("TryCatchGenerator") { b in
// Build either try-catch-finally, try-catch, or try-finally
withEqualProbability({
Expand Down
29 changes: 29 additions & 0 deletions Sources/Fuzzilli/CodeGen/CodeGenertorsUtil.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation

func randomLabelBreak(_ b: ProgramBuilder) {
if b.isLoopStaNotEmpty() {
if probability(0.2) {
if probability(0.5) {
b.loopLabelBreak(b.randomLabel())
} else {
b.loopLabelContinue(b.randomLabel())
}
}
}
}

func randomloadLabel(_ b: ProgramBuilder) -> Bool {
let labelValue = b.loadString(String(UUID().uuidString.prefix(6)))
let makelabel = probability(0.2)
if makelabel {
b.loadLabel(labelValue)
b.pushLabel(labelValue)
}
return makelabel
}

func popLabelIfMake(_ b: ProgramBuilder, _ makelabel: Bool) {
if makelabel {
b.popLabel()
}
}
49 changes: 39 additions & 10 deletions Sources/Fuzzilli/Compiler/Compiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -443,19 +443,48 @@ public class JavaScriptCompiler {

emit(EndForOfLoop())

case .breakStatement:
// If we're in both .loop and .switch context, then the loop must be the most recent context
// (switch blocks don't propagate an outer .loop context) so we just need to check for .loop here
if contextAnalyzer.context.contains(.loop){
emit(LoopBreak())
} else if contextAnalyzer.context.contains(.switchBlock){
emit(SwitchBreak())
case .breakStatement(let breakStatement):
let label = breakStatement.label
if !label.isEmpty {
let labelVar = emit(LoadString(value: label)).output // Assuming LoopLabelBreak needs a label loaded as input
emit(LoopLabelBreak(), withInputs: [labelVar]) // Use the loaded label as input
} else {
throw CompilerError.invalidNodeError("break statement outside of loop or switch")
// If we're in both .loop and .switch context, then the loop must be the most recent context
// (switch blocks don't propagate an outer .loop context) so we just need to check for .loop here
if contextAnalyzer.context.contains(.loop){
emit(LoopBreak())
} else if contextAnalyzer.context.contains(.switchBlock){
emit(SwitchBreak())
} else {
throw CompilerError.invalidNodeError("break statement outside of loop or switch")
}
}

case .continueStatement:
emit(LoopContinue())
case .continueStatement(let continueStatement):
let label = continueStatement.label
if !label.isEmpty {
let labelVar = emit(LoadString(value: label)).output
emit(LoopLabelContinue(), withInputs: [labelVar]) // Emit a labeled continue by loading the label and using it as input
} else {
emit(LoopContinue())
}

case .labeledStatement(let labeledStatement):
// Step 1: Load the label as a string first
let label: String = labeledStatement.label
// Emit the LoadString operation to convert the label to a variable
let loadStringInstr = emit(LoadString(value: label)) // This creates a FuzzIL instruction for the label string
// Extract the output variable from the LoadString instruction
let labelVar = loadStringInstr.output // Get the variable produced by LoadString
// Step 2: Emit LoadLabel, passing the label variable as input
emit(LoadLabel(), withInputs: [labelVar]) // Pass the output variable as input
// Step 3: Push the label to the context (track the label scope)
contextAnalyzer.enterLabeledBlock()
defer {
contextAnalyzer.exitLabeledBlock()
}
// Step 4: Compile the statement within the labeled block
try compileStatement(labeledStatement.statement)

case .tryStatement(let tryStatement):
emit(BeginTry())
Expand Down
20 changes: 18 additions & 2 deletions Sources/Fuzzilli/Compiler/Parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,19 @@ function parse(script, proto) {
return makeStatement('ForOfLoop', forOfLoop);
}
case 'BreakStatement': {
return makeStatement('BreakStatement', {});
let breakStatementProto = {};

if (node.label) {
breakStatementProto.label = node.label.name; // Extract the label if present
}
return makeStatement('BreakStatement', breakStatementProto);
}
case 'ContinueStatement': {
return makeStatement('ContinueStatement', {});
let continueStatementProto = {};
if (node.label) {
continueStatementProto.label = node.label.name; // Extract the label if present
}
return makeStatement('ContinueStatement', continueStatementProto);
}
case 'TryStatement': {
assert(node.block.type === 'BlockStatement', "Expected block statement as body of a try block");
Expand Down Expand Up @@ -332,6 +341,13 @@ function parse(script, proto) {
switchStatement.cases = node.cases.map(visitStatement);
return makeStatement('SwitchStatement', switchStatement);
}
case "LabeledStatement": {
let labeledStatementProto = {
label: node.label.name, // Store the label
statement: visitStatement(node.body)
};
return { labeledStatement: labeledStatementProto };
}
case 'SwitchCase': {
let switchCase = {};
if (node.test) {switchCase.test = visitExpression(node.test)}
Expand Down
23 changes: 23 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Analyzer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,30 @@ struct ContextAnalyzer: Analyzer {
return contextStack.top
}

// To track labeled block contexts
private var labelContextStack = Stack([false])
var isInLabeledContext: Bool {
return labelContextStack.top
}

// Enter a new labeled block (for example, a loop with a label)
mutating func enterLabeledBlock() {
labelContextStack.push(true)
}

// Exit the current labeled block
mutating func exitLabeledBlock() {
if !labelContextStack.isEmpty {
labelContextStack.pop()
} else {
print("Warning: Attempting to exit a labeled block, but stack is empty.")
}
}

mutating func analyze(_ instr: Instruction) {
if instr.isBlockEnd {
contextStack.pop()
labelContextStack.pop()
}
if instr.isBlockStart {
var newContext = instr.op.contextOpened
Expand Down Expand Up @@ -178,6 +199,8 @@ struct ContextAnalyzer: Analyzer {
newContext.remove(.switchCase)
}
contextStack.push(newContext)
// Push a new false label context for the block
labelContextStack.push(false)
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,12 @@ extension Instruction: ProtobufConvertible {
$0.loopBreak = Fuzzilli_Protobuf_LoopBreak()
case .loopContinue:
$0.loopContinue = Fuzzilli_Protobuf_LoopContinue()
case .loopLabelBreak:
$0.loopLabelBreak = Fuzzilli_Protobuf_LoopLabelBreak()
case .loadLabel:
$0.loadLabel = Fuzzilli_Protobuf_LoadLabel()
case .loopLabelContinue:
$0.loopLabelContinue = Fuzzilli_Protobuf_LoopLabelContinue()
case .beginTry:
$0.beginTry = Fuzzilli_Protobuf_BeginTry()
case .beginCatch:
Expand Down Expand Up @@ -1226,6 +1232,12 @@ extension Instruction: ProtobufConvertible {
op = BeginRepeatLoop(iterations: Int(p.iterations), exposesLoopCounter: p.exposesLoopCounter)
case .endRepeatLoop:
op = EndRepeatLoop()
case .loopLabelBreak:
op = LoopLabelBreak()
case .loopLabelContinue:
op = LoopLabelContinue()
case .loadLabel:
op = LoadLabel()
case .loopBreak:
op = LoopBreak()
case .loopContinue:
Expand Down
Loading