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/switch statement js compiler #440

43 changes: 41 additions & 2 deletions Sources/Fuzzilli/Compiler/Compiler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public class JavaScriptCompiler {
/// The next free FuzzIL variable.
private var nextVariable = 0

/// Context analyzer to track the context of the code being compiled. Used to distinguish switch and loop breaks.
private var contextAnalyzer = ContextAnalyzer()

public func compile(_ ast: AST) throws -> Program {
reset()

Expand Down Expand Up @@ -441,8 +444,16 @@ public class JavaScriptCompiler {
emit(EndForOfLoop())

case .breakStatement:
// TODO currently we assume this is a LoopBreak, but once we support switch-statements, it could also be a SwitchBreak
emit(LoopBreak())
switch contextAnalyzer.breakContext {
case .loop:
emit(LoopBreak())
break
case .switchBlock:
emit(SwitchBreak())
break
default:
throw CompilerError.invalidNodeError("break statement outside of loop or switch")
}

case .continueStatement:
emit(LoopContinue())
Expand Down Expand Up @@ -486,6 +497,33 @@ public class JavaScriptCompiler {
try compileBody(withStatement.body)
}
emit(EndWith())
case .switchStatement(let switchStatement):
// TODO Replace the precomputation of tests with compilation of the test expressions in the cases.
// To do this, we would need to redesign Switch statements in FuzzIL to (for example) have a BeginSwitchCaseHead, BeginSwitchCaseBody, and EndSwitchCase.
// Then the expression would go inside the header.
var precomputedTests = [Variable]()
for caseStatement in switchStatement.cases {
if caseStatement.hasTest {
let test = try compileExpression(caseStatement.test)
precomputedTests.append(test)
}
}
let discriminant = try compileExpression(switchStatement.discriminant)
emit(BeginSwitch(), withInputs: [discriminant])
for caseStatement in switchStatement.cases {
if caseStatement.hasTest {
emit(BeginSwitchCase(), withInputs: [precomputedTests.removeFirst()])
} else {
emit(BeginSwitchDefaultCase())
}
try enterNewScope {
for statement in caseStatement.consequent {
try compileStatement(statement)
}
}
emit(EndSwitchCase(fallsThrough: true))
}
emit(EndSwitch())
}
}

Expand Down Expand Up @@ -999,6 +1037,7 @@ public class JavaScriptCompiler {
let innerOutputs = (0..<op.numInnerOutputs).map { _ in nextFreeVariable() }
let inouts = inputs + outputs + innerOutputs
let instr = Instruction(op, inouts: inouts)
contextAnalyzer.analyze(instr)
return code.append(instr)
}

Expand Down
12 changes: 12 additions & 0 deletions Sources/Fuzzilli/Compiler/Parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,18 @@ function parse(script, proto) {
withStatement.body = visitStatement(node.body);
return makeStatement('WithStatement', withStatement);
}
case 'SwitchStatement': {
let switchStatement = {};
switchStatement.discriminant = visitExpression(node.discriminant);
switchStatement.cases = node.cases.map(visitStatement);
return makeStatement('SwitchStatement', switchStatement);
}
case 'SwitchCase': {
let switchCase = {};
if (node.test) {switchCase.test = visitExpression(node.test)}
switchCase.consequent = node.consequent.map(consequentNode => visitStatement(consequentNode));
return switchCase;
}
default: {
throw "Unhandled node type " + node.type;
}
Expand Down
14 changes: 14 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Analyzer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,16 @@ struct VariableAnalyzer: Analyzer {
/// Keeps track of the current context during program construction.
struct ContextAnalyzer: Analyzer {
private var contextStack = Stack([Context.javascript])
private var breakContextStack = Stack([Context.empty])

var context: Context {
return contextStack.top
}

var breakContext: Context {
return breakContextStack.top
}

mutating func analyze(_ instr: Instruction) {
if instr.isBlockEnd {
contextStack.pop()
Expand All @@ -170,6 +175,15 @@ struct ContextAnalyzer: Analyzer {
}
contextStack.push(newContext)
}
if instr.op.contextOpened.contains(.loop){
breakContextStack.push(.loop)
}
if instr.op.contextOpened.contains(.switchBlock) {
breakContextStack.push(.switchBlock)
}
if instr.isBreakableEnd {
breakContextStack.pop()
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ public struct Instruction {
return op.attributes.contains(.isNop)
}

/// Whether this instruction is the end of a breakable context.
public var isBreakableEnd: Bool {
return op.attributes.contains(.isBreakableEnd)
}


public init<Variables: Collection>(_ op: Operation, inouts: Variables, index: Int? = nil) where Variables.Element == Variable {
assert(op.numInputs + op.numOutputs + op.numInnerOutputs == inouts.count)
Expand Down
14 changes: 7 additions & 7 deletions Sources/Fuzzilli/FuzzIL/JsOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1851,7 +1851,7 @@ final class EndWhileLoop: JsOperation {
override var opcode: Opcode { .endWhileLoop(self) }

init() {
super.init(attributes: .isBlockEnd)
super.init(attributes: [.isBlockEnd, .isBreakableEnd])
}
}

Expand All @@ -1876,7 +1876,7 @@ final class EndDoWhileLoop: JsOperation {
override var opcode: Opcode { .endDoWhileLoop(self) }

init() {
super.init(numInputs: 1, attributes: .isBlockEnd)
super.init(numInputs: 1, attributes: [.isBlockEnd, .isBreakableEnd])
}
}

Expand Down Expand Up @@ -1960,7 +1960,7 @@ final class EndForLoop: JsOperation {
override var opcode: Opcode { .endForLoop(self) }

init() {
super.init(attributes: .isBlockEnd)
super.init(attributes: [.isBlockEnd, .isBreakableEnd])
}
}

Expand All @@ -1976,7 +1976,7 @@ final class EndForInLoop: JsOperation {
override var opcode: Opcode { .endForInLoop(self) }

init() {
super.init(attributes: .isBlockEnd)
super.init(attributes: [.isBlockEnd, .isBreakableEnd])
}
}

Expand Down Expand Up @@ -2006,7 +2006,7 @@ final class EndForOfLoop: JsOperation {
override var opcode: Opcode { .endForOfLoop(self) }

init() {
super.init(attributes: .isBlockEnd)
super.init(attributes: [.isBlockEnd, .isBreakableEnd])
}
}

Expand Down Expand Up @@ -2034,7 +2034,7 @@ final class EndRepeatLoop: JsOperation {
override var opcode: Opcode { .endRepeatLoop(self) }

init() {
super.init(attributes: .isBlockEnd)
super.init(attributes: [.isBlockEnd, .isBreakableEnd])
}
}

Expand Down Expand Up @@ -2222,7 +2222,7 @@ final class EndSwitch: JsOperation {
override var opcode: Opcode { .endSwitch(self) }

init() {
super.init(attributes: .isBlockEnd, requiredContext: .switchBlock)
super.init(attributes: [.isBlockEnd, .isBreakableEnd], requiredContext: .switchBlock)
}
}

Expand Down
3 changes: 3 additions & 0 deletions Sources/Fuzzilli/FuzzIL/Operation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ public class Operation {

// The instruction is a Nop operation.
static let isNop = Attributes(rawValue: 1 << 11)

// The instruction ends a breakable context.
static let isBreakableEnd = Attributes(rawValue: 1 << 12)
}
}

Expand Down
Loading
Loading