mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
Merge pull request #23416 from gooncreeper/improved-fuzzer
greatly improve capabilities of the fuzzer
This commit is contained in:
commit
164c598cd8
9 changed files with 1367 additions and 552 deletions
|
|
@ -4,6 +4,7 @@ const builtin = @import("builtin");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
const fuzz_abi = std.Build.abi.fuzz;
|
||||||
|
|
||||||
pub const std_options: std.Options = .{
|
pub const std_options: std.Options = .{
|
||||||
.logFn = log,
|
.logFn = log,
|
||||||
|
|
@ -57,7 +58,7 @@ pub fn main() void {
|
||||||
fba.reset();
|
fba.reset();
|
||||||
if (builtin.fuzz) {
|
if (builtin.fuzz) {
|
||||||
const cache_dir = opt_cache_dir orelse @panic("missing --cache-dir=[path] argument");
|
const cache_dir = opt_cache_dir orelse @panic("missing --cache-dir=[path] argument");
|
||||||
fuzzer_init(FuzzerSlice.fromSlice(cache_dir));
|
fuzz_abi.fuzzer_init(.fromSlice(cache_dir));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listen) {
|
if (listen) {
|
||||||
|
|
@ -78,7 +79,7 @@ fn mainServer() !void {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (builtin.fuzz) {
|
if (builtin.fuzz) {
|
||||||
const coverage_id = fuzzer_coverage_id();
|
const coverage_id = fuzz_abi.fuzzer_coverage_id();
|
||||||
try server.serveU64Message(.coverage_id, coverage_id);
|
try server.serveU64Message(.coverage_id, coverage_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,14 +153,19 @@ fn mainServer() !void {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
.start_fuzzing => {
|
.start_fuzzing => {
|
||||||
|
// This ensures that this code won't be analyzed and hence reference fuzzer symbols
|
||||||
|
// since they are not present.
|
||||||
if (!builtin.fuzz) unreachable;
|
if (!builtin.fuzz) unreachable;
|
||||||
|
|
||||||
const index = try server.receiveBody_u32();
|
const index = try server.receiveBody_u32();
|
||||||
const test_fn = builtin.test_functions[index];
|
const test_fn = builtin.test_functions[index];
|
||||||
const entry_addr = @intFromPtr(test_fn.func);
|
const entry_addr = @intFromPtr(test_fn.func);
|
||||||
|
|
||||||
try server.serveU64Message(.fuzz_start_addr, entry_addr);
|
try server.serveU64Message(.fuzz_start_addr, entry_addr);
|
||||||
defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
|
defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
|
||||||
is_fuzz_test = false;
|
is_fuzz_test = false;
|
||||||
fuzzer_set_name(test_fn.name.ptr, test_fn.name.len);
|
fuzz_test_index = index;
|
||||||
|
|
||||||
test_fn.func() catch |err| switch (err) {
|
test_fn.func() catch |err| switch (err) {
|
||||||
error.SkipZigTest => return,
|
error.SkipZigTest => return,
|
||||||
else => {
|
else => {
|
||||||
|
|
@ -184,6 +190,8 @@ fn mainServer() !void {
|
||||||
|
|
||||||
fn mainTerminal() void {
|
fn mainTerminal() void {
|
||||||
@disableInstrumentation();
|
@disableInstrumentation();
|
||||||
|
if (builtin.fuzz) @panic("fuzz test requires server");
|
||||||
|
|
||||||
const test_fn_list = builtin.test_functions;
|
const test_fn_list = builtin.test_functions;
|
||||||
var ok_count: usize = 0;
|
var ok_count: usize = 0;
|
||||||
var skip_count: usize = 0;
|
var skip_count: usize = 0;
|
||||||
|
|
@ -333,28 +341,8 @@ pub fn mainSimple() anyerror!void {
|
||||||
if (failed != 0) std.process.exit(1);
|
if (failed != 0) std.process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FuzzerSlice = extern struct {
|
|
||||||
ptr: [*]const u8,
|
|
||||||
len: usize,
|
|
||||||
|
|
||||||
/// Inline to avoid fuzzer instrumentation.
|
|
||||||
inline fn toSlice(s: FuzzerSlice) []const u8 {
|
|
||||||
return s.ptr[0..s.len];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inline to avoid fuzzer instrumentation.
|
|
||||||
inline fn fromSlice(s: []const u8) FuzzerSlice {
|
|
||||||
return .{ .ptr = s.ptr, .len = s.len };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var is_fuzz_test: bool = undefined;
|
var is_fuzz_test: bool = undefined;
|
||||||
|
var fuzz_test_index: u32 = undefined;
|
||||||
extern fn fuzzer_set_name(name_ptr: [*]const u8, name_len: usize) void;
|
|
||||||
extern fn fuzzer_init(cache_dir: FuzzerSlice) void;
|
|
||||||
extern fn fuzzer_init_corpus_elem(input_ptr: [*]const u8, input_len: usize) void;
|
|
||||||
extern fn fuzzer_start(testOne: *const fn ([*]const u8, usize) callconv(.c) void) void;
|
|
||||||
extern fn fuzzer_coverage_id() u64;
|
|
||||||
|
|
||||||
pub fn fuzz(
|
pub fn fuzz(
|
||||||
context: anytype,
|
context: anytype,
|
||||||
|
|
@ -385,12 +373,12 @@ pub fn fuzz(
|
||||||
const global = struct {
|
const global = struct {
|
||||||
var ctx: @TypeOf(context) = undefined;
|
var ctx: @TypeOf(context) = undefined;
|
||||||
|
|
||||||
fn fuzzer_one(input_ptr: [*]const u8, input_len: usize) callconv(.c) void {
|
fn test_one(input: fuzz_abi.Slice) callconv(.c) void {
|
||||||
@disableInstrumentation();
|
@disableInstrumentation();
|
||||||
testing.allocator_instance = .{};
|
testing.allocator_instance = .{};
|
||||||
defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
|
defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
|
||||||
log_err_count = 0;
|
log_err_count = 0;
|
||||||
testOne(ctx, input_ptr[0..input_len]) catch |err| switch (err) {
|
testOne(ctx, input.toSlice()) catch |err| switch (err) {
|
||||||
error.SkipZigTest => return,
|
error.SkipZigTest => return,
|
||||||
else => {
|
else => {
|
||||||
std.debug.lockStdErr();
|
std.debug.lockStdErr();
|
||||||
|
|
@ -411,10 +399,11 @@ pub fn fuzz(
|
||||||
testing.allocator_instance = .{};
|
testing.allocator_instance = .{};
|
||||||
defer testing.allocator_instance = prev_allocator_state;
|
defer testing.allocator_instance = prev_allocator_state;
|
||||||
|
|
||||||
for (options.corpus) |elem| fuzzer_init_corpus_elem(elem.ptr, elem.len);
|
|
||||||
|
|
||||||
global.ctx = context;
|
global.ctx = context;
|
||||||
fuzzer_start(&global.fuzzer_one);
|
fuzz_abi.fuzzer_init_test(&global.test_one, .fromSlice(builtin.test_functions[fuzz_test_index].name));
|
||||||
|
for (options.corpus) |elem|
|
||||||
|
fuzz_abi.fuzzer_new_input(.fromSlice(elem));
|
||||||
|
fuzz_abi.fuzzer_main();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
1726
lib/fuzzer.zig
1726
lib/fuzzer.zig
File diff suppressed because it is too large
Load diff
|
|
@ -252,9 +252,8 @@ pub fn sendUpdate(
|
||||||
const seen_pcs = cov_header.seenBits();
|
const seen_pcs = cov_header.seenBits();
|
||||||
const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic);
|
const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic);
|
||||||
const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic);
|
const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic);
|
||||||
if (prev.unique_runs != unique_runs) {
|
{
|
||||||
// There has been an update.
|
if (unique_runs != 0 and prev.unique_runs == 0) {
|
||||||
if (prev.unique_runs == 0) {
|
|
||||||
// We need to send initial context.
|
// We need to send initial context.
|
||||||
const header: abi.SourceIndexHeader = .{
|
const header: abi.SourceIndexHeader = .{
|
||||||
.directories_len = @intCast(coverage_map.coverage.directories.entries.len),
|
.directories_len = @intCast(coverage_map.coverage.directories.entries.len),
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,26 @@ pub const Rebuild = extern struct {
|
||||||
|
|
||||||
/// ABI bits specifically relating to the fuzzer interface.
|
/// ABI bits specifically relating to the fuzzer interface.
|
||||||
pub const fuzz = struct {
|
pub const fuzz = struct {
|
||||||
|
pub const TestOne = *const fn (Slice) callconv(.c) void;
|
||||||
|
pub extern fn fuzzer_init(cache_dir_path: Slice) void;
|
||||||
|
pub extern fn fuzzer_coverage_id() u64;
|
||||||
|
pub extern fn fuzzer_init_test(test_one: TestOne, unit_test_name: Slice) void;
|
||||||
|
pub extern fn fuzzer_new_input(bytes: Slice) void;
|
||||||
|
pub extern fn fuzzer_main() void;
|
||||||
|
|
||||||
|
pub const Slice = extern struct {
|
||||||
|
ptr: [*]const u8,
|
||||||
|
len: usize,
|
||||||
|
|
||||||
|
pub fn toSlice(s: Slice) []const u8 {
|
||||||
|
return s.ptr[0..s.len];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromSlice(s: []const u8) Slice {
|
||||||
|
return .{ .ptr = s.ptr, .len = s.len };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// libfuzzer uses this and its usize is the one that counts. To match the ABI,
|
/// libfuzzer uses this and its usize is the one that counts. To match the ABI,
|
||||||
/// make the ints be the size of the target used with libfuzzer.
|
/// make the ints be the size of the target used with libfuzzer.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -517,3 +517,20 @@ test isNumberFormattedLikeAnInteger {
|
||||||
try std.testing.expect(!isNumberFormattedLikeAnInteger("1e10"));
|
try std.testing.expect(!isNumberFormattedLikeAnInteger("1e10"));
|
||||||
try std.testing.expect(!isNumberFormattedLikeAnInteger("1E10"));
|
try std.testing.expect(!isNumberFormattedLikeAnInteger("1E10"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "fuzz" {
|
||||||
|
try std.testing.fuzz({}, fuzzTestOne, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fuzzTestOne(_: void, input: []const u8) !void {
|
||||||
|
var buf: [16384]u8 = undefined;
|
||||||
|
var fba: std.heap.FixedBufferAllocator = .init(&buf);
|
||||||
|
|
||||||
|
var scanner = Scanner.initCompleteInput(fba.allocator(), input);
|
||||||
|
// Property: There are at most input.len tokens
|
||||||
|
var tokens: usize = 0;
|
||||||
|
while ((scanner.next() catch return) != .end_of_document) {
|
||||||
|
tokens += 1;
|
||||||
|
if (tokens > input.len) return error.Overflow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6451,3 +6451,19 @@ fn testError(source: [:0]const u8, expected_errors: []const Error) !void {
|
||||||
try std.testing.expectEqual(expected, tree.errors[i].tag);
|
try std.testing.expectEqual(expected, tree.errors[i].tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "fuzz ast parse" {
|
||||||
|
try std.testing.fuzz({}, fuzzTestOneParse, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fuzzTestOneParse(_: void, input: []const u8) !void {
|
||||||
|
// The first byte holds if zig / zon
|
||||||
|
if (input.len == 0) return;
|
||||||
|
const mode: std.zig.Ast.Mode = if (input[0] & 1 == 0) .zig else .zon;
|
||||||
|
const bytes = input[1..];
|
||||||
|
|
||||||
|
var fba: std.heap.FixedBufferAllocator = .init(&fixed_buffer_mem);
|
||||||
|
const allocator = fba.allocator();
|
||||||
|
const source = allocator.dupeZ(u8, bytes) catch return;
|
||||||
|
_ = std.zig.Ast.parse(allocator, source, mode) catch return;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1721,10 +1721,14 @@ fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !v
|
||||||
try std.testing.expectEqual(source.len, last_token.loc.end);
|
try std.testing.expectEqual(source.len, last_token.loc.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testPropertiesUpheld(context: void, source: []const u8) anyerror!void {
|
fn testPropertiesUpheld(_: void, source: []const u8) !void {
|
||||||
_ = context;
|
var source0_buf: [512]u8 = undefined;
|
||||||
const source0 = try std.testing.allocator.dupeZ(u8, source);
|
if (source.len + 1 > source0_buf.len)
|
||||||
defer std.testing.allocator.free(source0);
|
return;
|
||||||
|
@memcpy(source0_buf[0..source.len], source);
|
||||||
|
source0_buf[source.len] = 0;
|
||||||
|
const source0 = source0_buf[0..source.len :0];
|
||||||
|
|
||||||
var tokenizer = Tokenizer.init(source0);
|
var tokenizer = Tokenizer.init(source0);
|
||||||
var tokenization_failed = false;
|
var tokenization_failed = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
@ -1750,18 +1754,15 @@ fn testPropertiesUpheld(context: void, source: []const u8) anyerror!void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source0.len > 0) for (source0, source0[1..][0..source0.len]) |cur, next| {
|
if (tokenization_failed) return;
|
||||||
|
for (source0) |cur| {
|
||||||
// Property: No null byte allowed except at end.
|
// Property: No null byte allowed except at end.
|
||||||
if (cur == 0) {
|
if (cur == 0) {
|
||||||
try std.testing.expect(tokenization_failed);
|
return error.TestUnexpectedResult;
|
||||||
}
|
}
|
||||||
// Property: No ASCII control characters other than \n and \t are allowed.
|
// Property: No ASCII control characters other than \n, \t, and \r are allowed.
|
||||||
if (std.ascii.isControl(cur) and cur != '\n' and cur != '\t') {
|
if (std.ascii.isControl(cur) and cur != '\n' and cur != '\t' and cur != '\r') {
|
||||||
try std.testing.expect(tokenization_failed);
|
return error.TestUnexpectedResult;
|
||||||
}
|
}
|
||||||
// Property: All '\r' must be followed by '\n'.
|
}
|
||||||
if (cur == '\r' and next != '\n') {
|
|
||||||
try std.testing.expect(tokenization_failed);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ pub fn build(b: *std.Build) void {
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
.fuzz = true,
|
.fuzz = true,
|
||||||
}),
|
}),
|
||||||
|
.use_llvm = true, // #23423
|
||||||
});
|
});
|
||||||
|
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,43 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const abi = std.Build.abi.fuzz;
|
||||||
|
const native_endian = @import("builtin").cpu.arch.endian();
|
||||||
|
|
||||||
const FuzzerSlice = extern struct {
|
fn testOne(in: abi.Slice) callconv(.c) void {
|
||||||
ptr: [*]const u8,
|
std.debug.assertReadable(in.toSlice());
|
||||||
len: usize,
|
}
|
||||||
|
|
||||||
fn fromSlice(s: []const u8) FuzzerSlice {
|
|
||||||
return .{ .ptr = s.ptr, .len = s.len };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
extern fn fuzzer_set_name(name_ptr: [*]const u8, name_len: usize) void;
|
|
||||||
extern fn fuzzer_init(cache_dir: FuzzerSlice) void;
|
|
||||||
extern fn fuzzer_init_corpus_elem(input_ptr: [*]const u8, input_len: usize) void;
|
|
||||||
extern fn fuzzer_coverage_id() u64;
|
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
var debug_gpa_ctx: std.heap.DebugAllocator(.{}) = .init;
|
||||||
defer _ = gpa.deinit();
|
defer _ = debug_gpa_ctx.deinit();
|
||||||
const args = try std.process.argsAlloc(gpa.allocator());
|
const gpa = debug_gpa_ctx.allocator();
|
||||||
defer std.process.argsFree(gpa.allocator(), args);
|
|
||||||
|
|
||||||
const cache_dir = args[1];
|
var args = try std.process.argsWithAllocator(gpa);
|
||||||
|
defer args.deinit();
|
||||||
|
_ = args.skip(); // executable name
|
||||||
|
|
||||||
fuzzer_init(FuzzerSlice.fromSlice(cache_dir));
|
const cache_dir_path = args.next() orelse @panic("expected cache directory path argument");
|
||||||
fuzzer_init_corpus_elem("hello".ptr, "hello".len);
|
var cache_dir = try std.fs.cwd().openDir(cache_dir_path, .{});
|
||||||
fuzzer_set_name("test".ptr, "test".len);
|
defer cache_dir.close();
|
||||||
_ = fuzzer_coverage_id();
|
|
||||||
|
abi.fuzzer_init(.fromSlice(cache_dir_path));
|
||||||
|
abi.fuzzer_init_test(testOne, .fromSlice("test"));
|
||||||
|
abi.fuzzer_new_input(.fromSlice(""));
|
||||||
|
abi.fuzzer_new_input(.fromSlice("hello"));
|
||||||
|
|
||||||
|
const pc_digest = abi.fuzzer_coverage_id();
|
||||||
|
const coverage_file_path = "v/" ++ std.fmt.hex(pc_digest);
|
||||||
|
const coverage_file = try cache_dir.openFile(coverage_file_path, .{});
|
||||||
|
defer coverage_file.close();
|
||||||
|
|
||||||
|
var read_buf: [@sizeOf(abi.SeenPcsHeader)]u8 = undefined;
|
||||||
|
var r = coverage_file.reader(&read_buf);
|
||||||
|
const pcs_header = r.interface.takeStruct(abi.SeenPcsHeader, native_endian) catch return r.err.?;
|
||||||
|
|
||||||
|
if (pcs_header.pcs_len == 0)
|
||||||
|
return error.ZeroPcs;
|
||||||
|
const expected_len = @sizeOf(abi.SeenPcsHeader) +
|
||||||
|
try std.math.divCeil(usize, pcs_header.pcs_len, @bitSizeOf(usize)) * @sizeOf(usize) +
|
||||||
|
pcs_header.pcs_len * @sizeOf(usize);
|
||||||
|
if (try coverage_file.getEndPos() != expected_len)
|
||||||
|
return error.WrongEnd;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue