Skip to content

Commit bf0ac48

Browse files
author
Samuel Groß
committed
Improve corpus import fixup further
With the preceeding change, we can now import failing programs by wrapping them in a big try-catch statement. This is somewhat inefficient, however, as we may end up with samples where a lot of the code is never executed (because an exception is thrown early on). This change now adds additional logic to improve that and ensure that as much code as possible will also be executed at runtime. Specifically, the fixup algorithm when importing programs now works like this: 1. We first try to replace known test functions (such as `assertEquals`) with a dummy function as these test functions aren't available outside the testing environment. This now replaces a mechanism in the compiler that would also try to remove calls to these functions. 2. If the first attempt fails, we'll then attempt to insert try-catch blocks around individual instructions. For that, we first enable all guardable operations (which will cause them to be wrapped in try-catch), then perform one round of fixup during which all unecessary guards will be disabled again. As a result, only those try-catch block that are necessary will stay in the imported program. 3. Only if the previous attempts fail do we now insert one big try-catch statement around the entire program.
1 parent 225a7c8 commit bf0ac48

File tree

8 files changed

+306
-183
lines changed

8 files changed

+306
-183
lines changed

Sources/FuzzILTool/main.swift

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,6 @@ let jsSuffix = ""
2424
let jsLifter = JavaScriptLifter(prefix: jsPrefix, suffix: jsSuffix, ecmaVersion: ECMAScriptVersion.es6)
2525
let fuzzILLifter = FuzzILLifter()
2626

