zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

fuzzer.zig (26234B) - Raw


      1 const builtin = @import("builtin");
      2 const std = @import("std");
      3 const Allocator = std.mem.Allocator;
      4 const assert = std.debug.assert;
      5 const fatal = std.process.fatal;
      6 const SeenPcsHeader = std.Build.abi.fuzz.SeenPcsHeader;
      7 
      8 pub const std_options = std.Options{
      9     .logFn = logOverride,
     10 };
     11 
     12 var log_file_buffer: [256]u8 = undefined;
     13 var log_file_writer: ?std.fs.File.Writer = null;
     14 
     15 fn logOverride(
     16     comptime level: std.log.Level,
     17     comptime scope: @Type(.enum_literal),
     18     comptime format: []const u8,
     19     args: anytype,
     20 ) void {
     21     const fw = if (log_file_writer) |*f| f else f: {
     22         const f = fuzzer.cache_dir.createFile("tmp/libfuzzer.log", .{}) catch
     23             @panic("failed to open fuzzer log file");
     24         log_file_writer = f.writer(&log_file_buffer);
     25         break :f &log_file_writer.?;
     26     };
     27     const prefix1 = comptime level.asText();
     28     const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
     29     fw.interface.print(prefix1 ++ prefix2 ++ format ++ "\n", args) catch
     30         @panic("failed to write to fuzzer log");
     31     fw.interface.flush() catch @panic("failed to flush fuzzer log");
     32 }
     33 
     34 /// Helps determine run uniqueness in the face of recursion.
     35 export threadlocal var __sancov_lowest_stack: usize = 0;
     36 
     37 export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void {
     38     handleCmp(@returnAddress(), arg1, arg2);
     39 }
     40 
     41 export fn __sanitizer_cov_trace_cmp1(arg1: u8, arg2: u8) void {
     42     handleCmp(@returnAddress(), arg1, arg2);
     43 }
     44 
     45 export fn __sanitizer_cov_trace_const_cmp2(arg1: u16, arg2: u16) void {
     46     handleCmp(@returnAddress(), arg1, arg2);
     47 }
     48 
     49 export fn __sanitizer_cov_trace_cmp2(arg1: u16, arg2: u16) void {
     50     handleCmp(@returnAddress(), arg1, arg2);
     51 }
     52 
     53 export fn __sanitizer_cov_trace_const_cmp4(arg1: u32, arg2: u32) void {
     54     handleCmp(@returnAddress(), arg1, arg2);
     55 }
     56 
     57 export fn __sanitizer_cov_trace_cmp4(arg1: u32, arg2: u32) void {
     58     handleCmp(@returnAddress(), arg1, arg2);
     59 }
     60 
     61 export fn __sanitizer_cov_trace_const_cmp8(arg1: u64, arg2: u64) void {
     62     handleCmp(@returnAddress(), arg1, arg2);
     63 }
     64 
     65 export fn __sanitizer_cov_trace_cmp8(arg1: u64, arg2: u64) void {
     66     handleCmp(@returnAddress(), arg1, arg2);
     67 }
     68 
     69 export fn __sanitizer_cov_trace_switch(val: u64, cases_ptr: [*]u64) void {
     70     const pc = @returnAddress();
     71     const len = cases_ptr[0];
     72     const val_size_in_bits = cases_ptr[1];
     73     const cases = cases_ptr[2..][0..len];
     74     fuzzer.traceValue(pc ^ val);
     75     _ = val_size_in_bits;
     76     _ = cases;
     77     //std.log.debug("0x{x}: switch on value {d} ({d} bits) with {d} cases", .{
     78     //    pc, val, val_size_in_bits, cases.len,
     79     //});
     80 }
     81 
     82 export fn __sanitizer_cov_trace_pc_indir(callee: usize) void {
     83     // Not valuable because we already have pc tracing via 8bit counters.
     84     _ = callee;
     85     //const pc = @returnAddress();
     86     //fuzzer.traceValue(pc ^ callee);
     87     //std.log.debug("0x{x}: indirect call to 0x{x}", .{ pc, callee });
     88 }
     89 export fn __sanitizer_cov_8bit_counters_init(start: usize, end: usize) void {
     90     // clang will emit a call to this function when compiling with code coverage instrumentation.
     91     // however fuzzer_init() does not need this information, since it directly reads from the symbol table.
     92     _ = start;
     93     _ = end;
     94 }
     95 export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {
     96     // clang will emit a call to this function when compiling with code coverage instrumentation.
     97     // however fuzzer_init() does not need this information, since it directly reads from the symbol table.
     98     _ = start;
     99     _ = end;
    100 }
    101 
    102 fn handleCmp(pc: usize, arg1: u64, arg2: u64) void {
    103     fuzzer.traceValue(pc ^ arg1 ^ arg2);
    104     //std.log.debug("0x{x}: comparison of {d} and {d}", .{ pc, arg1, arg2 });
    105 }
    106 
    107 const Fuzzer = struct {
    108     rng: std.Random.DefaultPrng,
    109     pcs: []const usize,
    110     pc_counters: []u8,
    111     n_runs: usize,
    112     traced_comparisons: std.AutoArrayHashMapUnmanaged(usize, void),
    113     /// Tracks which PCs have been seen across all runs that do not crash the fuzzer process.
    114     /// Stored in a memory-mapped file so that it can be shared with other
    115     /// processes and viewed while the fuzzer is running.
    116     seen_pcs: MemoryMappedList,
    117     cache_dir: std.fs.Dir,
    118     /// Identifies the file name that will be used to store coverage
    119     /// information, available to other processes.
    120     coverage_id: u64,
    121     unit_test_name: []const u8,
    122 
    123     /// The index corresponds to the file name within the f/ subdirectory.
    124     /// The string is the input.
    125     /// This data is read-only; it caches what is on the filesystem.
    126     corpus: std.ArrayListUnmanaged(Input),
    127     corpus_directory: std.Build.Cache.Directory,
    128 
    129     /// The next input that will be given to the testOne function. When the
    130     /// current process crashes, this memory-mapped file is used to recover the
    131     /// input.
    132     ///
    133     /// The file size corresponds to the capacity. The length is not stored
    134     /// and that is the next thing to work on!
    135     input: MemoryMappedList,
    136 
    137     const Input = struct {
    138         bytes: []u8,
    139         last_traced_comparison: usize,
    140     };
    141 
    142     const Slice = extern struct {
    143         ptr: [*]const u8,
    144         len: usize,
    145 
    146         fn toZig(s: Slice) []const u8 {
    147             return s.ptr[0..s.len];
    148         }
    149 
    150         fn fromZig(s: []const u8) Slice {
    151             return .{
    152                 .ptr = s.ptr,
    153                 .len = s.len,
    154             };
    155         }
    156     };
    157 
    158     fn init(f: *Fuzzer, cache_dir: std.fs.Dir, pc_counters: []u8, pcs: []const usize) !void {
    159         f.cache_dir = cache_dir;
    160         f.pc_counters = pc_counters;
    161         f.pcs = pcs;
    162 
    163         // Choose a file name for the coverage based on a hash of the PCs that will be stored within.
    164         const pc_digest = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(pcs));
    165         f.coverage_id = pc_digest;
    166         const hex_digest = std.fmt.hex(pc_digest);
    167         const coverage_file_path = "v/" ++ hex_digest;
    168 
    169         // Layout of this file:
    170         // - Header
    171         // - list of PC addresses (usize elements)
    172         // - list of hit flag, 1 bit per address (stored in u8 elements)
    173         const coverage_file = createFileBail(cache_dir, coverage_file_path, .{
    174             .read = true,
    175             .truncate = false,
    176         });
    177         const n_bitset_elems = (pcs.len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize);
    178         comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
    179         comptime assert(SeenPcsHeader.trailing[1] == .pc_addr);
    180         const bytes_len = @sizeOf(SeenPcsHeader) +
    181             n_bitset_elems * @sizeOf(usize) +
    182             pcs.len * @sizeOf(usize);
    183         const existing_len = coverage_file.getEndPos() catch |err| {
    184             fatal("unable to check len of coverage file: {s}", .{@errorName(err)});
    185         };
    186         if (existing_len == 0) {
    187             coverage_file.setEndPos(bytes_len) catch |err| {
    188                 fatal("unable to set len of coverage file: {s}", .{@errorName(err)});
    189             };
    190         } else if (existing_len != bytes_len) {
    191             fatal("incompatible existing coverage file (differing lengths)", .{});
    192         }
    193         f.seen_pcs = MemoryMappedList.init(coverage_file, existing_len, bytes_len) catch |err| {
    194             fatal("unable to init coverage memory map: {s}", .{@errorName(err)});
    195         };
    196         if (existing_len != 0) {
    197             const existing_pcs_bytes = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + @sizeOf(usize) * n_bitset_elems ..][0 .. pcs.len * @sizeOf(usize)];
    198             const existing_pcs = std.mem.bytesAsSlice(usize, existing_pcs_bytes);
    199             for (existing_pcs, pcs, 0..) |old, new, i| {
    200                 if (old != new) {
    201                     fatal("incompatible existing coverage file (differing PC at index {d}: {x} != {x})", .{
    202                         i, old, new,
    203                     });
    204                 }
    205             }
    206         } else {
    207             const header: SeenPcsHeader = .{
    208                 .n_runs = 0,
    209                 .unique_runs = 0,
    210                 .pcs_len = pcs.len,
    211             };
    212             f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header));
    213             f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize));
    214             f.seen_pcs.appendSliceAssumeCapacity(std.mem.sliceAsBytes(pcs));
    215         }
    216     }
    217 
    218     fn initNextInput(f: *Fuzzer) void {
    219         while (true) {
    220             const i = f.corpus.items.len;
    221             var buf: [30]u8 = undefined;
    222             const input_sub_path = std.fmt.bufPrint(&buf, "{d}", .{i}) catch unreachable;
    223             const input = f.corpus_directory.handle.readFileAlloc(gpa, input_sub_path, 1 << 31) catch |err| switch (err) {
    224                 error.FileNotFound => {
    225                     // Make this one the next input.
    226                     const input_file = f.corpus_directory.handle.createFile(input_sub_path, .{
    227                         .exclusive = true,
    228                         .truncate = false,
    229                         .read = true,
    230                     }) catch |e| switch (e) {
    231                         error.PathAlreadyExists => continue,
    232                         else => fatal("unable to create '{f}{d}: {s}", .{ f.corpus_directory, i, @errorName(err) }),
    233                     };
    234                     errdefer input_file.close();
    235                     // Initialize the mmap for the current input.
    236                     f.input = MemoryMappedList.create(input_file, 0, std.heap.page_size_max) catch |e| {
    237                         fatal("unable to init memory map for input at '{f}{d}': {s}", .{
    238                             f.corpus_directory, i, @errorName(e),
    239                         });
    240                     };
    241                     break;
    242                 },
    243                 else => fatal("unable to read '{f}{d}': {s}", .{ f.corpus_directory, i, @errorName(err) }),
    244             };
    245             errdefer gpa.free(input);
    246             f.corpus.append(gpa, .{
    247                 .bytes = input,
    248                 .last_traced_comparison = 0,
    249             }) catch |err| oom(err);
    250         }
    251     }
    252 
    253     fn addCorpusElem(f: *Fuzzer, input: []const u8) !void {
    254         try f.corpus.append(gpa, .{
    255             .bytes = try gpa.dupe(u8, input),
    256             .last_traced_comparison = 0,
    257         });
    258     }
    259 
    260     fn start(f: *Fuzzer) !void {
    261         const rng = fuzzer.rng.random();
    262 
    263         // Grab the corpus which is namespaced based on `unit_test_name`.
    264         {
    265             if (f.unit_test_name.len == 0) fatal("test runner never set unit test name", .{});
    266             const sub_path = try std.fmt.allocPrint(gpa, "f/{s}", .{f.unit_test_name});
    267             f.corpus_directory = .{
    268                 .handle = f.cache_dir.makeOpenPath(sub_path, .{}) catch |err|
    269                     fatal("unable to open corpus directory 'f/{s}': {t}", .{ sub_path, err }),
    270                 .path = sub_path,
    271             };
    272             initNextInput(f);
    273         }
    274 
    275         assert(f.n_runs == 0);
    276 
    277         // If the corpus is empty, synthesize one input.
    278         if (f.corpus.items.len == 0) {
    279             const len = rng.uintLessThanBiased(usize, 200);
    280             const slice = try gpa.alloc(u8, len);
    281             rng.bytes(slice);
    282             f.input.appendSliceAssumeCapacity(slice);
    283             try f.corpus.append(gpa, .{
    284                 .bytes = slice,
    285                 .last_traced_comparison = 0,
    286             });
    287             runOne(f, 0);
    288         }
    289 
    290         while (true) {
    291             const chosen_index = rng.uintLessThanBiased(usize, f.corpus.items.len);
    292             const modification = rng.enumValue(Mutation);
    293             f.mutateAndRunOne(chosen_index, modification);
    294         }
    295     }
    296 
    297     /// `x` represents a possible branch. It is the PC address of the possible
    298     /// branch site, hashed together with the value(s) used that determine to
    299     /// where it branches.
    300     fn traceValue(f: *Fuzzer, x: usize) void {
    301         errdefer |err| oom(err);
    302         try f.traced_comparisons.put(gpa, x, {});
    303     }
    304 
    305     const Mutation = enum {
    306         remove_byte,
    307         modify_byte,
    308         add_byte,
    309     };
    310 
    311     fn mutateAndRunOne(f: *Fuzzer, corpus_index: usize, mutation: Mutation) void {
    312         const rng = fuzzer.rng.random();
    313         f.input.clearRetainingCapacity();
    314         const old_input = f.corpus.items[corpus_index].bytes;
    315         f.input.ensureTotalCapacity(old_input.len + 1) catch @panic("mmap file resize failed");
    316         switch (mutation) {
    317             .remove_byte => {
    318                 const omitted_index = rng.uintLessThanBiased(usize, old_input.len);
    319                 f.input.appendSliceAssumeCapacity(old_input[0..omitted_index]);
    320                 f.input.appendSliceAssumeCapacity(old_input[omitted_index + 1 ..]);
    321             },
    322             .modify_byte => {
    323                 const modified_index = rng.uintLessThanBiased(usize, old_input.len);
    324                 f.input.appendSliceAssumeCapacity(old_input);
    325                 f.input.items[modified_index] = rng.int(u8);
    326             },
    327             .add_byte => {
    328                 const modified_index = rng.uintLessThanBiased(usize, old_input.len);
    329                 f.input.appendSliceAssumeCapacity(old_input[0..modified_index]);
    330                 f.input.appendAssumeCapacity(rng.int(u8));
    331                 f.input.appendSliceAssumeCapacity(old_input[modified_index..]);
    332             },
    333         }
    334         runOne(f, corpus_index);
    335     }
    336 
    337     fn runOne(f: *Fuzzer, corpus_index: usize) void {
    338         const header: *volatile SeenPcsHeader = @ptrCast(f.seen_pcs.items[0..@sizeOf(SeenPcsHeader)]);
    339 
    340         f.traced_comparisons.clearRetainingCapacity();
    341         @memset(f.pc_counters, 0);
    342         __sancov_lowest_stack = std.math.maxInt(usize);
    343 
    344         fuzzer_one(@volatileCast(f.input.items.ptr), f.input.items.len);
    345 
    346         f.n_runs += 1;
    347         _ = @atomicRmw(usize, &header.n_runs, .Add, 1, .monotonic);
    348 
    349         // Track code coverage from all runs.
    350         comptime assert(SeenPcsHeader.trailing[0] == .pc_bits_usize);
    351         const header_end_ptr: [*]volatile usize = @ptrCast(f.seen_pcs.items[@sizeOf(SeenPcsHeader)..]);
    352         const remainder = f.pcs.len % @bitSizeOf(usize);
    353         const aligned_len = f.pcs.len - remainder;
    354         const seen_pcs = header_end_ptr[0..aligned_len];
    355         const pc_counters = std.mem.bytesAsSlice([@bitSizeOf(usize)]u8, f.pc_counters[0..aligned_len]);
    356         const V = @Vector(@bitSizeOf(usize), u8);
    357         const zero_v: V = @splat(0);
    358         var fresh = false;
    359         var superset = true;
    360 
    361         for (header_end_ptr[0..pc_counters.len], pc_counters) |*elem, *array| {
    362             const v: V = array.*;
    363             const mask: usize = @bitCast(v != zero_v);
    364             const prev = @atomicRmw(usize, elem, .Or, mask, .monotonic);
    365             fresh = fresh or (prev | mask) != prev;
    366             superset = superset and (prev | mask) != mask;
    367         }
    368         if (remainder > 0) {
    369             const i = pc_counters.len;
    370             const elem = &seen_pcs[i];
    371             var mask: usize = 0;
    372             for (f.pc_counters[i * @bitSizeOf(usize) ..][0..remainder], 0..) |byte, bit_index| {
    373                 mask |= @as(usize, @intFromBool(byte != 0)) << @intCast(bit_index);
    374             }
    375             const prev = @atomicRmw(usize, elem, .Or, mask, .monotonic);
    376             fresh = fresh or (prev | mask) != prev;
    377             superset = superset and (prev | mask) != mask;
    378         }
    379 
    380         // First check if this is a better version of an already existing
    381         // input, replacing that input.
    382         if (superset or f.traced_comparisons.entries.len >= f.corpus.items[corpus_index].last_traced_comparison) {
    383             const new_input = gpa.realloc(f.corpus.items[corpus_index].bytes, f.input.items.len) catch |err| oom(err);
    384             f.corpus.items[corpus_index] = .{
    385                 .bytes = new_input,
    386                 .last_traced_comparison = f.traced_comparisons.count(),
    387             };
    388             @memcpy(new_input, @volatileCast(f.input.items));
    389             _ = @atomicRmw(usize, &header.unique_runs, .Add, 1, .monotonic);
    390             return;
    391         }
    392 
    393         if (!fresh) return;
    394 
    395         // Input is already committed to the file system, we just need to open a new file
    396         // for the next input.
    397         // Pre-add it to the corpus list so that it does not get redundantly picked up.
    398         f.corpus.append(gpa, .{
    399             .bytes = gpa.dupe(u8, @volatileCast(f.input.items)) catch |err| oom(err),
    400             .last_traced_comparison = f.traced_comparisons.entries.len,
    401         }) catch |err| oom(err);
    402         f.input.deinit();
    403         initNextInput(f);
    404 
    405         // TODO: also mark input as "hot" so it gets prioritized for checking mutations above others.
    406 
    407         _ = @atomicRmw(usize, &header.unique_runs, .Add, 1, .monotonic);
    408     }
    409 };
    410 
    411 fn createFileBail(dir: std.fs.Dir, sub_path: []const u8, flags: std.fs.File.CreateFlags) std.fs.File {
    412     return dir.createFile(sub_path, flags) catch |err| switch (err) {
    413         error.FileNotFound => {
    414             const dir_name = std.fs.path.dirname(sub_path).?;
    415             dir.makePath(dir_name) catch |e| {
    416                 fatal("unable to make path '{s}': {s}", .{ dir_name, @errorName(e) });
    417             };
    418             return dir.createFile(sub_path, flags) catch |e| {
    419                 fatal("unable to create file '{s}': {s}", .{ sub_path, @errorName(e) });
    420             };
    421         },
    422         else => fatal("unable to create file '{s}': {s}", .{ sub_path, @errorName(err) }),
    423     };
    424 }
    425 
    426 fn oom(err: anytype) noreturn {
    427     switch (err) {
    428         error.OutOfMemory => @panic("out of memory"),
    429     }
    430 }
    431 
    432 var debug_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
    433 
    434 const gpa = switch (builtin.mode) {
    435     .Debug => debug_allocator.allocator(),
    436     .ReleaseFast, .ReleaseSmall, .ReleaseSafe => std.heap.smp_allocator,
    437 };
    438 
    439 var fuzzer: Fuzzer = .{
    440     .rng = std.Random.DefaultPrng.init(0),
    441     .input = undefined,
    442     .pcs = undefined,
    443     .pc_counters = undefined,
    444     .n_runs = 0,
    445     .cache_dir = undefined,
    446     .seen_pcs = undefined,
    447     .coverage_id = undefined,
    448     .unit_test_name = &.{},
    449     .corpus = .empty,
    450     .corpus_directory = undefined,
    451     .traced_comparisons = .empty,
    452 };
    453 
    454 /// Invalid until `fuzzer_init` is called.
    455 export fn fuzzer_coverage_id() u64 {
    456     return fuzzer.coverage_id;
    457 }
    458 
    459 var fuzzer_one: *const fn (input_ptr: [*]const u8, input_len: usize) callconv(.c) void = undefined;
    460 
    461 export fn fuzzer_start(testOne: @TypeOf(fuzzer_one)) void {
    462     fuzzer_one = testOne;
    463     fuzzer.start() catch |err| oom(err);
    464 }
    465 
    466 export fn fuzzer_set_name(name_ptr: [*]const u8, name_len: usize) void {
    467     fuzzer.unit_test_name = name_ptr[0..name_len];
    468 }
    469 
    470 export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void {
    471     // Linkers are expected to automatically add `__start_<section>` and
    472     // `__stop_<section>` symbols when section names are valid C identifiers.
    473 
    474     const ofmt = builtin.object_format;
    475 
    476     const start_symbol_prefix: []const u8 = if (ofmt == .macho)
    477         "\x01section$start$__DATA$__"
    478     else
    479         "__start___";
    480     const end_symbol_prefix: []const u8 = if (ofmt == .macho)
    481         "\x01section$end$__DATA$__"
    482     else
    483         "__stop___";
    484 
    485     const pc_counters_start_name = start_symbol_prefix ++ "sancov_cntrs";
    486     const pc_counters_start = @extern([*]u8, .{
    487         .name = pc_counters_start_name,
    488         .linkage = .weak,
    489     }) orelse fatal("missing {s} symbol", .{pc_counters_start_name});
    490 
    491     const pc_counters_end_name = end_symbol_prefix ++ "sancov_cntrs";
    492     const pc_counters_end = @extern([*]u8, .{
    493         .name = pc_counters_end_name,
    494         .linkage = .weak,
    495     }) orelse fatal("missing {s} symbol", .{pc_counters_end_name});
    496 
    497     const pc_counters = pc_counters_start[0 .. pc_counters_end - pc_counters_start];
    498 
    499     const pcs_start_name = start_symbol_prefix ++ "sancov_pcs1";
    500     const pcs_start = @extern([*]usize, .{
    501         .name = pcs_start_name,
    502         .linkage = .weak,
    503     }) orelse fatal("missing {s} symbol", .{pcs_start_name});
    504 
    505     const pcs_end_name = end_symbol_prefix ++ "sancov_pcs1";
    506     const pcs_end = @extern([*]usize, .{
    507         .name = pcs_end_name,
    508         .linkage = .weak,
    509     }) orelse fatal("missing {s} symbol", .{pcs_end_name});
    510 
    511     const pcs = pcs_start[0 .. pcs_end - pcs_start];
    512 
    513     const cache_dir_path = cache_dir_struct.toZig();
    514     const cache_dir = if (cache_dir_path.len == 0)
    515         std.fs.cwd()
    516     else
    517         std.fs.cwd().makeOpenPath(cache_dir_path, .{ .iterate = true }) catch |err| {
    518             fatal("unable to open fuzz directory '{s}': {s}", .{ cache_dir_path, @errorName(err) });
    519         };
    520 
    521     fuzzer.init(cache_dir, pc_counters, pcs) catch |err|
    522         fatal("unable to init fuzzer: {s}", .{@errorName(err)});
    523 }
    524 
    525 export fn fuzzer_init_corpus_elem(input_ptr: [*]const u8, input_len: usize) void {
    526     fuzzer.addCorpusElem(input_ptr[0..input_len]) catch |err|
    527         fatal("failed to add corpus element: {s}", .{@errorName(err)});
    528 }
    529 
    530 /// Like `std.ArrayListUnmanaged(u8)` but backed by memory mapping.
    531 pub const MemoryMappedList = struct {
    532     /// Contents of the list.
    533     ///
    534     /// Pointers to elements in this slice are invalidated by various functions
    535     /// of this ArrayList in accordance with the respective documentation. In
    536     /// all cases, "invalidated" means that the memory has been passed to this
    537     /// allocator's resize or free function.
    538     items: []align(std.heap.page_size_min) volatile u8,
    539     /// How many bytes this list can hold without allocating additional memory.
    540     capacity: usize,
    541     /// The file is kept open so that it can be resized.
    542     file: std.fs.File,
    543 
    544     pub fn init(file: std.fs.File, length: usize, capacity: usize) !MemoryMappedList {
    545         const ptr = try std.posix.mmap(
    546             null,
    547             capacity,
    548             std.posix.PROT.READ | std.posix.PROT.WRITE,
    549             .{ .TYPE = .SHARED },
    550             file.handle,
    551             0,
    552         );
    553         return .{
    554             .file = file,
    555             .items = ptr[0..length],
    556             .capacity = capacity,
    557         };
    558     }
    559 
    560     pub fn create(file: std.fs.File, length: usize, capacity: usize) !MemoryMappedList {
    561         try file.setEndPos(capacity);
    562         return init(file, length, capacity);
    563     }
    564 
    565     pub fn deinit(l: *MemoryMappedList) void {
    566         l.file.close();
    567         std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));
    568         l.* = undefined;
    569     }
    570 
    571     /// Modify the array so that it can hold at least `additional_count` **more** items.
    572     /// Invalidates element pointers if additional memory is needed.
    573     pub fn ensureUnusedCapacity(l: *MemoryMappedList, additional_count: usize) !void {
    574         return l.ensureTotalCapacity(l.items.len + additional_count);
    575     }
    576 
    577     /// If the current capacity is less than `new_capacity`, this function will
    578     /// modify the array so that it can hold at least `new_capacity` items.
    579     /// Invalidates element pointers if additional memory is needed.
    580     pub fn ensureTotalCapacity(l: *MemoryMappedList, new_capacity: usize) !void {
    581         if (l.capacity >= new_capacity) return;
    582 
    583         const better_capacity = growCapacity(l.capacity, new_capacity);
    584         return l.ensureTotalCapacityPrecise(better_capacity);
    585     }
    586 
    587     pub fn ensureTotalCapacityPrecise(l: *MemoryMappedList, new_capacity: usize) !void {
    588         if (l.capacity >= new_capacity) return;
    589 
    590         std.posix.munmap(@volatileCast(l.items.ptr[0..l.capacity]));
    591         try l.file.setEndPos(new_capacity);
    592         l.* = try init(l.file, l.items.len, new_capacity);
    593     }
    594 
    595     /// Invalidates all element pointers.
    596     pub fn clearRetainingCapacity(l: *MemoryMappedList) void {
    597         l.items.len = 0;
    598     }
    599 
    600     /// Append the slice of items to the list.
    601     /// Asserts that the list can hold the additional items.
    602     pub fn appendSliceAssumeCapacity(l: *MemoryMappedList, items: []const u8) void {
    603         const old_len = l.items.len;
    604         const new_len = old_len + items.len;
    605         assert(new_len <= l.capacity);
    606         l.items.len = new_len;
    607         @memcpy(l.items[old_len..][0..items.len], items);
    608     }
    609 
    610     /// Extends the list by 1 element.
    611     /// Never invalidates element pointers.
    612     /// Asserts that the list can hold one additional item.
    613     pub fn appendAssumeCapacity(l: *MemoryMappedList, item: u8) void {
    614         const new_item_ptr = l.addOneAssumeCapacity();
    615         new_item_ptr.* = item;
    616     }
    617 
    618     /// Increase length by 1, returning pointer to the new item.
    619     /// The returned pointer becomes invalid when the list is resized.
    620     /// Never invalidates element pointers.
    621     /// Asserts that the list can hold one additional item.
    622     pub fn addOneAssumeCapacity(l: *MemoryMappedList) *volatile u8 {
    623         assert(l.items.len < l.capacity);
    624         l.items.len += 1;
    625         return &l.items[l.items.len - 1];
    626     }
    627 
    628     /// Append a value to the list `n` times.
    629     /// Never invalidates element pointers.
    630     /// The function is inline so that a comptime-known `value` parameter will
    631     /// have better memset codegen in case it has a repeated byte pattern.
    632     /// Asserts that the list can hold the additional items.
    633     pub inline fn appendNTimesAssumeCapacity(l: *MemoryMappedList, value: u8, n: usize) void {
    634         const new_len = l.items.len + n;
    635         assert(new_len <= l.capacity);
    636         @memset(l.items.ptr[l.items.len..new_len], value);
    637         l.items.len = new_len;
    638     }
    639 
    640     /// Resize the array, adding `n` new elements, which have `undefined` values.
    641     /// The return value is a slice pointing to the newly allocated elements.
    642     /// Never invalidates element pointers.
    643     /// The returned pointer becomes invalid when the list is resized.
    644     /// Asserts that the list can hold the additional items.
    645     pub fn addManyAsSliceAssumeCapacity(l: *MemoryMappedList, n: usize) []volatile u8 {
    646         assert(l.items.len + n <= l.capacity);
    647         const prev_len = l.items.len;
    648         l.items.len += n;
    649         return l.items[prev_len..][0..n];
    650     }
    651 
    652     /// Called when memory growth is necessary. Returns a capacity larger than
    653     /// minimum that grows super-linearly.
    654     fn growCapacity(current: usize, minimum: usize) usize {
    655         var new = current;
    656         while (true) {
    657             new = std.mem.alignForward(usize, new + new / 2, std.heap.page_size_max);
    658             if (new >= minimum) return new;
    659         }
    660     }
    661 };