master
  1const std = @import("std");
  2const builtin = @import("builtin");
  3const windows = std.os.windows;
  4const Allocator = std.mem.Allocator;
  5
  6pub fn main() !void {
  7    var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
  8    defer std.debug.assert(gpa.deinit() == .ok);
  9    const allocator = gpa.allocator();
 10
 11    const args = try std.process.argsAlloc(allocator);
 12    defer std.process.argsFree(allocator, args);
 13
 14    if (args.len < 2) return error.MissingArgs;
 15
 16    const verify_path_wtf8 = args[1];
 17    const verify_path_w = try std.unicode.wtf8ToWtf16LeAllocZ(allocator, verify_path_wtf8);
 18    defer allocator.free(verify_path_w);
 19
 20    const iterations: u64 = iterations: {
 21        if (args.len < 3) break :iterations 0;
 22        break :iterations try std.fmt.parseUnsigned(u64, args[2], 10);
 23    };
 24
 25    var rand_seed = false;
 26    const seed: u64 = seed: {
 27        if (args.len < 4) {
 28            rand_seed = true;
 29            var buf: [8]u8 = undefined;
 30            try std.posix.getrandom(&buf);
 31            break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian());
 32        }
 33        break :seed try std.fmt.parseUnsigned(u64, args[3], 10);
 34    };
 35    var random = std.Random.DefaultPrng.init(seed);
 36    const rand = random.random();
 37
 38    // If the seed was not given via the CLI, then output the
 39    // randomly chosen seed so that this run can be reproduced
 40    if (rand_seed) {
 41        std.debug.print("rand seed: {}\n", .{seed});
 42    }
 43
 44    var cmd_line_w_buf = std.array_list.Managed(u16).init(allocator);
 45    defer cmd_line_w_buf.deinit();
 46
 47    var i: u64 = 0;
 48    var errors: u64 = 0;
 49    while (iterations == 0 or i < iterations) {
 50        const cmd_line_w = try randomCommandLineW(allocator, rand);
 51        defer allocator.free(cmd_line_w);
 52
 53        // avoid known difference for 0-length command lines
 54        if (cmd_line_w.len == 0 or cmd_line_w[0] == '\x00') continue;
 55
 56        const exit_code = try spawnVerify(verify_path_w, cmd_line_w);
 57        if (exit_code != 0) {
 58            std.debug.print(">>> found discrepancy <<<\n", .{});
 59            const cmd_line_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, cmd_line_w);
 60            defer allocator.free(cmd_line_wtf8);
 61            std.debug.print("\"{f}\"\n\n", .{std.zig.fmtString(cmd_line_wtf8)});
 62
 63            errors += 1;
 64        }
 65
 66        i += 1;
 67    }
 68    if (errors > 0) {
 69        // we never get here if iterations is 0 so we don't have to worry about that case
 70        std.debug.print("found {} discrepancies in {} iterations\n", .{ errors, iterations });
 71        return error.FoundDiscrepancies;
 72    }
 73}
 74
 75fn randomCommandLineW(allocator: Allocator, rand: std.Random) ![:0]const u16 {
 76    const Choice = enum {
 77        backslash,
 78        quote,
 79        space,
 80        tab,
 81        control,
 82        printable,
 83        non_ascii,
 84    };
 85
 86    const choices = rand.uintAtMostBiased(u16, 256);
 87    var buf = try std.array_list.Managed(u16).initCapacity(allocator, choices);
 88    errdefer buf.deinit();
 89
 90    for (0..choices) |_| {
 91        const choice = rand.enumValue(Choice);
 92        const code_unit = switch (choice) {
 93            .backslash => '\\',
 94            .quote => '"',
 95            .space => ' ',
 96            .tab => '\t',
 97            .control => switch (rand.uintAtMostBiased(u8, 0x21)) {
 98                0x21 => '\x7F',
 99                else => |b| b,
100            },
101            .printable => '!' + rand.uintAtMostBiased(u8, '~' - '!'),
102            .non_ascii => rand.intRangeAtMostBiased(u16, 0x80, 0xFFFF),
103        };
104        try buf.append(std.mem.nativeToLittle(u16, code_unit));
105    }
106
107    return buf.toOwnedSliceSentinel(0);
108}
109
110/// Returns the exit code of the verify process
111fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWORD {
112    const child_proc = spawn: {
113        var startup_info: windows.STARTUPINFOW = .{
114            .cb = @sizeOf(windows.STARTUPINFOW),
115            .lpReserved = null,
116            .lpDesktop = null,
117            .lpTitle = null,
118            .dwX = 0,
119            .dwY = 0,
120            .dwXSize = 0,
121            .dwYSize = 0,
122            .dwXCountChars = 0,
123            .dwYCountChars = 0,
124            .dwFillAttribute = 0,
125            .dwFlags = windows.STARTF_USESTDHANDLES,
126            .wShowWindow = 0,
127            .cbReserved2 = 0,
128            .lpReserved2 = null,
129            .hStdInput = null,
130            .hStdOutput = null,
131            .hStdError = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null,
132        };
133        var proc_info: windows.PROCESS_INFORMATION = undefined;
134
135        try windows.CreateProcessW(
136            @constCast(verify_path.ptr),
137            @constCast(cmd_line.ptr),
138            null,
139            null,
140            windows.TRUE,
141            .{},
142            null,
143            null,
144            &startup_info,
145            &proc_info,
146        );
147        windows.CloseHandle(proc_info.hThread);
148
149        break :spawn proc_info.hProcess;
150    };
151    defer windows.CloseHandle(child_proc);
152    try windows.WaitForSingleObjectEx(child_proc, windows.INFINITE, false);
153
154    var exit_code: windows.DWORD = undefined;
155    if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) {
156        return error.UnableToGetExitCode;
157    }
158    return exit_code;
159}