as-test 1.0.11 → 1.0.13

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 CHANGED
@@ -1,6 +1,14 @@
1
1
  # Change Log
2
2
 
3
- ## Unreleased
3
+ ## 2025-05-03 - v1.0.13
4
+
5
+ - feat: add `--fuzzer` / `--fuzzers` filtering for `ast fuzz` and `ast test --fuzz`, accept `--suite` / `--suites` as fuzz aliases, and include target-specific repro commands in fuzz failure output.
6
+ - feat: add `--suite` / `--suites` filtering for `ast run` and `ast test`, and print suite-specific repro commands on failing test assertions.
7
+
8
+ ## 2026-04-28 - v1.0.12
9
+
10
+ - perf: faster seed generation
11
+ - feat: make fuzz campaigns use a random base seed by default when `fuzz.seed` and CLI seed overrides are not set, while keeping deterministic replay via `--seed` / `--fuzz-seed`.
4
12
 
5
13
  ## 2026-04-18 - v1.0.11
6
14
 
package/README.md CHANGED
@@ -134,6 +134,13 @@ Run one matching file:
134
134
  npx ast test math
135
135
  ```
136
136
 
137
+ Re-run one suite inside a matching file:
138
+
139
+ ```bash
140
+ npx ast run math --suite array-check
141
+ npx ast run math --suite array-manipulation/array-check
142
+ ```
143
+
137
144
  You do not need to learn every CLI flag to get started. Most projects can begin with `npx ast test`, then add more configuration only when they need it.
138
145
 
139
146
  ## Mocking
@@ -276,6 +283,8 @@ If you used `npx ast init` with a fuzzer example, the config is already there. O
276
283
 
277
284
  `ast fuzz` runs fuzz files across the selected modes, reports one result per file, and keeps the final summary separate from the normal test totals. If you want one combined command, use `ast test --fuzz`.
278
285
 
286
+ By default, each fuzz run campaign picks a new random base seed. Pin a seed with `--seed <n>` (or `--fuzz-seed <n>` on `ast test`) when you want deterministic replay.
287
+
279
288
  When a fuzzer fails, `as-test` now prints the exact failing seeds and one-run repro commands such as `ast fuzz ... --seed <seed+n> --runs 1`. Crash records in `.as-test/crashes` also include the captured inputs passed to `run(...)`, which helps when the generator itself has side effects.
280
289
 
281
290
  Run only fuzzers:
@@ -284,6 +293,12 @@ Run only fuzzers:
284
293
  npx ast fuzz
285
294
  ```
286
295
 
296
+ Run one matching fuzz target:
297
+
298
+ ```bash
299
+ npx ast fuzz string --fuzzer ascii-strings-survive-concatenation-boundaries
300
+ ```
301
+
287
302
  Run tests and fuzzers together:
288
303
 
