as-test 1.1.6 → 1.1.8

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +4 -9
  3. package/assembly/index.ts +10 -15
  4. package/assembly/src/expectation.ts +11 -11
  5. package/assembly/src/fuzz.ts +11 -7
  6. package/assembly/src/log.ts +2 -2
  7. package/assembly/src/suite.ts +5 -5
  8. package/assembly/src/tests.ts +8 -8
  9. package/assembly/util/wipc.ts +5 -1
  10. package/bin/build-worker-pool.js +146 -142
  11. package/bin/build-worker.js +37 -34
  12. package/bin/commands/build-core.js +577 -465
  13. package/bin/commands/build.js +49 -29
  14. package/bin/commands/clean-core.js +120 -113
  15. package/bin/commands/clean.js +14 -8
  16. package/bin/commands/doctor-core.js +288 -289
  17. package/bin/commands/doctor.js +1 -1
  18. package/bin/commands/fuzz-core.js +467 -414
  19. package/bin/commands/fuzz.js +27 -10
  20. package/bin/commands/init-core.js +908 -794
  21. package/bin/commands/init.js +2 -2
  22. package/bin/commands/run-core.js +2675 -2344
  23. package/bin/commands/run.js +43 -25
  24. package/bin/commands/test.js +56 -32
  25. package/bin/commands/web-runner-source.js +1 -1
  26. package/bin/commands/web-session.js +516 -525
  27. package/bin/coverage-points.js +363 -341
  28. package/bin/crash-store.js +56 -66
  29. package/bin/index.js +4092 -3150
  30. package/bin/reporters/default.js +1090 -890
  31. package/bin/reporters/tap.js +319 -325
  32. package/bin/types.js +67 -67
  33. package/bin/util.js +1290 -1239
  34. package/bin/wipc.js +70 -73
  35. package/lib/build/index.d.ts +3 -1
  36. package/lib/build/index.js +1039 -1034
  37. package/lib/build/web-runner/client.js +1 -1
  38. package/lib/build/web-runner/html.js +1 -1
  39. package/lib/build/web-runner/worker.js +1 -1
  40. package/package.json +6 -3
  41. package/transform/lib/log.js +9 -5
  42. package/assembly/util/json.ts +0 -112
@@ -9,465 +9,518 @@ const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
9
9
  const MAGIC = Buffer.from("WIPC");
10
10
  const HEADER_SIZE = 9;
11
11
  const MAX_DEFAULT_SEED = 0x7fffffff;
