@@ -21,57 +21,107 @@ pub fn is_dyn_sym(name: &str) -> bool {
2121}
2222
2323#[ 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 > > {
2525 // We are on Windows so we can simply let the host do this.
2626 interp_ok ( path:: absolute ( path) )
2727}
2828
2929#[ cfg( unix) ]
3030#[ 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.
3343 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+ {
3751 return interp_ok ( Ok ( path. into ( ) ) ) ;
3852 } ;
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 ( ) ;
4663 result. extend ( bytes) ;
4764 return interp_ok ( Ok ( bytes_to_os_str ( & result) ?. into ( ) ) ) ;
4865 }
4966 // 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 `/`.
5369 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 ( ) ) ) ;
6094 }
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+ }
68108 continue ;
69- } else {
70- // This was the last component and it did not have a trailing `/`.
71- break ;
72109 }
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 ( & [ ] ) ;
73122 }
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'/' ) ;
75125 interp_ok ( path:: absolute ( bytes_to_os_str ( & result) ?) )
76126}
77127
@@ -226,7 +276,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
226276 }
227277
228278 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) ? {
230280 Err ( err) => {
231281 this. set_last_error ( err) ?;
232282 Scalar :: from_u32 ( 0 ) // return zero upon failure
0 commit comments