1const std = @import("std");
2const builtin = @import("builtin");
3const windows = std.os.windows;
4const Allocator = std.mem.Allocator;
5
6pub 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
72fn 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
108fn 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}