Skip to content

Commit 0e99f51

Browse files
authored
Merge pull request #20958 from ziglang/fuzz
introduce a fuzz testing web interface
2 parents f9f8942 + d721d9a commit 0e99f51

34 files changed

+3921
-1078
lines changed

README.md

+3-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ Documentation** corresponding to the version of Zig that you are using by
1313
following the appropriate link on the
1414
[download page](https://ziglang.org/download).
1515

16-
Otherwise, you're looking at a release of Zig, and you can find documentation
17-
here:
18-
19-
* doc/langref.html
20-
* doc/std/index.html
16+
Otherwise, you're looking at a release of Zig, so you can find the language
17+
reference at `doc/langref.html`, and the standard library documentation by
18+
running `zig std`, which will open a browser tab.
2119

2220
## Installation
2321

lib/compiler/build_runner.zig

+36-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ const runner = @This();
1717
pub const root = @import("@build");
1818
pub const dependencies = @import("@dependencies");
1919

20+
pub const std_options: std.Options = .{
21+
.side_channels_mitigations = .none,
22+
.http_disable_tls = true,
23+
.crypto_fork_safety = false,
24+
};
25+
2026
pub fn main() !void {
2127
// Here we use an ArenaAllocator backed by a page allocator because a build is a short-lived,
2228
// one shot program. We don't need to waste time freeing memory and finding places to squish
@@ -106,6 +112,7 @@ pub fn main() !void {
106112
var watch = false;
107113
var fuzz = false;
108114
var debounce_interval_ms: u16 = 50;
115+
var listen_port: u16 = 0;
109116

110117
while (nextArg(args, &arg_idx)) |arg| {
111118
if (mem.startsWith(u8, arg, "-Z")) {
@@ -203,6 +210,14 @@ pub fn main() !void {
203210
next_arg, @errorName(err),
204211
});
205212
};
213+
} else if (mem.eql(u8, arg, "--port")) {
214+
const next_arg = nextArg(args, &arg_idx) orelse
215+
fatalWithHint("expected u16 after '{s}'", .{arg});
216+
listen_port = std.fmt.parseUnsigned(u16, next_arg, 10) catch |err| {
217+
fatal("unable to parse port '{s}' as unsigned 16-bit integer: {s}\n", .{
218+
next_arg, @errorName(err),
219+
});
220+
};
206221
} else if (mem.eql(u8, arg, "--debug-log")) {
207222
const next_arg = nextArgOrFatal(args, &arg_idx);
208223
try debug_log_scopes.append(next_arg);
@@ -403,7 +418,27 @@ pub fn main() !void {
403418
else => return err,
404419
};
405420
if (fuzz) {
406-
Fuzz.start(&run.thread_pool, run.step_stack.keys(), run.ttyconf, main_progress_node);
421+
switch (builtin.os.tag) {
422+
// Current implementation depends on two things that need to be ported to Windows:
423+
// * Memory-mapping to share data between the fuzzer and build runner.
424+
// * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
425+
// many addresses to source locations).
426+
.windows => fatal("--fuzz not yet implemented for {s}", .{@tagName(builtin.os.tag)}),
427+
else => {},
428+
}
429+
const listen_address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable;
430+
try Fuzz.start(
431+
gpa,
432+
arena,
433+
global_cache_directory,
434+
zig_lib_directory,
435+
zig_exe,
436+
&run.thread_pool,
437+
run.step_stack.keys(),
438+
run.ttyconf,
439+
listen_address,
440+
main_progress_node,
441+
);
407442
}
408443

409444
if (!watch) return cleanExit();

lib/compiler/std-docs.zig

+4-5
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,6 @@ fn buildWasmBinary(
275275
) ![]const u8 {
276276
const gpa = context.gpa;
277277

278-
const main_src_path = try std.fs.path.join(arena, &.{
279-
context.zig_lib_directory, "docs", "wasm", "main.zig",
280-
});
281-
282278
var argv: std.ArrayListUnmanaged([]const u8) = .{};
283279

284280
try argv.appendSlice(arena, &.{
@@ -298,7 +294,10 @@ fn buildWasmBinary(
298294
"--name",
299295
"autodoc",
300296
"-rdynamic",
301-
main_src_path,
297+
"--dep",
298+
"Walk",
299+
try std.fmt.allocPrint(arena, "-Mroot={s}/docs/wasm/main.zig", .{context.zig_lib_directory}),
300+
try std.fmt.allocPrint(arena, "-MWalk={s}/docs/wasm/Walk.zig", .{context.zig_lib_directory}),
302301
"--listen=-",
303302
});
304303

lib/compiler/test_runner.zig

+33-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
//! Default test runner for unit tests.
22
const builtin = @import("builtin");
3+
34
const std = @import("std");
45
const io = std.io;
56
const testing = std.testing;
7+
const assert = std.debug.assert;
68

79
pub const std_options = .{
810
.logFn = log,
@@ -28,19 +30,26 @@ pub fn main() void {
2830
@panic("unable to parse command line args");
2931

3032
var listen = false;
33+
var opt_cache_dir: ?[]const u8 = null;
3134

3235
for (args[1..]) |arg| {
3336
if (std.mem.eql(u8, arg, "--listen=-")) {
3437
listen = true;
3538
} else if (std.mem.startsWith(u8, arg, "--seed=")) {
3639
testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch
3740
@panic("unable to parse --seed command line argument");
41+
} else if (std.mem.startsWith(u8, arg, "--cache-dir")) {
42+
opt_cache_dir = arg["--cache-dir=".len..];
3843
} else {
3944
@panic("unrecognized command line argument");
4045
}
4146
}
4247

4348
fba.reset();
49+
if (builtin.fuzz) {
50+
const cache_dir = opt_cache_dir orelse @panic("missing --cache-dir=[path] argument");
51+
fuzzer_init(FuzzerSlice.fromSlice(cache_dir));
52+
}
4453

4554
if (listen) {
4655
return mainServer() catch @panic("internal test runner failure");
@@ -59,6 +68,11 @@ fn mainServer() !void {
5968
});
6069
defer server.deinit();
6170

71+
if (builtin.fuzz) {
72+
const coverage_id = fuzzer_coverage_id();
73+
try server.serveU64Message(.coverage_id, coverage_id);
74+
}
75+
6276
while (true) {
6377
const hdr = try server.receiveMessage();
6478
switch (hdr.tag) {
@@ -129,7 +143,9 @@ fn mainServer() !void {
129143
});
130144
},
131145
.start_fuzzing => {
146+
if (!builtin.fuzz) unreachable;
132147
const index = try server.receiveBody_u32();
148+
var first = true;
133149
const test_fn = builtin.test_functions[index];
134150
while (true) {
135151
testing.allocator_instance = .{};
@@ -148,6 +164,10 @@ fn mainServer() !void {
148164
};
149165
if (!is_fuzz_test) @panic("missed call to std.testing.fuzzInput");
150166
if (log_err_count != 0) @panic("error logs detected");
167+
if (first) {
168+
first = false;
169+
try server.serveU64Message(.fuzz_start_addr, entry_addr);
170+
}
151171
}
152172
},
153173

@@ -315,20 +335,32 @@ const FuzzerSlice = extern struct {
315335
ptr: [*]const u8,
316336
len: usize,
317337

338+
/// Inline to avoid fuzzer instrumentation.
318339
inline fn toSlice(s: FuzzerSlice) []const u8 {
319340
return s.ptr[0..s.len];
320341
}
342+
343+
/// Inline to avoid fuzzer instrumentation.
344+
inline fn fromSlice(s: []const u8) FuzzerSlice {
345+
return .{ .ptr = s.ptr, .len = s.len };
346+
}
321347
};
322348

323349
var is_fuzz_test: bool = undefined;
350+
var entry_addr: usize = 0;
324351

325352
extern fn fuzzer_next() FuzzerSlice;
353+
extern fn fuzzer_init(cache_dir: FuzzerSlice) void;
354+
extern fn fuzzer_coverage_id() u64;
326355

327356
pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 {
328357
@disableInstrumentation();
329358
if (crippled) return "";
330359
is_fuzz_test = true;
331-
if (builtin.fuzz) return fuzzer_next().toSlice();
360+
if (builtin.fuzz) {
361+
if (entry_addr == 0) entry_addr = @returnAddress();
362+
return fuzzer_next().toSlice();
363+
}
332364
if (options.corpus.len == 0) return "";
333365
var prng = std.Random.DefaultPrng.init(testing.random_seed);
334366
const random = prng.random();

lib/docs/wasm/Decl.zig

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
const Decl = @This();
2+
const std = @import("std");
3+
const Ast = std.zig.Ast;
4+
const Walk = @import("Walk.zig");
5+
const gpa = std.heap.wasm_allocator;
6+
const assert = std.debug.assert;
7+
const log = std.log;
8+
const Oom = error{OutOfMemory};
9+
110
ast_node: Ast.Node.Index,
211
file: Walk.File.Index,
312
/// The decl whose namespace this is in.
@@ -215,12 +224,3 @@ pub fn find(search_string: []const u8) Decl.Index {
215224
}
216225
return current_decl_index;
217226
}
218-
219-
const Decl = @This();
220-
const std = @import("std");
221-
const Ast = std.zig.Ast;
222-
const Walk = @import("Walk.zig");
223-
const gpa = std.heap.wasm_allocator;
224-
const assert = std.debug.assert;
225-
const log = std.log;
226-
const Oom = error{OutOfMemory};

lib/docs/wasm/Walk.zig

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
//! Find and annotate identifiers with links to their declarations.
2+
3+
const Walk = @This();
4+
const std = @import("std");
5+
const Ast = std.zig.Ast;
6+
const assert = std.debug.assert;
7+
const log = std.log;
8+
const gpa = std.heap.wasm_allocator;
9+
const Oom = error{OutOfMemory};
10+
11+
pub const Decl = @import("Decl.zig");
12+
213
pub var files: std.StringArrayHashMapUnmanaged(File) = .{};
314
pub var decls: std.ArrayListUnmanaged(Decl) = .{};
415
pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .{};
@@ -1120,15 +1131,6 @@ pub fn isPrimitiveNonType(name: []const u8) bool {
11201131
// try w.root();
11211132
//}
11221133

1123-
const Walk = @This();
1124-
const std = @import("std");
1125-
const Ast = std.zig.Ast;
1126-
const assert = std.debug.assert;
1127-
const Decl = @import("Decl.zig");
1128-
const log = std.log;
1129-
const gpa = std.heap.wasm_allocator;
1130-
const Oom = error{OutOfMemory};
1131-
11321134
fn shrinkToFit(m: anytype) void {
11331135
m.shrinkAndFree(gpa, m.entries.len);
11341136
}

0 commit comments

Comments
 (0)