@zigc/lib 0.16.0-dev.3121 → 0.16.0-dev.3128

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 (79) hide show
  1. package/c/math.zig +15 -13
  2. package/compiler/build_runner.zig +1 -0
  3. package/compiler/test_runner.zig +191 -59
  4. package/fuzzer.zig +855 -307
  5. package/package.json +1 -1
  6. package/std/Build/Fuzz.zig +6 -19
  7. package/std/Build/Step/Run.zig +530 -68
  8. package/std/Build/abi.zig +39 -7
  9. package/std/Build.zig +3 -0
  10. package/std/compress/flate/Compress.zig +3 -3
  11. package/std/priority_dequeue.zig +13 -12
  12. package/std/priority_queue.zig +5 -4
  13. package/std/zig/Client.zig +8 -3
  14. package/std/zig/Server.zig +26 -0
  15. package/libc/mingw/complex/cabs.c +0 -48
  16. package/libc/mingw/complex/cabsf.c +0 -48
  17. package/libc/mingw/complex/cacos.c +0 -50
  18. package/libc/mingw/complex/cacosf.c +0 -50
  19. package/libc/mingw/complex/carg.c +0 -48
  20. package/libc/mingw/complex/cargf.c +0 -48
  21. package/libc/mingw/complex/casin.c +0 -50
  22. package/libc/mingw/complex/casinf.c +0 -50
  23. package/libc/mingw/complex/catan.c +0 -50
  24. package/libc/mingw/complex/catanf.c +0 -50
  25. package/libc/mingw/complex/ccos.c +0 -50
  26. package/libc/mingw/complex/ccosf.c +0 -50
  27. package/libc/mingw/complex/cexp.c +0 -48
  28. package/libc/mingw/complex/cexpf.c +0 -48
  29. package/libc/mingw/complex/cimag.c +0 -48
  30. package/libc/mingw/complex/cimagf.c +0 -48
  31. package/libc/mingw/complex/clog.c +0 -48
  32. package/libc/mingw/complex/clog10.c +0 -49
  33. package/libc/mingw/complex/clog10f.c +0 -49
  34. package/libc/mingw/complex/clogf.c +0 -48
  35. package/libc/mingw/complex/conj.c +0 -48
  36. package/libc/mingw/complex/conjf.c +0 -48
  37. package/libc/mingw/complex/cpow.c +0 -48
  38. package/libc/mingw/complex/cpowf.c +0 -48
  39. package/libc/mingw/complex/cproj.c +0 -48
  40. package/libc/mingw/complex/cprojf.c +0 -48
  41. package/libc/mingw/complex/creal.c +0 -48
  42. package/libc/mingw/complex/crealf.c +0 -48
  43. package/libc/mingw/complex/csin.c +0 -50
  44. package/libc/mingw/complex/csinf.c +0 -50
  45. package/libc/mingw/complex/csqrt.c +0 -48
  46. package/libc/mingw/complex/csqrtf.c +0 -48
  47. package/libc/mingw/complex/ctan.c +0 -50
  48. package/libc/mingw/complex/ctanf.c +0 -50
  49. package/libc/mingw/math/arm/s_rint.c +0 -86
  50. package/libc/mingw/math/arm/s_rintf.c +0 -51
  51. package/libc/mingw/math/bsd_private_base.h +0 -148
  52. package/libc/mingw/math/x86/acosf.c +0 -29
  53. package/libc/mingw/math/x86/atanf.c +0 -23
  54. package/libc/mingw/math/x86/atanl.c +0 -18
  55. package/libc/mingw/math/x86/ldexp.c +0 -23
  56. package/libc/mingw/math/x86/scalbn.S +0 -41
  57. package/libc/mingw/math/x86/scalbnf.S +0 -40
  58. package/libc/mingw/misc/btowc.c +0 -28
  59. package/libc/mingw/misc/wcstof.c +0 -66
  60. package/libc/mingw/misc/wcstoimax.c +0 -132
  61. package/libc/mingw/misc/wcstoumax.c +0 -126
  62. package/libc/mingw/misc/wctob.c +0 -29
  63. package/libc/mingw/misc/winbs_uint64.c +0 -6
  64. package/libc/mingw/misc/winbs_ulong.c +0 -6
  65. package/libc/mingw/misc/winbs_ushort.c +0 -6
  66. package/libc/mingw/stdio/_Exit.c +0 -10
  67. package/libc/mingw/stdio/_findfirst64i32.c +0 -21
  68. package/libc/mingw/stdio/_findnext64i32.c +0 -21
  69. package/libc/mingw/stdio/_fstat64i32.c +0 -37
  70. package/libc/mingw/stdio/_stat64i32.c +0 -37
  71. package/libc/mingw/stdio/_wfindfirst64i32.c +0 -21
  72. package/libc/mingw/stdio/_wfindnext64i32.c +0 -21
  73. package/libc/mingw/stdio/_wstat64i32.c +0 -37
  74. package/libc/musl/src/legacy/valloc.c +0 -8
  75. package/libc/musl/src/math/exp_data.c +0 -182
  76. package/libc/musl/src/math/exp_data.h +0 -26
  77. package/libc/musl/src/math/pow_data.c +0 -180
  78. package/libc/musl/src/math/pow_data.h +0 -22
  79. package/libc/wasi/libc-bottom-half/sources/reallocarray.c +0 -14
