aboutsummaryrefslogtreecommitdiff
path: root/lib/fuzzer
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2024-07-31 22:54:05 -0700
committerAndrew Kelley <andrew@ziglang.org>2024-08-07 00:48:32 -0700
commite0ffac4e3c2271a617616760f3084f5f01fb0785 (patch)
tree5769c9280878ebb3cc5f76345cf00050012e8198 /lib/fuzzer
parentffc050e0557c5d951cd293ca365ed0cd3cdf83db (diff)
downloadzig-e0ffac4e3c2271a617616760f3084f5f01fb0785.tar.gz
zig-e0ffac4e3c2271a617616760f3084f5f01fb0785.zip
introduce a web interface for fuzzing
* new .zig-cache subdirectory: 'v' - stores coverage information with filename of hash of PCs that want coverage. This hash is a hex encoding of the 64-bit coverage ID. * build runner * fixed bug in file system inputs when a compile step has an overridden zig_lib_dir field set. * set some std lib options optimized for the build runner - no side channel mitigations - no Transport Layer Security - no crypto fork safety * add a --port CLI arg for choosing the port the fuzzing web interface listens on. it defaults to choosing a random open port. * introduce a web server, and serve a basic single page application - shares wasm code with autodocs - assets are created live on request, for convenient development experience. main.wasm is properly cached if nothing changes. - sources.tar comes from file system inputs (introduced with the `--watch` feature) * receives coverage ID from test runner and sends it on a thread-safe queue to the WebServer. * test runner - takes a zig cache directory argument now, for where to put coverage information. - sends coverage ID to parent process * fuzzer - puts its logs (in debug mode) in .zig-cache/tmp/libfuzzer.log - computes coverage_id and makes it available with `fuzzer_coverage_id` exported function. - the memory-mapped coverage file is now namespaced by the coverage id in hex encoding, in `.zig-cache/v` * tokenizer - add a fuzz test to check that several properties are upheld
Diffstat (limited to 'lib/fuzzer')
-rw-r--r--lib/fuzzer/index.html76
-rw-r--r--lib/fuzzer/main.js40
-rw-r--r--lib/fuzzer/wasm/main.zig99
3 files changed, 215 insertions, 0 deletions
diff --git a/lib/fuzzer/index.html b/lib/fuzzer/index.html
new file mode 100644
index 0000000000..c1ef059ad6
--- /dev/null
+++ b/lib/fuzzer/index.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Zig Documentation</title>
+ <style type="text/css">
+ body {
+ font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif;
+ color: #000000;
+ }
+ .tok-kw {
+ color: #333;
+ font-weight: bold;
+ }
+ .tok-str {
+ color: #d14;
+ }
+ .tok-builtin {
+ color: #0086b3;
+ }
+ .tok-comment {
+ color: #777;
+ font-style: italic;
+ }
+ .tok-fn {
+ color: #900;
+ font-weight: bold;
+ }
+ .tok-null {
+ color: #008080;
+ }
+ .tok-number {
+ color: #008080;
+ }
+ .tok-type {
+ color: #458;
+ font-weight: bold;
+ }
+
+ @media (prefers-color-scheme: dark) {
+ body {
+ background-color: #111;
+ color: #bbb;
+ }
+ .tok-kw {
+ color: #eee;
+ }
+ .tok-str {
+ color: #2e5;
+ }
+ .tok-builtin {
+ color: #ff894c;
+ }
+ .tok-comment {
+ color: #aa7;
+ }
+ .tok-fn {
+ color: #B1A0F8;
+ }
+ .tok-null {
+ color: #ff8080;
+ }
+ .tok-number {
+ color: #ff8080;
+ }
+ .tok-type {
+ color: #68f;
+ }
+ }
+ </style>
+ </head>
+ <body>
+ <script src="main.js"></script>
+ </body>
+</html>
+
diff --git a/lib/fuzzer/main.js b/lib/fuzzer/main.js
new file mode 100644
index 0000000000..9b0d4cd8c3
--- /dev/null
+++ b/lib/fuzzer/main.js
@@ -0,0 +1,40 @@
+(function() {
+ let wasm_promise = fetch("main.wasm");
+ let sources_promise = fetch("sources.tar").then(function(response) {
+ if (!response.ok) throw new Error("unable to download sources");
+ return response.arrayBuffer();
+ });
+ var wasm_exports = null;
+
+ const text_decoder = new TextDecoder();
+ const text_encoder = new TextEncoder();
+
+ WebAssembly.instantiateStreaming(wasm_promise, {
+ js: {
+ log: function(ptr, len) {
+ const msg = decodeString(ptr, len);
+ console.log(msg);
+ },
+ panic: function (ptr, len) {
+ const msg = decodeString(ptr, len);
+ throw new Error("panic: " + msg);
+ },
+ },
+ }).then(function(obj) {
+ wasm_exports = obj.instance.exports;
+ window.wasm = obj; // for debugging
+
+ sources_promise.then(function(buffer) {
+ const js_array = new Uint8Array(buffer);
+ const ptr = wasm_exports.alloc(js_array.length);
+ const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length);
+ wasm_array.set(js_array);
+ wasm_exports.unpack(ptr, js_array.length);
+ });
+ });
+
+ function decodeString(ptr, len) {
+ if (len === 0) return "";
+ return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len));
+ }
+})();
diff --git a/lib/fuzzer/wasm/main.zig b/lib/fuzzer/wasm/main.zig
new file mode 100644
index 0000000000..09b9d81068
--- /dev/null
+++ b/lib/fuzzer/wasm/main.zig
@@ -0,0 +1,99 @@
+const std = @import("std");
+const assert = std.debug.assert;
+
+const Walk = @import("Walk");
+
+const gpa = std.heap.wasm_allocator;
+const log = std.log;
+
+const js = struct {
+ extern "js" fn log(ptr: [*]const u8, len: usize) void;
+ extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn;
+};
+
+pub const std_options: std.Options = .{
+ .logFn = logFn,
+};
+
+pub fn panic(msg: []const u8, st: ?*std.builtin.StackTrace, addr: ?usize) noreturn {
+ _ = st;
+ _ = addr;
+ log.err("panic: {s}", .{msg});
+ @trap();
+}
+
+fn logFn(
+ comptime message_level: log.Level,
+ comptime scope: @TypeOf(.enum_literal),
+ comptime format: []const u8,
+ args: anytype,
+) void {
+ const level_txt = comptime message_level.asText();
+ const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
+ var buf: [500]u8 = undefined;
+ const line = std.fmt.bufPrint(&buf, level_txt ++ prefix2 ++ format, args) catch l: {
+ buf[buf.len - 3 ..][0..3].* = "...".*;
+ break :l &buf;
+ };
+ js.log(line.ptr, line.len);
+}
+
+export fn alloc(n: usize) [*]u8 {
+ const slice = gpa.alloc(u8, n) catch @panic("OOM");
+ return slice.ptr;
+}
+
+export fn unpack(tar_ptr: [*]u8, tar_len: usize) void {
+ const tar_bytes = tar_ptr[0..tar_len];
+ log.debug("received {d} bytes of tar file", .{tar_bytes.len});
+
+ unpackInner(tar_bytes) catch |err| {
+ fatal("unable to unpack tar: {s}", .{@errorName(err)});
+ };
+}
+
+fn unpackInner(tar_bytes: []u8) !void {
+ var fbs = std.io.fixedBufferStream(tar_bytes);
+ var file_name_buffer: [1024]u8 = undefined;
+ var link_name_buffer: [1024]u8 = undefined;
+ var it = std.tar.iterator(fbs.reader(), .{
+ .file_name_buffer = &file_name_buffer,
+ .link_name_buffer = &link_name_buffer,
+ });
+ while (try it.next()) |tar_file| {
+ switch (tar_file.kind) {
+ .file => {
+ if (tar_file.size == 0 and tar_file.name.len == 0) break;
+ if (std.mem.endsWith(u8, tar_file.name, ".zig")) {
+ log.debug("found file: '{s}'", .{tar_file.name});
+ const file_name = try gpa.dupe(u8, tar_file.name);
+ if (std.mem.indexOfScalar(u8, file_name, '/')) |pkg_name_end| {
+ const pkg_name = file_name[0..pkg_name_end];
+ const gop = try Walk.modules.getOrPut(gpa, pkg_name);
+ const file: Walk.File.Index = @enumFromInt(Walk.files.entries.len);
+ if (!gop.found_existing or
+ std.mem.eql(u8, file_name[pkg_name_end..], "/root.zig") or
+ std.mem.eql(u8, file_name[pkg_name_end + 1 .. file_name.len - ".zig".len], pkg_name))
+ {
+ gop.value_ptr.* = file;
+ }
+ const file_bytes = tar_bytes[fbs.pos..][0..@intCast(tar_file.size)];
+ assert(file == try Walk.add_file(file_name, file_bytes));
+ }
+ } else {
+ log.warn("skipping: '{s}' - the tar creation should have done that", .{tar_file.name});
+ }
+ },
+ else => continue,
+ }
+ }
+}
+
+fn fatal(comptime format: []const u8, args: anytype) noreturn {
+ var buf: [500]u8 = undefined;
+ const line = std.fmt.bufPrint(&buf, format, args) catch l: {
+ buf[buf.len - 3 ..][0..3].* = "...".*;
+ break :l &buf;
+ };
+ js.panic(line.ptr, line.len);
+}