Skip to content

Commit 322bba0

Browse files
committed
Auto merge of #139453 - compiler-errors:incr, r=jieyouxu
Prepend temp files with per-invocation random string to avoid temp filename conflicts rust-lang/rust#139407 uncovered a very subtle unsoundness with incremental codegen, failing compilation sessions (due to assembler errors), and the "prefer hard linking over copying files" strategy we use in the compiler for file management. Specifically, imagine we're building a single file 3 times, all with `-Csave-temps -Cincremental=...`. Let's call the object file we're building for the codegen unit for `main` "`XXX.o`" just for clarity since it's probably some gigantic hash name: ``` #[inline(never)] #[cfg(any(rpass1, rpass3))] fn a() -> i32 { 0 } #[cfg(any(cfail2))] fn a() -> i32 { 1 } fn main() { evil::evil(); assert_eq!(a(), 0); } mod evil { #[cfg(any(rpass1, rpass3))] pub fn evil() { unsafe { std::arch::asm!("/* */"); } } #[cfg(any(cfail2))] pub fn evil() { unsafe { std::arch::asm!("missing"); } } } ``` Session 1 (`rpass1`): * Type-check, borrow-check, etc. * Serialize the dep graph to the incremental working directory `.../s-...-working/`. * Codegen object file to a temp file `XXX.rcgu.o` which is spit out in the cwd. * Hard-link[^1] `XXX.rcgu.o` to the incremental working directory `.../s-...-working/XXX.o`. * Save-temps option means we don't delete `XXX.rgcu.o`. * Link the binary and stuff. * Finalize[^2] the working incremental session by renaming `.../s-...-working` to ` s-...-asjkdhsjakd` (some other finalized incr comp session dir name). Session 2 (`cfail2`): * Load artifacts from the previous *finalized* incremental session, namely the dep graph. * Type-check, borrow-check, etc. since the file has changed, so most dep graph nodes are red. * Serialize the dep graph to the incremental working directory `.../s-...-working/`. * Codegen object file to a temp file `XXX.rcgu.o`. **HERE IS THE PROBLEM**: The hard-link is still set up to point to the inode from `XXX.o` from the first session, so this also modifies the `XXX.o` in the previous finalized session directory. * Codegen emits an error b/c `missing` is not an instruction, so we abort before finalizing the incremental session. Specifically, this means that the *previous* session is the last finalized session. Session 3 (`rpass3`): * Load artifacts from the previous *finalized* incremental session, namely the dep graph. NOTE that this is from session 1. * All the dep graph nodes are green since we are basically replaying session 1. * codegen object file `XXX.o`, which is detected as *reused* from session 1 since dep nodes were green. That means we **reuse** `XXX.o` which had been dirtied from session 2. * Link the binary and stuff. This results in a binary which reuses some of the build artifacts from session 2, but thinks it's from session 1. At this point, I hope it's clear to see that the incremental results from session 1 were dirtied from session 2, but we reuse them as if session 1 was the previous (finalized) incremental session we ran. This is at best really buggy, and at worst **unsound**. This isn't limited to `-C save-temps`, since there are other combinations of flags that may keep around temporary files (hard linked) in the working directory (like `-C debuginfo=1 -C split-debuginfo=unpacked` on darwin, for example). --- This PR implements a fix which is to prepend temp filenames with a random string that is generated per invocation of rustc. This string is not *deterministic*, but temporary files are transient anyways, so I don't believe this is a problem. That means that temp files are now something like... `{crate-name}.{cgu}.{invocation_temp}.rcgu.o`, where `{invocation_temp}` is the new temporary string we generate per invocation of rustc. Fixes rust-lang/rust#139407 [^1]: https://github.com/rust-lang/rust/blob/175dcc7773d65c1b1542c351392080f48c05799f/compiler/rustc_fs_util/src/lib.rs#L60 [^2]: https://github.com/rust-lang/rust/blob/175dcc7773d65c1b1542c351392080f48c05799f/compiler/rustc_incremental/src/persist/fs.rs#L1-L40
2 parents cd8a1ad + 68dd8b3 commit 322bba0

