Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 1 | // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| 2 | // Exceptions. See /LICENSE for license information. |
| 3 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 4 | |
| 5 | //! The `run_compiler` crate mostly wraps and simplifies a subset of APIs |
Devin Jeanpierre | 0b1e988 | 2023-10-02 15:29:06 -0700 | [diff] [blame] | 6 | //! from the `rustc_driver` crate, providing an easy way to `run_compiler`. |
| 7 | #![feature(rustc_private)] |
| 8 | #![deny(rustc::internal)] |
| 9 | |
| 10 | extern crate rustc_driver; |
Devin Jeanpierre | 0b1e988 | 2023-10-02 15:29:06 -0700 | [diff] [blame] | 11 | extern crate rustc_interface; |
| 12 | extern crate rustc_lint_defs; |
Krasimir Georgiev | 75da3ef | 2023-11-21 01:52:53 -0800 | [diff] [blame] | 13 | extern crate rustc_log; |
Devin Jeanpierre | 0b1e988 | 2023-10-02 15:29:06 -0700 | [diff] [blame] | 14 | extern crate rustc_middle; |
| 15 | extern crate rustc_session; |
Krasimir Georgiev | 35e85b6 | 2024-02-22 07:08:52 -0800 | [diff] [blame] | 16 | extern crate rustc_span; |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 17 | |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 18 | use arc_anyhow::{anyhow, bail, Result}; |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 19 | use either::Either; |
| 20 | use rustc_interface::interface::Compiler; |
| 21 | use rustc_interface::Queries; |
| 22 | use rustc_middle::ty::TyCtxt; // See also <internal link>/ty.html#import-conventions |
Googler | 862d856 | 2023-07-06 23:30:36 -0700 | [diff] [blame] | 23 | use rustc_session::config::ErrorOutputType; |
Krasimir Georgiev | 03f5fc5 | 2023-12-21 07:39:58 -0800 | [diff] [blame] | 24 | use rustc_session::EarlyDiagCtxt; |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 25 | |
| 26 | /// Wrapper around `rustc_driver::RunCompiler::run` that exposes a |
| 27 | /// simplified API: |
| 28 | /// - Takes a `callback` that will be invoked from within Rust compiler, after |
| 29 | /// parsing and analysis are done, |
| 30 | /// - Compilation will stop after parsing, analysis, and the `callback` are |
| 31 | /// done, |
| 32 | /// - Returns the combined results from the Rust compiler *and* the `callback`. |
Lukasz Anforowicz | 087dff7 | 2023-02-17 12:13:32 -0800 | [diff] [blame] | 33 | /// - Is safe to run from unit tests (which may run in parallel / on multiple |
| 34 | /// threads). |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 35 | pub fn run_compiler<F, R>(rustc_args: &[String], callback: F) -> Result<R> |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 36 | where |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 37 | F: FnOnce(TyCtxt) -> Result<R> + Send, |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 38 | R: Send, |
| 39 | { |
Krasimir Georgiev | 75da3ef | 2023-11-21 01:52:53 -0800 | [diff] [blame] | 40 | // Calling `init_logger` 1) here and 2) via `sync::Lazy` helps to ensure |
Lukasz Anforowicz | 087dff7 | 2023-02-17 12:13:32 -0800 | [diff] [blame] | 41 | // that logging is intialized exactly once, even if the `run_compiler` |
| 42 | // function is invoked by mutliple unit tests running in parallel on |
| 43 | // separate threads. This is important for avoiding flaky/racy |
| 44 | // panics related to 1) multiple threads entering |
| 45 | // `!tracing::dispatcher::has_been_set()` code in `rustc_driver_impl/src/ |
| 46 | // lib.rs` and 2) `rustc_log/src/lib.rs` assumming that |
| 47 | // `tracing::subscriber::set_global_default` always succeeds. |
| 48 | use once_cell::sync::Lazy; |
| 49 | static ENV_LOGGER_INIT: Lazy<()> = Lazy::new(|| { |
Krasimir Georgiev | 03f5fc5 | 2023-12-21 07:39:58 -0800 | [diff] [blame] | 50 | let early_error_handler = EarlyDiagCtxt::new(ErrorOutputType::default()); |
Krasimir Georgiev | 75da3ef | 2023-11-21 01:52:53 -0800 | [diff] [blame] | 51 | rustc_driver::init_logger( |
| 52 | &early_error_handler, |
| 53 | rustc_log::LoggerConfig::from_env("CRUBIT_LOG"), |
| 54 | ); |
Lukasz Anforowicz | 087dff7 | 2023-02-17 12:13:32 -0800 | [diff] [blame] | 55 | }); |
| 56 | Lazy::force(&ENV_LOGGER_INIT); |
| 57 | |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 58 | AfterAnalysisCallback::new(rustc_args, callback).run() |
| 59 | } |
| 60 | |
| 61 | struct AfterAnalysisCallback<'a, F, R> |
| 62 | where |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 63 | F: FnOnce(TyCtxt) -> Result<R> + Send, |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 64 | R: Send, |
| 65 | { |
| 66 | args: &'a [String], |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 67 | callback_or_result: Either<F, Result<R>>, |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 68 | } |
| 69 | |
| 70 | impl<'a, F, R> AfterAnalysisCallback<'a, F, R> |
| 71 | where |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 72 | F: FnOnce(TyCtxt) -> Result<R> + Send, |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 73 | R: Send, |
| 74 | { |
| 75 | fn new(args: &'a [String], callback: F) -> Self { |
| 76 | Self { args, callback_or_result: Either::Left(callback) } |
| 77 | } |
| 78 | |
| 79 | /// Runs Rust compiler, and then invokes the stored callback (with |
| 80 | /// `TyCtxt` of the parsed+analyzed Rust crate as the callback's |
| 81 | /// argument), and then finally returns the combined results |
| 82 | /// from Rust compiler *and* the callback. |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 83 | fn run(mut self) -> Result<R> { |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 84 | // Rust compiler unwinds with a special sentinel value to abort compilation on |
| 85 | // fatal errors. We use `catch_fatal_errors` to 1) catch such panics and |
| 86 | // translate them into a Result, and 2) resume and propagate other panics. |
Krasimir Georgiev | 35e85b6 | 2024-02-22 07:08:52 -0800 | [diff] [blame] | 87 | let catch_fatal_errors_result: std::result::Result< |
| 88 | std::result::Result<(), rustc_span::ErrorGuaranteed>, |
| 89 | rustc_span::fatal_error::FatalError, |
| 90 | > = rustc_driver::catch_fatal_errors(|| { |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 91 | rustc_driver::RunCompiler::new(self.args, &mut self).run() |
| 92 | }); |
| 93 | |
Devin Jeanpierre | 2a8e83e | 2024-03-04 14:06:58 -0800 | [diff] [blame] | 94 | match catch_fatal_errors_result { |
| 95 | Ok(Ok(())) => {} |
| 96 | // We can ignore the `Err` payloads because the error types have only one value. |
| 97 | _ => bail!("Errors reported by Rust compiler."), |
Krasimir Georgiev | 35e85b6 | 2024-02-22 07:08:52 -0800 | [diff] [blame] | 98 | }; |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 99 | |
Devin Jeanpierre | 2a8e83e | 2024-03-04 14:06:58 -0800 | [diff] [blame] | 100 | self.callback_or_result.right_or_else(|_left| { |
| 101 | // When rustc cmdline arguments (i.e. `self.args`) are empty (or contain |
| 102 | // `--help`) then the `after_analysis` callback won't be invoked. Handle |
| 103 | // this case by emitting an explicit error at the Crubit level. |
| 104 | bail!("The Rust compiler had no crate to compile and analyze") |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 105 | }) |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | impl<'a, F, R> rustc_driver::Callbacks for AfterAnalysisCallback<'a, F, R> |
| 110 | where |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 111 | F: FnOnce(TyCtxt) -> Result<R> + Send, |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 112 | R: Send, |
| 113 | { |
Lukasz Anforowicz | 01a1d12 | 2023-03-02 06:37:01 -0800 | [diff] [blame] | 114 | /// Configures how `rustc` internals work when invoked via `run_compiler`. |
Devin Jeanpierre | 0b1e988 | 2023-10-02 15:29:06 -0700 | [diff] [blame] | 115 | /// Note that `run_compiler_test_support` uses a separate `Config`. |
Lukasz Anforowicz | 01a1d12 | 2023-03-02 06:37:01 -0800 | [diff] [blame] | 116 | fn config(&mut self, config: &mut rustc_interface::interface::Config) { |
| 117 | // Silence warnings in the target crate to avoid reporting them twice: once when |
| 118 | // compiling the crate via `rustc` and once when "compiling" the crate |
| 119 | // via `cc_bindings_from_rs` (the `config` here affects the latter one). |
| 120 | config.opts.lint_opts.push(("warnings".to_string(), rustc_lint_defs::Level::Allow)); |
| 121 | } |
| 122 | |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 123 | fn after_analysis<'tcx>( |
| 124 | &mut self, |
| 125 | _compiler: &Compiler, |
| 126 | queries: &'tcx Queries<'tcx>, |
| 127 | ) -> rustc_driver::Compilation { |
Devin Jeanpierre | 0b1e988 | 2023-10-02 15:29:06 -0700 | [diff] [blame] | 128 | // `after_analysis` is only called by `rustc_driver` if earlier compiler |
| 129 | // analysis was successful (which as the *last* compilation phase |
| 130 | // presumably covers *all* errors). |
| 131 | let mut query_context = queries |
| 132 | .global_ctxt() |
| 133 | .expect("Expecting no compile errors inside `after_analysis` callback."); |
| 134 | query_context.enter(|tcx| { |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 135 | let callback = { |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 136 | let temporary_placeholder = Either::Right(Err(anyhow!("unused"))); |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 137 | std::mem::replace(&mut self.callback_or_result, temporary_placeholder) |
| 138 | .left_or_else(|_| panic!("`after_analysis` should only run once")) |
| 139 | }; |
| 140 | self.callback_or_result = Either::Right(callback(tcx)); |
| 141 | }); |
| 142 | |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 143 | rustc_driver::Compilation::Stop |
| 144 | } |
| 145 | } |
| 146 | |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 147 | #[cfg(test)] |
Devin Jeanpierre | 0b1e988 | 2023-10-02 15:29:06 -0700 | [diff] [blame] | 148 | mod tests { |
| 149 | use super::*; |
| 150 | use run_compiler_test_support::get_sysroot_for_testing; |
Krasimir Georgiev | 96c1245 | 2024-07-11 00:27:40 -0700 | [diff] [blame] | 151 | use run_compiler_test_support::setup_rustc_target_for_testing; |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 152 | use tempfile::tempdir; |
| 153 | |
| 154 | const DEFAULT_RUST_SOURCE_FOR_TESTING: &'static str = r#" pub mod public_module { |
| 155 | pub fn public_function() { |
| 156 | private_function() |
| 157 | } |
| 158 | |
| 159 | fn private_function() {} |
| 160 | } |
| 161 | "#; |
| 162 | |
| 163 | #[test] |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 164 | fn test_run_compiler_rustc_error_propagation() -> Result<()> { |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 165 | let rustc_args = vec![ |
| 166 | "run_compiler_unittest_executable".to_string(), |
| 167 | "--unrecognized-rustc-flag".to_string(), |
| 168 | ]; |
| 169 | let err = run_compiler(&rustc_args, |_tcx| Ok(())) |
| 170 | .expect_err("--unrecognized-rustc-flag should trigger an error"); |
| 171 | |
| 172 | let msg = format!("{err:#}"); |
| 173 | assert_eq!("Errors reported by Rust compiler.", msg); |
| 174 | Ok(()) |
| 175 | } |
| 176 | |
| 177 | /// `test_run_compiler_empty_args` tests that we gracefully handle scenarios |
| 178 | /// where `rustc` doesn't compile anything (e.g. when there are no |
| 179 | /// cmdline args). |
| 180 | #[test] |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 181 | fn test_run_compiler_no_args_except_argv0() -> Result<()> { |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 182 | let rustc_args = vec!["run_compiler_unittest_executable".to_string()]; |
| 183 | let err = run_compiler(&rustc_args, |_tcx| Ok(())) |
| 184 | .expect_err("Empty `rustc_args` should trigger an error"); |
| 185 | |
| 186 | let msg = format!("{err:#}"); |
| 187 | assert_eq!("The Rust compiler had no crate to compile and analyze", msg); |
| 188 | Ok(()) |
| 189 | } |
| 190 | |
| 191 | /// `test_run_compiler_help` tests that we gracefully handle scenarios where |
| 192 | /// `rustc` doesn't compile anything (e.g. when passing `--help`). |
| 193 | #[test] |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 194 | fn test_run_compiler_help() -> Result<()> { |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 195 | let rustc_args = vec!["run_compiler_unittest_executable".to_string(), "--help".to_string()]; |
| 196 | let err = run_compiler(&rustc_args, |_tcx| Ok(())) |
| 197 | .expect_err("--help passed to rustc should trigger an error"); |
| 198 | |
| 199 | let msg = format!("{err:#}"); |
| 200 | assert_eq!("The Rust compiler had no crate to compile and analyze", msg); |
| 201 | Ok(()) |
| 202 | } |
| 203 | |
| 204 | /// `test_run_compiler_no_output_file` tests that we stop the compilation |
| 205 | /// midway (i.e. that we return `Stop` from `after_analysis`). |
| 206 | #[test] |
Devin Jeanpierre | 45845b9 | 2024-06-21 01:29:11 -0700 | [diff] [blame] | 207 | fn test_run_compiler_no_output_file() -> Result<()> { |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 208 | let tmpdir = tempdir()?; |
| 209 | |
| 210 | let rs_path = tmpdir.path().join("input_crate.rs"); |
| 211 | std::fs::write(&rs_path, DEFAULT_RUST_SOURCE_FOR_TESTING)?; |
| 212 | |
| 213 | let out_path = tmpdir.path().join("unexpected_output.o"); |
Krasimir Georgiev | 96c1245 | 2024-07-11 00:27:40 -0700 | [diff] [blame] | 214 | let mut rustc_args = vec![ |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 215 | // Default parameters. |
| 216 | "run_compiler_unittest_executable".to_string(), |
| 217 | "--crate-type=lib".to_string(), |
| 218 | format!("--sysroot={}", get_sysroot_for_testing().display()), |
| 219 | rs_path.display().to_string(), |
| 220 | // Test-specific parameter: asking for after-analysis output |
| 221 | "-o".to_string(), |
| 222 | out_path.display().to_string(), |
| 223 | ]; |
Krasimir Georgiev | 96c1245 | 2024-07-11 00:27:40 -0700 | [diff] [blame] | 224 | if let Some(target_arg) = setup_rustc_target_for_testing(tmpdir.path()) { |
| 225 | rustc_args.push(format!("--target={}", target_arg)); |
| 226 | } |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 227 | |
| 228 | run_compiler(&rustc_args, |_tcx| Ok(()))?; |
| 229 | |
| 230 | // Verify that compilation didn't continue after the initial analysis. |
| 231 | assert!(!out_path.exists()); |
| 232 | Ok(()) |
| 233 | } |
Lukasz Anforowicz | 0bef264 | 2023-01-05 09:20:31 -0800 | [diff] [blame] | 234 | } |