@@ -1068,7 +1068,6 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
1068
1068
  pub fn rerunInFuzzMode(
1069
1069
  run: *Run,
1070
1070
  fuzz: *std.Build.Fuzz,
1071
- unit_test_name: []const u8,
1072
1071
  prog_node: std.Progress.Node,
1073
1072
  ) !void {
1074
1073
  const step = &run.step;
@@ -1139,7 +1138,6 @@ pub fn rerunInFuzzMode(
1139
1138
  .unit_test_timeout_ns = null, // don't time out fuzz tests for now
1140
1139
  .gpa = fuzz.gpa,
1141
1140
  }, .{
1142
- .unit_test_name = unit_test_name,
1143
1141
  .fuzz = fuzz,
1144
1142
  });
1145
1143
  }
@@ -1211,7 +1209,6 @@ fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool {
1211
1209
 
1212
1210
  const FuzzContext = struct {
1213
1211
  fuzz: *std.Build.Fuzz,
1214
- unit_test_name: []const u8,
1215
1212
  };
1216
1213
 
1217
1214
  fn runCommand(
@@ -1655,6 +1652,11 @@ fn evalZigTest(
1655
1652
  options: Step.MakeOptions,
1656
1653
  fuzz_context: ?FuzzContext,
1657
1654
  ) !void {
1655
+ if (fuzz_context != null) {
1656
+ try evalFuzzTest(run, spawn_options, options, fuzz_context.?);
1657
+ return;
1658
+ }
1659
+
1658
1660
  const step_owner = run.step.owner;
1659
1661
  const gpa = step_owner.allocator;
1660
1662
  const arena = step_owner.allocator;
@@ -1693,7 +1695,6 @@ fn evalZigTest(
1693
1695
  run,
1694
1696
  &child,
1695
1697
  options,
1696
- fuzz_context,
1697
1698
  &multi_reader,
1698
1699
  &test_metadata,
1699
1700
  &test_results,
@@ -1815,7 +1816,6 @@ fn waitZigTest(
1815
1816
  run: *Run,
1816
1817
  child: *process.Child,
1817
1818
  options: Step.MakeOptions,
1818
- fuzz_context: ?FuzzContext,
1819
1819
  multi_reader: *Io.File.MultiReader,
1820
1820
  opt_metadata: *?TestMetadata,
1821
1821
  results: *Step.TestResults,
@@ -1837,29 +1837,7 @@ fn waitZigTest(
1837
1837
  var sub_prog_node: ?std.Progress.Node = null;
1838
1838
  defer if (sub_prog_node) |n| n.end();
1839
1839
 
1840
- if (fuzz_context) |ctx| {
1841
- assert(opt_metadata.* == null); // fuzz processes are never restarted
1842
- switch (ctx.fuzz.mode) {
1843
- .forever => {
1844
- sendRunFuzzTestMessage(
1845
- io,
1846
- child.stdin.?,
1847
- ctx.unit_test_name,
1848
- .forever,
1849
- 0, // instance ID; will be used by multiprocess forever fuzzing in the future
1850
- ) catch |err| return .{ .write_failed = err };
1851
- },
1852
- .limit => |limit| {
1853
- sendRunFuzzTestMessage(
1854
- io,
1855
- child.stdin.?,
1856
- ctx.unit_test_name,
1857
- .iterations,
1858
- limit.amount,
1859
- ) catch |err| return .{ .write_failed = err };
1860
- },
1861
- }
1862
- } else if (opt_metadata.*) |*md| {
1840
+ if (opt_metadata.*) |*md| {
1863
1841
  // Previous unit test process died or was killed; we're continuing where it left off
1864
1842
  requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
1865
1843
  } else {
@@ -1872,14 +1850,11 @@ fn waitZigTest(
1872
1850
 
1873
1851
  var last_update: Io.Clock.Timestamp = .now(io, .awake);
1874
1852
 
1875
- var coverage_id: ?u64 = null;
1876
-
1877
1853
  // This timeout is used when we're waiting on the test runner itself rather than a user-specified
1878
1854
  // test. For instance, if the test runner leaves this much time between us requesting a test to
1879
1855
  // start and it acknowledging the test starting, we terminate the child and raise an error. This
1880
1856
  // *should* never happen, but could in theory be caused by some very unlucky IB in a test.
1881
- const response_timeout: ?Io.Clock.Duration = t: {
1882
- if (fuzz_context != null) break :t null; // don't timeout fuzz tests
1857
+ const response_timeout: Io.Clock.Duration = t: {
1883
1858
  const ns = @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s);
1884
1859
  break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) };
1885
1860
  };
@@ -1947,8 +1922,6 @@ fn waitZigTest(
1947
1922
  );
1948
1923
  },
1949
1924
  .test_metadata => {
1950
- assert(fuzz_context == null);
1951
-
1952
1925
  // `metadata` would only be populated if we'd already seen a `test_metadata`, but we
1953
1926
  // only request it once (and importantly, we don't re-request it if we kill and
1954
1927
  // restart the test runner).
@@ -1986,7 +1959,6 @@ fn waitZigTest(
1986
1959
  last_update = .now(io, .awake);
1987
1960
  },
1988
1961
  .test_results => {
1989
- assert(fuzz_context == null);
1990
1962
  const md = &opt_metadata.*.?;
1991
1963
 
1992
1964
  const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;
@@ -2033,44 +2005,523 @@ fn waitZigTest(
2033
2005
 
2034
2006
  requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
2035
2007
  },
2008
+ else => {}, // ignore other messages
2009
+ }
2010
+ }
2011
+ }
2012
+
2013
+ const FuzzTestRunner = struct {
2014
+ run: *Run,
2015
+ ctx: FuzzContext,
2016
+ coverage_id: ?u64,
2017
+
2018
+ instances: []Instance,
2019
+ /// The indexes of this are layed out such that it is effectively an array
2020
+ /// of `[instances.len][3]Io.Operation.Storage` of stdin, stdout, stderr.
2021
+ batch: Io.Batch,
2022
+ /// LIFO. Stream of message bodies trailed by PendingBroadcastFooter.
2023
+ pending_broadcasts: std.ArrayList(u8),
2024
+ broadcast: std.ArrayList(u8),
2025
+ broadcast_undelivered: u32,
2026
+
2027
+ const Instance = struct {
2028
+ child: process.Child,
2029
+ message: std.ArrayListAligned(u8, .@"4"),
2030
+ broadcast_written: usize,
2031
+ stderr: std.ArrayList(u8),
2032
+ stdin_vec: [1][]u8,
2033
+ stdout_vec: [1][]u8,
2034
+ stderr_vec: [1][]u8,
2035
+ progress_node: std.Progress.Node,
2036
+
2037
+ fn messageHeader(instance: *Instance) InHeader {
2038
+ assert(instance.message.items.len >= @sizeOf(InHeader));
2039
+ const header_ptr: *InHeader = @ptrCast(instance.message.items);
2040
+ var header = header_ptr.*;
2041
+ if (std.builtin.Endian.native != .little) {
2042
+ std.mem.byteSwapAllFields(InHeader, &header);
2043
+ }
2044
+ return header;
2045
+ }
2046
+ };
2047
+
2048
+ const PendingBroadcastFooter = struct {
2049
+ from_id: u32,
2050
+ body_len: u32,
2051
+ };
2052
+
2053
+ const InHeader = std.zig.Server.Message.Header;
2054
+ const OutHeader = std.zig.Client.Message.Header;
2055
+
2056
+ const stdin_i = 0;
2057
+ const stdout_i = 1;
2058
+ const stderr_i = 2;
2059
+
2060
+ fn init(
2061
+ run: *Run,
2062
+ ctx: FuzzContext,
2063
+ progress_node: std.Progress.Node,
2064
+ spawn_options: process.SpawnOptions,
2065
+ ) !FuzzTestRunner {
2066
+ const step_owner = run.step.owner;
2067
+ const gpa = step_owner.allocator;
2068
+ const io = step_owner.graph.io;
2069
+
2070
+ const n_instances = switch (ctx.fuzz.mode) {
2071
+ .forever => step_owner.graph.max_jobs orelse @min(
2072
+ std.Thread.getCpuCount() catch 1,
2073
+ (std.math.maxInt(u32) - 2) / 3,
2074
+ ),
2075
+ .limit => 1,
2076
+ };
2077
+ const instances = try gpa.alloc(Instance, n_instances);
2078
+ errdefer gpa.free(instances);
2079
+ const batch_storage = try gpa.alloc(Io.Operation.Storage, instances.len * 3);
2080
+ errdefer gpa.free(batch_storage);
2081
+
2082
+ @memset(instances, .{
2083
+ .child = undefined,
2084
+ .message = .empty,
2085
+ .broadcast_written = undefined,
2086
+ .stderr = .empty,
2087
+ .stdin_vec = undefined,
2088
+ .stdout_vec = undefined,
2089
+ .stderr_vec = undefined,
2090
+ .progress_node = undefined,
2091
+ });
2092
+ for (0.., instances) |id, *instance| {
2093
+ errdefer for (instances[0..id]) |*spawned| {
2094
+ spawned.child.kill(io);
2095
+ spawned.progress_node.end();
2096
+ };
2097
+ instance.child = try process.spawn(io, spawn_options);
2098
+ instance.progress_node = progress_node.start("starting fuzzer", 0);
2099
+ }
2100
+
2101
+ return .{
2102
+ .run = run,
2103
+ .ctx = ctx,
2104
+ .coverage_id = null,
2105
+
2106
+ .instances = instances,
2107
+ .batch = .init(batch_storage),
2108
+ .pending_broadcasts = .empty,
2109
+ .broadcast = .empty,
2110
+ .broadcast_undelivered = 0,
2111
+ };
2112
+ }
2113
+
2114
+ fn deinit(f: *FuzzTestRunner) void {
2115
+ const step_owner = f.run.step.owner;
2116
+ const gpa = step_owner.allocator;
2117
+ const io = step_owner.graph.io;
2118
+
2119
+ f.batch.cancel(io);
2120
+ gpa.free(f.batch.storage);
2121
+ var total_rss: usize = 0;
2122
+ for (f.instances) |*instance| {
2123
+ instance.child.kill(io);
2124
+ instance.message.deinit(gpa);
2125
+ instance.stderr.deinit(gpa);
2126
+ instance.progress_node.end();
2127
+ total_rss += instance.child.resource_usage_statistics.getMaxRss() orelse 0;
2128
+ }
2129
+ f.run.step.result_peak_rss = @max(f.run.step.result_peak_rss, total_rss);
2130
+ gpa.free(f.instances);
2131
+ }
2132
+
2133
+ fn startInstances(f: *FuzzTestRunner) !void {
2134
+ const step_owner = f.run.step.owner;
2135
+ const io = step_owner.graph.io;
2136
+
2137
+ for (0.., f.instances) |id, *instance| {
2138
+ const id32: u32 = @intCast(id);
2139
+ (switch (f.ctx.fuzz.mode) {
2140
+ .forever => sendRunFuzzTestMessage(
2141
+ io,
2142
+ instance.child.stdin.?,
2143
+ f.run.fuzz_tests.items,
2144
+ .forever,
2145
+ id32,
2146
+ ),
2147
+ .limit => |limit| sendRunFuzzTestMessage(
2148
+ io,
2149
+ instance.child.stdin.?,
2150
+ f.run.fuzz_tests.items,
2151
+ .iterations,
2152
+ limit.amount,
2153
+ ),
2154
+ }) catch |write_err| {
2155
+ // The runner unexpectedly closed stdin, which means it crashed during initialization.
2156
+ // Clean up everything and wait for the child to exit.
2157
+ instance.child.stdin.?.close(io);
2158
+ instance.child.stdin = null;
2159
+ const term = try instance.child.wait(io);
2160
+ return f.run.step.fail(
2161
+ "unable to write stdin ({t}); test process unexpectedly {f}",
2162
+ .{ write_err, fmtTerm(term) },
2163
+ );
2164
+ };
2165
+
2166
+ try f.addStdoutRead(id32, @sizeOf(InHeader));
2167
+ try f.addStderrRead(id32);
2168
+ }
2169
+ }
2170
+
2171
+ fn listen(f: *FuzzTestRunner) !void {
2172
+ const step_owner = f.run.step.owner;
2173
+ const io = step_owner.graph.io;
2174
+
2175
+ while (true) {
2176
+ try f.batch.awaitConcurrent(io, .none);
2177
+ while (f.batch.next()) |completion| {
2178
+ const id = completion.index / 3;
2179
+ const result = completion.result;
2180
+ switch (completion.index % 3) {
2181
+ 0 => try f.completeStdinWrite(id, result.file_write_streaming catch |e| switch (e) {
2182
+ error.BrokenPipe => return f.instanceEos(id),
2183
+ else => |write_e| return write_e,
2184
+ }),
2185
+ 1 => try f.completeStdoutRead(id, result.file_read_streaming catch |e| switch (e) {
2186
+ error.EndOfStream => return f.instanceEos(id),
2187
+ else => |read_e| return read_e,
2188
+ }),
2189
+ 2 => try f.completeStderrRead(id, result.file_read_streaming catch |e| switch (e) {
2190
+ error.EndOfStream => return f.instanceEos(id),
2191
+ else => |read_e| return read_e,
2192
+ }),
2193
+ else => unreachable,
2194
+ }
2195
+ }
2196
+ }
2197
+ }
2198
+
2199
+ fn completeStdoutRead(f: *FuzzTestRunner, id: u32, n: usize) !void {
2200
+ const step_owner = f.run.step.owner;
2201
+ const gpa = step_owner.allocator;
2202
+ const io = step_owner.graph.io;
2203
+ const instance = &f.instances[id];
2204
+
2205
+ instance.message.items.len += n;
2206
+ const total_read = instance.message.items.len;
2207
+ if (total_read < @sizeOf(InHeader)) {
2208
+ try f.addStdoutRead(id, @sizeOf(InHeader));
2209
+ return;
2210
+ }
2211
+
2212
+ const header = instance.messageHeader();
2213
+ const body = instance.message.items[@sizeOf(InHeader)..];
2214
+ if (body.len != header.bytes_len) {
2215
+ try f.addStdoutRead(id, @sizeOf(InHeader) + header.bytes_len);
2216
+ return;
2217
+ }
2218
+
2219
+ switch (header.tag) {
2220
+ .zig_version => {
2221
+ if (!std.mem.eql(u8, builtin.zig_version_string, body)) return f.run.step.fail(
2222
+ "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
2223
+ .{ builtin.zig_version_string, body },
2224
+ );
2225
+ },
2036
2226
  .coverage_id => {
2037
- coverage_id = body_r.takeInt(u64, .little) catch unreachable;
2227
+ var body_r: Io.Reader = .fixed(body);
2228
+ f.coverage_id = body_r.takeInt(u64, .little) catch unreachable;
2038
2229
  const cumulative_runs = body_r.takeInt(u64, .little) catch unreachable;
2039
2230
  const cumulative_unique = body_r.takeInt(u64, .little) catch unreachable;
2040
2231
  const cumulative_coverage = body_r.takeInt(u64, .little) catch unreachable;
2041
2232
 
2042
- {
2043
- const fuzz = fuzz_context.?.fuzz;
2044
- fuzz.queue_mutex.lockUncancelable(io);
2045
- defer fuzz.queue_mutex.unlock(io);
2046
- try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{
2047
- .id = coverage_id.?,
2048
- .cumulative = .{
2049
- .runs = cumulative_runs,
2050
- .unique = cumulative_unique,
2051
- .coverage = cumulative_coverage,
2052
- },
2053
- .run = run,
2054
- } });
2055
- fuzz.queue_cond.signal(io);
2056
- }
2233
+ const fuzz = f.ctx.fuzz;
2234
+ fuzz.queue_mutex.lockUncancelable(io);
2235
+ defer fuzz.queue_mutex.unlock(io);
2236
+ try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{
2237
+ .id = f.coverage_id.?,
2238
+ .cumulative = .{
2239
+ .runs = cumulative_runs,
2240
+ .unique = cumulative_unique,
2241
+ .coverage = cumulative_coverage,
2242
+ },
2243
+ .run = f.run,
2244
+ } });
2245
+ fuzz.queue_cond.signal(io);
2057
2246
  },
2058
2247
  .fuzz_start_addr => {
2059
- const fuzz = fuzz_context.?.fuzz;
2248
+ var body_r: Io.Reader = .fixed(body);
2249
+ const fuzz = f.ctx.fuzz;
2060
2250
  const addr = body_r.takeInt(u64, .little) catch unreachable;
2061
- {
2062
- fuzz.queue_mutex.lockUncancelable(io);
2063
- defer fuzz.queue_mutex.unlock(io);
2064
- try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{
2065
- .addr = addr,
2066
- .coverage_id = coverage_id.?,
2067
- } });
2068
- fuzz.queue_cond.signal(io);
2251
+
2252
+ fuzz.queue_mutex.lockUncancelable(io);
2253
+ defer fuzz.queue_mutex.unlock(io);
2254
+ try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{
2255
+ .addr = addr,
2256
+ .coverage_id = f.coverage_id.?,
2257
+ } });
2258
+ fuzz.queue_cond.signal(io);
2259
+ },
2260
+ .fuzz_test_change => {
2261
+ const test_i = std.mem.readInt(u32, body[0..4], .little);
2262
+ instance.progress_node.setName(f.run.fuzz_tests.items[test_i]);
2263
+ },
2264
+ .broadcast_fuzz_input => {
2265
+ if (f.instances.len == 1) {
2266
+ // No other processes to broadcast to.
2267
+ } else if (f.broadcast_undelivered == 0) {
2268
+ try f.instanceBroadcast(id, body);
2269
+ } else {
2270
+ const footer: PendingBroadcastFooter = .{
2271
+ .from_id = id,
2272
+ .body_len = @intCast(body.len),
2273
+ };
2274
+ // There is another broadcast in progress so add this one to the queue.
2275
+ const size = @sizeOf(PendingBroadcastFooter) + body.len;
2276
+ try f.pending_broadcasts.ensureUnusedCapacity(gpa, size);
2277
+ f.pending_broadcasts.appendSliceAssumeCapacity(body);
2278
+ f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer));
2069
2279
  }
