authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2024-07-15 12:03:25+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-07-15 12:03:25+03:00
log5675553aedf72c3cf6135025fe216fb92f09f3fa
tree89ac98703b7d5266dfcfa7f6895cb3631bb72186
parentc50f3003874d561aa696feb71f2089562307cdca
parentd48251d0f0d2de2f618afe65db3ba425b332d5c4
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #20622 from squeek502/windows-arg-iterator-cleanup

`ArgIteratorWindows`: Cleanup and some optimizations

2 files changed, 62 insertions(+), 57 deletions(-)

lib/std/os/windows/kernel32.zig-3
...@@ -161,9 +161,6 @@ pub extern "kernel32" fn FormatMessageW(dwFlags: DWORD, lpSource: ?LPVOID, dwMes...@@ -161,9 +161,6 @@ pub extern "kernel32" fn FormatMessageW(dwFlags: DWORD, lpSource: ?LPVOID, dwMes
161161
162pub extern "kernel32" fn FreeEnvironmentStringsW(penv: [*:0]u16) callconv(WINAPI) BOOL;162pub extern "kernel32" fn FreeEnvironmentStringsW(penv: [*:0]u16) callconv(WINAPI) BOOL;
163163
164pub extern "kernel32" fn GetCommandLineA() callconv(WINAPI) LPSTR;
165pub extern "kernel32" fn GetCommandLineW() callconv(WINAPI) LPWSTR;
166
167pub extern "kernel32" fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) callconv(WINAPI) BOOL;164pub extern "kernel32" fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) callconv(WINAPI) BOOL;
168pub extern "kernel32" fn SetConsoleMode(in_hConsoleHandle: HANDLE, in_dwMode: DWORD) callconv(WINAPI) BOOL;165pub extern "kernel32" fn SetConsoleMode(in_hConsoleHandle: HANDLE, in_dwMode: DWORD) callconv(WINAPI) BOOL;
169166
lib/std/process.zig+62-54
...@@ -664,7 +664,7 @@ pub const ArgIteratorWasi = struct {...@@ -664,7 +664,7 @@ pub const ArgIteratorWasi = struct {
664pub const ArgIteratorWindows = struct {664pub const ArgIteratorWindows = struct {
665 allocator: Allocator,665 allocator: Allocator,
666 /// Encoded as WTF-16 LE.666 /// Encoded as WTF-16 LE.
667 cmd_line: [:0]const u16,667 cmd_line: []const u16,
668 index: usize = 0,668 index: usize = 0,
669 /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices669 /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices
670 /// of each argument encoded as WTF-8.670 /// of each argument encoded as WTF-8.
...@@ -678,21 +678,22 @@ pub const ArgIteratorWindows = struct {...@@ -678,21 +678,22 @@ pub const ArgIteratorWindows = struct {
678 ///678 ///
679 /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for679 /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for
680 /// at least as long as the returned ArgIteratorWindows.680 /// at least as long as the returned ArgIteratorWindows.
681 pub fn init(allocator: Allocator, cmd_line_w: [*:0]const u16) InitError!ArgIteratorWindows {681 pub fn init(allocator: Allocator, cmd_line_w: []const u16) InitError!ArgIteratorWindows {
682 const cmd_line = mem.sliceTo(cmd_line_w, 0);682 const wtf8_len = unicode.calcWtf8Len(cmd_line_w);
683 const wtf8_len = unicode.calcWtf8Len(cmd_line);
684683
685 // This buffer must be large enough to contain contiguous NUL-terminated slices684 // This buffer must be large enough to contain contiguous NUL-terminated slices
686 // of each argument. For arguments past the first one, space for the NUL-terminator685 // of each argument.
687 // is guaranteed due to the necessary whitespace between arugments. However, we need686 // - During parsing, the length of a parsed argument will always be equal to
688 // one extra byte to guarantee enough room for the NUL terminator if the command line687 // to less than its unparsed length
689 // ends up being exactly 1 argument long with no quotes, etc.688 // - The first argument needs one extra byte of space allocated for its NUL
689 // terminator, but for each subsequent argument the necessary whitespace
690 // between arguments guarantees room for their NUL terminator(s).
690 const buffer = try allocator.alloc(u8, wtf8_len + 1);691 const buffer = try allocator.alloc(u8, wtf8_len + 1);
691 errdefer allocator.free(buffer);692 errdefer allocator.free(buffer);
692693
693 return .{694 return .{
694 .allocator = allocator,695 .allocator = allocator,
695 .cmd_line = cmd_line,696 .cmd_line = cmd_line_w,
696 .buffer = buffer,697 .buffer = buffer,
697 };698 };
698 }699 }
...@@ -715,14 +716,20 @@ pub const ArgIteratorWindows = struct {...@@ -715,14 +716,20 @@ pub const ArgIteratorWindows = struct {
715716
716 const eof = null;717 const eof = null;
717718
718 fn emitBackslashes(self: *ArgIteratorWindows, count: usize) void {719 /// Returns '\' if any backslashes are emitted, otherwise returns `last_emitted_code_unit`.
719 for (0..count) |_| emitCharacter(self, '\\');720 fn emitBackslashes(self: *ArgIteratorWindows, count: usize, last_emitted_code_unit: ?u16) ?u16 {
721 for (0..count) |_| {
722 self.buffer[self.end] = '\\';
723 self.end += 1;
724 }
725 return if (count != 0) '\\' else last_emitted_code_unit;
720 }726 }
721727
722 fn emitCharacter(self: *ArgIteratorWindows, code_unit: u16) void {728 /// If `last_emitted_code_unit` and `code_unit` form a surrogate pair, then
723 const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable;729 /// the previously emitted high surrogate is overwritten by the codepoint encoded
724 self.end += wtf8_len;730 /// by the surrogate pair, and `null` is returned.
725731 /// Otherwise, `code_unit` is emitted and returned.
732 fn emitCharacter(self: *ArgIteratorWindows, code_unit: u16, last_emitted_code_unit: ?u16) ?u16 {
726 // Because we are emitting WTF-8, we need to733 // Because we are emitting WTF-8, we need to
727 // check to see if we've emitted two consecutive surrogate734 // check to see if we've emitted two consecutive surrogate
728 // codepoints that form a valid surrogate pair in order735 // codepoints that form a valid surrogate pair in order
...@@ -743,28 +750,24 @@ pub const ArgIteratorWindows = struct {...@@ -743,28 +750,24 @@ pub const ArgIteratorWindows = struct {
743 // and emit the codepoint it encodes, which in this750 // and emit the codepoint it encodes, which in this
744 // example is U+10437 (𐐷), which is encoded in UTF-8 as:751 // example is U+10437 (𐐷), which is encoded in UTF-8 as:
745 // <0xF0><0x90><0x90><0xB7>752 // <0xF0><0x90><0x90><0xB7>
746 concatSurrogatePair(self);753 if (last_emitted_code_unit != null and
747 }754 std.unicode.utf16IsLowSurrogate(code_unit) and
748755 std.unicode.utf16IsHighSurrogate(last_emitted_code_unit.?))
749 fn concatSurrogatePair(self: *ArgIteratorWindows) void {756 {
750 // Surrogate codepoints are always encoded as 3 bytes, so there757 const codepoint = std.unicode.utf16DecodeSurrogatePair(&.{ last_emitted_code_unit.?, code_unit }) catch unreachable;
751 // must be 6 bytes for a surrogate pair to exist.758
752 if (self.end - self.start >= 6) {759 // Unpaired surrogate is 3 bytes long
753 const window = self.buffer[self.end - 6 .. self.end];760 const dest = self.buffer[self.end - 3 ..];
754 const view = unicode.Wtf8View.init(window) catch return;761 const len = unicode.utf8Encode(codepoint, dest) catch unreachable;
755 var it = view.iterator();762 // All codepoints that require a surrogate pair (> U+FFFF) are encoded as 4 bytes
756 var pair: [2]u16 = undefined;763 assert(len == 4);
757 pair[0] = std.mem.nativeToLittle(u16, std.math.cast(u16, it.nextCodepoint().?) orelse return);764 self.end += 1;
758 if (!unicode.utf16IsHighSurrogate(std.mem.littleToNative(u16, pair[0]))) return;765 return null;
759 pair[1] = std.mem.nativeToLittle(u16, std.math.cast(u16, it.nextCodepoint().?) orelse return);
760 if (!unicode.utf16IsLowSurrogate(std.mem.littleToNative(u16, pair[1]))) return;
761 // We know we have a valid surrogate pair, so convert
762 // it to UTF-8, overwriting the surrogate pair's bytes
763 // and then chop off the extra bytes.
764 const len = unicode.utf16LeToUtf8(window, &pair) catch unreachable;
765 const delta = 6 - len;
766 self.end -= delta;
767 }766 }
767
768 const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable;
769 self.end += wtf8_len;
770 return code_unit;
768 }771 }
769772
770 fn yieldArg(self: *ArgIteratorWindows) [:0]const u8 {773 fn yieldArg(self: *ArgIteratorWindows) [:0]const u8 {
...@@ -781,9 +784,13 @@ pub const ArgIteratorWindows = struct {...@@ -781,9 +784,13 @@ pub const ArgIteratorWindows = struct {
781784
782 const eof = false;785 const eof = false;
783786
784 fn emitBackslashes(_: *ArgIteratorWindows, _: usize) void {}787 fn emitBackslashes(_: *ArgIteratorWindows, _: usize, last_emitted_code_unit: ?u16) ?u16 {
788 return last_emitted_code_unit;
789 }
785790
786 fn emitCharacter(_: *ArgIteratorWindows, _: u16) void {}791 fn emitCharacter(_: *ArgIteratorWindows, _: u16, last_emitted_code_unit: ?u16) ?u16 {
792 return last_emitted_code_unit;
793 }
787794
788 fn yieldArg(_: *ArgIteratorWindows) bool {795 fn yieldArg(_: *ArgIteratorWindows) bool {
789 return true;796 return true;
...@@ -791,6 +798,7 @@ pub const ArgIteratorWindows = struct {...@@ -791,6 +798,7 @@ pub const ArgIteratorWindows = struct {
791 };798 };
792799
793 fn nextWithStrategy(self: *ArgIteratorWindows, comptime strategy: type) strategy.T {800 fn nextWithStrategy(self: *ArgIteratorWindows, comptime strategy: type) strategy.T {
801 var last_emitted_code_unit: ?u16 = null;
794 // The first argument (the executable name) uses different parsing rules.802 // The first argument (the executable name) uses different parsing rules.
795 if (self.index == 0) {803 if (self.index == 0) {
796 if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) {804 if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) {
...@@ -813,15 +821,15 @@ pub const ArgIteratorWindows = struct {...@@ -813,15 +821,15 @@ pub const ArgIteratorWindows = struct {
813 inside_quotes = !inside_quotes;821 inside_quotes = !inside_quotes;
814 },822 },
815 ' ', '\t' => {823 ' ', '\t' => {
816 if (inside_quotes)824 if (inside_quotes) {
817 strategy.emitCharacter(self, char)825 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
818 else {826 } else {
819 self.index += 1;827 self.index += 1;
820 return strategy.yieldArg(self);828 return strategy.yieldArg(self);
821 }829 }
822 },830 },
823 else => {831 else => {
824 strategy.emitCharacter(self, char);832 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
825 },833 },
826 }834 }
827 }835 }
...@@ -859,29 +867,28 @@ pub const ArgIteratorWindows = struct {...@@ -859,29 +867,28 @@ pub const ArgIteratorWindows = struct {
859 0;867 0;
860 switch (char) {868 switch (char) {
861 0 => {869 0 => {
862 strategy.emitBackslashes(self, backslash_count);870 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
863 return strategy.yieldArg(self);871 return strategy.yieldArg(self);
864 },872 },
865 ' ', '\t' => {873 ' ', '\t' => {
866 strategy.emitBackslashes(self, backslash_count);874 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
867 backslash_count = 0;875 backslash_count = 0;
868 if (inside_quotes)876 if (inside_quotes) {
869 strategy.emitCharacter(self, char)877 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
870 else878 } else return strategy.yieldArg(self);
871 return strategy.yieldArg(self);
872 },879 },
873 '"' => {880 '"' => {
874 const char_is_escaped_quote = backslash_count % 2 != 0;881 const char_is_escaped_quote = backslash_count % 2 != 0;
875 strategy.emitBackslashes(self, backslash_count / 2);882 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count / 2, last_emitted_code_unit);
876 backslash_count = 0;883 backslash_count = 0;
877 if (char_is_escaped_quote) {884 if (char_is_escaped_quote) {
878 strategy.emitCharacter(self, '"');885 last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
879 } else {886 } else {
880 if (inside_quotes and887 if (inside_quotes and
881 self.index + 1 != self.cmd_line.len and888 self.index + 1 != self.cmd_line.len and
882 mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"')889 mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"')
883 {890 {
884 strategy.emitCharacter(self, '"');891 last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
885 self.index += 1;892 self.index += 1;
886 } else {893 } else {
887 inside_quotes = !inside_quotes;894 inside_quotes = !inside_quotes;
...@@ -892,9 +899,9 @@ pub const ArgIteratorWindows = struct {...@@ -892,9 +899,9 @@ pub const ArgIteratorWindows = struct {
892 backslash_count += 1;899 backslash_count += 1;
893 },900 },
894 else => {901 else => {
895 strategy.emitBackslashes(self, backslash_count);902 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
896 backslash_count = 0;903 backslash_count = 0;
897 strategy.emitCharacter(self, char);904 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
898 },905 },
899 }906 }
900 }907 }
...@@ -1142,7 +1149,8 @@ pub const ArgIterator = struct {...@@ -1142,7 +1149,8 @@ pub const ArgIterator = struct {
1142 return ArgIterator{ .inner = try InnerType.init(allocator) };1149 return ArgIterator{ .inner = try InnerType.init(allocator) };
1143 }1150 }
1144 if (native_os == .windows) {1151 if (native_os == .windows) {
1145 const cmd_line_w = windows.kernel32.GetCommandLineW();1152 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
1153 const cmd_line_w = cmd_line.Buffer.?[0 .. cmd_line.Length / 2];
1146 return ArgIterator{ .inner = try InnerType.init(allocator, cmd_line_w) };1154 return ArgIterator{ .inner = try InnerType.init(allocator, cmd_line_w) };
1147 }1155 }
11481156