Skip to content

Commit b3f6af0

Browse files
committed
fuzzer: write inputs to shared memory before running
breaking change to the fuzz testing API; it now passes a type-safe context parameter to the fuzz function. libfuzzer is reworked to select inputs from the entire corpus. I tested that it's roughly as good as it was before in that it can find the panics in the simple examples, as well as achieve decent coverage on the tokenizer fuzz test. however I think the next step here will be figuring out why so many points of interest are missing from the tokenizer in both Debug and ReleaseSafe modes. does not quite close #20803 yet since there are some more important things to be done, such as opening the previous corpus, continuing fuzzing after finding bugs, storing the length of the inputs, etc.
1 parent 31c1320 commit b3f6af0

File tree

5 files changed

+319
-198
lines changed

5 files changed

+319
-198
lines changed

Diff for: lib/compiler/test_runner.zig

+16-6
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ fn mainServer() !void {
150150
try server.serveU64Message(.fuzz_start_addr, entry_addr);
151151
defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
152152
is_fuzz_test = false;
153+
fuzzer_set_name(test_fn.name.ptr, test_fn.name.len);
153154
test_fn.func() catch |err| switch (err) {
154155
error.SkipZigTest => return,
155156
else => {
@@ -341,12 +342,15 @@ const FuzzerSlice = extern struct {
341342

342343
var is_fuzz_test: bool = undefined;
343344

344-
extern fn fuzzer_start(testOne: *const fn ([*]const u8, usize) callconv(.C) void) void;
345+
extern fn fuzzer_set_name(name_ptr: [*]const u8, name_len: usize) void;
345346
extern fn fuzzer_init(cache_dir: FuzzerSlice) void;
347+
extern fn fuzzer_init_corpus_elem(input_ptr: [*]const u8, input_len: usize) void;
348+
extern fn fuzzer_start(testOne: *const fn ([*]const u8, usize) callconv(.C) void) void;
346349
extern fn fuzzer_coverage_id() u64;
347350

348351
pub fn fuzz(
349-
comptime testOne: fn ([]const u8) anyerror!void,
352+
context: anytype,
353+
comptime testOne: fn (context: @TypeOf(context), []const u8) anyerror!void,
350354
options: testing.FuzzInputOptions,
351355
) anyerror!void {
352356
// Prevent this function from confusing the fuzzer by omitting its own code
@@ -371,12 +375,14 @@ pub fn fuzz(
371375
// our standard unit test checks such as memory leaks, and interaction with
372376
// error logs.
373377
const global = struct {
378+
var ctx: @TypeOf(context) = undefined;
379+
374380
fn fuzzer_one(input_ptr: [*]const u8, input_len: usize) callconv(.C) void {
375381
@disableInstrumentation();
376382
testing.allocator_instance = .{};
377383
defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
378384
log_err_count = 0;
379-
testOne(input_ptr[0..input_len]) catch |err| switch (err) {
385+
testOne(ctx, input_ptr[0..input_len]) catch |err| switch (err) {
380386
error.SkipZigTest => return,
381387
else => {
382388
std.debug.lockStdErr();
@@ -395,18 +401,22 @@ pub fn fuzz(
395401
if (builtin.fuzz) {
396402
const prev_allocator_state = testing.allocator_instance;
397403
testing.allocator_instance = .{};
404+
defer testing.allocator_instance = prev_allocator_state;
405+
406+
for (options.corpus) |elem| fuzzer_init_corpus_elem(elem.ptr, elem.len);
407+
408+
global.ctx = context;
398409
fuzzer_start(&global.fuzzer_one);
399-
testing.allocator_instance = prev_allocator_state;
400410
return;
401411
}
402412

403413
// When the unit test executable is not built in fuzz mode, only run the
404414
// provided corpus.
405415
for (options.corpus) |input| {
406-
try testOne(input);
416+
try testOne(context, input);
407417
}
408418

409419
// In case there is no provided corpus, also use an empty
410420
// string as a smoke test.
411-
try testOne("");
421+
try testOne(context, "");
412422
}

0 commit comments

Comments
 (0)