2070
2280
  },
2071
2281
  else => {}, // ignore other messages
2072
2282
  }
2283
+
2284
+ instance.message.clearRetainingCapacity();
2285
+ try f.addStdoutRead(id, @sizeOf(InHeader));
2286
+ }
2287
+
2288
+ fn completeStderrRead(f: *FuzzTestRunner, id: u32, n: usize) !void {
2289
+ const instance = &f.instances[id];
2290
+ instance.stderr.items.len += n;
2291
+ try f.addStderrRead(id);
2292
+ }
2293
+
2294
+ fn completeStdinWrite(f: *FuzzTestRunner, id: u32, n: usize) !void {
2295
+ const instance = &f.instances[id];
2296
+
2297
+ instance.broadcast_written += n;
2298
+ if (instance.broadcast_written == f.broadcast.items.len) {
2299
+ f.broadcast_undelivered -= 1;
2300
+ if (f.broadcast_undelivered == 0) {
2301
+ try f.broadcastComplete();
2302
+ }
2303
+ } else {
2304
+ f.addStdinWrite(id);
2305
+ }
2073
2306
  }
2307
+
2308
+ fn addStdoutRead(f: *FuzzTestRunner, id: u32, end: usize) !void {
2309
+ const step_owner = f.run.step.owner;
2310
+ const gpa = step_owner.allocator;
2311
+ const instance = &f.instances[id];
2312
+
2313
+ try instance.message.ensureTotalCapacity(gpa, end);
2314
+ const start = instance.message.items.len;
2315
+ instance.stdout_vec = .{instance.message.allocatedSlice()[start..end]};
2316
+ f.batch.addAt(id * 3 + stdout_i, .{ .file_read_streaming = .{
2317
+ .file = instance.child.stdout.?,
2318
+ .data = &instance.stdout_vec,
2319
+ } });
2320
+ }
2321
+
2322
+ fn addStderrRead(f: *FuzzTestRunner, id: u32) !void {
2323
+ const step_owner = f.run.step.owner;
2324
+ const gpa = step_owner.allocator;
2325
+ const instance = &f.instances[id];
2326
+
2327
+ try instance.stderr.ensureUnusedCapacity(gpa, 1);
2328
+ instance.stderr_vec = .{instance.stderr.unusedCapacitySlice()};
2329
+ f.batch.addAt(id * 3 + stderr_i, .{ .file_read_streaming = .{
2330
+ .file = instance.child.stderr.?,
2331
+ .data = &instance.stderr_vec,
2332
+ } });
2333
+ }
2334
+
2335
+ fn addStdinWrite(f: *FuzzTestRunner, id: u32) void {
2336
+ const instance = &f.instances[id];
2337
+
2338
+ assert(f.broadcast.items.len != instance.broadcast_written);
2339
+ instance.stdin_vec = .{f.broadcast.items[instance.broadcast_written..]};
2340
+ f.batch.addAt(id * 3 + stdin_i, .{ .file_write_streaming = .{
2341
+ .file = instance.child.stdin.?,
2342
+ .data = &instance.stdin_vec,
2343
+ } });
2344
+ }
2345
+
2346
+ fn instanceEos(f: *FuzzTestRunner, id: u32) !void {
2347
+ const step_owner = f.run.step.owner;
2348
+ const io = step_owner.graph.io;
2349
+ const instance = &f.instances[id];
2350
+
2351
+ instance.child.stdin.?.close(io);
2352
+ instance.child.stdin = null;
2353
+ const term = try instance.child.wait(io);
2354
+ if (!termMatches(.{ .exited = 0 }, term)) {
2355
+ f.run.step.result_stderr = try f.mergedStderr();
2356
+ try f.saveCrash(id, term);
2357
+ return f.run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)});
2358
+ }
2359
+ }
2360
+
2361
+ fn saveCrash(f: *FuzzTestRunner, id: u32, term: process.Child.Term) !void {
2362
+ const step = &f.run.step;
2363
+ const b = step.owner;
2364
+ const io = b.graph.io;
2365
+
2366
+ if (f.coverage_id == null) return;
2367
+
2368
+ // Search for the input file corresponding to the instance
2369
+ const InputHeader = Build.abi.fuzz.MmapInputHeader;
2370
+ var in_r_buf: [@sizeOf(InputHeader)]u8 = undefined;
2371
+ var in_r: Io.File.Reader = undefined;
2372
+ var in_f: Io.File = undefined;
2373
+ var in_name_buf: [12]u8 = undefined;
2374
+ var in_name: []const u8 = undefined;
2375
+ var i: u32 = 0;
2376
+ const header: InputHeader = while (true) {
2377
+ const name_prefix = "f" ++ Io.Dir.path.sep_str ++ "in";
2378
+ in_name = std.fmt.bufPrint(&in_name_buf, name_prefix ++ "{x}", .{i}) catch unreachable;
2379
+ in_f = b.cache_root.handle.openFile(io, in_name, .{
2380
+ .lock = .exclusive,
2381
+ .lock_nonblocking = true,
2382
+ }) catch |e| switch (e) {
2383
+ error.FileNotFound => return,
2384
+ error.WouldBlock => continue, // Can not be from
2385
+ // the crashed instance since it is still locked.
2386
+ else => return step.fail("failed to open file '{f}{s}': {t}", .{
2387
+ b.cache_root, in_name, e,
2388
+ }),
2389
+ };
2390
+
2391
+ in_r = in_f.readerStreaming(io, &in_r_buf);
2392
+ const header = in_r.interface.takeStruct(InputHeader, .little) catch |e| {
2393
+ in_f.close(io);
2394
+ switch (e) {
2395
+ error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{
2396
+ b.cache_root, in_name, in_r.err.?,
2397
+ }),
2398
+ error.EndOfStream => continue,
2399
+ }
2400
+ };
2401
+
2402
+ if (header.pc_digest == f.coverage_id.? and
2403
+ header.instance_id == id and
2404
+ header.test_i < f.run.fuzz_tests.items.len)
2405
+ {
2406
+ break header;
2407
+ }
2408
+
2409
+ in_f.close(io);
2410
+ if (i == std.math.maxInt(u32)) return;
2411
+ i += 1;
2412
+ };
2413
+ defer in_f.close(io);
2414
+
2415
+ // Save it to a seperate file
2416
+ const crash_name = "f" ++ Io.Dir.path.sep_str ++ "crash";
2417
+ const out = b.cache_root.handle.createFile(io, crash_name, .{
2418
+ .lock = .exclusive, // Multiple run steps could have found a crash at the same time
2419
+ }) catch |e| return step.fail("failed to create file '{f}{s}': {t}", .{
2420
+ b.cache_root, crash_name, e,
2421
+ });
2422
+ defer out.close(io);
2423
+
2424
+ var out_w_buf: [512]u8 = undefined;
2425
+ var out_w = out.writerStreaming(io, &out_w_buf);
2426
+ _ = out_w.interface.sendFileAll(&in_r, .limited(header.len)) catch |e| switch (e) {
2427
+ error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{
2428
+ b.cache_root, in_name, in_r.err.?,
2429
+ }),
2430
+ error.WriteFailed => return step.fail("failed to write file '{f}{s}': {t}", .{
2431
+ b.cache_root, crash_name, out_w.err.?,
2432
+ }),
2433
+ };
2434
+
2435
+ return f.run.step.fail("test '{s}' {f}; input saved to '{f}{s}'", .{
2436
+ f.run.fuzz_tests.items[header.test_i],
2437
+ fmtTerm(term),
2438
+ b.cache_root,
2439
+ crash_name,
2440
+ });
2441
+ }
2442
+
2443
+ fn instanceBroadcast(f: *FuzzTestRunner, from_id: u32, bytes: []const u8) !void {
2444
+ assert(f.instances.len > 1);
2445
+ assert(f.broadcast_undelivered == 0); // no other broadcast is progress
2446
+ assert(f.broadcast.items.len == 0);
2447
+ assert(from_id < f.instances.len);
2448
+
2449
+ const step_owner = f.run.step.owner;
2450
+ const gpa = step_owner.allocator;
2451
+
2452
+ var out_header: OutHeader = .{
2453
+ .tag = .new_fuzz_input,
2454
+ .bytes_len = @intCast(bytes.len),
2455
+ };
2456
+ if (std.builtin.Endian.native != .little) {
2457
+ std.mem.byteSwapAllFields(OutHeader, &out_header);
2458
+ }
2459
+ try f.broadcast.ensureTotalCapacity(gpa, @sizeOf(OutHeader) + bytes.len);
2460
+ f.broadcast.appendSliceAssumeCapacity(@ptrCast(&out_header));
2461
+ f.broadcast.appendSliceAssumeCapacity(bytes);
2462
+
2463
+ f.broadcast_undelivered = @intCast(f.instances.len - 1);
2464
+ for (0.., f.instances) |to_id, *instance| {
2465
+ if (to_id == from_id) continue;
2466
+ instance.broadcast_written = 0;
2467
+ f.addStdinWrite(@intCast(to_id));
2468
+ }
2469
+ }
2470
+
2471
+ fn broadcastComplete(f: *FuzzTestRunner) !void {
2472
+ assert(f.instances.len > 1);
2473
+ assert(f.broadcast_undelivered == 0);
2474
+ f.broadcast.clearRetainingCapacity();
2475
+
2476
+ const pending = &f.pending_broadcasts;
2477
+ if (pending.items.len != 0) {
2478
+ // Another broadcast is pending; copy it over to `broadcast`
2479
+
2480
+ const footer_len = @sizeOf(PendingBroadcastFooter);
2481
+ const footer_bytes = pending.items[pending.items.len - footer_len ..];
2482
+ const footer: *align(1) PendingBroadcastFooter = @ptrCast(footer_bytes);
2483
+ pending.items.len -= footer_len;
2484
+
2485
+ const body = pending.items[pending.items.len - footer.body_len ..];
2486
+ try f.instanceBroadcast(footer.from_id, body);
2487
+ pending.items.len -= body.len;
2488
+ }
2489
+ }
2490
+
2491
+ fn mergedStderr(f: *FuzzTestRunner) std.mem.Allocator.Error![]const u8 {
2492
+ const step_owner = f.run.step.owner;
2493
+ const arena = step_owner.allocator;
2494
+
2495
+ // Collect any remaining stderr
2496
+ while (f.batch.next()) |completion| {
2497
+ if (completion.index % 3 != 2) continue;
2498
+ const len = completion.result.file_read_streaming catch continue;
2499
+ f.instances[completion.index / 3].stderr.items.len += len;
2500
+ }
2501
+
2502
+ var stderr_len: usize = 0;
2503
+ for (f.instances) |*instance| stderr_len += instance.stderr.items.len;
2504
+ const stderr = try arena.alloc(u8, stderr_len);
2505
+
2506
+ stderr_len = 0;
2507
+ for (f.instances) |*instance| {
2508
+ @memcpy(stderr[stderr_len..][0..instance.stderr.items.len], instance.stderr.items);
2509
+ stderr_len += instance.stderr.items.len;
2510
+ }
2511
+ return stderr;
2512
+ }
2513
+ };
2514
+
2515
+ fn evalFuzzTest(
2516
+ run: *Run,
2517
+ spawn_options: process.SpawnOptions,
2518
+ options: Step.MakeOptions,
2519
+ fuzz_context: FuzzContext,
2520
+ ) !void {
2521
+ var f: FuzzTestRunner = try .init(run, fuzz_context, options.progress_node, spawn_options);
2522
+ defer f.deinit();
2523
+ try f.startInstances();
2524
+ try f.listen();
2074
2525
  }
