Change:
This allows giving a better error when attempting to use the original source file with -Zlink-only rather than crashing and allows rejecting rlink files produced by a different rustc version.
To clarify: here's what I thought you had to do: But that gives an inscrutable error like this: The correct way to invoke is actually this If the file has a header, the code that reads it can check for that header. If it's not present (e.g. because somebody passed a file by mistake) then it can give a nice error message like "this doesn't look like a .rlink file".
I tried to look at this. is a struct that is serialized to files using the machinery. I tried to create a wrapper struct that would serialize and deserialize a header to provide nicer messages, but it's not really easy to provide a custom message if decoding of the header fails currently, since you removed the s from methods :sweat_smile: I see several solutions to this: 1) Catch panic that could be caused by invalid decoding of the header and provide a nicer message in that case (seems dubious). 2) Implement serialization and deserialization of the header using manual byte writes/reads, going around the traits. 3) Return sto methods (probably not worth it because of this). What do you suggest, solution 2)?
The crate metadata encodes and decodes the header separately from the contents. For crate metadata the header is of the form "rust" + 32bit be version number (currently 5 I believe) + 32bit offset of the crate root + rustc version string. During decoding the header is checked for "rust", the right version number and then the right rustc version. Only after that is a created to decode the contents. The rlink header could work the same way except omitting the offset of the crate root and using say "rsld" (rust ld) or "rustlink" as start instead of "rust" like the crate metadata.
Thanks for the hint. I checked rmeta decoding and it basically decodes the magic header byte slice with , which should be easy, but then it uses for decoding the rustc version, which will panic if the version is missing. So with this approach we could have a nice error if header magic is missing, nice error if rust version is wrong, but weird error if rust version if missing. That is basically the same that we get for parsing, so I suppose that's ok?
Yeah, the header magic is supposed to be changed every time the rustc version needs to be decoded in a different way. IMO it is ok.

Discussion:
use rustc_middle::dep_graph::WorkProduct; use rustc_middle::middle::dependency_format::Dependencies; use rustc_middle::ty::query::{ExternProviders, Providers}; <ins> use rustc_serialize::{opaque, Decodable, Decoder, Encoder}; </ins> use rustc_session::config::{CrateType, OutputFilenames, OutputType, RUST_CGU_EXT}; use rustc_session::cstore::{self, CrateSource}; use rustc_session::utils::NativeLibKind;

// Check if the "inner" extension ext2 == Some(RUST_CGU_EXT) } <ins> const RLINK_VERSION: u32 = 1; const RLINK_MAGIC: &[u8] = b"rustlink"; const RUSTC_VERSION: Option<&str> = option_env!("CFG_VERSION"); impl CodegenResults { pub fn serialize_rlink(codegen_results: &CodegenResults) -> Vec<u8> { let mut encoder = opaque::Encoder::new(vec![]); encoder.emit_raw_bytes(RLINK_MAGIC).unwrap(); // `emit_raw_bytes` is used to make sure that the version representation does not depend on // Encoder's inner representation of `u32`. encoder.emit_raw_bytes(&RLINK_VERSION.to_be_bytes()).unwrap(); encoder.emit_str(RUSTC_VERSION.unwrap()).unwrap(); let mut encoder = rustc_serialize::opaque::Encoder::new(encoder.into_inner()); rustc_serialize::Encodable::encode(codegen_results, &mut encoder).unwrap(); encoder.into_inner() } pub fn deserialize_rlink(data: Vec<u8>) -> Result<Self, String> { // The Decodable machinery is not used here because it panics if the input data is invalid // and because its internal representation may change. if !data.starts_with(RLINK_MAGIC) { return Err("The input does not look like a .rlink file".to_string()); } let data = &data[RLINK_MAGIC.len()..]; if data.len() < 4 { return Err("The input does not contain version number".to_string()); } let mut version_array: [u8; 4] = Default::default(); version_array.copy_from_slice(&data[..4]); if u32::from_be_bytes(version_array) != RLINK_VERSION { return Err(".rlink file was produced with encoding version {version_array}, but the current version is {RLINK_VERSION}".to_string()); } let mut decoder = opaque::Decoder::new(&data[4..], 0); let rustc_version = decoder.read_str(); let current_version = RUSTC_VERSION.unwrap(); if rustc_version != current_version { return Err(format!( ".rlink file was produced by rustc version {rustc_version}, but the current version is {current_version}." )); } let codegen_results = CodegenResults::decode(&mut decoder); Ok(codegen_results) } } </ins>

let rlink_data = fs::read(file).unwrap_or_else(|err| { sess.fatal(&format!("failed to read rlink file: {}", err)); }); <del> let mut decoder = rustc_serialize::opaque::Decoder::new(&rlink_data, 0); let codegen_results: CodegenResults = rustc_serialize::Decodable::decode(&mut decoder); </del> <ins> let codegen_results = match CodegenResults::deserialize_rlink(rlink_data) { Ok(codegen) => codegen, Err(error) => { sess.fatal(&format!("Could not deserialize .rlink file: {error}")); } }; </ins> let result = compiler.codegen_backend().link(sess, codegen_results, &outputs); abort_on_err(result, sess); } else {

use rustc_ast as ast; use rustc_codegen_ssa::traits::CodegenBackend; <ins> use rustc_codegen_ssa::CodegenResults; </ins> use rustc_data_structures::svh::Svh; use rustc_data_structures::sync::{Lrc, OnceCell, WorkerLocal}; use rustc_hir::def_id::LOCAL_CRATE;

} if sess.opts.debugging_opts.no_link { <del> let mut encoder = rustc_serialize::opaque::Encoder::new(Vec::new()); rustc_serialize::Encodable::encode(&codegen_results, &mut encoder).unwrap(); </del> <ins> let encoded = CodegenResults::serialize_rlink(&codegen_results); </ins> let rlink_file = self.prepare_outputs.with_extension(config::RLINK_EXT); <del> std::fs::write(&rlink_file, encoder.into_inner()).map_err(|err| { </del> <ins> std::fs::write(&rlink_file, encoded).map_err(|err| { </ins> sess.fatal(&format!("failed to write file {}: {}", rlink_file.display(), err)); })?; return Ok(());

<ins> -include ../tools.mk all: echo 'fn main(){}' > $(TMPDIR)/main.rs # Make sure that this fails ! $(RUSTC) -Z link-only $(TMPDIR)/main.rs 2> $(TMPDIR)/stderr.txt $(CGREP) "The input does not look like a .rlink file" < $(TMPDIR)/stderr.txt </ins>