@@ -26,57 +26,107 @@ pub fn is_dyn_sym(name: &str) -> bool {
2626}
2727
2828#[ 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 > > {
3030 // We are on Windows so we can simply let the host do this.
3131 interp_ok ( path:: absolute ( path) )
3232}
3333
3434#[ cfg( unix) ]
3535#[ 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.
3848 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+ {
4256 return interp_ok ( Ok ( path. into ( ) ) ) ;
4357 } ;
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 ( ) ;
5168 result. extend ( bytes) ;
5269 return interp_ok ( Ok ( bytes_to_os_str ( & result) ?. into ( ) ) ) ;
5370 }
5471 // 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 `/`.
5874 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 ( ) ) ) ;
6599 }
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+ }
73113 continue ;
74- } else {
75- // This was the last component and it did not have a trailing `/`.
76- break ;
77114 }
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 ( & [ ] ) ;
78127 }
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'/' ) ;
80130 interp_ok ( path:: absolute ( bytes_to_os_str ( & result) ?) )
81131}
82132
@@ -231,7 +281,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
231281 }
232282
233283 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) ? {
235285 Err ( err) => {
236286 this. set_last_error ( err) ?;
237287 Scalar :: from_u32 ( 0 ) // return zero upon failure
0 commit comments