2075
2526
 
2076
2527
  const TestMetadata = struct {
@@ -2149,30 +2600,41 @@ fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, in
2149
2600
  fn sendRunFuzzTestMessage(
2150
2601
  io: Io,
2151
2602
  file: Io.File,
2152
- test_name: []const u8,
2603
+ test_names: []const []const u8,
2153
2604
  kind: std.Build.abi.fuzz.LimitKind,
2154
2605
  amount_or_instance: u64,
2155
2606
  ) !void {
2156
2607
  const header: std.zig.Client.Message.Header = .{
2157
2608
  .tag = .start_fuzzing,
2158
- .bytes_len = 4 + 1 + 8,
2609
+ .bytes_len = 1 + 8 + 4 + count: {
2610
+ var c: u32 = @intCast(test_names.len * 4);
2611
+ for (test_names) |name| {
2612
+ c += @intCast(name.len);
2613
+ }
2614
+ break :count c;
2615
+ },
2159
2616
  };
2160
2617
  var w = file.writerStreaming(io, &.{});
2161
2618
  w.interface.writeStruct(header, .little) catch |err| switch (err) {
2162
2619
  error.WriteFailed => return w.err.?,
2163
2620
  };
2164
- w.interface.writeInt(u32, @intCast(test_name.len), .little) catch |err| switch (err) {
2165
- error.WriteFailed => return w.err.?,
2166
- };
2167
- w.interface.writeAll(test_name) catch |err| switch (err) {
2168
- error.WriteFailed => return w.err.?,
2169
- };
2170
2621
  w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) {
2171
2622
  error.WriteFailed => return w.err.?,
2172
2623
  };
2173
2624
  w.interface.writeInt(u64, amount_or_instance, .little) catch |err| switch (err) {
2174
2625
  error.WriteFailed => return w.err.?,
2175
2626
  };
2627
+ w.interface.writeInt(u32, @intCast(test_names.len), .little) catch |err| switch (err) {
2628
+ error.WriteFailed => return w.err.?,
2629
+ };
2630
+ for (test_names) |test_name| {
2631
+ w.interface.writeInt(u32, @intCast(test_name.len), .little) catch |err| switch (err) {
2632
+ error.WriteFailed => return w.err.?,
2633
+ };
2634
+ w.interface.writeAll(test_name) catch |err| switch (err) {
2635
+ error.WriteFailed => return w.err.?,
2636
+ };
2637
+ }
2176
2638
  }
2177
2639
 
2178
2640
  fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResult {