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