@@ -21,57 +21,107 @@ pub fn is_dyn_sym(name: &str) -> bool {
21
21
}
22
22
23
23
#[ cfg( windows) ]
24
- fn win_absolute < ' tcx > ( path : & Path ) -> InterpResult < ' tcx , io:: Result < PathBuf > > {
24
+ fn win_get_full_path_name < ' tcx > ( path : & Path ) -> InterpResult < ' tcx , io:: Result < PathBuf > > {
25
25
// We are on Windows so we can simply let the host do this.
26
26
interp_ok ( path:: absolute ( path) )
27
27
}
28
28
29
29
#[ cfg( unix) ]
30
30
#[ expect( clippy:: get_first, clippy:: arithmetic_side_effects) ]
31
- fn win_absolute < ' tcx > ( path : & Path ) -> InterpResult < ' tcx , io:: Result < PathBuf > > {
32
- // We are on Unix, so we need to implement parts of the logic ourselves.
31
+ fn win_get_full_path_name < ' tcx > ( path : & Path ) -> InterpResult < ' tcx , io:: Result < PathBuf > > {
32
+ use std:: sync:: LazyLock ;
33
+
34
+ use rustc_data_structures:: fx:: FxHashSet ;
35
+
36
+ // We are on Unix, so we need to implement parts of the logic ourselves. `path` will use `/`
37
+ // separators, and the result should also use `/`.
38
+ // See <https://chrisdenton.github.io/omnipath/Overview.html#absolute-win32-paths> for more
39
+ // information about Windows paths.
40
+ // This does not handle all corner cases correctly, see
41
+ // <https://github.com/rust-lang/miri/pull/4262#issuecomment-2792168853> for more cursed
42
+ // examples.
33
43
let bytes = path. as_os_str ( ) . as_encoded_bytes ( ) ;
34
- // If it starts with `//` (these were backslashes but are already converted)
35
- // then this is a magic special path, we just leave it unchanged.
36
- if bytes. get ( 0 ) . copied ( ) == Some ( b'/' ) && bytes. get ( 1 ) . copied ( ) == Some ( b'/' ) {
44
+ // If it starts with `//./` or `//?/` then this is a magic special path, we just leave it
45
+ // unchanged.
46
+ if bytes. get ( 0 ) . copied ( ) == Some ( b'/' )
47
+ && bytes. get ( 1 ) . copied ( ) == Some ( b'/' )
48
+ && matches ! ( bytes. get( 2 ) , Some ( b'.' | b'?' ) )
49
+ && bytes. get ( 3 ) . copied ( ) == Some ( b'/' )
50
+ {
37
51
return interp_ok ( Ok ( path. into ( ) ) ) ;
38
52
} ;
39
- // Special treatment for Windows' magic filenames: they are treated as being relative to `\\.\`.
40
- let magic_filenames = & [
41
- "CON" , "PRN" , "AUX" , "NUL" , "COM1" , "COM2" , "COM3" , "COM4" , "COM5" , "COM6" , "COM7" , "COM8" ,
42
- "COM9" , "LPT1" , "LPT2" , "LPT3" , "LPT4" , "LPT5" , "LPT6" , "LPT7" , "LPT8" , "LPT9" ,
43
- ] ;
44
- if magic_filenames. iter ( ) . any ( |m| m. as_bytes ( ) == bytes) {
45
- let mut result: Vec < u8 > = br"//./" . into ( ) ;
53
+ let is_unc = bytes. starts_with ( b"//" ) ;
54
+ // Special treatment for Windows' magic filenames: they are treated as being relative to `//./`.
55
+ static MAGIC_FILENAMES : LazyLock < FxHashSet < & ' static str > > = LazyLock :: new ( || {
56
+ FxHashSet :: from_iter ( [
57
+ "CON" , "PRN" , "AUX" , "NUL" , "COM1" , "COM2" , "COM3" , "COM4" , "COM5" , "COM6" , "COM7" ,
58
+ "COM8" , "COM9" , "LPT1" , "LPT2" , "LPT3" , "LPT4" , "LPT5" , "LPT6" , "LPT7" , "LPT8" , "LPT9" ,
59
+ ] )
60
+ } ) ;
61
+ if str:: from_utf8 ( bytes) . is_ok_and ( |s| MAGIC_FILENAMES . contains ( & * s. to_ascii_uppercase ( ) ) ) {
62
+ let mut result: Vec < u8 > = b"//./" . into ( ) ;
46
63
result. extend ( bytes) ;
47
64
return interp_ok ( Ok ( bytes_to_os_str ( & result) ?. into ( ) ) ) ;
48
65
}
49
66
// Otherwise we try to do something kind of close to what Windows does, but this is probably not
50
- // right in all cases. We iterate over the components between `/`, and remove trailing `.`,
51
- // except that trailing `..` remain unchanged.
52
- let mut result = vec ! [ ] ;
67
+ // right in all cases.
68
+ let mut result: Vec < & [ u8 ] > = vec ! [ ] ; // will be a vecot of components, joined by `/`.
53
69
let mut bytes = bytes; // the remaining bytes to process
54
- loop {
55
- let len = bytes. iter ( ) . position ( |& b| b == b'/' ) . unwrap_or ( bytes. len ( ) ) ;
56
- let mut component = & bytes[ ..len] ;
57
- if len >= 2 && component[ len - 1 ] == b'.' && component[ len - 2 ] != b'.' {
58
- // Strip trailing `.`
59
- component = & component[ ..len - 1 ] ;
70
+ let mut stop = false ;
71
+ while !stop {
72
+ // Find next component, and advance `bytes`.
73
+ let mut component = match bytes. iter ( ) . position ( |& b| b == b'/' ) {
74
+ Some ( pos) => {
75
+ let ( component, tail) = bytes. split_at ( pos) ;
76
+ bytes = & tail[ 1 ..] ; // remove the `/`.
77
+ component
78
+ }
79
+ None => {
80
+ // There's no more `/`.
81
+ stop = true ;
82
+ let component = bytes;
83
+ bytes = & [ ] ;
84
+ component
85
+ }
86
+ } ;
87
+ // `NUL` and only `NUL` also gets changed to be relative to `//./` later in the path.
88
+ // (This changed with Windows 11; previously, all magic filenames behaved like this.)
89
+ // Also, this does not apply to UNC paths.
90
+ if !is_unc && component. eq_ignore_ascii_case ( b"NUL" ) {
91
+ let mut result: Vec < u8 > = b"//./" . into ( ) ;
92
+ result. extend ( component) ;
93
+ return interp_ok ( Ok ( bytes_to_os_str ( & result) ?. into ( ) ) ) ;
60
94
}
61
- // Add this component to output.
62
- result. extend ( component) ;
63
- // Prepare next iteration.
64
- if len < bytes. len ( ) {
65
- // There's a component after this; add `/` and process remaining bytes.
66
- result. push ( b'/' ) ;
67
- bytes = & bytes[ len + 1 ..] ;
95
+ // Deal with `..` -- Windows handles this entirely syntactically.
96
+ if component == b".." {
97
+ // Remove previous component, unless we are at the "root" already, then just ignore the `..`.
98
+ let is_root = {
99
+ // Paths like `/C:`.
100
+ result. len ( ) == 2 && matches ! ( result[ 0 ] , [ ] ) && matches ! ( result[ 1 ] , [ _, b':' ] )
101
+ } || {
102
+ // Paths like `//server/share`
103
+ result. len ( ) == 4 && matches ! ( result[ 0 ] , [ ] ) && matches ! ( result[ 1 ] , [ ] )
104
+ } ;
105
+ if !is_root {
106
+ result. pop ( ) ;
107
+ }
68
108
continue ;
69
- } else {
70
- // This was the last component and it did not have a trailing `/`.
71
- break ;
72
109
}
110
+ // Preserve this component.
111
+ // Strip trailing `.`, but preserve trailing `..`. But not for UNC paths!
112
+ let len = component. len ( ) ;
113
+ if !is_unc && len >= 2 && component[ len - 1 ] == b'.' && component[ len - 2 ] != b'.' {
114
+ component = & component[ ..len - 1 ] ;
115
+ }
116
+ // Add this component to output.
117
+ result. push ( component) ;
118
+ }
119
+ // Drive letters must be followed by a `/`.
120
+ if result. len ( ) == 2 && matches ! ( result[ 0 ] , [ ] ) && matches ! ( result[ 1 ] , [ _, b':' ] ) {
121
+ result. push ( & [ ] ) ;
73
122
}
74
- // Let the host `absolute` function do working-dir handling
123
+ // Let the host `absolute` function do working-dir handling.
124
+ let result = result. join ( & b'/' ) ;
75
125
interp_ok ( path:: absolute ( bytes_to_os_str ( & result) ?) )
76
126
}
77
127
@@ -226,7 +276,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
226
276
}
227
277
228
278
let filename = this. read_path_from_wide_str ( filename) ?;
229
- let result = match win_absolute ( & filename) ? {
279
+ let result = match win_get_full_path_name ( & filename) ? {
230
280
Err ( err) => {
231
281
this. set_last_error ( err) ?;
232
282
Scalar :: from_u32 ( 0 ) // return zero upon failure
0 commit comments