Skip to content

Commit 27bdae7

Browse files
author
Samuel Groß
committed
Implement Minimization Post-Processing
The new MinimizationPostProcessor runs at the end of minimization and attempts to add certain features - which are deemed useful for future code mutations - back to the program as long as they don't alter the interesting behaviour. For example, it attempts to add return statements at the end of functions and add arguments to function calls.
1 parent d267afb commit 27bdae7

File tree

11 files changed

+202
-71
lines changed

11 files changed

+202
-71
lines changed

Sources/Fuzzilli/FuzzIL/Instruction.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ public struct Instruction {
7777
return op.firstVariadicInput
7878
}
7979

80+
/// Whether this instruction has any variadic inputs.
81+
public var hasAnyVariadicInputs: Bool {
82+
return firstVariadicInput < numInputs
83+
}
84+
8085
/// Whether this instruction has any outputs.
8186
public var hasOutputs: Bool {
8287
return numOutputs + numInnerOutputs > 0

Sources/Fuzzilli/Minimization/BlockReducer.swift

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
/// Reducer to remove unecessary block groups.
1616
struct BlockReducer: Reducer {
17-
func reduce(_ code: inout Code, with tester: ReductionTester) {
17+
func reduce(_ code: inout Code, with helper: MinimizationHelper) {
1818
for group in Blocks.findAllBlockGroups(in: code) {
1919
switch group.begin.op {
2020
case is BeginWhileLoop,
@@ -24,37 +24,37 @@ struct BlockReducer: Reducer {
2424
is BeginForOfLoop,
2525
is BeginForOfWithDestructLoop:
2626
assert(group.numBlocks == 1)
27-
reduceLoop(loop: group.block(0), in: &code, with: tester)
27+
reduceLoop(loop: group.block(0), in: &code, with: helper)
2828

2929
case is BeginTry:
30-
reduceTryCatchFinally(tryCatch: group, in: &code, with: tester)
30+
reduceTryCatchFinally(tryCatch: group, in: &code, with: helper)
3131

3232
case is BeginIf:
3333
// We reduce ifs simply by removing the whole block group.
3434
// This works OK since minimization is a fixpoint iteration,
3535
// so if only one branch is required, the other one will
3636
// eventually be empty.
37-
reduceGenericBlockGroup(group, in: &code, with: tester)
37+
reduceGenericBlockGroup(group, in: &code, with: helper)
3838

3939
case is BeginSwitch:
40-
reduceBeginSwitch(group, in: &code, with: tester)
40+
reduceBeginSwitch(group, in: &code, with: helper)
4141

4242
case is BeginSwitchCase,
4343
is BeginSwitchDefaultCase:
4444
// These instructions are handled in reduceBeginSwitch.
4545
continue
4646

4747
case is BeginWith:
48-
reduceGenericBlockGroup(group, in: &code, with: tester)
48+
reduceGenericBlockGroup(group, in: &code, with: helper)
4949

5050
case is BeginAnyFunction:
51-
reduceFunction(group, in: &code, with: tester)
51+
reduceFunction(group, in: &code, with: helper)
5252

5353
case is BeginCodeString:
54-
reduceCodeString(group, in: &code, with: tester)
54+
reduceCodeString(group, in: &code, with: helper)
5555

5656
case is BeginBlockStatement:
57-
reduceGenericBlockGroup(group, in: &code, with: tester)
57+
reduceGenericBlockGroup(group, in: &code, with: helper)
5858

5959
case is BeginClass:
6060
// TODO we need a custom reduceClass here that will also attempt to replace the class output variable
@@ -65,15 +65,15 @@ struct BlockReducer: Reducer {
6565
// ...
6666
// EndClass
6767
// v42 <- Construct v0
68-
reduceGenericBlockGroup(group, in: &code, with: tester)
68+
reduceGenericBlockGroup(group, in: &code, with: helper)
6969

7070
default:
7171
fatalError("Unknown block group: \(group.begin.op.name)")
7272
}
7373
}
7474
}
7575

76-
private func reduceLoop(loop: Block, in code: inout Code, with tester: ReductionTester) {
76+
private func reduceLoop(loop: Block, in code: inout Code, with helper: MinimizationHelper) {
7777
assert(loop.begin.isLoop)
7878
assert(loop.end.isLoop)
7979

@@ -93,12 +93,12 @@ struct BlockReducer: Reducer {
9393
}
9494
}
9595

96-
tester.tryNopping(candidates, in: &code)
96+
helper.tryNopping(candidates, in: &code)
9797
}
9898

99-
private func reduceGenericBlockGroup(_ group: BlockGroup, in code: inout Code, with tester: ReductionTester) {
99+
private func reduceGenericBlockGroup(_ group: BlockGroup, in code: inout Code, with helper: MinimizationHelper) {
100100
var candidates = group.excludingContent().map({ $0.index })
101-
if tester.tryNopping(candidates, in: &code) {
101+
if helper.tryNopping(candidates, in: &code) {
102102
// Success!
103103
return
104104
}
@@ -121,19 +121,19 @@ struct BlockReducer: Reducer {
121121
// can be removed independently, since they have data dependencies on each other. As such,
122122
// the only option is to remove the entire block, including its content.
123123
candidates = group.includingContent().map { $0.index }
124-
tester.tryNopping(candidates, in: &code)
124+
helper.tryNopping(candidates, in: &code)
125125
}
126126

127127
/// Try to reduce a BeginSwitch/EndSwitch Block.
128128
/// (1) reduce it by aggressively trying to remove the whole thing.
129129
/// (2) reduce it by removing the BeginSwitch(Default)Case/EndSwitchCase instructions but keeping the content.
130130
/// (3) reduce it by removing individual BeginSwitchCase/EndSwitchCase blocks.
131-
private func reduceBeginSwitch(_ group: BlockGroup, in code: inout Code, with tester: ReductionTester) {
131+
private func reduceBeginSwitch(_ group: BlockGroup, in code: inout Code, with helper: MinimizationHelper) {
132132
assert(group.begin.op is BeginSwitch)
133133

134134
var candidates = group.includingContent().map { $0.index }
135135

136-
if tester.tryNopping(candidates, in: &code) {
136+
if helper.tryNopping(candidates, in: &code) {
137137
// (1)
138138
// We successfully removed the whole switch statement.
139139
return
@@ -159,7 +159,7 @@ struct BlockReducer: Reducer {
159159
instructionIdx += 1
160160
}
161161

162-
if tester.tryNopping(candidates, in: &code) {
162+
if helper.tryNopping(candidates, in: &code) {
163163
// (2)
164164
// We successfully removed the switch case while keeping the
165165
// content inside.
@@ -171,11 +171,11 @@ struct BlockReducer: Reducer {
171171
// currently do not have a way of generating them.
172172
if block.begin.op is BeginSwitchDefaultCase { continue }
173173
// (3) Try to remove the cases here.
174-
tester.tryNopping(Array(block.head...block.tail), in: &code)
174+
helper.tryNopping(Array(block.head...block.tail), in: &code)
175175
}
176176
}
177177

178-
private func reduceFunction(_ function: BlockGroup, in code: inout Code, with tester: ReductionTester) {
178+
private func reduceFunction(_ function: BlockGroup, in code: inout Code, with helper: MinimizationHelper) {
179179
assert(function.begin.op is BeginAnyFunction)
180180
assert(function.end.op is EndAnyFunction)
181181

@@ -194,10 +194,10 @@ struct BlockReducer: Reducer {
194194
//
195195
// So that the calls to the function can be removed by a subsequent reducer if only the body is important.
196196
// But its likely not worth the effort as the InliningReducer will do a better job at solving this.
197-
reduceGenericBlockGroup(function, in: &code, with: tester)
197+
reduceGenericBlockGroup(function, in: &code, with: helper)
198198
}
199199

200-
private func reduceCodeString(_ codestring: BlockGroup, in code: inout Code, with tester: ReductionTester) {
200+
private func reduceCodeString(_ codestring: BlockGroup, in code: inout Code, with helper: MinimizationHelper) {
201201
assert(codestring.begin.op is BeginCodeString)
202202
assert(codestring.end.op is EndCodeString)
203203

@@ -208,16 +208,16 @@ struct BlockReducer: Reducer {
208208
var replacements = [(Int, Instruction)]()
209209
replacements.append((codestring.head, Instruction(LoadString(value: ""), output: codestring.begin.output)))
210210
replacements.append((codestring.tail, Instruction(Nop())))
211-
if tester.tryReplacements(replacements, in: &code) {
211+
if helper.tryReplacements(replacements, in: &code) {
212212
// Success!
213213
return
214214
}
215215

216216
// If unsuccessful, default to generic block reduction
217-
reduceGenericBlockGroup(codestring, in: &code, with: tester)
217+
reduceGenericBlockGroup(codestring, in: &code, with: helper)
218218
}
219219

220-
private func reduceTryCatchFinally(tryCatch: BlockGroup, in code: inout Code, with tester: ReductionTester) {
220+
private func reduceTryCatchFinally(tryCatch: BlockGroup, in code: inout Code, with helper: MinimizationHelper) {
221221
assert(tryCatch.begin.op is BeginTry)
222222
assert(tryCatch.end.op is EndTryCatchFinally)
223223

@@ -228,7 +228,7 @@ struct BlockReducer: Reducer {
228228
candidates.append(tryCatch[i].index)
229229
}
230230

231-
if tester.tryNopping(candidates, in: &code) {
231+
if helper.tryNopping(candidates, in: &code) {
232232
return
233233
}
234234

@@ -266,7 +266,7 @@ struct BlockReducer: Reducer {
266266
}
267267
}
268268

269-
if removedLastTryBlockInstruction && tester.tryNopping(candidates, in: &code) {
269+
if removedLastTryBlockInstruction && helper.tryNopping(candidates, in: &code) {
270270
return
271271
}
272272

@@ -293,10 +293,10 @@ struct BlockReducer: Reducer {
293293
}
294294
}
295295

296-
tester.tryNopping(candidates, in: &code)
296+
helper.tryNopping(candidates, in: &code)
297297

298298
// Finally, fall back to generic block group reduction, which will attempt to remove the
299299
// entire try-catch block including its content
300-
reduceGenericBlockGroup(tryCatch, in: &code, with: tester)
300+
reduceGenericBlockGroup(tryCatch, in: &code, with: helper)
301301
}
302302
}

Sources/Fuzzilli/Minimization/GenericInstructionReducer.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414

1515
/// Removes simple instructions from a program if they are not required.
1616
struct GenericInstructionReducer: Reducer {
17-
func reduce(_ code: inout Code, with tester: ReductionTester) {
17+
func reduce(_ code: inout Code, with helper: MinimizationHelper) {
1818
for instr in code.reversed() {
1919
if !instr.isSimple || instr.op is Nop {
2020
continue
2121
}
2222

23-
tester.tryNopping(instructionAt: instr.index, in: &code)
23+
helper.tryNopping(instructionAt: instr.index, in: &code)
2424
}
2525
}
2626
}

Sources/Fuzzilli/Minimization/InliningReducer.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414

1515
/// Attempts to inline functions at their callsite. This reducer is necessary to prevent deep nesting of functions.
1616
struct InliningReducer: Reducer {
17-
func reduce(_ code: inout Code, with tester: ReductionTester) {
17+
func reduce(_ code: inout Code, with helper: MinimizationHelper) {
1818
var candidates = identifyInlineableFunctions(in: code)
1919
while !candidates.isEmpty {
2020
let funcIndex = candidates.removeLast()
2121
let newCode = inline(functionAt: funcIndex, in: code)
22-
if tester.test(newCode) {
22+
if helper.test(newCode) {
2323
code = newCode
2424
// Inlining changes the program so we need to redo our analysis.
2525
// In particular, instruction are reordered and variables are renamed. Further, there may now also be new inlining candidates, for example

Sources/Fuzzilli/Minimization/ReductionTester.swift renamed to Sources/Fuzzilli/Minimization/MinimizationHelper.swift

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,23 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
/// A ReductionTester tests whether a reducton is valid and doesn't alter the important aspects of a program.
16-
class ReductionTester {
15+
/// The MinimizationHelper provides functions for testing whether a code change alters the programs interesting behaviour. It also provides access to the fuzzer instance for executing programs or other tasks.
16+
class MinimizationHelper {
1717
var totalReductions = 0
1818
var failedReductions = 0
1919
var didReduce = false
2020

21+
/// Fuzzer instance to schedule execution of programs on.
22+
let fuzzer: Fuzzer
23+
2124
/// The aspects of the program to preserve during minimization.
2225
private let aspects: ProgramAspects
2326

24-
/// Fuzzer instance to schedule execution of programs on.
25-
private let fuzzer: Fuzzer
26-
2727
/// Whether we are running on the fuzzer queue (synchronous minimization) or not (asynchronous minimization).
2828
private let runningOnFuzzerQueue: Bool
2929

3030
/// The minimizer can select instructions that should be kept regardless of whether they are important or not. This set tracks those instructions.
31-
private let instructionsToKeep: Set<Int>
31+
private var instructionsToKeep: Set<Int>
3232

3333
init(for aspects: ProgramAspects, of fuzzer: Fuzzer, keeping instructionsToKeep: Set<Int>, runningOnFuzzerQueue: Bool) {
3434
self.aspects = aspects
@@ -37,6 +37,18 @@ class ReductionTester {
3737
self.runningOnFuzzerQueue = runningOnFuzzerQueue
3838
}
3939

40+
func performOnFuzzerQueue(_ task: () -> Void) {
41+
if runningOnFuzzerQueue {
42+
return task()
43+
} else {
44+
return fuzzer.sync(do: task)
45+
}
46+
}
47+
48+
func clearInstructionsToKeep() {
49+
instructionsToKeep.removeAll()
50+
}
51+
4052
/// Test a reduction and return true if the reduction was Ok, false otherwise.
4153
func test(_ code: Code) -> Bool {
4254
// Reducers are allowed to nop instructions without verifying whether their outputs are used.
@@ -49,15 +61,10 @@ class ReductionTester {
4961

5062
// Run the modified program and see if the patch changed its behaviour
5163
var stillHasAspects = false
52-
func executeAndEvaluate() {
64+
performOnFuzzerQueue {
5365
let execution = fuzzer.execute(Program(with: code), withTimeout: fuzzer.config.timeout * 2)
5466
stillHasAspects = fuzzer.evaluator.hasAspects(execution, aspects)
5567
}
56-
if runningOnFuzzerQueue {
57-
executeAndEvaluate()
58-
} else {
59-
fuzzer.sync(do: executeAndEvaluate)
60-
}
6168

6269
if stillHasAspects {
6370
didReduce = true
@@ -78,7 +85,6 @@ class ReductionTester {
7885
}
7986

8087
let origInstr = code[index]
81-
assert(!(origInstr.op is Nop))
8288
code[index] = newInstr
8389

8490
let result = test(code)
@@ -91,6 +97,29 @@ class ReductionTester {
9197
return result
9298
}
9399

100+
@discardableResult
101+
func tryInserting(_ newInstr: Instruction, at index: Int, in code: inout Code) -> Bool {
102+
// Inserting instructions will invalidate the instructionsToKeep list, so that list must be empty here.
103+
assert(instructionsToKeep.isEmpty)
104+
105+
// For simplicity, just build a copy of the input code here. This logic is not particularly performance sensitive.
106+
var newCode = Code()
107+
for instr in code {
108+
if instr.index == index {
109+
newCode.append(newInstr)
110+
}
111+
newCode.append(instr)
112+
}
113+
114+
let result = test(newCode)
115+
116+
if result {
117+
code = newCode
118+
}
119+
120+
return result
121+
}
122+
94123
/// Remove the instruction at the given index if it does not negatively influence the programs previous behaviour.
95124
@discardableResult
96125
func tryNopping(instructionAt index: Int, in code: inout Code) -> Bool {
@@ -149,5 +178,5 @@ protocol Reducer {
149178
/// Attempt to reduce the given program in some way and return the result.
150179
///
151180
/// The returned program can have non-contiguous variable names but must otherwise be valid.
152-
func reduce(_ code: inout Code, with tester: ReductionTester)
181+
func reduce(_ code: inout Code, with tester: MinimizationHelper)
153182
}

0 commit comments

Comments
 (0)