-
Notifications
You must be signed in to change notification settings - Fork 307
/
ProgramTemplates.swift
294 lines (246 loc) · 11.7 KB
/
ProgramTemplates.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// Builtin program templates to target specific types of bugs.
public let ProgramTemplates = [
ProgramTemplate("Codegen100") { b in
b.buildPrefix()
// Go wild.
b.build(n: 100)
},
ProgramTemplate("Codegen50") { b in
b.buildPrefix()
// Go (a little less) wild.
b.build(n: 50)
},
ProgramTemplate("JIT1Function") { b in
let smallCodeBlockSize = 5
let numIterations = 100
// Start with a random prefix and some random code.
b.buildPrefix()
b.build(n: smallCodeBlockSize)
// Generate a larger function
let f = b.buildPlainFunction(with: b.randomParameters()) { args in
assert(args.count > 0)
// Generate (larger) function body
b.build(n: 30)
b.doReturn(b.randomVariable())
}
// Generate some random instructions now
b.build(n: smallCodeBlockSize)
// trigger JIT
b.buildRepeatLoop(n: numIterations) { _ in
b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
}
// more random instructions
b.build(n: smallCodeBlockSize)
b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
// maybe trigger recompilation
b.buildRepeatLoop(n: numIterations) { _ in
b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
}
// more random instructions
b.build(n: smallCodeBlockSize)
b.callFunction(f, withArgs: b.randomArguments(forCalling: f))
},
ProgramTemplate("JIT2Functions") { b in
let smallCodeBlockSize = 5
let numIterations = 100
// Start with a random prefix and some random code.
b.buildPrefix()
b.build(n: smallCodeBlockSize)
// Generate a larger function
let f1 = b.buildPlainFunction(with: b.randomParameters()) { args in
assert(args.count > 0)
// Generate (larger) function body
b.build(n: 20)
b.doReturn(b.randomVariable())
}
// Generate a second larger function
let f2 = b.buildPlainFunction(with: b.randomParameters()) { args in
assert(args.count > 0)
// Generate (larger) function body
b.build(n: 20)
b.doReturn(b.randomVariable())
}
// Generate some random instructions now
b.build(n: smallCodeBlockSize)
// trigger JIT for first function
b.buildRepeatLoop(n: numIterations) { _ in
b.callFunction(f1, withArgs: b.randomArguments(forCalling: f1))
}
// trigger JIT for second function
b.buildRepeatLoop(n: numIterations) { _ in
b.callFunction(f2, withArgs: b.randomArguments(forCalling: f2))
}
// more random instructions
b.build(n: smallCodeBlockSize)
b.callFunction(f2, withArgs: b.randomArguments(forCalling: f2))
b.callFunction(f1, withArgs: b.randomArguments(forCalling: f1))
// maybe trigger recompilation
b.buildRepeatLoop(n: numIterations) { _ in
b.callFunction(f1, withArgs: b.randomArguments(forCalling: f1))
}
// maybe trigger recompilation
b.buildRepeatLoop(n: numIterations) { _ in
b.callFunction(f2, withArgs: b.randomArguments(forCalling: f2))
}
// more random instructions
b.build(n: smallCodeBlockSize)
b.callFunction(f1, withArgs: b.randomArguments(forCalling: f1))
b.callFunction(f2, withArgs: b.randomArguments(forCalling: f2))
},
ProgramTemplate("JITTrickyFunction") { b in
// This templates generates functions that behave differently in some of the iterations.
// The functions will essentially look like this:
//
// function f(arg1, arg2, i) {
// if (i == N) {
// // do stuff
// }
// // do stuff
// }
//
// Or like this:
//
// function f(arg1, arg2, i) {
// if (i % N == 0) {
// // do stuff
// }
// // do stuff
// }
//
let smallCodeBlockSize = 5
let numIterations = 100
// Helper function to generate code that only runs during some of the iterations.
func buildCodeThatRunsInOnlySomeIterations(iterationCount: Variable) {
// Decide when to run the code.
let cond: Variable
if probability(0.5) {
// Run the code in one specific iteration
let selectedIteration = withEqualProbability({
// Prefer to perform the action during one of the last iterations
assert(numIterations > 10)
return Int.random(in: (numIterations - 10)..<numIterations)
}, {
return Int.random(in: 0..<numIterations)
})
cond = b.compare(iterationCount, with: b.loadInt(Int64(selectedIteration)), using: .equal)
} else {
// Run the code every nth iteration
let modulus = b.loadInt(chooseUniform(from: [2, 5, 10, 25]))
let remainder = b.binary(iterationCount, modulus, with: .Mod)
cond = b.compare(remainder, with: b.loadInt(0), using: .equal)
}
// We hide the cond variable since it's probably not very useful for subsequent code to use it.
// The other variables (e.g. remainder) are maybe a bit more useful, so we leave them visible.
b.hide(cond)
// Now build the code, wrapped in an if block.
b.buildIf(cond) {
b.build(n: 5)
}
}
// Start with a random prefix and some random code.
b.buildPrefix()
b.build(n: smallCodeBlockSize)
// Generate the target function.
// Here we simply prepend the iteration count to randomly generated parameters.
// This way, the signature is still valid even if the last parameter is a rest parameter.
let baseParams = b.randomParameters().parameterTypes
let actualParams = [.integer] + baseParams
let f = b.buildPlainFunction(with: .parameters(actualParams)) { args in
// Generate a few "prefix" instructions
b.build(n: smallCodeBlockSize)
// Build code that will only be executed in some of the iterations.
buildCodeThatRunsInOnlySomeIterations(iterationCount: args[0])
// Build the main body.
b.build(n: 20)
b.doReturn(b.randomVariable())
}
// Generate some more random instructions.
b.build(n: smallCodeBlockSize)
// Call the function repeatedly to trigger JIT compilation, then perform additional steps in the final iteration. Do this 2 times to potentially trigger recompilation.
b.buildRepeatLoop(n: 2) {
b.buildRepeatLoop(n: numIterations) { i in
buildCodeThatRunsInOnlySomeIterations(iterationCount: i)
var args = [i] + b.randomArguments(forCallingFunctionWithParameters: baseParams)
b.callFunction(f, withArgs: args)
}
}
// Call the function again, this time with potentially different arguments.
b.buildRepeatLoop(n: numIterations) { i in
buildCodeThatRunsInOnlySomeIterations(iterationCount: i)
var args = [i] + b.randomArguments(forCallingFunctionWithParameters: baseParams)
b.callFunction(f, withArgs: args)
}
},
ProgramTemplate("JSONFuzzer") { b in
b.buildPrefix()
// Create some random values that will be JSON.stringified below.
b.build(n: 25)
// Generate random JSON payloads by stringifying random values
let JSON = b.loadBuiltin("JSON")
var jsonPayloads = [Variable]()
for _ in 0..<Int.random(in: 1...5) {
let json = b.callMethod("stringify", on: JSON, withArgs: [b.randomVariable()])
jsonPayloads.append(json)
}
// Optionally mutate (some of) the json string
let mutateJson = b.buildPlainFunction(with: .parameters(.string)) { args in
let json = args[0]
// Helper function to pick a random index in the json string.
let randIndex = b.buildPlainFunction(with: .parameters(.integer)) { args in
let max = args[0]
let Math = b.loadBuiltin("Math")
// We "hardcode" the random value here (instead of calling `Math.random()` in JS) so that testcases behave deterministically.
var random = b.loadFloat(Double.random(in: 0..<1))
random = b.binary(random, max, with: .Mul)
random = b.callMethod("floor", on: Math, withArgs: [random])
b.doReturn(random)
}
// Flip a random character of the JSON string:
// Select a random index at which to flip the character.
let String = b.loadBuiltin("String")
let length = b.getProperty("length", of: json)
let index = b.callFunction(randIndex, withArgs: [length])
// Save the substrings before and after the character that will be changed.
let zero = b.loadInt(0)
let prefix = b.callMethod("substring", on: json, withArgs: [zero, index])
let indexPlusOne = b.binary(index, b.loadInt(1), with: .Add)
let suffix = b.callMethod("substring", on: json, withArgs: [indexPlusOne])
// Extract the original char code, xor it with a random 7-bit number, then construct the new character value.
let originalCharCode = b.callMethod("charCodeAt", on: json, withArgs: [index])
let newCharCode = b.binary(originalCharCode, b.loadInt(Int64.random(in: 1..<128)), with: .Xor)
let newChar = b.callMethod("fromCharCode", on: String, withArgs: [newCharCode])
// And finally construct the mutated string.
let tmp = b.binary(prefix, newChar, with: .Add)
let newJson = b.binary(tmp, suffix, with: .Add)
b.doReturn(newJson)
}
for (i, json) in jsonPayloads.enumerated() {
// Performing (essentially binary) mutations on the JSON content will mostly end up fuzzing the JSON parser, not the JSON object
// building logic (which, in optimized JS engines, is likely much more complex). So perform these mutations somewhat rarely.
guard probability(0.25) else { continue }
jsonPayloads[i] = b.callFunction(mutateJson, withArgs: [json])
}
// Parse the JSON payloads back into JS objects.
// Instead of shuffling the jsonString array, we generate random indices so that there is a chance that the same string is parsed multiple times.
for _ in 0..<(jsonPayloads.count * 2) {
let json = chooseUniform(from: jsonPayloads)
// Parsing will throw if the input is invalid, so add guards
b.callMethod("parse", on: JSON, withArgs: [json], guard: true)
}
// Generate some more random code to (hopefully) use the parsed JSON in some interesting way.
b.build(n: 25)
},
]