12
- export async function fuzz(configPath = DEFAULT_CONFIG_PATH, selectors = [], modeName, overrides = {}, fuzzerSelectors = []) {
13
- const loadedConfig = loadConfig(configPath, false);
14
- const mode = applyMode(loadedConfig, modeName);
15
- const activeConfig = mode.config;
16
- const config = resolveFuzzConfig(activeConfig.fuzz, overrides);
17
- const inputPatterns = resolveFuzzInputPatterns(config.input, selectors);
18
- const inputFiles = (await glob(inputPatterns)).sort((a, b) => a.localeCompare(b));
19
- if (!inputFiles.length) {
20
- throw new Error(`No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`);
21
- }
22
- const duplicateBasenames = resolveDuplicateBasenames(inputFiles);
23
- const results = [];
24
- for (const file of inputFiles) {
25
- const buildStartedAt = Date.now();
26
- await build(configPath, [file], modeName, { coverage: false }, { target: "bindings", args: ["--use", "AS_TEST_FUZZ=1"], kind: "fuzz" }, loadedConfig);
27
- const buildFinishedAt = Date.now();
28
- const buildTime = buildFinishedAt - buildStartedAt;
29
- results.push(await runFuzzTarget(file, activeConfig.outDir, duplicateBasenames, config, fuzzerSelectors, buildStartedAt, buildFinishedAt, buildTime, modeName));
30
- }
31
- return results;
12
+ export async function fuzz(
13
+ configPath = DEFAULT_CONFIG_PATH,
14
+ selectors = [],
15
+ modeName,
16
+ overrides = {},
17
+ fuzzerSelectors = [],
18
+ ) {
19
+ const loadedConfig = loadConfig(configPath, false);
20
+ const mode = applyMode(loadedConfig, modeName);
21
+ const activeConfig = mode.config;
22
+ const config = resolveFuzzConfig(activeConfig.fuzz, overrides);
23
+ const inputPatterns = resolveFuzzInputPatterns(config.input, selectors);
24
+ const inputFiles = (await glob(inputPatterns)).sort((a, b) =>
25
+ a.localeCompare(b),
26
+ );
27
+ if (!inputFiles.length) {
28
+ throw new Error(
29
+ `No fuzz files matched: ${selectors.length ? selectors.join(", ") : "configured input patterns"}`,
30
+ );
31
+ }
32
+ const duplicateBasenames = resolveDuplicateBasenames(inputFiles);
33
+ const results = [];
34
+ for (const file of inputFiles) {
35
+ const buildStartedAt = Date.now();
36
+ await build(
37
+ configPath,
38
+ [file],
39
+ modeName,
40
+ { coverage: false },
41
+ { target: "bindings", args: ["--use", "AS_TEST_FUZZ=1"], kind: "fuzz" },
42
+ loadedConfig,
43
+ );
44
+ const buildFinishedAt = Date.now();
45
+ const buildTime = buildFinishedAt - buildStartedAt;
46
+ results.push(
47
+ await runFuzzTarget(
48
+ file,
49
+ activeConfig.outDir,
50
+ duplicateBasenames,
51
+ config,
52
+ fuzzerSelectors,
53
+ buildStartedAt,
54
+ buildFinishedAt,
55
+ buildTime,
56
+ modeName,
57
+ ),
58
+ );
59
+ }
60
+ return results;
32
61
  }
33
62
  function resolveFuzzConfig(raw, overrides) {
34
- const config = Object.assign({}, raw);
35
- if (typeof overrides.seed == "number") {
36
- config.seed = overrides.seed;
37
- }
38
- else if (config.seed < 0) {
39
- config.seed = generateRandomSeed();
63
+ const config = Object.assign({}, raw);
64
+ if (typeof overrides.seed == "number") {
65
+ config.seed = overrides.seed;
66
+ } else if (config.seed < 0) {
67
+ config.seed = generateRandomSeed();
68
+ }
69
+ if (typeof overrides.runs == "number") {
70
+ config.runs = overrides.runs;
71
+ }
72
+ config.runsOverrideKind = 0;
73
+ config.runsOverrideValue = 0;
74
+ if (overrides.runsOverride) {
75
+ config.runsOverrideKind = encodeRunsOverrideKind(
76
+ overrides.runsOverride.kind,
77
+ );
78
+ config.runsOverrideValue = overrides.runsOverride.value;
79
+ if (overrides.runsOverride.kind == "set") {
80
+ config.runs = Math.max(1, Math.round(overrides.runsOverride.value));
40
81
  }
41
- if (typeof overrides.runs == "number") {
42
- config.runs = overrides.runs;
43
- }
44
- config.runsOverrideKind = 0;
45
- config.runsOverrideValue = 0;
46
- if (overrides.runsOverride) {
47
- config.runsOverrideKind = encodeRunsOverrideKind(overrides.runsOverride.kind);
48
- config.runsOverrideValue = overrides.runsOverride.value;
49
- if (overrides.runsOverride.kind == "set") {
50
- config.runs = Math.max(1, Math.round(overrides.runsOverride.value));
51
- }
52
- }
53
- if (config.target != "bindings") {
54
- throw new Error(`fuzz target must be "bindings"; received "${config.target}"`);
55
- }
56
- return config;
82
+ }
83
+ if (config.target != "bindings") {
84
+ throw new Error(
85
+ `fuzz target must be "bindings"; received "${config.target}"`,
86
+ );
87
+ }
88
+ return config;
57
89
  }
58
90
  function generateRandomSeed() {
59
- return Math.floor(Math.random() * (MAX_DEFAULT_SEED + 1));
91
+ return Math.floor(Math.random() * (MAX_DEFAULT_SEED + 1));
60
92
  }
61
93
  function encodeRunsOverrideKind(kind) {
62
- switch (kind) {
63
- case "set":
64
- return 1;
65
- case "scale":
66
- return 2;
67
- case "add":
68
- return 3;
69
- case "percent-add":
70
- return 4;
71
- }
94
+ switch (kind) {
95
+ case "set":
96
+ return 1;
97
+ case "scale":
98
+ return 2;
99
+ case "add":
100
+ return 3;
101
+ case "percent-add":
102
+ return 4;
103
+ }
72
104
  }
73
- async function runFuzzTarget(file, outDir, duplicateBasenames, config, fuzzerSelectors, buildStartedAt, buildFinishedAt, buildTime, modeName) {
74
- const startedAt = Date.now();
75
- const artifact = resolveArtifactFileName(file, duplicateBasenames, modeName);
76
- const wasmPath = path.resolve(process.cwd(), outDir, artifact);
77
- const jsPath = resolveBindingsHelperPath(wasmPath);
78
- const helper = await import(pathToFileURL(jsPath).href + `?t=${Date.now()}`);
79
- const binary = readFileSync(wasmPath);
80
- const module = new WebAssembly.Module(binary);
81
- let report = null;
82
- let reportParseError = null;
83
- const reportStream = {
84
- sawChunkStart: false,
85
- sawChunkEnd: false,
86
- chunkCountExpected: 0,
87
- chunkTotalBytesExpected: 0,
88
- chunkFramesReceived: 0,
89
- chunkBytesReceived: 0,
90
- chunks: [],
91
- };
92
- const captured = captureFrames((type, payload, respond) => {
93
- if (type == 0x02) {
94
- const event = JSON.parse(payload.toString("utf8"));
95
- const kind = String(event.kind ?? "");
96
- if (kind == "fuzz:config") {
97
- const resolved = config;
98
- respond(`${config.runs}\n${config.seed}\n${resolved.runsOverrideKind ?? 0}\n${resolved.runsOverrideValue ?? 0}`);
99
- }
100
- else if (kind == "report:start") {
101
- reportStream.sawChunkStart = true;
102
- reportStream.sawChunkEnd = false;
103
- reportStream.chunkCountExpected = Number(event.chunkCount ?? 0);
104
- reportStream.chunkTotalBytesExpected = Number(event.totalBytes ?? 0);
105
- reportStream.chunkFramesReceived = 0;
106
- reportStream.chunkBytesReceived = 0;
107
- reportStream.chunks = [];
108
- }
109
- else if (kind == "report:end") {
110
- reportStream.sawChunkEnd = true;
111
- }
112
- else {
113
- respond("");
114
- }
115
- return;
116
- }
117
- if (type == 0x03) {
118
- if (reportStream.sawChunkStart && !reportStream.sawChunkEnd) {
119
- reportStream.chunkFramesReceived++;
120
- reportStream.chunkBytesReceived += payload.length;
121
- reportStream.chunks.push(payload.toString("utf8"));
122
- }
123
- else {
124
- try {
125
- report = JSON.parse(payload.toString("utf8"));
126
- reportParseError = null;
127
- }
128
- catch (error) {
129
- reportParseError = String(error);
130
- }
131
- }
132
- }
133
- });
134
- try {
135
- await helper.instantiate(module, {});
136
- }
137
- catch (error) {
138
- const passthrough = captured.restore();
139
- const crashMessage = error instanceof Error ? (error.stack ?? error.message) : String(error);
140
- const crash = persistCrashRecord(config.crashDir, {
141
- kind: "fuzz",
142
- file,
143
- entryKey: buildFuzzCrashEntryKey(file, modeName ?? "default"),
144
- mode: modeName ?? "default",
145
- seed: config.seed,
146
- error: crashMessage,
147
- stdout: passthrough.stdout,
148
- stderr: "",
149
- });
150
- return {
151
- file,
152
- target: path.basename(file),
153
- modeName: modeName ?? "default",
154
- runs: config.runs,
155
- crashes: 1,
156
- crashFiles: [crash.jsonPath],
157
- seed: config.seed,
158
- time: Date.now() - startedAt,
159
- buildTime,
160
- buildStartedAt,
161
- buildFinishedAt,
162
- fuzzers: [],
163
- };
105
+ async function runFuzzTarget(
106
+ file,
107
+ outDir,
108
+ duplicateBasenames,
109
+ config,
110
+ fuzzerSelectors,
111
+ buildStartedAt,
112
+ buildFinishedAt,
113
+ buildTime,
114
+ modeName,
115
+ ) {
116
+ const startedAt = Date.now();
117
+ const artifact = resolveArtifactFileName(file, duplicateBasenames, modeName);
118
+ const wasmPath = path.resolve(process.cwd(), outDir, artifact);
119
+ const jsPath = resolveBindingsHelperPath(wasmPath);
120
+ const helper = await import(pathToFileURL(jsPath).href + `?t=${Date.now()}`);
121
+ const binary = readFileSync(wasmPath);
122
+ const module = new WebAssembly.Module(binary);
123
+ let report = null;
124
+ let reportParseError = null;
125
+ const reportStream = {
126
+ sawChunkStart: false,
127
+ sawChunkEnd: false,
128
+ chunkCountExpected: 0,
129
+ chunkTotalBytesExpected: 0,
130
+ chunkFramesReceived: 0,
131
+ chunkBytesReceived: 0,
132
+ chunks: [],
133
+ };
134
+ const captured = captureFrames((type, payload, respond) => {
135
+ if (type == 0x02) {
136
+ const event = JSON.parse(payload.toString("utf8"));
137
+ const kind = String(event.kind ?? "");
138
+ if (kind == "fuzz:config") {
139
+ const resolved = config;
140
+ respond(
141
+ `${config.runs}\n${config.seed}\n${resolved.runsOverrideKind ?? 0}\n${resolved.runsOverrideValue ?? 0}`,
142
+ );
143
+ } else if (kind == "report:start") {
144
+ reportStream.sawChunkStart = true;
145
+ reportStream.sawChunkEnd = false;
146
+ reportStream.chunkCountExpected = Number(event.chunkCount ?? 0);
147
+ reportStream.chunkTotalBytesExpected = Number(event.totalBytes ?? 0);
148
+ reportStream.chunkFramesReceived = 0;
149
+ reportStream.chunkBytesReceived = 0;
150
+ reportStream.chunks = [];
151
+ } else if (kind == "report:end") {
152
+ reportStream.sawChunkEnd = true;
153
+ } else {
154
+ respond("");
155
+ }
156
+ return;
164
157
  }
165
- const passthrough = captured.restore();
166
- if (reportStream.sawChunkStart) {
167
- if (reportStream.sawChunkEnd) {
168
- const chunkedPayload = reportStream.chunks.join("");
169
- try {
170
- report = JSON.parse(chunkedPayload);
171
- reportParseError = null;
172
- }
173
- catch (error) {
174
- reportParseError = String(error);
175
- }
158
+ if (type == 0x03) {
159
+ if (reportStream.sawChunkStart && !reportStream.sawChunkEnd) {
160
+ reportStream.chunkFramesReceived++;
161
+ reportStream.chunkBytesReceived += payload.length;
162
+ reportStream.chunks.push(payload.toString("utf8"));
163
+ } else {
164
+ try {
165
+ report = JSON.parse(payload.toString("utf8"));
166
+ reportParseError = null;
167
+ } catch (error) {
168
+ reportParseError = String(error);
176
169
  }
170
+ }
177
171
  }
178
- if (!report?.fuzzers) {
179
- const diagnostics = [
180
- `chunked=${reportStream.sawChunkStart ? "yes" : "no"}`,
181
- `chunkStart=${reportStream.sawChunkStart ? "yes" : "no"}`,
182
- `chunkEnd=${reportStream.sawChunkEnd ? "yes" : "no"}`,
183
- `chunkFrames=${reportStream.chunkFramesReceived}`,
184
- `expectedChunkFrames=${reportStream.chunkCountExpected}`,
185
- `chunkBytes=${reportStream.chunkBytesReceived}`,
186
- `expectedChunkBytes=${reportStream.chunkTotalBytesExpected}`,
187
- ].join(", ");
188
- const crash = persistCrashRecord(config.crashDir, {
189
- kind: "fuzz",
190
- file,
191
- entryKey: buildFuzzCrashEntryKey(file, modeName ?? "default"),
192
- mode: modeName ?? "default",
193
- seed: config.seed,
194
- error: `${reportParseError ? `invalid fuzz report payload: ${reportParseError}` : `missing fuzz report payload from ${path.basename(file)}`} (${diagnostics})`,
195
- stdout: passthrough.stdout,
196
- stderr: "",
197
- });
198
- return {
199
- file,
200
- target: path.basename(file),
201
- modeName: modeName ?? "default",
202
- runs: config.runs,
203
- crashes: 1,
204
- crashFiles: [crash.jsonPath],
205
- seed: config.seed,
206
- time: Date.now() - startedAt,
207
- buildTime,
208
- buildStartedAt,
209
- buildFinishedAt,
210
- fuzzers: [],
211
- };
212
- }
213
- const crashFiles = [];
214
- const selectedFuzzers = fuzzerSelectors.length
215
- ? filterSelectedFuzzers(report.fuzzers, fuzzerSelectors, file)
216
- : report.fuzzers;
217
- for (const fuzzer of selectedFuzzers) {
218
- if (fuzzer.failed <= 0 && fuzzer.crashed <= 0)
219
- continue;
220
- const firstFailureSeed = typeof fuzzer.failures?.[0]?.seed == "number"
221
- ? fuzzer.failures[0].seed
222
- : config.seed;
223
- const crash = persistCrashRecord(config.crashDir, {
224
- kind: "fuzz",
225
- file,
226
- entryKey: buildFuzzFailureEntryKey(file, fuzzer.name, modeName ?? "default"),
227
- mode: modeName ?? "default",
228
- seed: firstFailureSeed,
229
- reproCommand: buildFuzzReproCommand(file, firstFailureSeed, modeName ?? "default", fuzzer.selector, 1),
230
- error: fuzzer.failure?.message ||
231
- `fuzz failure in ${fuzzer.name} after ${fuzzer.runs} runs`,
232
- stdout: passthrough.stdout,
233
- stderr: "",
234
- failure: fuzzer.failure,
235
- failures: fuzzer.failures,
236
- });
237
- crashFiles.push(crash.jsonPath);
238
- fuzzer.crashFile = crash.jsonPath;
172
+ });
173
+ try {
174
+ await helper.instantiate(module, {});
175
+ } catch (error) {
176
+ const passthrough = captured.restore();
177
+ const crashMessage =
178
+ error instanceof Error ? (error.stack ?? error.message) : String(error);
179
+ const crash = persistCrashRecord(config.crashDir, {
180
+ kind: "fuzz",
181
+ file,
182
+ entryKey: buildFuzzCrashEntryKey(file, modeName ?? "default"),
183
+ mode: modeName ?? "default",
184
+ seed: config.seed,
185
+ error: crashMessage,
186
+ stdout: passthrough.stdout,
187
+ stderr: "",
188
+ });
189
+ return {
190
+ file,
191
+ target: path.basename(file),
192
+ modeName: modeName ?? "default",
193
+ runs: config.runs,
194
+ crashes: 1,
195
+ crashFiles: [crash.jsonPath],
196
+ seed: config.seed,
197
+ time: Date.now() - startedAt,
198
+ buildTime,
199
+ buildStartedAt,
200
+ buildFinishedAt,
201
+ fuzzers: [],
202
+ };
203
+ }
204
+ const passthrough = captured.restore();
205
+ if (reportStream.sawChunkStart) {
206
+ if (reportStream.sawChunkEnd) {
207
+ const chunkedPayload = reportStream.chunks.join("");
208
+ try {
209
+ report = JSON.parse(chunkedPayload);
210
+ reportParseError = null;
211
+ } catch (error) {
212
+ reportParseError = String(error);
213
+ }
239
214
  }
215
+ }
216
+ if (!report?.fuzzers) {
217
+ const diagnostics = [
218
+ `chunked=${reportStream.sawChunkStart ? "yes" : "no"}`,
219
+ `chunkStart=${reportStream.sawChunkStart ? "yes" : "no"}`,
220
+ `chunkEnd=${reportStream.sawChunkEnd ? "yes" : "no"}`,
221
+ `chunkFrames=${reportStream.chunkFramesReceived}`,
222
+ `expectedChunkFrames=${reportStream.chunkCountExpected}`,
223
+ `chunkBytes=${reportStream.chunkBytesReceived}`,
224
+ `expectedChunkBytes=${reportStream.chunkTotalBytesExpected}`,
225
+ ].join(", ");
226
+ const crash = persistCrashRecord(config.crashDir, {
227
+ kind: "fuzz",
228
+ file,
229
+ entryKey: buildFuzzCrashEntryKey(file, modeName ?? "default"),
230
+ mode: modeName ?? "default",
231
+ seed: config.seed,
232
+ error: `${reportParseError ? `invalid fuzz report payload: ${reportParseError}` : `missing fuzz report payload from ${path.basename(file)}`} (${diagnostics})`,
233
+ stdout: passthrough.stdout,
234
+ stderr: "",
235
+ });
240
236
  return {
241
- file,
242
- target: path.basename(file),
243
- modeName: modeName ?? "default",
244
- runs: selectedFuzzers.reduce((sum, item) => sum + item.runs, 0),
245
- crashes: selectedFuzzers.reduce((sum, item) => sum + item.crashed, 0),
246
- crashFiles,
247
- seed: config.seed,
248
- time: Date.now() - startedAt,
249
- buildTime,
250
- buildStartedAt,
251
- buildFinishedAt,
252
- fuzzers: selectedFuzzers,
237
+ file,
238
+ target: path.basename(file),
239
+ modeName: modeName ?? "default",
240
+ runs: config.runs,
241
+ crashes: 1,
242
+ crashFiles: [crash.jsonPath],
243
+ seed: config.seed,
244
+ time: Date.now() - startedAt,
245
+ buildTime,
246
+ buildStartedAt,
247
+ buildFinishedAt,
248
+ fuzzers: [],
253
249
  };
250
+ }
251
+ const crashFiles = [];
252
+ const selectedFuzzers = fuzzerSelectors.length
253
+ ? filterSelectedFuzzers(report.fuzzers, fuzzerSelectors, file)
254
+ : report.fuzzers;
255
+ for (const fuzzer of selectedFuzzers) {
256
+ if (fuzzer.failed <= 0 && fuzzer.crashed <= 0) continue;
257
+ const firstFailureSeed =
258
+ typeof fuzzer.failures?.[0]?.seed == "number"
259
+ ? fuzzer.failures[0].seed
260
+ : config.seed;
261
+ const crash = persistCrashRecord(config.crashDir, {
262
+ kind: "fuzz",
263
+ file,
264
+ entryKey: buildFuzzFailureEntryKey(
265
+ file,
266
+ fuzzer.name,
267
+ modeName ?? "default",
268
+ ),
269
+ mode: modeName ?? "default",
270
+ seed: firstFailureSeed,
271
+ reproCommand: buildFuzzReproCommand(
272
+ file,
273
+ firstFailureSeed,
274
+ modeName ?? "default",
275
+ fuzzer.selector,
276
+ 1,
277
+ ),
278
+ error:
279
+ fuzzer.failure?.message ||
280
+ `fuzz failure in ${fuzzer.name} after ${fuzzer.runs} runs`,
281
+ stdout: passthrough.stdout,
282
+ stderr: "",
283
+ failure: fuzzer.failure,
284
+ failures: fuzzer.failures,
285
+ });
286
+ crashFiles.push(crash.jsonPath);
287
+ fuzzer.crashFile = crash.jsonPath;
288
+ }
289
+ return {
290
+ file,
291
+ target: path.basename(file),
292
+ modeName: modeName ?? "default",
293
+ runs: selectedFuzzers.reduce((sum, item) => sum + item.runs, 0),
294
+ crashes: selectedFuzzers.reduce((sum, item) => sum + item.crashed, 0),
295
+ crashFiles,
296
+ seed: config.seed,
297
+ time: Date.now() - startedAt,
298
+ buildTime,
299
+ buildStartedAt,
300
+ buildFinishedAt,
301
+ fuzzers: selectedFuzzers,
302
+ };
254
303
  }
255
304
  function filterSelectedFuzzers(fuzzers, selectors, file) {
256
- const annotated = fuzzers.map((fuzzer) => ({
257
- ...fuzzer,
258
- selector: slugifyFuzzerSelector(fuzzer.name),
259
- }));
260
- const selected = new Set();
261
- for (const selector of selectors) {
262
- const slug = slugifyFuzzerSelector(selector);
263
- if (!slug.length)
264
- continue;
265
- const matches = annotated.filter((fuzzer) => fuzzer.selector == slug);
266
- if (!matches.length) {
267
- throw new Error(`No fuzz targets matched "${selector}" in ${path.basename(file)}.`);
268
- }
269
- for (const match of matches) {
270
- selected.add(match.selector);
271
- }
305
+ const annotated = fuzzers.map((fuzzer) => ({
306
+ ...fuzzer,
307
+ selector: slugifyFuzzerSelector(fuzzer.name),
308
+ }));
309
+ const selected = new Set();
310
+ for (const selector of selectors) {
311
+ const slug = slugifyFuzzerSelector(selector);
312
+ if (!slug.length) continue;
313
+ const matches = annotated.filter((fuzzer) => fuzzer.selector == slug);
314
+ if (!matches.length) {
315
+ throw new Error(
316
+ `No fuzz targets matched "${selector}" in ${path.basename(file)}.`,
317
+ );
272
318
  }
273
- return annotated.filter((fuzzer) => selected.has(fuzzer.selector ?? ""));
319
+ for (const match of matches) {
320
+ selected.add(match.selector);
321
+ }
322
+ }
323
+ return annotated.filter((fuzzer) => selected.has(fuzzer.selector ?? ""));
274
324
  }
275
325
  function slugifyFuzzerSelector(value) {
276
- return value
277
- .trim()
278
- .toLowerCase()
279
- .replace(/[^a-z0-9]+/g, "-")
280
- .replace(/^-+|-+$/g, "");
326
+ return value
327
+ .trim()
328
+ .toLowerCase()
329
+ .replace(/[^a-z0-9]+/g, "-")
330
+ .replace(/^-+|-+$/g, "");
281
331
  }
282
332
  function buildFuzzReproCommand(file, seed, modeName, fuzzer, runs) {
283
- const modeArg = modeName != "default" ? ` --mode ${modeName}` : "";
284
- const fuzzerArg = fuzzer?.length ? ` --fuzzer ${fuzzer}` : "";
285
- const runsArg = typeof runs == "number" ? ` --runs ${runs}` : "";
286
- return `ast fuzz ${file}${modeArg}${fuzzerArg} --seed ${seed}${runsArg}`;
333
+ const modeArg = modeName != "default" ? ` --mode ${modeName}` : "";
334
+ const fuzzerArg = fuzzer?.length ? ` --fuzzer ${fuzzer}` : "";
335
+ const runsArg = typeof runs == "number" ? ` --runs ${runs}` : "";
336
+ return `ast fuzz ${file}${modeArg}${fuzzerArg} --seed ${seed}${runsArg}`;
287
337
  }
288
338
  function buildFuzzFailureEntryKey(file, name, modeName) {
289
- return `${path.basename(file).replace(/\.ts$/, "")}.${sanitizeEntryName(modeName)}.${sanitizeEntryName(name)}`;
339
+ return `${path.basename(file).replace(/\.ts$/, "")}.${sanitizeEntryName(modeName)}.${sanitizeEntryName(name)}`;
290
340
  }
291
341
  function buildFuzzCrashEntryKey(file, modeName) {
292
- return `${path.basename(file).replace(/\.ts$/, "")}.${sanitizeEntryName(modeName)}`;
342
+ return `${path.basename(file).replace(/\.ts$/, "")}.${sanitizeEntryName(modeName)}`;
293
343
  }
294
344
  function sanitizeEntryName(name) {
295
- return (name
296
- .toLowerCase()
297
- .replace(/[^a-z0-9]+/g, "-")
298
- .replace(/^-+|-+$/g, "") || "fuzzer");
345
+ return (
346
+ name
347
+ .toLowerCase()
348
+ .replace(/[^a-z0-9]+/g, "-")
349
+ .replace(/^-+|-+$/g, "") || "fuzzer"
350
+ );
299
351
  }
300
352
  function captureFrames(onFrame) {
301
- const originalWrite = process.stdout.write.bind(process.stdout);
302
- const originalRead = typeof process.stdin.read == "function"
303
- ? process.stdin.read.bind(process.stdin)
304
- : null;
305
- let buffer = Buffer.alloc(0);
306
- let passthrough = Buffer.alloc(0);
307
- let replies = Buffer.alloc(0);
308
- function encodeReply(body) {
309
- const payload = Buffer.from(body, "utf8");
310
- const header = Buffer.alloc(HEADER_SIZE);
311
- MAGIC.copy(header, 0);
312
- header.writeUInt8(0x02, 4);
313
- header.writeUInt32LE(payload.length, 5);
314
- return Buffer.concat([header, payload]);
315
- }
316
- function dequeueReply(length) {
317
- const available = Math.min(length, replies.length);
318
- const view = replies.subarray(0, available);
319
- replies = replies.subarray(available);
320
- return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
353
+ const originalWrite = process.stdout.write.bind(process.stdout);
354
+ const originalRead =
355
+ typeof process.stdin.read == "function"
356
+ ? process.stdin.read.bind(process.stdin)
357
+ : null;
358
+ let buffer = Buffer.alloc(0);
359
+ let passthrough = Buffer.alloc(0);
360
+ let replies = Buffer.alloc(0);
361
+ function encodeReply(body) {
362
+ const payload = Buffer.from(body, "utf8");
363
+ const header = Buffer.alloc(HEADER_SIZE);
364
+ MAGIC.copy(header, 0);
365
+ header.writeUInt8(0x02, 4);
366
+ header.writeUInt32LE(payload.length, 5);
367
+ return Buffer.concat([header, payload]);
368
+ }
369
+ function dequeueReply(length) {
370
+ const available = Math.min(length, replies.length);
371
+ const view = replies.subarray(0, available);
372
+ replies = replies.subarray(available);
373
+ return view.buffer.slice(
374
+ view.byteOffset,
375
+ view.byteOffset + view.byteLength,
376
+ );
377
+ }
378
+ process.stdout.write = (chunk, ...args) => {
379
+ if (!(chunk instanceof ArrayBuffer) && !Buffer.isBuffer(chunk)) {
380
+ return originalWrite(chunk, ...args);
321
381
  }
322
- process.stdout.write = ((chunk, ...args) => {
323
- if (!(chunk instanceof ArrayBuffer) && !Buffer.isBuffer(chunk)) {
324
- return originalWrite(chunk, ...args);
325
- }
326
- const incoming = Buffer.from(chunk);
327
- buffer = Buffer.concat([buffer, incoming]);
328
- while (true) {
329
- const index = buffer.indexOf(MAGIC);
330
- if (index == -1) {
331
- if (buffer.length) {
332
- passthrough = Buffer.concat([passthrough, buffer]);
333
- originalWrite(buffer);
334
- buffer = Buffer.alloc(0);
335
- }
336
- return true;
337
- }
338
- if (index > 0) {
339
- const raw = buffer.subarray(0, index);
340
- passthrough = Buffer.concat([passthrough, raw]);
341
- originalWrite(raw);
342
- buffer = buffer.subarray(index);
343
- }
344
- if (buffer.length < HEADER_SIZE)
345
- return true;
346
- const type = buffer.readUInt8(4);
347
- const length = buffer.readUInt32LE(5);
348
- const frameSize = HEADER_SIZE + length;
349
- if (buffer.length < frameSize)
350
- return true;
351
- const payload = buffer.subarray(HEADER_SIZE, frameSize);
352
- buffer = buffer.subarray(frameSize);
353
- onFrame(type, payload, (body) => {
354
- replies = Buffer.concat([replies, encodeReply(body)]);
355
- });
356
- }
357
- });
358
- process.stdin.read = ((size) => {
359
- const max = size == null ? 0 : Number(size);
360
- if (max > 0 && replies.length) {
361
- return dequeueReply(max);
362
- }
363
- if (originalRead) {
364
- return originalRead(size === null ? undefined : size);
382
+ const incoming = Buffer.from(chunk);
383
+ buffer = Buffer.concat([buffer, incoming]);
384
+ while (true) {
385
+ const index = buffer.indexOf(MAGIC);
386
+ if (index == -1) {
387
+ if (buffer.length) {
388
+ passthrough = Buffer.concat([passthrough, buffer]);
389
+ originalWrite(buffer);
390
+ buffer = Buffer.alloc(0);
365
391
  }
366
- return null;
367
- });
368
- return {
369
- restore() {
370
- process.stdout.write = originalWrite;
371
- if (originalRead) {
372
- process.stdin.read = originalRead;
373
- }
374
- return {
375
- stdout: passthrough.toString("utf8"),
376
- };
377
- },
378
- };
392
+ return true;
393
+ }
394
+ if (index > 0) {
395
+ const raw = buffer.subarray(0, index);
396
+ passthrough = Buffer.concat([passthrough, raw]);
397
+ originalWrite(raw);
398
+ buffer = buffer.subarray(index);
399
+ }
400
+ if (buffer.length < HEADER_SIZE) return true;
401
+ const type = buffer.readUInt8(4);
402
+ const length = buffer.readUInt32LE(5);
403
+ const frameSize = HEADER_SIZE + length;
404
+ if (buffer.length < frameSize) return true;
405
+ const payload = buffer.subarray(HEADER_SIZE, frameSize);
406
+ buffer = buffer.subarray(frameSize);
407
+ onFrame(type, payload, (body) => {
408
+ replies = Buffer.concat([replies, encodeReply(body)]);
409
+ });
410
+ }
411
+ };
412
+ process.stdin.read = (size) => {
413
+ const max = size == null ? 0 : Number(size);
414
+ if (max > 0 && replies.length) {
415
+ return dequeueReply(max);
416
+ }
417
+ if (originalRead) {
418
+ return originalRead(size === null ? undefined : size);
419
+ }
420
+ return null;
421
+ };
422
+ return {
423
+ restore() {
424
+ process.stdout.write = originalWrite;
425
+ if (originalRead) {
426
+ process.stdin.read = originalRead;
427
+ }
428
+ return {
429
+ stdout: passthrough.toString("utf8"),
430
+ };
431
+ },
432
+ };
379
433
  }
380
434
  function resolveFuzzInputPatterns(configured, selectors) {
381
- const configuredInputs = Array.isArray(configured)
382
- ? configured
383
- : [configured];
384
- if (!selectors.length)
385
- return configuredInputs;
386
- const patterns = new Set();
387
- for (const selector of expandSelectors(selectors)) {
388
- if (!selector)
389
- continue;
390
- if (isBareSelector(selector)) {
391
- const base = selector.replace(/\.fuzz\.ts$/, "").replace(/\.ts$/, "");
392
- for (const configuredInput of configuredInputs) {
393
- patterns.add(path.join(path.dirname(configuredInput), `${base}.fuzz.ts`));
394
- }
395
- continue;
396
- }
397
- patterns.add(selector);
435
+ const configuredInputs = Array.isArray(configured)
436
+ ? configured
437
+ : [configured];
438
+ if (!selectors.length) return configuredInputs;
439
+ const patterns = new Set();
440
+ for (const selector of expandSelectors(selectors)) {
441
+ if (!selector) continue;
442
+ if (isBareSelector(selector)) {
443
+ const base = selector.replace(/\.fuzz\.ts$/, "").replace(/\.ts$/, "");
444
+ for (const configuredInput of configuredInputs) {
445
+ patterns.add(
446
+ path.join(path.dirname(configuredInput), `${base}.fuzz.ts`),
447
+ );
448
+ }
449
+ continue;
398
450
  }
399
- return [...patterns];
451
+ patterns.add(selector);
452
+ }
453
+ return [...patterns];
400
454
  }
401
455
  function resolveArtifactFileName(file, duplicateBasenames, modeName) {
402
- const base = path
403
- .basename(file)
404
- .replace(/\.spec\.ts$/, "")
405
- .replace(/\.ts$/, "");
406
- const legacy = !modeName
407
- ? `${path.basename(file).replace(".ts", ".wasm")}`
408
- : `${base}.${modeName}.bindings.wasm`;
409
- if (!duplicateBasenames.has(path.basename(file))) {
410
- return legacy;
411
- }
412
- const disambiguator = resolveDisambiguator(file);
413
- if (!disambiguator.length) {
414
- return legacy;
415
- }
416
- const ext = path.extname(legacy);
417
- const stem = ext.length ? legacy.slice(0, -ext.length) : legacy;
418
- return `${stem}.${disambiguator}${ext}`;
456
+ const base = path
457
+ .basename(file)
458
+ .replace(/\.spec\.ts$/, "")
459
+ .replace(/\.ts$/, "");
460
+ const legacy = !modeName
461
+ ? `${path.basename(file).replace(".ts", ".wasm")}`
462
+ : `${base}.${modeName}.bindings.wasm`;
463
+ if (!duplicateBasenames.has(path.basename(file))) {
464
+ return legacy;
465
+ }
466
+ const disambiguator = resolveDisambiguator(file);
467
+ if (!disambiguator.length) {
468
+ return legacy;
469
+ }
470
+ const ext = path.extname(legacy);
471
+ const stem = ext.length ? legacy.slice(0, -ext.length) : legacy;
472
+ return `${stem}.${disambiguator}${ext}`;
419
473
  }
420
474
  function resolveDuplicateBasenames(files) {
421
- const counts = new Map();
422
- for (const file of files) {
423
- const base = path.basename(file);
424
- counts.set(base, (counts.get(base) ?? 0) + 1);
425
- }
426
- const duplicates = new Set();
427
- for (const [base, count] of counts) {
428
- if (count > 1)
429
- duplicates.add(base);
430
- }
431
- return duplicates;
475
+ const counts = new Map();
476
+ for (const file of files) {
477
+ const base = path.basename(file);
478
+ counts.set(base, (counts.get(base) ?? 0) + 1);
479
+ }
480
+ const duplicates = new Set();
481
+ for (const [base, count] of counts) {
482
+ if (count > 1) duplicates.add(base);
483
+ }
484
+ return duplicates;
432
485
  }
433
486
  function resolveDisambiguator(file) {
434
- const relDir = path.dirname(path.relative(process.cwd(), file));
435
- if (!relDir.length || relDir == ".")
436
- return "";
437
- return relDir
438
- .replace(/[\\/]+/g, "__")
439
- .replace(/[^A-Za-z0-9._-]/g, "_")
440
- .replace(/^_+|_+$/g, "");
487
+ const relDir = path.dirname(path.relative(process.cwd(), file));
488
+ if (!relDir.length || relDir == ".") return "";
489
+ return relDir
490
+ .replace(/[\\/]+/g, "__")
491
+ .replace(/[^A-Za-z0-9._-]/g, "_")
492
+ .replace(/^_+|_+$/g, "");
441
493
  }
442
494
  function resolveBindingsHelperPath(wasmPath) {
443
- const bindingsPath = wasmPath.replace(/\.wasm$/, ".bindings.js");
444
- if (existsSync(bindingsPath))
445
- return bindingsPath;
446
- const directPath = wasmPath.replace(/\.wasm$/, ".js");
447
- if (existsSync(directPath))
448
- return directPath;
449
- return bindingsPath;
495
+ const bindingsPath = wasmPath.replace(/\.wasm$/, ".bindings.js");
496
+ if (existsSync(bindingsPath)) return bindingsPath;
497
+ const directPath = wasmPath.replace(/\.wasm$/, ".js");
498
+ if (existsSync(directPath)) return directPath;
499
+ return bindingsPath;
450
500
  }
451
501
  function expandSelectors(selectors) {
452
- const expanded = [];
453
- for (const selector of selectors) {
454
- if (selector.includes(",") &&
455
- !selector.includes("/") &&
456
- !selector.includes("\\") &&
457
- !/[*?[\]{}]/.test(selector)) {
458
- for (const token of selector.split(",")) {
459
- const trimmed = token.trim();
460
- if (trimmed.length)
461
- expanded.push(trimmed);
462
- }
463
- continue;
464
- }
465
- expanded.push(selector);
502
+ const expanded = [];
503
+ for (const selector of selectors) {
504
+ if (
505
+ selector.includes(",") &&
506
+ !selector.includes("/") &&
507
+ !selector.includes("\\") &&
508
+ !/[*?[\]{}]/.test(selector)
509
+ ) {
510
+ for (const token of selector.split(",")) {
511
+ const trimmed = token.trim();
512
+ if (trimmed.length) expanded.push(trimmed);
513
+ }
514
+ continue;
466
515
  }
467
- return expanded;
516
+ expanded.push(selector);
517
+ }
518
+ return expanded;
468
519
  }
469
520
  function isBareSelector(selector) {
470
- return (!selector.includes("/") &&
471
- !selector.includes("\\") &&
472
- !/[*?[\]{}]/.test(selector));
521
+ return (
522
+ !selector.includes("/") &&
523
+ !selector.includes("\\") &&
524
+ !/[*?[\]{}]/.test(selector)
525
+ );
473
526
  }