authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-05-30 13:22:13+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-05-30 13:22:13+02:00
log76aa1fffb7a06f0be0d803cb3379f3102c0b2590
treeabcf7206b7fe0c4619dc98ff3c87273e9adcc754
parent1ab008d89dbc18cd79abed88f306ad9bd0397622
parent28df1d09dc8c5e79db3ddc4b3da701d151f09e0c
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #15905 from jacobly0/x86_64-hotfix

x86_64: hotfix for crash during in-memory coercion of large type

6 files changed, 455 insertions(+), 97 deletions(-)

lib/std/child_process.zig+3-6
...@@ -957,15 +957,12 @@ fn windowsCreateProcessPathExt(...@@ -957,15 +957,12 @@ fn windowsCreateProcessPathExt(
957 // NtQueryDirectoryFile calls.957 // NtQueryDirectoryFile calls.
958958
959 var dir = dir: {959 var dir = dir: {
960 if (fs.path.isAbsoluteWindowsWTF16(dir_buf.items[0..dir_path_len])) {
961 const prefixed_path = try windows.wToPrefixedFileW(dir_buf.items[0..dir_path_len]);
962 break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{}, true) catch return error.FileNotFound;
963 }
964 // needs to be null-terminated960 // needs to be null-terminated
965 try dir_buf.append(allocator, 0);961 try dir_buf.append(allocator, 0);
966 defer dir_buf.shrinkRetainingCapacity(dir_buf.items[0..dir_path_len].len);962 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
967 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];963 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
968 break :dir std.fs.cwd().openDirW(dir_path_z.ptr, .{}, true) catch return error.FileNotFound;964 const prefixed_path = try windows.wToPrefixedFileW(dir_path_z);
965 break :dir fs.cwd().openDirW(prefixed_path.span().ptr, .{}, true) catch return error.FileNotFound;
969 };966 };
970 defer dir.close();967 defer dir.close();
971968
lib/std/os/windows.zig+242-65
...@@ -1157,9 +1157,9 @@ pub fn GetFinalPathNameByHandle(...@@ -1157,9 +1157,9 @@ pub fn GetFinalPathNameByHandle(
11571157
1158 // This surprising path is a filesystem path to the mount manager on Windows.1158 // This surprising path is a filesystem path to the mount manager on Windows.
1159 // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points1159 // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points
1160 const mgmt_path = "\\MountPointManager";1160 // This is the NT namespaced version of \\.\MountPointManager
1161 const mgmt_path_u16 = sliceToPrefixedFileW(mgmt_path) catch unreachable;1161 const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager");
1162 const mgmt_handle = OpenFile(mgmt_path_u16.span(), .{1162 const mgmt_handle = OpenFile(mgmt_path_u16, .{
1163 .access_mask = SYNCHRONIZE,1163 .access_mask = SYNCHRONIZE,
1164 .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,1164 .share_access = FILE_SHARE_READ | FILE_SHARE_WRITE,
1165 .creation = FILE_OPEN,1165 .creation = FILE_OPEN,
...@@ -1997,43 +1997,248 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace {...@@ -1997,43 +1997,248 @@ pub fn cStrToPrefixedFileW(s: [*:0]const u8) !PathSpace {
1997 return sliceToPrefixedFileW(mem.sliceTo(s, 0));1997 return sliceToPrefixedFileW(mem.sliceTo(s, 0));
1998}1998}
19991999
2000/// Converts the path `s` to WTF16, null-terminated. If the path is absolute,2000/// Same as `wToPrefixedFileW` but accepts a UTF-8 encoded path.
2001/// it will get NT-style prefix `\??\` prepended automatically.2001pub fn sliceToPrefixedFileW(path: []const u8) !PathSpace {
2002pub fn sliceToPrefixedFileW(s: []const u8) !PathSpace {2002 var temp_path: PathSpace = undefined;
2003 // TODO https://github.com/ziglang/zig/issues/27652003 temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, path);
2004 var path_space: PathSpace = undefined;2004 temp_path.data[temp_path.len] = 0;
2005 const prefix = "\\??\\";2005 return wToPrefixedFileW(temp_path.span());
2006 const prefix_index: usize = if (mem.startsWith(u8, s, prefix)) prefix.len else 0;2006}
2007 for (s[prefix_index..]) |byte| {2007
2008 switch (byte) {2008/// Converts the `path` to WTF16, null-terminated. If the path contains any
2009 '*', '?', '"', '<', '>', '|' => return error.BadPathName,2009/// namespace prefix, or is anything but a relative path (rooted, drive relative,
2010 else => {},2010/// etc) the result will have the NT-style prefix `\??\`.
2011 }2011///
2012 }2012/// Similar to RtlDosPathNameToNtPathName_U with a few differences:
2013 const prefix_u16 = [_]u16{ '\\', '?', '?', '\\' };2013/// - Does not allocate on the heap.
2014 const start_index = if (prefix_index > 0 or !std.fs.path.isAbsolute(s)) 0 else blk: {2014/// - Relative paths are kept as relative unless they contain too many ..
2015 path_space.data[0..prefix_u16.len].* = prefix_u16;2015/// components, in which case they are treated as drive-relative and resolved
2016 break :blk prefix_u16.len;2016/// against the CWD.
2017 };2017/// - Special case device names like COM1, NUL, etc are not handled specially (TODO)
2018 path_space.len = start_index + try std.unicode.utf8ToUtf16Le(path_space.data[start_index..], s);2018/// - . and space are not stripped from the end of relative paths (potential TODO)
2019 if (path_space.len > path_space.data.len) return error.NameTooLong;2019pub fn wToPrefixedFileW(path: [:0]const u16) !PathSpace {
2020 path_space.len = start_index + (normalizePath(u16, path_space.data[start_index..path_space.len]) catch |err| switch (err) {2020 const nt_prefix = [_]u16{ '\\', '?', '?', '\\' };
2021 error.TooManyParentDirs => {2021 switch (getNamespacePrefix(u16, path)) {
2022 if (!std.fs.path.isAbsolute(s)) {2022 // TODO: Figure out a way to design an API that can avoid the copy for .nt,
2023 var temp_path: PathSpace = undefined;2023 // since it is always returned fully unmodified.
2024 temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, s);2024 .nt, .verbatim => {
2025 std.debug.assert(temp_path.len == path_space.len);2025 var path_space: PathSpace = undefined;
2026 temp_path.data[path_space.len] = 0;2026 path_space.data[0..nt_prefix.len].* = nt_prefix;
2027 path_space.len = prefix_u16.len + try getFullPathNameW(&temp_path.data, path_space.data[prefix_u16.len..]);2027 const len_after_prefix = path.len - nt_prefix.len;
2028 path_space.data[0..prefix_u16.len].* = prefix_u16;2028 @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]);
2029 std.debug.assert(path_space.data[path_space.len] == 0);2029 path_space.len = path.len;
2030 path_space.data[path_space.len] = 0;
2031 return path_space;
2032 },
2033 .local_device, .fake_verbatim => {
2034 var path_space: PathSpace = undefined;
2035 const path_byte_len = ntdll.RtlGetFullPathName_U(
2036 path.ptr,
2037 path_space.data.len * 2,
2038 &path_space.data,
2039 null,
2040 );
2041 if (path_byte_len == 0) {
2042 // TODO: This may not be the right error
2043 return error.BadPathName;
2044 } else if (path_byte_len / 2 > path_space.data.len) {
2045 return error.NameTooLong;
2046 }
2047 path_space.len = path_byte_len / 2;
2048 // Both prefixes will be normalized but retained, so all
2049 // we need to do now is replace them with the NT prefix
2050 path_space.data[0..nt_prefix.len].* = nt_prefix;
2051 return path_space;
2052 },
2053 .none => {
2054 const path_type = getUnprefixedPathType(u16, path);
2055 var path_space: PathSpace = undefined;
2056 relative: {
2057 if (path_type == .relative) {
2058 // TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc.
2059 // See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
2060
2061 // TODO: Potentially strip all trailing . and space characters from the
2062 // end of the path. This is something that both RtlDosPathNameToNtPathName_U
2063 // and RtlGetFullPathName_U do. Technically, trailing . and spaces
2064 // are allowed, but such paths may not interact well with Windows (i.e.
2065 // files with these paths can't be deleted from explorer.exe, etc).
2066 // This could be something that normalizePath may want to do.
2067
2068 @memcpy(path_space.data[0..path.len], path);
2069 // Try to normalize, but if we get too many parent directories,
2070 // then this is effectively a 'drive relative' path, so we need to
2071 // start over and use RtlGetFullPathName_U instead.
2072 path_space.len = normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) {
2073 error.TooManyParentDirs => break :relative,
2074 };
2075 path_space.data[path_space.len] = 0;
2076 return path_space;
2077 }
2078 }
2079 // We now know we are going to return an absolute NT path, so
2080 // we can unconditionally prefix it with the NT prefix.
2081 path_space.data[0..nt_prefix.len].* = nt_prefix;
2082 if (path_type == .root_local_device) {
2083 // `\\.` and `\\?` always get converted to `\??\` exactly, so
2084 // we can just stop here
2085 path_space.len = nt_prefix.len;
2086 path_space.data[path_space.len] = 0;
2030 return path_space;2087 return path_space;
2031 }2088 }
2032 return error.BadPathName;2089 const path_buf_offset = switch (path_type) {
2090 // UNC paths will always start with `\\`. However, we want to
2091 // end up with something like `\??\UNC\server\share`, so to get
2092 // RtlGetFullPathName to write into the spot we want the `server`
2093 // part to end up, we need to provide an offset such that
2094 // the `\\` part gets written where the `C\` of `UNC\` will be
2095 // in the final NT path.
2096 .unc_absolute => nt_prefix.len + 2,
2097 else => nt_prefix.len,
2098 };
2099 const buf_len = @intCast(u32, path_space.data.len - path_buf_offset);
2100 const path_byte_len = ntdll.RtlGetFullPathName_U(
2101 path.ptr,
2102 buf_len * 2,
2103 path_space.data[path_buf_offset..].ptr,
2104 null,
2105 );
2106 if (path_byte_len == 0) {
2107 // TODO: This may not be the right error
2108 return error.BadPathName;
2109 } else if (path_byte_len / 2 > buf_len) {
2110 return error.NameTooLong;
2111 }
2112 path_space.len = path_buf_offset + (path_byte_len / 2);
2113 if (path_type == .unc_absolute) {
2114 // Now add in the UNC, the `C` should overwrite the first `\` of the
2115 // FullPathName, ultimately resulting in `\??\UNC\<the rest of the path>`
2116 std.debug.assert(path_space.data[path_buf_offset] == '\\');
2117 std.debug.assert(path_space.data[path_buf_offset + 1] == '\\');
2118 const unc = [_]u16{ 'U', 'N', 'C' };
2119 path_space.data[nt_prefix.len..][0..unc.len].* = unc;
2120 }
2121 return path_space;
2033 },2122 },
2034 });2123 }
2035 path_space.data[path_space.len] = 0;2124}
2036 return path_space;2125
2126pub const NamespacePrefix = enum {
2127 none,
2128 /// `\\.\` (path separators can be `\` or `/`)
2129 local_device,
2130 /// `\\?\`
2131 /// When converted to an NT path, everything past the prefix is left
2132 /// untouched and `\\?\` is replaced by `\??\`.
2133 verbatim,
2134 /// `\\?\` without all path separators being `\`.
2135 /// This seems to be recognized as a prefix, but the 'verbatim' aspect
2136 /// is not respected (i.e. if `//?/C:/foo` is converted to an NT path,
2137 /// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't
2138 /// be treated as part of the final path])
2139 fake_verbatim,
2140 /// `\??\`
2141 nt,
2142};
2143
2144pub fn getNamespacePrefix(comptime T: type, path: []const T) NamespacePrefix {
2145 if (path.len < 4) return .none;
2146 var all_backslash = switch (path[0]) {
2147 '\\' => true,
2148 '/' => false,
2149 else => return .none,
2150 };
2151 all_backslash = all_backslash and switch (path[3]) {
2152 '\\' => true,
2153 '/' => false,
2154 else => return .none,
2155 };
2156 switch (path[1]) {
2157 '?' => if (path[2] == '?' and all_backslash) return .nt else return .none,
2158 '\\' => {},
2159 '/' => all_backslash = false,
2160 else => return .none,
2161 }
2162 return switch (path[2]) {
2163 '?' => if (all_backslash) .verbatim else .fake_verbatim,
2164 '.' => .local_device,
2165 else => .none,
2166 };
2167}
2168
2169test getNamespacePrefix {
2170 try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, ""));
2171 try std.testing.expectEqual(NamespacePrefix.nt, getNamespacePrefix(u8, "\\??\\"));
2172 try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "/??/"));
2173 try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "/??\\"));
2174 try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "\\?\\\\"));
2175 try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "\\\\.\\"));
2176 try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "\\\\./"));
2177 try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "/\\./"));
2178 try std.testing.expectEqual(NamespacePrefix.local_device, getNamespacePrefix(u8, "//./"));
2179 try std.testing.expectEqual(NamespacePrefix.none, getNamespacePrefix(u8, "/.//"));
2180 try std.testing.expectEqual(NamespacePrefix.verbatim, getNamespacePrefix(u8, "\\\\?\\"));
2181 try std.testing.expectEqual(NamespacePrefix.fake_verbatim, getNamespacePrefix(u8, "\\/?\\"));
2182 try std.testing.expectEqual(NamespacePrefix.fake_verbatim, getNamespacePrefix(u8, "\\/?/"));
2183 try std.testing.expectEqual(NamespacePrefix.fake_verbatim, getNamespacePrefix(u8, "//?/"));
2184}
2185
2186pub const UnprefixedPathType = enum {
2187 unc_absolute,
2188 drive_absolute,
2189 drive_relative,
2190 rooted,
2191 relative,
2192 root_local_device,
2193};
2194
2195inline fn isSepW(c: u16) bool {
2196 return c == '/' or c == '\\';
2197}
2198
2199/// Get the path type of a path that is known to not have any namespace prefixes
2200/// (`\\?\`, `\\.\`, `\??\`).
2201pub fn getUnprefixedPathType(comptime T: type, path: []const T) UnprefixedPathType {
2202 if (path.len < 1) return .relative;
2203
2204 if (std.debug.runtime_safety) {
2205 std.debug.assert(getNamespacePrefix(T, path) == .none);
2206 }
2207
2208 if (isSepW(path[0])) {
2209 // \x
2210 if (path.len < 2 or !isSepW(path[1])) return .rooted;
2211 // exactly \\. or \\? with nothing trailing
2212 if (path.len == 3 and (path[2] == '.' or path[2] == '?')) return .root_local_device;
2213 // \\x
2214 return .unc_absolute;
2215 } else {
2216 // x
2217 if (path.len < 2 or path[1] != ':') return .relative;
2218 // x:\
2219 if (path.len > 2 and isSepW(path[2])) return .drive_absolute;
2220 // x:
2221 return .drive_relative;
2222 }
2223}
2224
2225test getUnprefixedPathType {
2226 try std.testing.expectEqual(UnprefixedPathType.relative, getUnprefixedPathType(u8, ""));
2227 try std.testing.expectEqual(UnprefixedPathType.relative, getUnprefixedPathType(u8, "x"));
2228 try std.testing.expectEqual(UnprefixedPathType.relative, getUnprefixedPathType(u8, "x\\"));
2229 try std.testing.expectEqual(UnprefixedPathType.root_local_device, getUnprefixedPathType(u8, "//."));
2230 try std.testing.expectEqual(UnprefixedPathType.root_local_device, getUnprefixedPathType(u8, "/\\?"));
2231 try std.testing.expectEqual(UnprefixedPathType.root_local_device, getUnprefixedPathType(u8, "\\\\?"));
2232 try std.testing.expectEqual(UnprefixedPathType.unc_absolute, getUnprefixedPathType(u8, "\\\\x"));
2233 try std.testing.expectEqual(UnprefixedPathType.unc_absolute, getUnprefixedPathType(u8, "//x"));
2234 try std.testing.expectEqual(UnprefixedPathType.rooted, getUnprefixedPathType(u8, "\\x"));
2235 try std.testing.expectEqual(UnprefixedPathType.rooted, getUnprefixedPathType(u8, "/"));
2236 try std.testing.expectEqual(UnprefixedPathType.drive_relative, getUnprefixedPathType(u8, "x:"));
2237 try std.testing.expectEqual(UnprefixedPathType.drive_relative, getUnprefixedPathType(u8, "x:abc"));
2238 try std.testing.expectEqual(UnprefixedPathType.drive_relative, getUnprefixedPathType(u8, "x:a/b/c"));
2239 try std.testing.expectEqual(UnprefixedPathType.drive_absolute, getUnprefixedPathType(u8, "x:\\"));
2240 try std.testing.expectEqual(UnprefixedPathType.drive_absolute, getUnprefixedPathType(u8, "x:\\abc"));
2241 try std.testing.expectEqual(UnprefixedPathType.drive_absolute, getUnprefixedPathType(u8, "x:/a/b/c"));
2037}2242}
20382243
2039fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize {2244fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize {
...@@ -2046,34 +2251,6 @@ fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize {...@@ -2046,34 +2251,6 @@ fn getFullPathNameW(path: [*:0]const u16, out: []u16) !usize {
2046 return result;2251 return result;
2047}2252}
20482253
2049/// Assumes an absolute path.
2050pub fn wToPrefixedFileW(s: []const u16) !PathSpace {
2051 // TODO https://github.com/ziglang/zig/issues/2765
2052 var path_space: PathSpace = undefined;
2053
2054 const start_index = if (mem.startsWith(u16, s, &[_]u16{ '\\', '?' })) 0 else blk: {
2055 const prefix = [_]u16{ '\\', '?', '?', '\\' };
2056 path_space.data[0..prefix.len].* = prefix;
2057 break :blk prefix.len;
2058 };
2059 path_space.len = start_index + s.len;
2060 if (path_space.len > path_space.data.len) return error.NameTooLong;
2061 @memcpy(path_space.data[start_index..][0..s.len], s);
2062 // > File I/O functions in the Windows API convert "/" to "\" as part of
2063 // > converting the name to an NT-style name, except when using the "\\?\"
2064 // > prefix as detailed in the following sections.
2065 // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
2066 // Because we want the larger maximum path length for absolute paths, we
2067 // convert forward slashes to backward slashes here.
2068 for (path_space.data[0..path_space.len]) |*elem| {
2069 if (elem.* == '/') {
2070 elem.* = '\\';
2071 }
2072 }
2073 path_space.data[path_space.len] = 0;
2074 return path_space;
2075}
2076
2077inline fn MAKELANGID(p: c_ushort, s: c_ushort) LANGID {2254inline fn MAKELANGID(p: c_ushort, s: c_ushort) LANGID {
2078 return (s << 10) | p;2255 return (s << 10) | p;
2079}2256}
lib/std/os/windows/ntdll.zig+10
...@@ -158,6 +158,16 @@ pub extern "ntdll" fn RtlDosPathNameToNtPathName_U(...@@ -158,6 +158,16 @@ pub extern "ntdll" fn RtlDosPathNameToNtPathName_U(
158) callconv(WINAPI) BOOL;158) callconv(WINAPI) BOOL;
159pub extern "ntdll" fn RtlFreeUnicodeString(UnicodeString: *UNICODE_STRING) callconv(WINAPI) void;159pub extern "ntdll" fn RtlFreeUnicodeString(UnicodeString: *UNICODE_STRING) callconv(WINAPI) void;
160160
161/// Returns the number of bytes written to `Buffer`.
162/// If the returned count is larger than `BufferByteLength`, the buffer was too small.
163/// If the returned count is zero, an error occurred.
164pub extern "ntdll" fn RtlGetFullPathName_U(
165 FileName: [*:0]const u16,
166 BufferByteLength: ULONG,
167 Buffer: [*]u16,
168 ShortName: ?*[*:0]const u16,
169) callconv(windows.WINAPI) windows.ULONG;
170
161pub extern "ntdll" fn NtQueryDirectoryFile(171pub extern "ntdll" fn NtQueryDirectoryFile(
162 FileHandle: HANDLE,172 FileHandle: HANDLE,
163 Event: ?HANDLE,173 Event: ?HANDLE,
lib/std/os/windows/test.zig+175-1
...@@ -3,7 +3,181 @@ const builtin = @import("builtin");...@@ -3,7 +3,181 @@ const builtin = @import("builtin");
3const windows = std.os.windows;3const windows = std.os.windows;
4const mem = std.mem;4const mem = std.mem;
5const testing = std.testing;5const testing = std.testing;
6const expect = testing.expect;6
7/// Wrapper around RtlDosPathNameToNtPathName_U for use in comparing
8/// the behavior of RtlDosPathNameToNtPathName_U with wToPrefixedFileW
9/// Note: RtlDosPathNameToNtPathName_U is not used in the Zig implementation
10// because it allocates.
11fn RtlDosPathNameToNtPathName_U(path: [:0]const u16) !windows.PathSpace {
12 var out: windows.UNICODE_STRING = undefined;
13 const rc = windows.ntdll.RtlDosPathNameToNtPathName_U(path, &out, null, null);
14 if (rc != windows.TRUE) return error.BadPathName;
15 defer windows.ntdll.RtlFreeUnicodeString(&out);
16
17 var path_space: windows.PathSpace = undefined;
18 const out_path = out.Buffer[0 .. out.Length / 2];
19 std.mem.copy(u16, path_space.data[0..], out_path);
20 path_space.len = out.Length / 2;
21 path_space.data[path_space.len] = 0;
22
23 return path_space;
24}
25
26/// Test that the Zig conversion matches the expected_path (for instances where
27/// the Zig implementation intentionally diverges from what RtlDosPathNameToNtPathName_U does).
28fn testToPrefixedFileNoOracle(comptime path: []const u8, comptime expected_path: []const u8) !void {
29 const path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(path);
30 const expected_path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(expected_path);
31 const actual_path = try windows.wToPrefixedFileW(path_utf16);
32 std.testing.expectEqualSlices(u16, expected_path_utf16, actual_path.span()) catch |e| {
33 std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16le(actual_path.span()), std.unicode.fmtUtf16le(expected_path_utf16) });
34 return e;
35 };
36}
37
38/// Test that the Zig conversion matches the expected_path and that the
39/// expected_path matches the conversion that RtlDosPathNameToNtPathName_U does.
40fn testToPrefixedFileWithOracle(comptime path: []const u8, comptime expected_path: []const u8) !void {
41 try testToPrefixedFileNoOracle(path, expected_path);
42 try testToPrefixedFileOnlyOracle(path);
43}
44
45/// Test that the Zig conversion matches the conversion that RtlDosPathNameToNtPathName_U does.
46fn testToPrefixedFileOnlyOracle(comptime path: []const u8) !void {
47 const path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(path);
48 const zig_result = try windows.wToPrefixedFileW(path_utf16);
49 const win32_api_result = try RtlDosPathNameToNtPathName_U(path_utf16);
50 std.testing.expectEqualSlices(u16, win32_api_result.span(), zig_result.span()) catch |e| {
51 std.debug.print("got '{s}', expected '{s}'\n", .{ std.unicode.fmtUtf16le(zig_result.span()), std.unicode.fmtUtf16le(win32_api_result.span()) });
52 return e;
53 };
54}
55
56test "toPrefixedFileW" {
57 if (builtin.os.tag != .windows)
58 return;
59
60 // Most test cases come from https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
61 // Note that these tests do not actually touch the filesystem or care about whether or not
62 // any of the paths actually exist or are otherwise valid.
63
64 // Drive Absolute
65 try testToPrefixedFileWithOracle("X:\\ABC\\DEF", "\\??\\X:\\ABC\\DEF");
66 try testToPrefixedFileWithOracle("X:\\", "\\??\\X:\\");
67 try testToPrefixedFileWithOracle("X:\\ABC\\", "\\??\\X:\\ABC\\");
68 // Trailing . and space characters are stripped
69 try testToPrefixedFileWithOracle("X:\\ABC\\DEF. .", "\\??\\X:\\ABC\\DEF");
70 try testToPrefixedFileWithOracle("X:/ABC/DEF", "\\??\\X:\\ABC\\DEF");
71 try testToPrefixedFileWithOracle("X:\\ABC\\..\\XYZ", "\\??\\X:\\XYZ");
72 try testToPrefixedFileWithOracle("X:\\ABC\\..\\..\\..", "\\??\\X:\\");
73 // Drive letter casing is unchanged
74 try testToPrefixedFileWithOracle("x:\\", "\\??\\x:\\");
75
76 // Drive Relative
77 // These tests depend on the CWD of the specified drive letter which can vary,
78 // so instead we just test that the Zig implementation matches the result of
79 // RtlDosPathNameToNtPathName_U.
80 // TODO: Setting the =X: environment variable didn't seem to affect
81 // RtlDosPathNameToNtPathName_U, not sure why that is but getting that
82 // to work could be an avenue to making these cases environment-independent.
83 // All -> are examples of the result if the X drive's cwd was X:\ABC
84 try testToPrefixedFileOnlyOracle("X:DEF\\GHI"); // -> \??\X:\ABC\DEF\GHI
85 try testToPrefixedFileOnlyOracle("X:"); // -> \??\X:\ABC
86 try testToPrefixedFileOnlyOracle("X:DEF. ."); // -> \??\X:\ABC\DEF
87 try testToPrefixedFileOnlyOracle("X:ABC\\..\\XYZ"); // -> \??\X:\ABC\XYZ
88 try testToPrefixedFileOnlyOracle("X:ABC\\..\\..\\.."); // -> \??\X:\
89 try testToPrefixedFileOnlyOracle("x:"); // -> \??\X:\ABC
90
91 // Rooted
92 // These tests depend on the drive letter of the CWD which can vary, so
93 // instead we just test that the Zig implementation matches the result of
94 // RtlDosPathNameToNtPathName_U.
95 // TODO: Getting the CWD path, getting the drive letter from it, and using it to
96 // construct the expected NT paths could be an avenue to making these cases
97 // environment-independent and therefore able to use testToPrefixedFileWithOracle.
98 // All -> are examples of the result if the CWD's drive letter was X
99 try testToPrefixedFileOnlyOracle("\\ABC\\DEF"); // -> \??\X:\ABC\DEF
100 try testToPrefixedFileOnlyOracle("\\"); // -> \??\X:\
101 try testToPrefixedFileOnlyOracle("\\ABC\\DEF. ."); // -> \??\X:\ABC\DEF
102 try testToPrefixedFileOnlyOracle("/ABC/DEF"); // -> \??\X:\ABC\DEF
103 try testToPrefixedFileOnlyOracle("\\ABC\\..\\XYZ"); // -> \??\X:\XYZ
104 try testToPrefixedFileOnlyOracle("\\ABC\\..\\..\\.."); // -> \??\X:\
105
106 // Relative
107 // These cases differ in functionality to RtlDosPathNameToNtPathName_U.
108 // Relative paths remain relative if they don't have enough .. components
109 // to error with TooManyParentDirs
110 try testToPrefixedFileNoOracle("ABC\\DEF", "ABC\\DEF");
111 // TODO: enable this if trailing . and spaces are stripped from relative paths
112 //try testToPrefixedFileNoOracle("ABC\\DEF. .", "ABC\\DEF");
113 try testToPrefixedFileNoOracle("ABC/DEF", "ABC\\DEF");
114 try testToPrefixedFileNoOracle("./ABC/.././DEF", "DEF");
115 // TooManyParentDirs, so resolved relative to the CWD
116 // All -> are examples of the result if the CWD was X:\ABC\DEF
117 try testToPrefixedFileOnlyOracle("..\\GHI"); // -> \??\X:\ABC\GHI
118 try testToPrefixedFileOnlyOracle("GHI\\..\\..\\.."); // -> \??\X:\
119
120 // UNC Absolute
121 try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\DEF", "\\??\\UNC\\server\\share\\ABC\\DEF");
122 try testToPrefixedFileWithOracle("\\\\server", "\\??\\UNC\\server");
123 try testToPrefixedFileWithOracle("\\\\server\\share", "\\??\\UNC\\server\\share");
124 try testToPrefixedFileWithOracle("\\\\server\\share\\ABC. .", "\\??\\UNC\\server\\share\\ABC");
125 try testToPrefixedFileWithOracle("//server/share/ABC/DEF", "\\??\\UNC\\server\\share\\ABC\\DEF");
126 try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\..\\XYZ", "\\??\\UNC\\server\\share\\XYZ");
127 try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\..\\..\\..", "\\??\\UNC\\server\\share");
128
129 // Local Device
130 try testToPrefixedFileWithOracle("\\\\.\\COM20", "\\??\\COM20");
131 try testToPrefixedFileWithOracle("\\\\.\\pipe\\mypipe", "\\??\\pipe\\mypipe");
132 try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\DEF. .", "\\??\\X:\\ABC\\DEF");
133 try testToPrefixedFileWithOracle("\\\\.\\X:/ABC/DEF", "\\??\\X:\\ABC\\DEF");
134 try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\..\\XYZ", "\\??\\X:\\XYZ");
135 // Can replace the first component of the path (contrary to drive absolute and UNC absolute paths)
136 try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\..\\..\\C:\\", "\\??\\C:\\");
137 try testToPrefixedFileWithOracle("\\\\.\\pipe\\mypipe\\..\\notmine", "\\??\\pipe\\notmine");
138
139 // Special-case device names
140 // TODO: Enable once these are supported
141 // more cases to test here: https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
142 //try testToPrefixedFileWithOracle("COM1", "\\??\\COM1");
143 // Sometimes the special-cased device names are not respected
144 try testToPrefixedFileWithOracle("\\\\.\\X:\\COM1", "\\??\\X:\\COM1");
145 try testToPrefixedFileWithOracle("\\\\abc\\xyz\\COM1", "\\??\\UNC\\abc\\xyz\\COM1");
146
147 // Verbatim
148 // Left untouched except \\?\ is replaced by \??\
149 try testToPrefixedFileWithOracle("\\\\?\\X:", "\\??\\X:");
150 try testToPrefixedFileWithOracle("\\\\?\\X:\\COM1", "\\??\\X:\\COM1");
151 try testToPrefixedFileWithOracle("\\\\?\\X:/ABC/DEF. .", "\\??\\X:/ABC/DEF. .");
152 try testToPrefixedFileWithOracle("\\\\?\\X:\\ABC\\..\\..\\..", "\\??\\X:\\ABC\\..\\..\\..");
153 // NT Namespace
154 // Fully unmodified
155 try testToPrefixedFileWithOracle("\\??\\X:", "\\??\\X:");
156 try testToPrefixedFileWithOracle("\\??\\X:\\COM1", "\\??\\X:\\COM1");
157 try testToPrefixedFileWithOracle("\\??\\X:/ABC/DEF. .", "\\??\\X:/ABC/DEF. .");
158 try testToPrefixedFileWithOracle("\\??\\X:\\ABC\\..\\..\\..", "\\??\\X:\\ABC\\..\\..\\..");
159
160 // 'Fake' Verbatim
161 // If the prefix looks like the verbatim prefix but not all path separators in the
162 // prefix are backslashes, then it gets canonicalized and the prefix is dropped in favor
163 // of the NT prefix.
164 try testToPrefixedFileWithOracle("//?/C:/ABC", "\\??\\C:\\ABC");
165 // 'Fake' NT
166 // If the prefix looks like the NT prefix but not all path separators in the prefix
167 // are backslashes, then it gets canonicalized and the /??/ is not dropped but
168 // rather treated as part of the path. In other words, the path is treated
169 // as a rooted path, so the final path is resolved relative to the CWD's
170 // drive letter.
171 // The -> shows an example of the result if the CWD's drive letter was X
172 try testToPrefixedFileOnlyOracle("/??/C:/ABC"); // -> \??\X:\??\C:\ABC
173
174 // Root Local Device
175 // \\. and \\? always get converted to \??\
176 try testToPrefixedFileWithOracle("\\\\.", "\\??\\");
177 try testToPrefixedFileWithOracle("\\\\?", "\\??\\");
178 try testToPrefixedFileWithOracle("//?", "\\??\\");
179 try testToPrefixedFileWithOracle("//.", "\\??\\");
180}
7181
8fn testRemoveDotDirs(str: []const u8, expected: []const u8) !void {182fn testRemoveDotDirs(str: []const u8, expected: []const u8) !void {
9 const mutable = try testing.allocator.dupe(u8, str);183 const mutable = try testing.allocator.dupe(u8, str);
src/arch/x86_64/CodeGen.zig+25-24
...@@ -10175,37 +10175,38 @@ fn airBitCast(self: *Self, inst: Air.Inst.Index) !void {...@@ -10175,37 +10175,38 @@ fn airBitCast(self: *Self, inst: Air.Inst.Index) !void {
10175 if (dst_ty.isAbiInt()) dst_ty.intInfo(self.target.*).signedness else .unsigned;10175 if (dst_ty.isAbiInt()) dst_ty.intInfo(self.target.*).signedness else .unsigned;
10176 const src_signedness =10176 const src_signedness =
10177 if (src_ty.isAbiInt()) src_ty.intInfo(self.target.*).signedness else .unsigned;10177 if (src_ty.isAbiInt()) src_ty.intInfo(self.target.*).signedness else .unsigned;
10178 if (dst_signedness == src_signedness) break :result dst_mcv;
10179
10178 const abi_size = @intCast(u16, dst_ty.abiSize(self.target.*));10180 const abi_size = @intCast(u16, dst_ty.abiSize(self.target.*));
10179 const bit_size = @intCast(u16, dst_ty.bitSize(self.target.*));10181 const bit_size = @intCast(u16, dst_ty.bitSize(self.target.*));
10180 const dst_limbs_len = math.divCeil(u16, bit_size, 64) catch unreachable;10182 if (abi_size * 8 <= bit_size) break :result dst_mcv;
10181 if (dst_signedness != src_signedness and abi_size * 8 > bit_size) {
10182 const high_reg = if (dst_mcv.isRegister())
10183 dst_mcv.getReg().?
10184 else
10185 try self.copyToTmpRegister(
10186 Type.usize,
10187 dst_mcv.address().offset((dst_limbs_len - 1) * 8).deref(),
10188 );
10189 const high_lock = self.register_manager.lockReg(high_reg);
10190 defer if (high_lock) |lock| self.register_manager.unlockReg(lock);
10191
10192 var high_pl = Type.Payload.Bits{
10193 .base = .{ .tag = switch (dst_signedness) {
10194 .signed => .int_signed,
10195 .unsigned => .int_unsigned,
10196 } },
10197 .data = bit_size % 64,
10198 };
10199 const high_ty = Type.initPayload(&high_pl.base);
1020010183
10201 try self.truncateRegister(high_ty, high_reg);10184 const dst_limbs_len = math.divCeil(i32, bit_size, 64) catch unreachable;
10202 if (!dst_mcv.isRegister()) try self.genCopy(10185 const high_reg = if (dst_mcv.isRegister())
10186 dst_mcv.getReg().?
10187 else
10188 try self.copyToTmpRegister(
10203 Type.usize,10189 Type.usize,
10204 dst_mcv.address().offset((dst_limbs_len - 1) * 8).deref(),10190 dst_mcv.address().offset((dst_limbs_len - 1) * 8).deref(),
10205 .{ .register = high_reg },
10206 );10191 );
10207 }10192 const high_lock = self.register_manager.lockReg(high_reg);
10193 defer if (high_lock) |lock| self.register_manager.unlockReg(lock);
10194
10195 var high_pl = Type.Payload.Bits{
10196 .base = .{ .tag = switch (dst_signedness) {
10197 .signed => .int_signed,
10198 .unsigned => .int_unsigned,
10199 } },
10200 .data = bit_size % 64,
10201 };
10202 const high_ty = Type.initPayload(&high_pl.base);
1020810203
10204 try self.truncateRegister(high_ty, high_reg);
10205 if (!dst_mcv.isRegister()) try self.genCopy(
10206 Type.usize,
10207 dst_mcv.address().offset((dst_limbs_len - 1) * 8).deref(),
10208 .{ .register = high_reg },
10209 );
10209 break :result dst_mcv;10210 break :result dst_mcv;
10210 };10211 };
10211 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });10212 return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
test/behavior/maximum_minimum.zig-1
...@@ -197,7 +197,6 @@ test "@min/@max notices vector bounds" {...@@ -197,7 +197,6 @@ test "@min/@max notices vector bounds" {
197197
198test "@min/@max on comptime_int" {198test "@min/@max on comptime_int" {
199 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO199 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
200 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
201 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO200 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
202 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO201 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
203 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO202 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO