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