289
304
  ```bash
@@ -259,8 +259,7 @@
259
259
  },
260
260
  "seed": {
261
261
  "type": "number",
262
- "description": "Base deterministic seed for input mutation.",
263
- "default": 1337
262
+ "description": "Optional base seed for input mutation. If omitted, a random seed is generated each run."
264
263
  },
265
264
  "maxInputBytes": {
266
265
  "type": "number",
@@ -287,7 +286,6 @@
287
286
  "default": {
288
287
  "input": ["./assembly/__fuzz__/*.fuzz.ts"],
289
288
  "runs": 1000,
290
- "seed": 1337,
291
289
  "maxInputBytes": 4096,
292
290
  "target": "bindings",
293
291
  "corpusDir": "./.as-test/fuzz/corpus",
@@ -0,0 +1,68 @@
1
+ import {
2
+ fuzz,
3
+ FuzzSeed,
4
+ IntegerOptions,
5
+ FloatOptions,
6
+ StringOptions,
7
+ BytesOptions,
8
+ ArrayOptions,
9
+ } from "as-test";
10
+
11
+ const PERF_I32 = new IntegerOptions<i32>();
12
+ PERF_I32.min = -100000;
13
+ PERF_I32.max = 100000;
14
+ const PERF_U32 = new IntegerOptions<u32>();
15
+ PERF_U32.min = 0;
16
+ PERF_U32.max = 1000000;
17
+ const PERF_F64 = new FloatOptions<f64>();
18
+ PERF_F64.min = -1.0;
19
+ PERF_F64.max = 1.0;
20
+ const PERF_ASCII = new StringOptions();
21
+ PERF_ASCII.charset = "ascii";
22
+ PERF_ASCII.min = 4;
23
+ PERF_ASCII.max = 24;
24
+ const PERF_BYTES = new BytesOptions();
25
+ PERF_BYTES.min = 8;
26
+ PERF_BYTES.max = 32;
27
+ const PERF_ARRAY = new ArrayOptions();
28
+ PERF_ARRAY.min = 4;
29
+ PERF_ARRAY.max = 16;
30
+
31
+ fuzz("seed perf i32/u32/f64", (_n: i32): bool => true).generate(
32
+ (seed: FuzzSeed, run: (n: i32) => bool): void => {
33
+ let accI: i32 = 0;
34
+ let accU: u32 = 0;
35
+ let accF: f64 = 0.0;
36
+ for (let i = 0; i < 128; i++) {
37
+ accI += seed.i32(PERF_I32);
38
+ accU ^= seed.u32(PERF_U32);
39
+ accF += seed.f64(PERF_F64);
40
+ }
41
+ run(accI + <i32>accU + <i32>accF);
42
+ },
43
+ 20000,
44
+ );
45
+
46
+ fuzz("seed perf strings", (_n: i32): bool => true).generate(
47
+ (seed: FuzzSeed, run: (n: i32) => bool): void => {
48
+ let total = 0;
49
+ for (let i = 0; i < 96; i++) {
50
+ total += seed.string(PERF_ASCII).length;
51
+ }
52
+ run(total);
53
+ },
54
+ 20000,
55
+ );
56
+
57
+ fuzz("seed perf bytes/array", (_n: i32): bool => true).generate(
58
+ (seed: FuzzSeed, run: (n: i32) => bool): void => {
59
+ let score = 0;
60
+ for (let i = 0; i < 64; i++) {
61
+ score += seed.bytes(PERF_BYTES).length;
62
+ score += seed.array<i32>((s) => s.i32({ min: -9, max: 9 }), PERF_ARRAY)
63
+ .length;
64
+ }
65
+ run(score);
66
+ },
67
+ 20000,
68
+ );
@@ -34,8 +34,28 @@ export class ArrayOptions {
34
34
  max: i32 = 16;
35
35
  }
36
36
 
37
+ const DEFAULT_I32_OPTIONS = new IntegerOptions<i32>();
38
+ const DEFAULT_U32_OPTIONS = new IntegerOptions<u32>();
39
+ const DEFAULT_F32_OPTIONS = new FloatOptions<f32>();
40
+ const DEFAULT_F64_OPTIONS = new FloatOptions<f64>();
41
+ const DEFAULT_STRING_OPTIONS = new StringOptions();
42
+ const DEFAULT_BYTES_OPTIONS = new BytesOptions();
43
+ const DEFAULT_ARRAY_OPTIONS = new ArrayOptions();
44
+
37
45
  export class FuzzSeed {
38
- constructor(private state: u64) {}
46
+ private state: u32;
47
+
48
+ constructor(seed: u64) {
49
+ this.reseed(seed);
50
+ }
51
+
52
+ reseed(seed: u64): void {
53
+ const lo = <u32>seed;
54
+ const hi = <u32>(seed >> 32);
55
+ let mixed = lo ^ (hi * 0x9e3779b9) ^ 0xa341316c;
56
+ if (mixed == 0) mixed = 0x6d2b79f5;
57
+ this.state = mixed;
58
+ }
39
59
 
40
60
  boolean(): bool {
41
61
  return (this.nextU32() & 1) == 1;
@@ -46,59 +66,108 @@ export class FuzzSeed {
46
66
  return unchecked(values[this.nextRange(0, values.length - 1)]);
47
67
  }
48
68
 
49
- i32(options: IntegerOptions<i32> = new IntegerOptions<i32>()): i32 {
50
- return this.nextI32InRange(options.min, options.max, options.exclude);
69
+ i32(options: IntegerOptions<i32> | null = null): i32 {
70
+ const config = options != null ? options : DEFAULT_I32_OPTIONS;
71
+ return this.nextI32InRange(config.min, config.max, config.exclude);
51
72
  }
52
73
 
53
- u32(options: IntegerOptions<u32> = new IntegerOptions<u32>()): u32 {
54
- return this.nextU32InRange(options.min, options.max, options.exclude);
74
+ u32(options: IntegerOptions<u32> | null = null): u32 {
75
+ const config = options != null ? options : DEFAULT_U32_OPTIONS;
76
+ return this.nextU32InRange(config.min, config.max, config.exclude);
55
77
  }
56
78
 
57
- f32(options: FloatOptions<f32> = new FloatOptions<f32>()): f32 {
79
+ f32(options: FloatOptions<f32> | null = null): f32 {
80
+ const config = options != null ? options : DEFAULT_F32_OPTIONS;
58
81
  return <f32>(
59
- this.nextF64InRange<f32>(options.min, options.max, options.exclude)
82
+ this.nextF64InRange<f32>(config.min, config.max, config.exclude)
60
83
  );
61
84
  }
62
85
 
63
- f64(options: FloatOptions<f64> = new FloatOptions<f64>()): f64 {
64
- return this.nextF64InRange<f64>(options.min, options.max, options.exclude);
86
+ f64(options: FloatOptions<f64> | null = null): f64 {
87
+ const config = options != null ? options : DEFAULT_F64_OPTIONS;
88
+ return this.nextF64InRange<f64>(config.min, config.max, config.exclude);
65
89
  }
66
90
 
67
- bytes(options: BytesOptions = new BytesOptions()): Uint8Array {
68
- validateLengthRange("seed.bytes()", options.min, options.max);
69
- const length = this.nextRange(options.min, options.max);
91
+ bytes(options: BytesOptions | null = null): Uint8Array {
92
+ const config = options != null ? options : DEFAULT_BYTES_OPTIONS;
93
+ validateLengthRange("seed.bytes()", config.min, config.max);
94
+ const length = this.nextRange(config.min, config.max);
70
95
  const out = new Uint8Array(length);
96
+ const include = config.include;
97
+ const exclude = config.exclude;
98
+ if (include.length) {
99
+ if (!exclude.length) {
100
+ for (let i = 0; i < length; i++) {
101
+ unchecked(
102
+ (out[i] = <u8>unchecked(include[this.nextRange(0, include.length - 1)])),
103
+ );
104
+ }
105
+ return out;
106
+ }
107
+ for (let i = 0; i < length; i++) {
108
+ unchecked((out[i] = this.byteFromOptions(config)));
109
+ }
110
+ return out;
111
+ }
112
+ if (!exclude.length) {
113
+ for (let i = 0; i < length; i++) {
114
+ unchecked((out[i] = <u8>(this.nextU32() & 0xff)));
115
+ }
116
+ return out;
117
+ }
71
118
  for (let i = 0; i < length; i++) {
72
- out[i] = this.byteFromOptions(options);
119
+ unchecked((out[i] = this.byteFromOptions(config)));
73
120
  }
74
121
  return out;
75
122
  }
76
123
 
77
- buffer(options: BytesOptions = new BytesOptions()): ArrayBuffer {
124
+ buffer(options: BytesOptions | null = null): ArrayBuffer {
78
125
  return this.bytes(options).buffer;
79
126
  }
80
127
 
81
- string(options: StringOptions = new StringOptions()): string {
82
- validateLengthRange("seed.string()", options.min, options.max);
83
- const alphabet = buildAlphabet(options);
128
+ string(options: StringOptions | null = null): string {
129
+ const config = options != null ? options : DEFAULT_STRING_OPTIONS;
130
+ validateLengthRange("seed.string()", config.min, config.max);
131
+ const alphabet = buildAlphabet(config);
84
132
  if (!alphabet.length) {
85
133
  panic();
86
134
  }
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));
135
+ const coreLength = this.nextRange(config.min, config.max);
136
+ const prefixLength = config.prefix.length;
137
+ const suffixLength = config.suffix.length;
138
+ const totalLength = prefixLength + coreLength + suffixLength;
139
+ if (!totalLength) return "";
140
+
141
+ // Allocate UTF-16 payload directly and fill code units in one pass.
142
+ const outPtr = __new(<usize>(totalLength << 1), idof<string>());
143
+ let cursor: usize = outPtr;
144
+
145
+ for (let i = 0; i < prefixLength; i++) {
146
+ store<u16>(cursor, <u16>config.prefix.charCodeAt(i));
147
+ cursor += 2;
91
148
  }
92
- out += options.suffix;
93
- return out;
149
+
150
+ const last = alphabet.length - 1;
151
+ for (let i = 0; i < coreLength; i++) {
152
+ store<u16>(cursor, <u16>unchecked(alphabet[this.nextRange(0, last)]));
153
+ cursor += 2;
154
+ }
155
+
156
+ for (let i = 0; i < suffixLength; i++) {
157
+ store<u16>(cursor, <u16>config.suffix.charCodeAt(i));
158
+ cursor += 2;
159
+ }
160
+
161
+ return changetype<string>(outPtr);
94
162
  }
95
163
 
96
164
  array<T>(
97
165
  item: (seed: FuzzSeed) => T,
98
- options: ArrayOptions = new ArrayOptions(),
166
+ options: ArrayOptions | null = null,
99
167
  ): Array<T> {
100
- validateLengthRange("seed.array()", options.min, options.max);
101
- const length = this.nextRange(options.min, options.max);
168
+ const config = options != null ? options : DEFAULT_ARRAY_OPTIONS;
169
+ validateLengthRange("seed.array()", config.min, config.max);
170
+ const length = this.nextRange(config.min, config.max);
102
171
  const out = new Array<T>(length);
103
172
  for (let i = 0; i < length; i++) {
104
173
  unchecked((out[i] = item(this)));
@@ -110,17 +179,23 @@ export class FuzzSeed {
110
179
  const include = options.include;
111
180
  const exclude = options.exclude;
112
181
  if (include.length) {
182
+ if (!exclude.length) {
183
+ return <u8>unchecked(include[this.nextRange(0, include.length - 1)]);
184
+ }
113
185
  for (let attempts = 0; attempts < 1024; attempts++) {
114
186
  const picked = unchecked(
115
187
  include[this.nextRange(0, include.length - 1)],
116
188
  );
117
- if (!exclude.includes(picked)) return picked;
189
+ if (!containsValue<u8>(exclude, picked)) return picked;
118
190
  }
119
191
  panic();
120
192
  }
193
+ if (!exclude.length) {
194
+ return <u8>(this.nextU32() & 0xff);
195
+ }
121
196
  for (let attempts = 0; attempts < 1024; attempts++) {
122
197
  const value = <u8>(this.nextU32() & 0xff);
123
- if (!exclude.includes(value)) return value;
198
+ if (!containsValue<u8>(exclude, value)) return value;
124
199
  }
125
200
  panic();
126
201
  return 0;
@@ -128,6 +203,9 @@ export class FuzzSeed {
128
203
 
129
204
  private nextI32InRange(min: i32, max: i32, exclude: i32[]): i32 {
130
205
  if (max < min) panic();
206
+ if (!exclude.length) {
207
+ return max <= min ? min : min + <i32>(this.nextU32() % <u32>(max - min + 1));
208
+ }
131
209
  for (let attempts = 0; attempts < 1024; attempts++) {
132
210
  const value =
133
211
  max <= min ? min : min + <i32>(this.nextU32() % <u32>(max - min + 1));
@@ -139,6 +217,9 @@ export class FuzzSeed {
139
217
 
140
218
  private nextU32InRange(min: u32, max: u32, exclude: u32[]): u32 {
141
219
  if (max < min) panic();
220
+ if (!exclude.length) {
221
+ return max <= min ? min : min + (this.nextU32() % (max - min + 1));
222
+ }
142
223
  for (let attempts = 0; attempts < 1024; attempts++) {
143
224
  const value = max <= min ? min : min + (this.nextU32() % (max - min + 1));
144
225
  if (!containsValue<u32>(exclude, value)) return value;
@@ -151,6 +232,9 @@ export class FuzzSeed {
151
232
  const left = <f64>min;
152
233
  const right = <f64>max;
153
234
  if (right < left) panic();
235
+ if (!exclude.length) {
236
+ return left + (right - left) * this.nextUnit();
237
+ }
154
238
  for (let attempts = 0; attempts < 1024; attempts++) {
155
239
  const value = left + (right - left) * this.nextUnit();
156
240
  if (!containsFloatValue<T>(exclude, changetype<T>(value))) return value;
@@ -169,11 +253,14 @@ export class FuzzSeed {
169
253
  }
170
254
 
171
255
  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));
256
+ // mulberry32: very fast integer-only PRNG suitable for fuzz input generation.
257
+ let x = this.state;
258
+ x += 0x6d2b79f5;
259
+ this.state = x;
260
+ let z = x;
261
+ z = <u32>Math.imul(z ^ (z >> 15), z | 1);
262
+ z ^= z + <u32>Math.imul(z ^ (z >> 7), z | 61);
263
+ return z ^ (z >> 14);
177
264
  }
178
265
 
179
266
  private nextU64(): u64 {
@@ -183,6 +270,21 @@ export class FuzzSeed {
183
270
  }
184
271
  }
185
272
 
273
+ const ASCII_ALPHABET: i32[] = rangeChars(32, 126);
274
+ const ALPHA_ALPHABET: i32[] = rangeChars(65, 90).concat(rangeChars(97, 122));
275
+ const DIGIT_ALPHABET: i32[] = rangeChars(48, 57);
276
+ const HEX_ALPHABET: i32[] = DIGIT_ALPHABET.concat(rangeChars(97, 102));
277
+ const ALNUM_ALPHABET: i32[] = ALPHA_ALPHABET.concat(DIGIT_ALPHABET);
278
+ const BASE64_ALPHABET: i32[] = ALPHA_ALPHABET.concat(DIGIT_ALPHABET).concat([
279
+ 43,
280
+ 47,
281
+ 61,
282
+ ]);
283
+ const IDENTIFIER_ALPHABET: i32[] = [95].concat(ALPHA_ALPHABET).concat(
284
+ DIGIT_ALPHABET,
285
+ );
286
+ const WHITESPACE_ALPHABET: i32[] = [9, 10, 13, 32];
287
+
186
288
  export abstract class FuzzerBase {
187
289
  public name: string;
188
290
  public skipped: bool;
@@ -409,12 +511,13 @@ export class Fuzzer0<R> extends FuzzerBase {
409
511
  run(seedBase: u64, runs: i32): FuzzerResult {
410
512
  if (this.skipped) return createSkippedResult(this.name);
411
513
  const result = createResult(this.name, runs);
514
+ const seed = new FuzzSeed(seedBase);
412
515
  __fuzz_callback0 = changetype<() => usize>(this.callback);
413
516
  __fuzz_returns_bool = this.returnsBool;
414
517
  for (let i = 0; i < runs; i++) {
415
518
  prepareFuzzIteration();
416
519
  __fuzz_calls = 0;
417
- const seed = new FuzzSeed(seedBase + <u64>i);
520
+ seed.reseed(seedBase + <u64>i);
418
521
  if (this.generator) {
419
522
  this.generator(seed, changetype<() => R>(__fuzz_run0));
420
523
  } else {
@@ -469,12 +572,13 @@ export class Fuzzer1<A, R> extends FuzzerBase {
469
572
  run(seedBase: u64, runs: i32): FuzzerResult {
470
573
  if (this.skipped) return createSkippedResult(this.name);
471
574
  const result = createResult(this.name, runs);
575
+ const seed = new FuzzSeed(seedBase);
472
576
  __fuzz_callback1 = changetype<(a: usize) => usize>(this.callback);
473
577
  __fuzz_returns_bool = this.returnsBool;
474
578
  for (let i = 0; i < runs; i++) {
475
579
  prepareFuzzIteration();
476
580
  __fuzz_calls = 0;
477
- const seed = new FuzzSeed(seedBase + <u64>i);
581
+ seed.reseed(seedBase + <u64>i);
478
582
  if (!this.generator) {
479
583
  failFuzzIteration(
480
584
  "generate",
@@ -538,12 +642,13 @@ export class Fuzzer2<A, B, R> extends FuzzerBase {
538
642
  run(seedBase: u64, runs: i32): FuzzerResult {
539
643
  if (this.skipped) return createSkippedResult(this.name);
540
644
  const result = createResult(this.name, runs);
645
+ const seed = new FuzzSeed(seedBase);
541
646
  __fuzz_callback2 = changetype<(a: usize, b: usize) => usize>(this.callback);
542
647
  __fuzz_returns_bool = this.returnsBool;
543
648
  for (let i = 0; i < runs; i++) {
544
649
  prepareFuzzIteration();
545
650
  __fuzz_calls = 0;
546
- const seed = new FuzzSeed(seedBase + <u64>i);
651
+ seed.reseed(seedBase + <u64>i);
547
652
  if (!this.generator) {
548
653
  failFuzzIteration(
549
654
  "generate",
@@ -606,6 +711,7 @@ export class Fuzzer3<A, B, C, R> extends FuzzerBase {
606
711
  run(seedBase: u64, runs: i32): FuzzerResult {
607
712
  if (this.skipped) return createSkippedResult(this.name);
608
713
  const result = createResult(this.name, runs);
714
+ const seed = new FuzzSeed(seedBase);
609
715
  __fuzz_callback3 = changetype<(a: usize, b: usize, c: usize) => usize>(
610
716
  this.callback,
611
717
  );
@@ -613,7 +719,7 @@ export class Fuzzer3<A, B, C, R> extends FuzzerBase {
613
719
  for (let i = 0; i < runs; i++) {
614
720
  prepareFuzzIteration();
615
721
  __fuzz_calls = 0;
616
- const seed = new FuzzSeed(seedBase + <u64>i);
722
+ seed.reseed(seedBase + <u64>i);
617
723
  if (!this.generator) {
618
724
  failFuzzIteration(
619
725
  "generate",
@@ -701,13 +807,13 @@ export function createFuzzer<T extends Function>(
701
807
  }
702
808
 
703
809
  function buildAlphabet(options: StringOptions): i32[] {
704
- const out = baseAlphabet(options.charset);
705
- if (options.charset == "custom") {
706
- out.length = 0;
810
+ if (!options.include.length && !options.exclude.length) {
811
+ return baseAlphabet(options.charset);
707
812
  }
813
+ const out = baseAlphabet(options.charset).slice(0);
708
814
  for (let i = 0; i < options.include.length; i++) {
709
815
  const value = unchecked(options.include[i]);
710
- if (!out.includes(value)) out.push(value);
816
+ if (!containsValue<i32>(out, value)) out.push(value);
711
817
  }
712
818
  for (let i = 0; i < options.exclude.length; i++) {
713
819
  removeFirst(out, unchecked(options.exclude[i]));
@@ -716,21 +822,15 @@ function buildAlphabet(options: StringOptions): i32[] {
716
822
  }
717
823
 
718
824
  function baseAlphabet(charset: string): i32[] {
719
- if (charset == "alpha") return rangeChars(65, 90).concat(rangeChars(97, 122));
720
- if (charset == "alnum")
721
- return baseAlphabet("alpha").concat(rangeChars(48, 57));
722
- if (charset == "digit") return rangeChars(48, 57);
723
- if (charset == "hex") return rangeChars(48, 57).concat(rangeChars(97, 102));
724
- if (charset == "base64")
725
- return rangeChars(65, 90)
726
- .concat(rangeChars(97, 122))
727
- .concat(rangeChars(48, 57))
728
- .concat([43, 47, 61]);
729
- if (charset == "identifier")
730
- return [95].concat(baseAlphabet("alpha")).concat(rangeChars(48, 57));
731
- if (charset == "whitespace") return [9, 10, 13, 32];
825
+ if (charset == "alpha") return ALPHA_ALPHABET;
826
+ if (charset == "alnum") return ALNUM_ALPHABET;
827
+ if (charset == "digit") return DIGIT_ALPHABET;
828
+ if (charset == "hex") return HEX_ALPHABET;
829
+ if (charset == "base64") return BASE64_ALPHABET;
830
+ if (charset == "identifier") return IDENTIFIER_ALPHABET;
831
+ if (charset == "whitespace") return WHITESPACE_ALPHABET;
732
832
  if (charset == "custom") return [];
733
- return rangeChars(32, 126);
833
+ return ASCII_ALPHABET;
734
834
  }
735
835
 
736
836
  function rangeChars(start: i32, end: i32): i32[] {
@@ -746,6 +846,7 @@ function removeFirst(values: i32[], needle: i32): void {
746
846
  if (index >= 0) values.splice(index, 1);
747
847
  }
748
848
 
849
+
749
850
  function validateLengthRange(label: string, min: i32, max: i32): void {
750
851
  if (min < 0 || max < 0) panic();
751
852
  if (max < min) panic();
@@ -33,6 +33,7 @@ const MAGIC_C: u8 = 0x43; // C
33
33
  const HEADER_SIZE: i32 = 9;
34
34
  const IOV_SIZE: usize = sizeof<usize>() * 2;
35
35
  const U32_SIZE: usize = sizeof<u32>();
36
+ const REPORT_CHUNK_BYTES: i32 = 65536;
36
37
 
37
38
  // @ts-ignore
38
39
  const IS_BINDINGS: bool = isDefined(AS_TEST_BINDINGS);
@@ -162,7 +163,35 @@ export function requestFuzzConfig(): FuzzConfigReply {
162
163
  }
163
164
 
164
165
  export function sendReport(report: string): void {
165
- sendFrame(MessageType.DATA, String.UTF8.encode(report));
166
+ const payload = String.UTF8.encode(report);
167
+ if (payload.byteLength <= REPORT_CHUNK_BYTES) {
168
+ sendFrame(MessageType.DATA, payload);
169
+ sendFrame(MessageType.CLOSE, new ArrayBuffer(0));
170
+ return;
171
+ }
172
+
173
+ const totalBytes = payload.byteLength;
174
+ const chunkCount = (totalBytes + REPORT_CHUNK_BYTES - 1) / REPORT_CHUNK_BYTES;
175
+ sendJson(
176
+ MessageType.CALL,
177
+ `{"kind":"report:start","encoding":"utf8-chunks","totalBytes":${totalBytes.toString()},"chunkCount":${chunkCount.toString()},"chunkBytes":${REPORT_CHUNK_BYTES.toString()}}`,
178
+ );
179
+
180
+ const ptr = changetype<usize>(payload);
181
+ let offset = 0;
182
+ while (offset < totalBytes) {
183
+ const size = min<i32>(REPORT_CHUNK_BYTES, totalBytes - offset);
184
+ const chunk = new ArrayBuffer(size);
185
+ memory.copy(changetype<usize>(chunk), ptr + offset, size);
186
+ sendFrame(MessageType.DATA, chunk);
187
+ offset += size;
188
+ }
189
+
190
+ sendJson(
191
+ MessageType.CALL,
192
+ `{"kind":"report:end","totalBytes":${totalBytes.toString()},"chunkCount":${chunkCount.toString()}}`,
193
+ );
194
+ sendFrame(MessageType.CLOSE, new ArrayBuffer(0));
166
195
  }
167
196
 
168
197
  export function sendWarning(message: string): void {