as-test 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -1
- package/README.md +138 -406
- package/as-test.config.schema.json +210 -17
- package/assembly/__fuzz__/array.fuzz.ts +10 -0
- package/assembly/__fuzz__/bytes.fuzz.ts +8 -0
- package/assembly/__fuzz__/math.fuzz.ts +9 -0
- package/assembly/__fuzz__/string.fuzz.ts +21 -0
- package/assembly/index.ts +141 -86
- package/assembly/src/expectation.ts +104 -19
- package/assembly/src/fuzz.ts +723 -0
- package/assembly/src/log.ts +6 -1
- package/assembly/src/suite.ts +45 -3
- package/assembly/util/json.ts +38 -4
- package/assembly/util/wipc.ts +35 -26
- package/bin/build-worker-pool.js +149 -0
- package/bin/build-worker.js +43 -0
- package/bin/commands/build-core.js +221 -29
- package/bin/commands/build.js +1 -0
- package/bin/commands/fuzz-core.js +306 -0
- package/bin/commands/fuzz.js +10 -0
- package/bin/commands/init-core.js +129 -24
- package/bin/commands/run-core.js +525 -123
- package/bin/commands/run.js +4 -1
- package/bin/commands/test.js +8 -3
- package/bin/commands/web-runner-source.js +634 -0
- package/bin/crash-store.js +64 -0
- package/bin/index.js +1484 -169
- package/bin/reporters/default.js +281 -49
- package/bin/reporters/tap.js +83 -2
- package/bin/types.js +19 -2
- package/bin/util.js +315 -33
- package/bin/wipc.js +79 -0
- package/package.json +19 -9
- package/transform/lib/coverage.js +1 -2
- package/transform/lib/index.js +3 -3
- package/transform/lib/log.js +1 -1
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
import { quote } from "../util/json";
|
|
2
|
+
|
|
3
|
+
export class StringOptions {
|
|
4
|
+
charset: string = "ascii";
|
|
5
|
+
min: i32 = 0;
|
|
6
|
+
max: i32 = 32;
|
|
7
|
+
include: i32[] = [];
|
|
8
|
+
exclude: i32[] = [];
|
|
9
|
+
prefix: string = "";
|
|
10
|
+
suffix: string = "";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class IntegerOptions<T> {
|
|
14
|
+
min!: T;
|
|
15
|
+
max!: T;
|
|
16
|
+
exclude: T[] = [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class FloatOptions<T> {
|
|
20
|
+
min!: T;
|
|
21
|
+
max!: T;
|
|
22
|
+
exclude: T[] = [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class BytesOptions {
|
|
26
|
+
min: i32 = 0;
|
|
27
|
+
max: i32 = 32;
|
|
28
|
+
include: u8[] = [];
|
|
29
|
+
exclude: u8[] = [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class ArrayOptions {
|
|
33
|
+
min: i32 = 0;
|
|
34
|
+
max: i32 = 16;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class FuzzSeed {
|
|
38
|
+
constructor(private state: u64) {}
|
|
39
|
+
|
|
40
|
+
boolean(): bool {
|
|
41
|
+
return (this.nextU32() & 1) == 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pick<T>(values: T[]): T {
|
|
45
|
+
if (!values.length) panic();
|
|
46
|
+
return unchecked(values[this.nextRange(0, values.length - 1)]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
i32(options: IntegerOptions<i32> = new IntegerOptions<i32>()): i32 {
|
|
50
|
+
return this.nextI32InRange(options.min, options.max, options.exclude);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
u32(options: IntegerOptions<u32> = new IntegerOptions<u32>()): u32 {
|
|
54
|
+
return this.nextU32InRange(options.min, options.max, options.exclude);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
f32(options: FloatOptions<f32> = new FloatOptions<f32>()): f32 {
|
|
58
|
+
return <f32>(
|
|
59
|
+
this.nextF64InRange<f32>(options.min, options.max, options.exclude)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
f64(options: FloatOptions<f64> = new FloatOptions<f64>()): f64 {
|
|
64
|
+
return this.nextF64InRange<f64>(options.min, options.max, options.exclude);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
bytes(options: BytesOptions = new BytesOptions()): Uint8Array {
|
|
68
|
+
validateLengthRange("seed.bytes()", options.min, options.max);
|
|
69
|
+
const length = this.nextRange(options.min, options.max);
|
|
70
|
+
const out = new Uint8Array(length);
|
|
71
|
+
for (let i = 0; i < length; i++) {
|
|
72
|
+
out[i] = this.byteFromOptions(options);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
buffer(options: BytesOptions = new BytesOptions()): ArrayBuffer {
|
|
78
|
+
return this.bytes(options).buffer;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
string(options: StringOptions = new StringOptions()): string {
|
|
82
|
+
validateLengthRange("seed.string()", options.min, options.max);
|
|
83
|
+
const alphabet = buildAlphabet(options);
|
|
84
|
+
if (!alphabet.length) {
|
|
85
|
+
panic();
|
|
86
|
+
}
|
|
87
|
+
const length = this.nextRange(options.min, options.max);
|
|
88
|
+
let out = options.prefix;
|
|
89
|
+
for (let i = 0; i < length; i++) {
|
|
90
|
+
out += String.fromCharCode(this.pick(alphabet));
|
|
91
|
+
}
|
|
92
|
+
out += options.suffix;
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
array<T>(
|
|
97
|
+
item: (seed: FuzzSeed) => T,
|
|
98
|
+
options: ArrayOptions = new ArrayOptions(),
|
|
99
|
+
): Array<T> {
|
|
100
|
+
validateLengthRange("seed.array()", options.min, options.max);
|
|
101
|
+
const length = this.nextRange(options.min, options.max);
|
|
102
|
+
const out = new Array<T>(length);
|
|
103
|
+
for (let i = 0; i < length; i++) {
|
|
104
|
+
unchecked((out[i] = item(this)));
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private byteFromOptions(options: BytesOptions): u8 {
|
|
110
|
+
const include = options.include;
|
|
111
|
+
const exclude = options.exclude;
|
|
112
|
+
if (include.length) {
|
|
113
|
+
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
114
|
+
const picked = unchecked(
|
|
115
|
+
include[this.nextRange(0, include.length - 1)],
|
|
116
|
+
);
|
|
117
|
+
if (!exclude.includes(picked)) return picked;
|
|
118
|
+
}
|
|
119
|
+
panic();
|
|
120
|
+
}
|
|
121
|
+
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
122
|
+
const value = <u8>(this.nextU32() & 0xff);
|
|
123
|
+
if (!exclude.includes(value)) return value;
|
|
124
|
+
}
|
|
125
|
+
panic();
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private nextI32InRange(min: i32, max: i32, exclude: i32[]): i32 {
|
|
130
|
+
if (max < min) panic();
|
|
131
|
+
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
132
|
+
const value =
|
|
133
|
+
max <= min ? min : min + <i32>(this.nextU32() % <u32>(max - min + 1));
|
|
134
|
+
if (!containsValue<i32>(exclude, value)) return value;
|
|
135
|
+
}
|
|
136
|
+
panic();
|
|
137
|
+
return min;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private nextU32InRange(min: u32, max: u32, exclude: u32[]): u32 {
|
|
141
|
+
if (max < min) panic();
|
|
142
|
+
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
143
|
+
const value = max <= min ? min : min + (this.nextU32() % (max - min + 1));
|
|
144
|
+
if (!containsValue<u32>(exclude, value)) return value;
|
|
145
|
+
}
|
|
146
|
+
panic();
|
|
147
|
+
return min;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private nextF64InRange<T>(min: T, max: T, exclude: T[]): f64 {
|
|
151
|
+
const left = <f64>min;
|
|
152
|
+
const right = <f64>max;
|
|
153
|
+
if (right < left) panic();
|
|
154
|
+
for (let attempts = 0; attempts < 1024; attempts++) {
|
|
155
|
+
const value = left + (right - left) * this.nextUnit();
|
|
156
|
+
if (!containsFloatValue<T>(exclude, changetype<T>(value))) return value;
|
|
157
|
+
}
|
|
158
|
+
panic();
|
|
159
|
+
return left;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private nextRange(min: i32, max: i32): i32 {
|
|
163
|
+
if (max <= min) return min;
|
|
164
|
+
return min + <i32>(this.nextU32() % <u32>(max - min + 1));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private nextUnit(): f64 {
|
|
168
|
+
return <f64>this.nextU32() / <f64>u32.MAX_VALUE;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private nextU32(): u32 {
|
|
172
|
+
this.state += 0x9e3779b97f4a7c15;
|
|
173
|
+
let z = this.state;
|
|
174
|
+
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
|
|
175
|
+
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
|
|
176
|
+
return <u32>(z ^ (z >> 31));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private nextU64(): u64 {
|
|
180
|
+
const hi = <u64>this.nextU32();
|
|
181
|
+
const lo = <u64>this.nextU32();
|
|
182
|
+
return (hi << 32) | lo;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export abstract class FuzzerBase {
|
|
187
|
+
public name: string;
|
|
188
|
+
public skipped: bool;
|
|
189
|
+
constructor(name: string, skipped: bool = false) {
|
|
190
|
+
this.name = name;
|
|
191
|
+
this.skipped = skipped;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
generate<T extends Function>(_generator: T): this {
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
abstract run(seed: u64, runs: i32): FuzzerResult;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export class FuzzerResult {
|
|
202
|
+
public name: string = "";
|
|
203
|
+
public runs: i32 = 0;
|
|
204
|
+
public passed: i32 = 0;
|
|
205
|
+
public failed: i32 = 0;
|
|
206
|
+
public crashed: i32 = 0;
|
|
207
|
+
public skipped: i32 = 0;
|
|
208
|
+
public timeStart: f64 = 0;
|
|
209
|
+
public timeEnd: f64 = 0;
|
|
210
|
+
public failureInstr: string = "";
|
|
211
|
+
public failureLeft: string = "";
|
|
212
|
+
public failureRight: string = "";
|
|
213
|
+
public failureMessage: string = "";
|
|
214
|
+
|
|
215
|
+
serialize(): string {
|
|
216
|
+
return (
|
|
217
|
+
'{"name":"' +
|
|
218
|
+
this.name +
|
|
219
|
+
'","runs":' +
|
|
220
|
+
this.runs.toString() +
|
|
221
|
+
',"passed":' +
|
|
222
|
+
this.passed.toString() +
|
|
223
|
+
',"failed":' +
|
|
224
|
+
this.failed.toString() +
|
|
225
|
+
',"crashed":' +
|
|
226
|
+
this.crashed.toString() +
|
|
227
|
+
',"skipped":' +
|
|
228
|
+
this.skipped.toString() +
|
|
229
|
+
',"time":{"start":' +
|
|
230
|
+
this.timeStart.toString() +
|
|
231
|
+
',"end":' +
|
|
232
|
+
this.timeEnd.toString() +
|
|
233
|
+
'},"failure":{"instr":' +
|
|
234
|
+
quote(this.failureInstr) +
|
|
235
|
+
',"left":' +
|
|
236
|
+
quote(this.failureLeft) +
|
|
237
|
+
',"right":' +
|
|
238
|
+
quote(this.failureRight) +
|
|
239
|
+
',"message":' +
|
|
240
|
+
quote(this.failureMessage) +
|
|
241
|
+
"}}"
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let __fuzz_calls: i32 = 0;
|
|
247
|
+
let __fuzz_returns_bool: bool = false;
|
|
248
|
+
let __fuzz_callback0: (() => usize) | null = null;
|
|
249
|
+
let __fuzz_callback1: ((a: usize) => usize) | null = null;
|
|
250
|
+
let __fuzz_callback2: ((a: usize, b: usize) => usize) | null = null;
|
|
251
|
+
let __fuzz_callback3: ((a: usize, b: usize, c: usize) => usize) | null = null;
|
|
252
|
+
|
|
253
|
+
function __fuzz_run0(): usize {
|
|
254
|
+
__fuzz_calls++;
|
|
255
|
+
const callback = __fuzz_callback0;
|
|
256
|
+
if (callback == null) panic();
|
|
257
|
+
const result = callback();
|
|
258
|
+
if (__fuzz_returns_bool && result == 0) {
|
|
259
|
+
failFuzzIteration(
|
|
260
|
+
"return",
|
|
261
|
+
"false",
|
|
262
|
+
"true",
|
|
263
|
+
"fuzz callback returned false",
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function __fuzz_run1(a: usize): usize {
|
|
270
|
+
__fuzz_calls++;
|
|
271
|
+
const callback = __fuzz_callback1;
|
|
272
|
+
if (callback == null) panic();
|
|
273
|
+
const result = callback(a);
|
|
274
|
+
if (__fuzz_returns_bool && result == 0) {
|
|
275
|
+
failFuzzIteration(
|
|
276
|
+
"return",
|
|
277
|
+
"false",
|
|
278
|
+
"true",
|
|
279
|
+
"fuzz callback returned false",
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function __fuzz_run2(a: usize, b: usize): usize {
|
|
286
|
+
__fuzz_calls++;
|
|
287
|
+
const callback = __fuzz_callback2;
|
|
288
|
+
if (callback == null) panic();
|
|
289
|
+
const result = callback(a, b);
|
|
290
|
+
if (__fuzz_returns_bool && result == 0) {
|
|
291
|
+
failFuzzIteration(
|
|
292
|
+
"return",
|
|
293
|
+
"false",
|
|
294
|
+
"true",
|
|
295
|
+
"fuzz callback returned false",
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function __fuzz_run3(a: usize, b: usize, c: usize): usize {
|
|
302
|
+
__fuzz_calls++;
|
|
303
|
+
const callback = __fuzz_callback3;
|
|
304
|
+
if (callback == null) panic();
|
|
305
|
+
const result = callback(a, b, c);
|
|
306
|
+
if (__fuzz_returns_bool && result == 0) {
|
|
307
|
+
failFuzzIteration(
|
|
308
|
+
"return",
|
|
309
|
+
"false",
|
|
310
|
+
"true",
|
|
311
|
+
"fuzz callback returned false",
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function createResult(name: string, runs: i32): FuzzerResult {
|
|
318
|
+
const result = new FuzzerResult();
|
|
319
|
+
result.name = name;
|
|
320
|
+
result.runs = runs;
|
|
321
|
+
result.timeStart = performance.now();
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function createSkippedResult(name: string): FuzzerResult {
|
|
326
|
+
const result = createResult(name, 0);
|
|
327
|
+
result.skipped = 1;
|
|
328
|
+
result.timeEnd = result.timeStart;
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function recordResult(result: FuzzerResult): void {
|
|
333
|
+
if (__as_test_fuzz_failed) {
|
|
334
|
+
result.failed++;
|
|
335
|
+
if (!result.failureInstr.length && __as_test_fuzz_failure_instr.length) {
|
|
336
|
+
result.failureInstr = __as_test_fuzz_failure_instr;
|
|
337
|
+
result.failureLeft = __as_test_fuzz_failure_left;
|
|
338
|
+
result.failureRight = __as_test_fuzz_failure_right;
|
|
339
|
+
result.failureMessage = __as_test_fuzz_failure_message;
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
result.passed++;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export class Fuzzer0<R> extends FuzzerBase {
|
|
347
|
+
private generator: ((seed: FuzzSeed, run: () => R) => void) | null = null;
|
|
348
|
+
private returnsBool: bool;
|
|
349
|
+
|
|
350
|
+
constructor(
|
|
351
|
+
name: string,
|
|
352
|
+
private callback: () => R,
|
|
353
|
+
skipped: bool = false,
|
|
354
|
+
) {
|
|
355
|
+
super(name, skipped);
|
|
356
|
+
this.returnsBool = !isVoid<R>();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
generate<T extends Function>(generator: T): this {
|
|
360
|
+
this.generator =
|
|
361
|
+
changetype<(seed: FuzzSeed, run: () => R) => void>(generator);
|
|
362
|
+
return this;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
generateTyped(generator: (seed: FuzzSeed, run: () => R) => void): this {
|
|
366
|
+
this.generator = generator;
|
|
367
|
+
return this;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
run(seedBase: u64, runs: i32): FuzzerResult {
|
|
371
|
+
if (this.skipped) return createSkippedResult(this.name);
|
|
372
|
+
const result = createResult(this.name, runs);
|
|
373
|
+
__fuzz_callback0 = changetype<() => usize>(this.callback);
|
|
374
|
+
__fuzz_returns_bool = this.returnsBool;
|
|
375
|
+
for (let i = 0; i < runs; i++) {
|
|
376
|
+
prepareFuzzIteration();
|
|
377
|
+
__fuzz_calls = 0;
|
|
378
|
+
const seed = new FuzzSeed(seedBase + <u64>i);
|
|
379
|
+
if (this.generator) {
|
|
380
|
+
this.generator(seed, changetype<() => R>(__fuzz_run0));
|
|
381
|
+
} else {
|
|
382
|
+
__fuzz_run0();
|
|
383
|
+
}
|
|
384
|
+
if (__fuzz_calls != 1) {
|
|
385
|
+
failFuzzIteration(
|
|
386
|
+
"generator",
|
|
387
|
+
__fuzz_calls.toString(),
|
|
388
|
+
"1",
|
|
389
|
+
"fuzz generator must call run() exactly once",
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
recordResult(result);
|
|
393
|
+
}
|
|
394
|
+
__fuzz_callback0 = null;
|
|
395
|
+
result.timeEnd = performance.now();
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export class Fuzzer1<A, R> extends FuzzerBase {
|
|
401
|
+
private generator: ((seed: FuzzSeed, run: (a: A) => R) => void) | null = null;
|
|
402
|
+
private returnsBool: bool;
|
|
403
|
+
|
|
404
|
+
constructor(
|
|
405
|
+
name: string,
|
|
406
|
+
private callback: (a: A) => R,
|
|
407
|
+
skipped: bool = false,
|
|
408
|
+
) {
|
|
409
|
+
super(name, skipped);
|
|
410
|
+
this.returnsBool = !isVoid<R>();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
generate<T extends Function>(generator: T): this {
|
|
414
|
+
this.generator =
|
|
415
|
+
changetype<(seed: FuzzSeed, run: (a: A) => R) => void>(generator);
|
|
416
|
+
return this;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
generateTyped(generator: (seed: FuzzSeed, run: (a: A) => R) => void): this {
|
|
420
|
+
this.generator = generator;
|
|
421
|
+
return this;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
run(seedBase: u64, runs: i32): FuzzerResult {
|
|
425
|
+
if (this.skipped) return createSkippedResult(this.name);
|
|
426
|
+
const result = createResult(this.name, runs);
|
|
427
|
+
__fuzz_callback1 = changetype<(a: usize) => usize>(this.callback);
|
|
428
|
+
__fuzz_returns_bool = this.returnsBool;
|
|
429
|
+
for (let i = 0; i < runs; i++) {
|
|
430
|
+
prepareFuzzIteration();
|
|
431
|
+
__fuzz_calls = 0;
|
|
432
|
+
const seed = new FuzzSeed(seedBase + <u64>i);
|
|
433
|
+
if (!this.generator) {
|
|
434
|
+
failFuzzIteration(
|
|
435
|
+
"generate",
|
|
436
|
+
"missing",
|
|
437
|
+
"present",
|
|
438
|
+
"fuzzers with arguments must call .generate(...)",
|
|
439
|
+
);
|
|
440
|
+
} else {
|
|
441
|
+
this.generator(seed, changetype<(a: A) => R>(__fuzz_run1));
|
|
442
|
+
}
|
|
443
|
+
if (__fuzz_calls != 1) {
|
|
444
|
+
failFuzzIteration(
|
|
445
|
+
"generator",
|
|
446
|
+
__fuzz_calls.toString(),
|
|
447
|
+
"1",
|
|
448
|
+
"fuzz generator must call run() exactly once",
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
recordResult(result);
|
|
452
|
+
}
|
|
453
|
+
__fuzz_callback1 = null;
|
|
454
|
+
result.timeEnd = performance.now();
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export class Fuzzer2<A, B, R> extends FuzzerBase {
|
|
460
|
+
private generator: ((seed: FuzzSeed, run: (a: A, b: B) => R) => void) | null =
|
|
461
|
+
null;
|
|
462
|
+
private returnsBool: bool;
|
|
463
|
+
|
|
464
|
+
constructor(
|
|
465
|
+
name: string,
|
|
466
|
+
private callback: (a: A, b: B) => R,
|
|
467
|
+
skipped: bool = false,
|
|
468
|
+
) {
|
|
469
|
+
super(name, skipped);
|
|
470
|
+
this.returnsBool = !isVoid<R>();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
generate<T extends Function>(generator: T): this {
|
|
474
|
+
this.generator =
|
|
475
|
+
changetype<(seed: FuzzSeed, run: (a: A, b: B) => R) => void>(generator);
|
|
476
|
+
return this;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
generateTyped(
|
|
480
|
+
generator: (seed: FuzzSeed, run: (a: A, b: B) => R) => void,
|
|
481
|
+
): this {
|
|
482
|
+
this.generator = generator;
|
|
483
|
+
return this;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
run(seedBase: u64, runs: i32): FuzzerResult {
|
|
487
|
+
if (this.skipped) return createSkippedResult(this.name);
|
|
488
|
+
const result = createResult(this.name, runs);
|
|
489
|
+
__fuzz_callback2 = changetype<(a: usize, b: usize) => usize>(this.callback);
|
|
490
|
+
__fuzz_returns_bool = this.returnsBool;
|
|
491
|
+
for (let i = 0; i < runs; i++) {
|
|
492
|
+
prepareFuzzIteration();
|
|
493
|
+
__fuzz_calls = 0;
|
|
494
|
+
const seed = new FuzzSeed(seedBase + <u64>i);
|
|
495
|
+
if (!this.generator) {
|
|
496
|
+
failFuzzIteration(
|
|
497
|
+
"generate",
|
|
498
|
+
"missing",
|
|
499
|
+
"present",
|
|
500
|
+
"fuzzers with arguments must call .generate(...)",
|
|
501
|
+
);
|
|
502
|
+
} else {
|
|
503
|
+
this.generator(seed, changetype<(a: A, b: B) => R>(__fuzz_run2));
|
|
504
|
+
}
|
|
505
|
+
if (__fuzz_calls != 1) {
|
|
506
|
+
failFuzzIteration(
|
|
507
|
+
"generator",
|
|
508
|
+
__fuzz_calls.toString(),
|
|
509
|
+
"1",
|
|
510
|
+
"fuzz generator must call run() exactly once",
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
recordResult(result);
|
|
514
|
+
}
|
|
515
|
+
__fuzz_callback2 = null;
|
|
516
|
+
result.timeEnd = performance.now();
|
|
517
|
+
return result;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export class Fuzzer3<A, B, C, R> extends FuzzerBase {
|
|
522
|
+
private generator: usize = 0;
|
|
523
|
+
private returnsBool: bool;
|
|
524
|
+
|
|
525
|
+
constructor(
|
|
526
|
+
name: string,
|
|
527
|
+
private callback: (a: A, b: B, c: C) => R,
|
|
528
|
+
skipped: bool = false,
|
|
529
|
+
) {
|
|
530
|
+
super(name, skipped);
|
|
531
|
+
this.returnsBool = !isVoid<R>();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
generate<T extends Function>(generator: T): this {
|
|
535
|
+
this.generator = changetype<usize>(generator);
|
|
536
|
+
return this;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
generateTyped(
|
|
540
|
+
generator: (seed: FuzzSeed, run: (a: A, b: B, c: C) => R) => void,
|
|
541
|
+
): this {
|
|
542
|
+
this.generator = changetype<usize>(generator);
|
|
543
|
+
return this;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
run(seedBase: u64, runs: i32): FuzzerResult {
|
|
547
|
+
if (this.skipped) return createSkippedResult(this.name);
|
|
548
|
+
const result = createResult(this.name, runs);
|
|
549
|
+
__fuzz_callback3 = changetype<(a: usize, b: usize, c: usize) => usize>(
|
|
550
|
+
this.callback,
|
|
551
|
+
);
|
|
552
|
+
__fuzz_returns_bool = this.returnsBool;
|
|
553
|
+
for (let i = 0; i < runs; i++) {
|
|
554
|
+
prepareFuzzIteration();
|
|
555
|
+
__fuzz_calls = 0;
|
|
556
|
+
const seed = new FuzzSeed(seedBase + <u64>i);
|
|
557
|
+
if (!this.generator) {
|
|
558
|
+
failFuzzIteration(
|
|
559
|
+
"generate",
|
|
560
|
+
"missing",
|
|
561
|
+
"present",
|
|
562
|
+
"fuzzers with arguments must call .generate(...)",
|
|
563
|
+
);
|
|
564
|
+
} else {
|
|
565
|
+
changetype<(seed: FuzzSeed, run: (a: A, b: B, c: C) => R) => void>(
|
|
566
|
+
this.generator,
|
|
567
|
+
)(seed, changetype<(a: A, b: B, c: C) => R>(__fuzz_run3));
|
|
568
|
+
}
|
|
569
|
+
if (__fuzz_calls != 1) {
|
|
570
|
+
failFuzzIteration(
|
|
571
|
+
"generator",
|
|
572
|
+
__fuzz_calls.toString(),
|
|
573
|
+
"1",
|
|
574
|
+
"fuzz generator must call run() exactly once",
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
recordResult(result);
|
|
578
|
+
}
|
|
579
|
+
__fuzz_callback3 = null;
|
|
580
|
+
result.timeEnd = performance.now();
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export function createFuzzer<T extends Function>(
|
|
586
|
+
name: string,
|
|
587
|
+
callback: T,
|
|
588
|
+
skipped: bool = false,
|
|
589
|
+
): FuzzerBase {
|
|
590
|
+
const length = callback.length;
|
|
591
|
+
if (length == 0) {
|
|
592
|
+
return new Fuzzer0<usize>(name, changetype<() => usize>(callback), skipped);
|
|
593
|
+
}
|
|
594
|
+
if (length == 1) {
|
|
595
|
+
return new Fuzzer1<usize, usize>(
|
|
596
|
+
name,
|
|
597
|
+
changetype<(a: usize) => usize>(callback),
|
|
598
|
+
skipped,
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
if (length == 2) {
|
|
602
|
+
return new Fuzzer2<usize, usize, usize>(
|
|
603
|
+
name,
|
|
604
|
+
changetype<(a: usize, b: usize) => usize>(callback),
|
|
605
|
+
skipped,
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (length == 3) {
|
|
609
|
+
return new Fuzzer3<usize, usize, usize, usize>(
|
|
610
|
+
name,
|
|
611
|
+
changetype<(a: usize, b: usize, c: usize) => usize>(callback),
|
|
612
|
+
skipped,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
panic();
|
|
616
|
+
return new Fuzzer0<usize>(name, changetype<() => usize>(callback), skipped);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function buildAlphabet(options: StringOptions): i32[] {
|
|
620
|
+
const out = baseAlphabet(options.charset);
|
|
621
|
+
if (options.charset == "custom") {
|
|
622
|
+
out.length = 0;
|
|
623
|
+
}
|
|
624
|
+
for (let i = 0; i < options.include.length; i++) {
|
|
625
|
+
const value = unchecked(options.include[i]);
|
|
626
|
+
if (!out.includes(value)) out.push(value);
|
|
627
|
+
}
|
|
628
|
+
for (let i = 0; i < options.exclude.length; i++) {
|
|
629
|
+
removeFirst(out, unchecked(options.exclude[i]));
|
|
630
|
+
}
|
|
631
|
+
return out;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function baseAlphabet(charset: string): i32[] {
|
|
635
|
+
if (charset == "alpha") return rangeChars(65, 90).concat(rangeChars(97, 122));
|
|
636
|
+
if (charset == "alnum")
|
|
637
|
+
return baseAlphabet("alpha").concat(rangeChars(48, 57));
|
|
638
|
+
if (charset == "digit") return rangeChars(48, 57);
|
|
639
|
+
if (charset == "hex") return rangeChars(48, 57).concat(rangeChars(97, 102));
|
|
640
|
+
if (charset == "base64")
|
|
641
|
+
return rangeChars(65, 90)
|
|
642
|
+
.concat(rangeChars(97, 122))
|
|
643
|
+
.concat(rangeChars(48, 57))
|
|
644
|
+
.concat([43, 47, 61]);
|
|
645
|
+
if (charset == "identifier")
|
|
646
|
+
return [95].concat(baseAlphabet("alpha")).concat(rangeChars(48, 57));
|
|
647
|
+
if (charset == "whitespace") return [9, 10, 13, 32];
|
|
648
|
+
if (charset == "custom") return [];
|
|
649
|
+
return rangeChars(32, 126);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function rangeChars(start: i32, end: i32): i32[] {
|
|
653
|
+
const out = new Array<i32>();
|
|
654
|
+
for (let value = start; value <= end; value++) {
|
|
655
|
+
out.push(value);
|
|
656
|
+
}
|
|
657
|
+
return out;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function removeFirst(values: i32[], needle: i32): void {
|
|
661
|
+
const index = values.indexOf(needle);
|
|
662
|
+
if (index >= 0) values.splice(index, 1);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function validateLengthRange(label: string, min: i32, max: i32): void {
|
|
666
|
+
if (min < 0 || max < 0) panic();
|
|
667
|
+
if (max < min) panic();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function containsValue<T>(values: T[], needle: T): bool {
|
|
671
|
+
for (let i = 0; i < values.length; i++) {
|
|
672
|
+
if (unchecked(values[i]) == needle) return true;
|
|
673
|
+
}
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function containsFloatValue<T>(values: T[], needle: T): bool {
|
|
678
|
+
const value = <f64>needle;
|
|
679
|
+
for (let i = 0; i < values.length; i++) {
|
|
680
|
+
const candidate = <f64>unchecked(values[i]);
|
|
681
|
+
if (isNaN(value) && isNaN(candidate)) return true;
|
|
682
|
+
if (candidate == value) return true;
|
|
683
|
+
}
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// @ts-ignore
|
|
688
|
+
@global export let __as_test_fuzz_failed: bool = false;
|
|
689
|
+
// @ts-ignore
|
|
690
|
+
@global export let __as_test_fuzz_failure_instr: string = "";
|
|
691
|
+
// @ts-ignore
|
|
692
|
+
@global export let __as_test_fuzz_failure_left: string = "";
|
|
693
|
+
// @ts-ignore
|
|
694
|
+
@global export let __as_test_fuzz_failure_right: string = "";
|
|
695
|
+
// @ts-ignore
|
|
696
|
+
@global export let __as_test_fuzz_failure_message: string = "";
|
|
697
|
+
|
|
698
|
+
export function prepareFuzzIteration(): void {
|
|
699
|
+
__as_test_fuzz_failed = false;
|
|
700
|
+
__as_test_fuzz_failure_instr = "";
|
|
701
|
+
__as_test_fuzz_failure_left = "";
|
|
702
|
+
__as_test_fuzz_failure_right = "";
|
|
703
|
+
__as_test_fuzz_failure_message = "";
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
export function failFuzzIteration(
|
|
707
|
+
instr: string,
|
|
708
|
+
left: string,
|
|
709
|
+
right: string,
|
|
710
|
+
message: string,
|
|
711
|
+
): void {
|
|
712
|
+
__as_test_fuzz_failed = true;
|
|
713
|
+
if (!__as_test_fuzz_failure_instr.length) {
|
|
714
|
+
__as_test_fuzz_failure_instr = instr;
|
|
715
|
+
__as_test_fuzz_failure_left = left;
|
|
716
|
+
__as_test_fuzz_failure_right = right;
|
|
717
|
+
__as_test_fuzz_failure_message = message;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function panic(): void {
|
|
722
|
+
unreachable();
|
|
723
|
+
}
|