about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_codegen_ssa/Cargo.toml4
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link.rs55
-rw-r--r--tests/ui/native-library-link-flags/msvc-non-utf8-output.rs6
-rw-r--r--tests/ui/native-library-link-flags/msvc-non-utf8-output.stderr7
5 files changed, 72 insertions, 1 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 06a2a36f455..cd319574f79 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3250,6 +3250,7 @@ dependencies = [
  "tempfile",
  "thorin-dwp",
  "tracing",
+ "windows 0.46.0",
 ]
 
 [[package]]
diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml
index a421535c9b4..4f73b731f5a 100644
--- a/compiler/rustc_codegen_ssa/Cargo.toml
+++ b/compiler/rustc_codegen_ssa/Cargo.toml
@@ -49,3 +49,7 @@ libc = "0.2.50"
 version = "0.30.1"
 default-features = false
 features = ["read_core", "elf", "macho", "pe", "unaligned", "archive", "write"]
+
+[target.'cfg(windows)'.dependencies.windows]
+version = "0.46.0"
+features = ["Win32_Globalization"]
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 02e21e74fad..feab57e9820 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -857,7 +857,7 @@ fn link_natively<'a>(
             if !prog.status.success() {
                 let mut output = prog.stderr.clone();
                 output.extend_from_slice(&prog.stdout);
-                let escaped_output = escape_string(&output);
+                let escaped_output = escape_linker_output(&output, flavor);
                 // FIXME: Add UI tests for this error.
                 let err = errors::LinkingFailed {
                     linker_path: &linker_path,
@@ -1049,6 +1049,59 @@ fn escape_string(s: &[u8]) -> String {
     }
 }
 
+#[cfg(not(windows))]
+fn escape_linker_output(s: &[u8], _flavour: LinkerFlavor) -> String {
+    escape_string(s)
+}
+
+/// If the output of the msvc linker is not UTF-8 and the host is Windows,
+/// then try to convert the string from the OEM encoding.
+#[cfg(windows)]
+fn escape_linker_output(s: &[u8], flavour: LinkerFlavor) -> String {
+    // This only applies to the actual MSVC linker.
+    if flavour != LinkerFlavor::Msvc(Lld::No) {
+        return escape_string(s);
+    }
+    match str::from_utf8(s) {
+        Ok(s) => return s.to_owned(),
+        Err(_) if s.len() <= i32::MAX as usize => {
+            use windows::Win32::Globalization::{
+                GetLocaleInfoEx, MultiByteToWideChar, CP_OEMCP, LOCALE_IUSEUTF8LEGACYOEMCP,
+                LOCALE_NAME_SYSTEM_DEFAULT, LOCALE_RETURN_NUMBER, MB_ERR_INVALID_CHARS,
+            };
+            // Get the legacy system OEM code page.
+            let code_page = unsafe {
+                let mut cp: u32 = 0;
+                // We're using the `LOCALE_RETURN_NUMBER` flag to return a u32.
+                // But the API requires us to pass the data as though it's a [u16] string.
+                let len = std::mem::size_of::<u32>() / std::mem::size_of::<u16>();
+                let data = std::slice::from_raw_parts_mut(&mut cp as *mut u32 as *mut u16, len);
+                let len_written = GetLocaleInfoEx(
+                    LOCALE_NAME_SYSTEM_DEFAULT,
+                    LOCALE_IUSEUTF8LEGACYOEMCP | LOCALE_RETURN_NUMBER,
+                    Some(data),
+                );
+                if len_written as usize == len { cp } else { CP_OEMCP }
+            };
+            // Error if the string is not valid for the expected code page.
+            let flags = MB_ERR_INVALID_CHARS;
+            // Call MultiByteToWideChar twice.
+            // First to calculate the length then to convert the string.
+            let mut len = unsafe { MultiByteToWideChar(code_page, flags, s, None) };
+            if len > 0 {
+                let mut utf16 = vec![0; len as usize];
+                len = unsafe { MultiByteToWideChar(code_page, flags, s, Some(&mut utf16)) };
+                if len > 0 {
+                    return String::from_utf16_lossy(&utf16[..len as usize]);
+                }
+            }
+        }
+        _ => {}
+    };
+    // The string is not UTF-8 and isn't valid for the OEM code page
+    format!("Non-UTF-8 output: {}", s.escape_ascii())
+}
+
 fn add_sanitizer_libraries(sess: &Session, crate_type: CrateType, linker: &mut dyn Linker) {
     // On macOS the runtimes are distributed as dylibs which should be linked to
     // both executables and dynamic shared objects. Everywhere else the runtimes
diff --git a/tests/ui/native-library-link-flags/msvc-non-utf8-output.rs b/tests/ui/native-library-link-flags/msvc-non-utf8-output.rs
new file mode 100644
index 00000000000..3fb2842d694
--- /dev/null
+++ b/tests/ui/native-library-link-flags/msvc-non-utf8-output.rs
@@ -0,0 +1,6 @@
+// build-fail
+// compile-flags:-C link-arg=märchenhaft
+// only-msvc
+// error-pattern:= note: LINK : fatal error LNK1181:
+// normalize-stderr-test "(\s*\|\n)\s*= note: .*\n" -> "$1"
+pub fn main() {}
diff --git a/tests/ui/native-library-link-flags/msvc-non-utf8-output.stderr b/tests/ui/native-library-link-flags/msvc-non-utf8-output.stderr
new file mode 100644
index 00000000000..f843aad782c
--- /dev/null
+++ b/tests/ui/native-library-link-flags/msvc-non-utf8-output.stderr
@@ -0,0 +1,7 @@
+error: linking with `link.exe` failed: exit code: 1181
+   |
+   = note: LINK : fatal error LNK1181: cannot open input file 'märchenhaft.obj'
+           
+
+error: aborting due to previous error
+