27-
// Default list of functions that are filtered out during compilation. These are functions that may be used in testcases but which do not influence the test's behaviour and so should be omitted for fuzzing.
28-
// The functions can use the wildcard '*' character as _last_ character, in which case a prefix match will be performed.
29-
let filteredFunctionsForCompiler = [
30-
// Functions used in V8's test suite
31-
"assert*",
32-
"print*",
33-
// Functions used in Mozilla's test suite
34-
"startTest",
35-
"enterFunc",
36-
"exitFunc",
37-
"report*",
38-
"options*",
39-
]
40-
4127
// Loads a serialized FuzzIL program from the given file
4228
func loadProgram(from path: String) throws -> Program {
4329
let data = try Data(contentsOf: URL(fileURLWithPath: path))
@@ -188,7 +174,7 @@ else if args.has("--compile") {
188174
exit(-1)
189175
}
190176

191-
let compiler = JavaScriptCompiler(deletingCallTo: filteredFunctionsForCompiler)
177+
let compiler = JavaScriptCompiler()
192178
let program: Program
193179
do {
194180
program = try compiler.compile(ast)

Sources/Fuzzilli/Compiler/Compiler.swift

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,11 @@ public class JavaScriptCompiler {
2727
case unsupportedFeatureError(String)
2828
}
2929

30-
public init(deletingCallTo filteredFunctions: [String] = []) {
31-
self.filteredFunctions = filteredFunctions
32-
}
30+
public init() {}
3331

3432
/// The compiled code.
3533
private var code = Code()
3634

37-
/// A list of function names or prefixes (e.g. `assert*`) which should be deleted from the output program.
38-
/// The function calls can in general only be removed if their return value isn't used, and so currently they are only
39-
/// removed if they make up a full ExpressionStatement, in which case the entire statement is ignored.
40-
/// This functionality is useful to remove calls to functions such as `assert*` or `print*` from tests
41-
/// as those are not useful for fuzzing.
42-
/// The function names may contain the wildcard character `*`, but _only_ as last character, in which case
43-
/// a prefix match will be performed instead of a string comparison.
44-
private let filteredFunctions: [String]
45-
4635
/// The environment is used to determine if an identifier identifies a builtin object.
4736
/// TODO we should probably use the correct target environment, with any additional builtins etc. here. But for now, we just manually add `gc` since that's relatively common.
4837
private var environment = JavaScriptEnvironment(additionalBuiltins: ["gc": .function()])
@@ -82,11 +71,6 @@ public class JavaScriptCompiler {
8271
}
8372

8473
private func compileStatement(_ node: StatementNode) throws {
85-
let shouldIgnoreStatement = try performStatementFiltering(node)
86-
guard !shouldIgnoreStatement else {
87-
return
88-
}
89-
9074
guard let stmt = node.statement else {
9175
throw CompilerError.invalidASTError("missing concrete statement in statement node")
9276
}
@@ -1239,40 +1223,6 @@ public class JavaScriptCompiler {
12391223
return (variables, spreads)
12401224
}
12411225

1242-
/// Determine whether the given statement should be filtered out.
1243-
///
1244-
/// Currently this function only performs function call filtering based on the `filteredFunctions` array.
1245-
private func performStatementFiltering(_ statement: StatementNode) throws -> Bool {
1246-
guard case .expressionStatement(let expressionStatement) = statement.statement else { return false }
1247-
guard case .callExpression(let callExpression) = expressionStatement.expression.expression else { return false }
1248-
guard case .identifier(let identifier) = callExpression.callee.expression else { return false }
1249-
1250-
let functionName = identifier.name
1251-
var shouldIgnore = false
1252-
for filteredFunction in filteredFunctions {
1253-
if filteredFunction.last == "*" {
1254-
if functionName.starts(with: filteredFunction.dropLast()) {
1255-
shouldIgnore = true
1256-
}
1257-
} else {
1258-
assert(!filteredFunction.contains("*"))
1259-
if functionName == filteredFunction {
1260-
shouldIgnore = true
1261-
}
1262-
}
1263-
}
1264-
1265-
if shouldIgnore {
1266-
// Still generate code for the arguments.
1267-
// For example, we may still want to emit the function call for something like `assertEq(f(), 42);`
1268-
for arg in callExpression.arguments {
1269-
try compileExpression(arg)
1270-
}
1271-
}
1272-
1273-
return shouldIgnore
1274-
}
1275-
12761226
private func reset() {
12771227
code = Code()
12781228
scopes.removeAll()

Sources/Fuzzilli/Execution/Execution.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ public enum ExecutionOutcome: CustomStringConvertible, Equatable, Hashable {
4141
return false
4242
}
4343
}
44+
45+
public func isFailure() -> Bool {
46+
if case .failed = self {
47+
return true
48+
} else {
49+
return false
50+
}
51+
}
4452
}
4553

4654
/// The result of executing a program.

Sources/Fuzzilli/FuzzIL/JsOperations.swift

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,90 @@ class GuardableOperation: JsOperation {
5555
self.isGuarded = isGuarded
5656
super.init(numInputs: numInputs, numOutputs: numOutputs, numInnerOutputs: numInnerOutputs, firstVariadicInput: firstVariadicInput, attributes: attributes, requiredContext: requiredContext)
5757
}
58+
59+
// Helper functions to enable guards.
60+
// If the given operation already has guarding enabled, then this function does
61+
// nothing and simply returns the input. Otherwise it creates a copy of the
62+
// operations which has guarding enabled.
63+
static func enableGuard(of operation: GuardableOperation) -> GuardableOperation {
64+
if operation.isGuarded {
65+
return operation
66+
}
67+
switch operation.opcode {
68+
case .getProperty(let op):
69+
return GetProperty(propertyName: op.propertyName, isGuarded: true)
70+
case .deleteProperty(let op):
71+
return DeleteProperty(propertyName: op.propertyName, isGuarded: true)
72+
case .getElement(let op):
73+
return GetElement(index: op.index, isGuarded: true)
74+
case .deleteElement(let op):
75+
return DeleteElement(index: op.index, isGuarded: true)
76+
case .getComputedProperty:
77+
return GetComputedProperty(isGuarded: true)
78+
case .deleteComputedProperty:
79+
return DeleteComputedProperty(isGuarded: true)
80+
case .callFunction(let op):
81+
return CallFunction(numArguments: op.numArguments, isGuarded: true)
82+
case .callFunctionWithSpread(let op):
83+
return CallFunctionWithSpread(numArguments: op.numArguments, spreads: op.spreads, isGuarded: true)
84+
case .construct(let op):
85+
return Construct(numArguments: op.numArguments, isGuarded: true)
86+
case .constructWithSpread(let op):
87+
return ConstructWithSpread(numArguments: op.numArguments, spreads: op.spreads, isGuarded: true)
88+
case .callMethod(let op):
89+
return CallMethod(methodName: op.methodName, numArguments: op.numArguments, isGuarded: true)
90+
case .callMethodWithSpread(let op):
91+
return CallMethodWithSpread(methodName: op.methodName, numArguments: op.numArguments, spreads: op.spreads, isGuarded: true)
92+
case .callComputedMethod(let op):
93+
return CallComputedMethod(numArguments: op.numArguments, isGuarded: true)
94+
case .callComputedMethodWithSpread(let op):
95+
return CallComputedMethodWithSpread(numArguments: op.numArguments, spreads: op.spreads, isGuarded: true)
96+
default:
97+
fatalError("All guardable operations should be handled")
98+
}
99+
}
100+
101+
// Helper functions to disable guards.
102+
// If the given operation already has guarding disabled, then this function does
103+
// nothing and simply returns the input. Otherwise it creates a copy of the
104+
// operations which has guarding disabled.
105+
static func disableGuard(of operation: GuardableOperation) -> GuardableOperation {
106+
if !operation.isGuarded {
107+
return operation
108+
}
109+
switch operation.opcode {
110+
case .getProperty(let op):
111+
return GetProperty(propertyName: op.propertyName, isGuarded: false)
112+
case .deleteProperty(let op):
113+
return DeleteProperty(propertyName: op.propertyName, isGuarded: false)
114+
case .getElement(let op):
115+
return GetElement(index: op.index, isGuarded: false)
116+
case .deleteElement(let op):
117+
return DeleteElement(index: op.index, isGuarded: false)
118+
case .getComputedProperty:
119+
return GetComputedProperty(isGuarded: false)
120+
case .deleteComputedProperty:
121+
return DeleteComputedProperty(isGuarded: false)
122+
case .callFunction(let op):
123+
return CallFunction(numArguments: op.numArguments, isGuarded: false)
124+
case .callFunctionWithSpread(let op):
125+
return CallFunctionWithSpread(numArguments: op.numArguments, spreads: op.spreads, isGuarded: false)
126+
case .construct(let op):
127+
return Construct(numArguments: op.numArguments, isGuarded: false)
128+
case .constructWithSpread(let op):
129+
return ConstructWithSpread(numArguments: op.numArguments, spreads: op.spreads, isGuarded: false)
130+
case .callMethod(let op):
131+
return CallMethod(methodName: op.methodName, numArguments: op.numArguments, isGuarded: false)
132+
case .callMethodWithSpread(let op):
133+
return CallMethodWithSpread(methodName: op.methodName, numArguments: op.numArguments, spreads: op.spreads, isGuarded: false)
134+
case .callComputedMethod(let op):
135+
return CallComputedMethod(numArguments: op.numArguments, isGuarded: false)
136+
case .callComputedMethodWithSpread(let op):
137+
return CallComputedMethodWithSpread(numArguments: op.numArguments, spreads: op.spreads, isGuarded: false)
138+
default:
139+
fatalError("All guardable operations should be handled")
140+
}
141+
}
58142
}
59143

60144
final class LoadInteger: JsOperation {

0 commit comments

Comments
 (0)