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}