1use crate::CharonFailure;
3use crate::translate::translate_crate;
4use charon_lib::errors::ErrorCtx;
5use charon_lib::options::{self, CliOpts};
6use charon_lib::transform::TransformCtx;
7use charon_lib::utils::arg_value;
8use itertools::Itertools;
9use rustc_driver::{Callbacks, Compilation};
10use rustc_interface::Config;
11use rustc_interface::interface::Compiler;
12use rustc_middle::ty::{InstanceKind, TyCtxt};
13use rustc_middle::util::Providers;
14use rustc_session::config::{OutputType, OutputTypes};
15use rustc_span::ErrorGuaranteed;
16use std::num::NonZero;
17use std::path::PathBuf;
18use std::process::Command;
19use std::sync::atomic::{AtomicBool, Ordering};
20use std::{env, fmt};
21
22fn run_compiler_with_callbacks(
24 args: Vec<String>,
25 callbacks: &mut (dyn Callbacks + Send),
26) -> Result<(), CharonFailure> {
27 rustc_driver::catch_fatal_errors(|| rustc_driver::run_compiler(&args, callbacks))
28 .map_err(|_| CharonFailure::RustcError)
29}
30
31fn set_mir_options(config: &mut Config) {
33 config.opts.unstable_opts.always_encode_mir = true;
34 config.opts.unstable_opts.mir_opt_level = Some(0);
35 config.opts.unstable_opts.mir_preserve_ub = true;
36 let disabled_mir_passes = ["CheckAlignment", "CheckNull"];
37 for pass in disabled_mir_passes {
38 config
39 .opts
40 .unstable_opts
41 .mir_enable_passes
42 .push((pass.to_owned(), false));
43 }
44}
45
46fn set_parallel_frontend(config: &mut Config) {
48 if config.opts.jobs.frontend.is_none() {
49 const RUSTC_MAX_THREADS_CAP: usize = u8::MAX as usize;
51 let threads = std::thread::available_parallelism()
52 .map_or(1, |n| n.get())
53 .min(RUSTC_MAX_THREADS_CAP);
54 config.opts.jobs.frontend = NonZero::new(threads).filter(|n| n.get() > 1);
55 }
56}
57
58static SKIP_BORROWCK: AtomicBool = AtomicBool::new(false);
60fn set_skip_borrowck() {
61 SKIP_BORROWCK.store(true, Ordering::SeqCst);
62}
63fn skip_borrowck_if_set(providers: &mut Providers) {
64 if SKIP_BORROWCK.load(Ordering::SeqCst) {
65 providers.queries.mir_borrowck = |tcx, _def_id| {
66 Ok(tcx.arena.alloc(Default::default()))
68 }
69 }
70}
71
72fn setup_compiler(
73 config: &mut Config,
74 options: &CliOpts,
75 do_translate: bool,
76 emit_artifacts: bool,
77 codegen: bool,
78) {
79 if do_translate {
80 if options.skip_borrowck {
81 set_skip_borrowck();
83 }
84
85 config.override_queries = Some(|_sess, providers| {
86 skip_borrowck_if_set(providers);
87
88 });
96
97 config.opts.unstable_opts.no_codegen = !codegen;
98 if !emit_artifacts {
99 config.opts.output_types = OutputTypes::new(&[(OutputType::Object, None)]);
100 }
101 set_parallel_frontend(config);
102 }
103 set_mir_options(config);
104}
105
106fn precheck_rustc_errors(tcx: TyCtxt<'_>) -> bool {
110 type QueryResult = Result<(), ErrorGuaranteed>;
111
112 tcx.par_hir_for_each_module(|module| {
113 tcx.ensure_ok().check_mod_attrs(module);
114 tcx.ensure_ok().check_mod_unstable_api_usage(module);
115 });
116
117 let _: QueryResult = tcx.ensure_result().check_type_wf(());
118 for &trait_def_id in tcx.all_local_trait_impls(()).keys() {
119 let _: QueryResult = tcx.ensure_result().coherent_trait(trait_def_id);
120 }
121 let _: QueryResult = tcx.ensure_result().crate_inherent_impls_validity_check(());
122 let _: QueryResult = tcx.ensure_result().crate_inherent_impls_overlap_check(());
123
124 tcx.par_hir_body_owners(|def_id| {
125 let def_kind = tcx.def_kind(def_id);
126 if !matches!(def_kind, rustc_hir::def::DefKind::AnonConst)
127 && !tcx.is_typeck_child(def_id.to_def_id())
128 {
129 tcx.ensure_ok().typeck(def_id);
130 }
131 });
132
133 tcx.dcx().has_errors().is_some()
134}
135
136fn check_late_rustc_errors(tcx: TyCtxt<'_>) {
139 tcx.par_hir_body_owners(|def_id| {
140 let _ = tcx.instance_mir(InstanceKind::Item(def_id.to_def_id()));
141 });
142
143 if tcx.dcx().err_count() == 0 {
144 let _ = tcx.collect_and_partition_mono_items(());
145 }
146}
147
148fn setup_miri_sysroot(target: &str) -> Option<PathBuf> {
151 if let Some(root) = env::var_os("CHARON_MIRI_SYSROOTS")
152 && let sysroot = PathBuf::from(root)
153 && sysroot.join("lib").join("rustlib").join(target).is_dir()
154 {
155 return Some(sysroot);
156 }
157
158 let mut cmd = Command::new("cargo");
159 cmd.arg("miri")
160 .arg("setup")
161 .arg(format!("--target={target}"))
162 .arg("--print-sysroot")
163 .env_remove("RUSTC_WORKSPACE_WRAPPER")
164 .env_remove("RUSTC_WRAPPER");
165
166 let output = match cmd.output() {
167 Ok(output) => output,
168 Err(err) => {
169 eprintln!(
170 "warning: failed to run `cargo miri setup` for target `{target}`; \
171 falling back to rustc's default sysroot: {err}"
172 );
173 return None;
174 }
175 };
176
177 if !output.status.success() {
178 let stderr = String::from_utf8_lossy(&output.stderr);
179 eprintln!(
180 "warning: `cargo miri setup` failed for target `{target}`; \
181 falling back to rustc's default sysroot: {}",
182 stderr.trim()
183 );
184 return None;
185 }
186
187 let stdout = String::from_utf8_lossy(&output.stdout);
188 let sysroot = stdout.lines().map(str::trim).find(|line| !line.is_empty());
189 match sysroot {
190 Some(sysroot) => Some(PathBuf::from(sysroot)),
191 None => {
192 eprintln!(
193 "warning: `cargo miri setup --print-sysroot` printed no sysroot for target \
194 `{target}`; falling back to rustc's default sysroot"
195 );
196 None
197 }
198 }
199}
200
201pub fn run_rustc_driver() -> Result<Option<(TransformCtx, CliOpts)>, CharonFailure> {
205 let mut compiler_args: Vec<String> = env::args().skip(1).collect();
208 if compiler_args
211 .first()
212 .is_some_and(|arg| arg.starts_with("charon-dont-cache-this-"))
213 {
214 compiler_args.remove(0);
215 }
216 trace!(
217 "charon-driver called with args: {}",
218 compiler_args.iter().format(" ")
219 );
220
221 let is_workspace_dependency =
226 env::var("CHARON_USING_CARGO").is_ok() && env::var("CARGO_PRIMARY_PACKAGE").is_err();
227 let emit_artifacts =
230 env::var("CHARON_USING_CARGO").is_ok() || env::var("CHARON_EMIT_ARTIFACTS").is_ok();
231 let mut codegen = emit_artifacts;
233 let target = arg_value(&compiler_args, "--target");
241 let is_selected_crate = !is_workspace_dependency && target.is_some();
243
244 let mut error_ctx = ErrorCtx::new();
245
246 let mut options = match env::var(options::CHARON_ARGS)
250 .ok()
251 .map(|opts| serde_json::from_str::<options::CliOpts>(&opts).unwrap())
252 {
253 Some(options) => options,
254 None if !is_selected_crate => Default::default(),
255 None => {
256 register_error!(
257 error_ctx,
258 no_crate,
259 "environment variable `CHARON_ARGS` not set; \
260 don't call `charon-driver` directly, call `charon rustc` instead"
261 );
262 return Err(CharonFailure::CharonError(1));
263 }
264 };
265
266 if options.sysroot.as_deref() == Some("default") {
267 } else if let Some(sysroot) = options.sysroot.as_ref()
269 && sysroot != "miri"
270 {
271 compiler_args.push(format!("--sysroot={sysroot}"));
272 } else if let Some(target) = target
273 && let Some(sysroot) = setup_miri_sysroot(target)
274 {
275 compiler_args.push(format!("--sysroot={}", sysroot.display()));
277 codegen = false;
279 }
280
281 let output = if !is_selected_crate {
282 trace!("Skipping charon; running compiler normally instead.");
283 run_compiler_with_callbacks(compiler_args, &mut RunCompilerNormallyCallbacks)?;
285 None
286 } else {
287 options.apply_preset();
288
289 error_ctx.continue_on_failure = !options.abort_on_error;
290 error_ctx.error_on_warnings = options.error_on_warnings;
291
292 for extra_flag in options.rustc_args.iter().cloned() {
293 compiler_args.push(extra_flag);
294 }
295
296 let mut callback = CharonCallbacks {
298 options: &options,
299 emit_artifacts,
300 codegen,
301 error_ctx: Some(error_ctx),
302 transform_ctx: None,
303 };
304 run_compiler_with_callbacks(compiler_args, &mut callback)?;
305 let ctx = callback.transform_ctx.ok_or(CharonFailure::RustcError)?;
307 Some((ctx, options))
308 };
309 Ok(output)
310}
311
312pub struct CharonCallbacks<'a> {
314 options: &'a CliOpts,
315 emit_artifacts: bool,
318 codegen: bool,
320 error_ctx: Option<ErrorCtx>,
322 transform_ctx: Option<TransformCtx>,
325}
326impl<'a> Callbacks for CharonCallbacks<'a> {
327 fn config(&mut self, config: &mut Config) {
328 setup_compiler(
329 config,
330 self.options,
331 true,
332 self.emit_artifacts,
333 self.codegen,
334 );
335 }
336
337 fn after_expansion<'tcx>(&mut self, compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
342 rustc_hir::def_id::DEF_ID_DEBUG
344 .swap(&(def_id_debug as fn(_, &mut fmt::Formatter<'_>) -> _));
345
346 if precheck_rustc_errors(tcx) {
347 return Compilation::Continue;
348 }
349
350 self.transform_ctx = translate_crate::translate(
351 tcx,
352 self.options,
353 self.error_ctx.take().unwrap(),
354 compiler.sess.opts.sysroot.path().to_owned(),
355 )
356 .ok();
357
358 Compilation::Continue
359 }
360 fn after_analysis<'tcx>(&mut self, _compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
361 if !self.emit_artifacts {
362 check_late_rustc_errors(tcx);
363 }
364 Compilation::Continue
365 }
366}
367
368pub struct RunCompilerNormallyCallbacks;
370
371impl Callbacks for RunCompilerNormallyCallbacks {
372 fn config(&mut self, config: &mut Config) {
373 setup_compiler(config, &Default::default(), false, true, true);
374 }
375}
376
377fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 rustc_middle::ty::tls::with_opt(|opt_tcx| {
380 if let Some(tcx) = opt_tcx {
381 let crate_name = if def_id.is_local() {
382 tcx.crate_name(rustc_hir::def_id::LOCAL_CRATE)
383 } else {
384 tcx.cstore_untracked().crate_name(def_id.krate)
385 };
386 write!(
387 f,
388 "{}{}",
389 crate_name,
390 tcx.def_path(def_id).to_string_no_crate_verbose()
391 )?;
392 } else {
393 write!(f, "<can't access `tcx` to print `DefId` path>")?;
394 }
395 Ok(())
396 })
397}