File tree

3 files changed

+35
-19
lines changed

3 files changed

+35
-19
lines changed

Diff for: src/driver/aot.rs

+31-18
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,11 @@ fn produce_final_output_artifacts(
169169
if codegen_results.modules.len() == 1 {
170170
// 1) Only one codegen unit. In this case it's no difficulty
171171
// to copy `foo.0.x` to `foo.x`.
172-
let module_name = Some(&codegen_results.modules[0].name[..]);
173-
let path = crate_output.temp_path(output_type, module_name);
172+
let path = crate_output.temp_path_for_cgu(
173+
output_type,
174+
&codegen_results.modules[0].name,
175+
sess.invocation_temp.as_deref(),
176+
);
174177
let output = crate_output.path(output_type);
175178
if !output_type.is_text_output() && output.is_tty() {
176179
sess.dcx()
@@ -183,22 +186,16 @@ fn produce_final_output_artifacts(
183186
ensure_removed(sess.dcx(), &path);
184187
}
185188
} else {
186-
let extension = crate_output
187-
.temp_path(output_type, None)
188-
.extension()
189-
.unwrap()
190-
.to_str()
191-
.unwrap()
192-
.to_owned();
193-
194189
if crate_output.outputs.contains_explicit_name(&output_type) {
195190
// 2) Multiple codegen units, with `--emit foo=some_name`. We have
196191
// no good solution for this case, so warn the user.
197-
sess.dcx().emit_warn(ssa_errors::IgnoringEmitPath { extension });
192+
sess.dcx()
193+
.emit_warn(ssa_errors::IgnoringEmitPath { extension: output_type.extension() });
198194
} else if crate_output.single_output_file.is_some() {
199195
// 3) Multiple codegen units, with `-o some_name`. We have
200196
// no good solution for this case, so warn the user.
201-
sess.dcx().emit_warn(ssa_errors::IgnoringOutput { extension });
197+
sess.dcx()
198+
.emit_warn(ssa_errors::IgnoringOutput { extension: output_type.extension() });
202199
} else {
203200
// 4) Multiple codegen units, but no explicit name. We
204201
// just leave the `foo.0.x` files in place.
@@ -351,6 +348,7 @@ fn make_module(sess: &Session, name: String) -> UnwindModule<ObjectModule> {
351348

352349
fn emit_cgu(
353350
output_filenames: &OutputFilenames,
351+
invocation_temp: Option<&str>,
354352
prof: &SelfProfilerRef,
355353
name: String,
356354
module: UnwindModule<ObjectModule>,
@@ -366,6 +364,7 @@ fn emit_cgu(
366364

367365
let module_regular = emit_module(
368366
output_filenames,
367+
invocation_temp,
369368
prof,
370369
product.object,
371370
ModuleKind::Regular,
@@ -391,6 +390,7 @@ fn emit_cgu(
391390

392391
fn emit_module(
393392
output_filenames: &OutputFilenames,
393+
invocation_temp: Option<&str>,
394394
prof: &SelfProfilerRef,
395395
mut object: cranelift_object::object::write::Object<'_>,
396396
kind: ModuleKind,
@@ -409,7 +409,7 @@ fn emit_module(
409409
object.set_section_data(comment_section, producer, 1);
410410
}
411411

412-
let tmp_file = output_filenames.temp_path(OutputType::Object, Some(&name));
412+
let tmp_file = output_filenames.temp_path_for_cgu(OutputType::Object, &name, invocation_temp);
413413
let file = match File::create(&tmp_file) {
414414
Ok(file) => file,
415415
Err(err) => return Err(format!("error creating object file: {}", err)),
@@ -449,8 +449,11 @@ fn reuse_workproduct_for_cgu(
449449
cgu: &CodegenUnit<'_>,
450450
) -> Result<ModuleCodegenResult, String> {
451451
let work_product = cgu.previous_work_product(tcx);
452-
let obj_out_regular =
453-
tcx.output_filenames(()).temp_path(OutputType::Object, Some(cgu.name().as_str()));
452+
let obj_out_regular = tcx.output_filenames(()).temp_path_for_cgu(
453+
OutputType::Object,
454+
cgu.name().as_str(),
455+
tcx.sess.invocation_temp.as_deref(),
456+
);
454457
let source_file_regular = rustc_incremental::in_incr_comp_dir_sess(
455458
&tcx.sess,
456459
&work_product.saved_files.get("o").expect("no saved object file in work product"),
@@ -595,13 +598,19 @@ fn module_codegen(
595598

596599
let global_asm_object_file =
597600
profiler.generic_activity_with_arg("compile assembly", &*cgu_name).run(|| {
598-
crate::global_asm::compile_global_asm(&global_asm_config, &cgu_name, &cx.global_asm)
601+
crate::global_asm::compile_global_asm(
602+
&global_asm_config,
603+
&cgu_name,
604+
&cx.global_asm,
605+
cx.invocation_temp.as_deref(),
606+
)
599607
})?;
600608

601609
let codegen_result =
602610
profiler.generic_activity_with_arg("write object file", &*cgu_name).run(|| {
603611
emit_cgu(
604612
&global_asm_config.output_filenames,
613+
cx.invocation_temp.as_deref(),
605614
&profiler,
606615
cgu_name,
607616
module,
@@ -626,8 +635,11 @@ fn emit_metadata_module(tcx: TyCtxt<'_>, metadata: &EncodedMetadata) -> Compiled
626635
.as_str()
627636
.to_string();
628637

629-
let tmp_file =
630-
tcx.output_filenames(()).temp_path(OutputType::Metadata, Some(&metadata_cgu_name));
638+
let tmp_file = tcx.output_filenames(()).temp_path_for_cgu(
639+
OutputType::Metadata,
640+
&metadata_cgu_name,
641+
tcx.sess.invocation_temp.as_deref(),
642+
);
631643

632644
let symbol_name = rustc_middle::middle::exported_symbols::metadata_symbol_name(tcx);
633645
let obj = create_compressed_metadata_file(tcx.sess, metadata, &symbol_name);
@@ -657,6 +669,7 @@ fn emit_allocator_module(tcx: TyCtxt<'_>) -> Option<CompiledModule> {
657669

658670
match emit_module(
659671
tcx.output_filenames(()),
672+
tcx.sess.invocation_temp.as_deref(),
660673
&tcx.sess.prof,
661674
product.object,
662675
ModuleKind::Allocator,

Diff for: src/global_asm.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ pub(crate) fn compile_global_asm(
132132
config: &GlobalAsmConfig,
133133
cgu_name: &str,
134134
global_asm: &str,
135+
invocation_temp: Option<&str>,
135136
) -> Result<Option<PathBuf>, String> {
136137
if global_asm.is_empty() {
137138
return Ok(None);
@@ -146,7 +147,7 @@ pub(crate) fn compile_global_asm(
146147
global_asm.push('\n');
147148

148149
let global_asm_object_file = add_file_stem_postfix(
149-
config.output_filenames.temp_path(OutputType::Object, Some(cgu_name)),
150+
config.output_filenames.temp_path_for_cgu(OutputType::Object, cgu_name, invocation_temp),
150151
".asm",
151152
);
152153

Diff for: src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ impl<F: Fn() -> String> Drop for PrintOnPanic<F> {
124124
/// inside a single codegen unit with the exception of the Cranelift [`Module`](cranelift_module::Module).
125125
struct CodegenCx {
126126
output_filenames: Arc<OutputFilenames>,
127+
invocation_temp: Option<String>,
127128
should_write_ir: bool,
128129
global_asm: String,
129130
inline_asm_index: usize,
@@ -142,6 +143,7 @@ impl CodegenCx {
142143
};
143144
CodegenCx {
144145
output_filenames: tcx.output_filenames(()).clone(),
146+
invocation_temp: tcx.sess.invocation_temp.clone(),
145147
should_write_ir: crate::pretty_clif::should_write_ir(tcx),
146148
global_asm: String::new(),
147149
inline_asm_index: 0,

0 commit comments

Comments
 (0)