1use crate::CharonFailure;
3use crate::translate::translate_crate;
4use charon_lib::common::arg_value;
5use charon_lib::errors::ErrorCtx;
6use charon_lib::options::{self, CliOpts};
7use charon_lib::transform::TransformCtx;
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::path::PathBuf;
17use std::process::Command;
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::{env, fmt};
20
21fn run_compiler_with_callbacks(
23 args: Vec<String>,
24 callbacks: &mut (dyn Callbacks + Send),
25) -> Result<(), CharonFailure> {
26 rustc_driver::catch_fatal_errors(|| rustc_driver::run_compiler(&args, callbacks))
27 .map_err(|_| CharonFailure::RustcError)
28}
29
30fn set_mir_options(config: &mut Config) {
32 config.opts.unstable_opts.always_encode_mir = true;
33 config.opts.unstable_opts.mir_opt_level = Some(0);
34 config.opts.unstable_opts.mir_preserve_ub = true;
35 let disabled_mir_passes = ["CheckAlignment", "CheckNull"];
36 for pass in disabled_mir_passes {
37 config
38 .opts
39 .unstable_opts
40 .mir_enable_passes
41 .push((pass.to_owned(), false));
42 }
43}
44
45fn set_parallel_frontend(config: &mut Config) {
47 if config.opts.unstable_opts.threads.is_none_or(|t| t <= 1) {
48 const RUSTC_MAX_THREADS_CAP: usize = 256;
50 config.opts.unstable_opts.threads = Some(
51 std::thread::available_parallelism()
52 .map_or(1, |n| n.get())
53 .min(RUSTC_MAX_THREADS_CAP),
54 );
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) && !def_kind.is_typeck_child() {
127 tcx.ensure_ok().typeck(def_id);
128 }
129 });
130
131 tcx.dcx().has_errors().is_some()
132}
133
134fn check_late_rustc_errors(tcx: TyCtxt<'_>) {
137 tcx.par_hir_body_owners(|def_id| {
138 let _ = tcx.instance_mir(InstanceKind::Item(def_id.to_def_id()));
139 });
140
141 if tcx.dcx().err_count() == 0 {
142 let _ = tcx.collect_and_partition_mono_items(());
143 }
144}
145
146fn setup_miri_sysroot(target: &str) -> Option<PathBuf> {
149 if let Some(root) = env::var_os("CHARON_MIRI_SYSROOTS")
150 && let sysroot = PathBuf::from(root)
151 && sysroot.join("lib").join("rustlib").join(target).is_dir()
152 {
153 return Some(sysroot);
154 }
155
156 let mut cmd = Command::new("cargo");
157 cmd.arg("miri")
158 .arg("setup")
159 .arg(format!("--target={target}"))
160 .arg("--print-sysroot")
161 .env_remove("RUSTC_WORKSPACE_WRAPPER")
162 .env_remove("RUSTC_WRAPPER");
163
164 let output = match cmd.output() {
165 Ok(output) => output,
166 Err(err) => {
167 eprintln!(
168 "warning: failed to run `cargo miri setup` for target `{target}`; \
169 falling back to rustc's default sysroot: {err}"
170 );
171 return None;
172 }
173 };
174
175 if !output.status.success() {
176 let stderr = String::from_utf8_lossy(&output.stderr);
177 eprintln!(
178 "warning: `cargo miri setup` failed for target `{target}`; \
179 falling back to rustc's default sysroot: {}",
180 stderr.trim()
181 );
182 return None;
183 }
184
185 let stdout = String::from_utf8_lossy(&output.stdout);
186 let sysroot = stdout.lines().map(str::trim).find(|line| !line.is_empty());
187 match sysroot {
188 Some(sysroot) => Some(PathBuf::from(sysroot)),
189 None => {
190 eprintln!(
191 "warning: `cargo miri setup --print-sysroot` printed no sysroot for target \
192 `{target}`; falling back to rustc's default sysroot"
193 );
194 None
195 }
196 }
197}
198
199pub fn run_rustc_driver() -> Result<Option<(TransformCtx, CliOpts)>, CharonFailure> {
203 let mut compiler_args: Vec<String> = env::args().skip(1).collect();
206 if compiler_args
209 .first()
210 .is_some_and(|arg| arg.starts_with("charon-dont-cache-this-"))
211 {
212 compiler_args.remove(0);
213 }
214 trace!(
215 "charon-driver called with args: {}",
216 compiler_args.iter().format(" ")
217 );
218
219 let is_workspace_dependency =
224 env::var("CHARON_USING_CARGO").is_ok() && env::var("CARGO_PRIMARY_PACKAGE").is_err();
225 let emit_artifacts =
228 env::var("CHARON_USING_CARGO").is_ok() || env::var("CHARON_EMIT_ARTIFACTS").is_ok();
229 let mut codegen = emit_artifacts;
231 let target = arg_value(&compiler_args, "--target");
239 let is_selected_crate = !is_workspace_dependency && target.is_some();
241
242 let mut error_ctx = ErrorCtx::new();
243
244 let mut options = match env::var(options::CHARON_ARGS)
248 .ok()
249 .map(|opts| serde_json::from_str::<options::CliOpts>(&opts).unwrap())
250 {
251 Some(options) => options,
252 None if !is_selected_crate => Default::default(),
253 None => {
254 register_error!(
255 error_ctx,
256 no_crate,
257 "environment variable `CHARON_ARGS` not set; \
258 don't call `charon-driver` directly, call `charon rustc` instead"
259 );
260 return Err(CharonFailure::CharonError(1));
261 }
262 };
263
264 if options.sysroot.as_deref() == Some("default") {
265 } else if let Some(sysroot) = options.sysroot.as_ref()
267 && sysroot != "miri"
268 {
269 compiler_args.push(format!("--sysroot={sysroot}"));
270 } else if let Some(target) = target
271 && let Some(sysroot) = setup_miri_sysroot(target)
272 {
273 compiler_args.push(format!("--sysroot={}", sysroot.display()));
275 codegen = false;
277 }
278
279 let output = if !is_selected_crate {
280 trace!("Skipping charon; running compiler normally instead.");
281 run_compiler_with_callbacks(compiler_args, &mut RunCompilerNormallyCallbacks)?;
283 None
284 } else {
285 options.apply_preset();
286
287 error_ctx.continue_on_failure = !options.abort_on_error;
288 error_ctx.error_on_warnings = options.error_on_warnings;
289
290 for extra_flag in options.rustc_args.iter().cloned() {
291 compiler_args.push(extra_flag);
292 }
293
294 let mut callback = CharonCallbacks {
296 options: &options,
297 emit_artifacts,
298 codegen,
299 error_ctx: Some(error_ctx),
300 transform_ctx: None,
301 };
302 run_compiler_with_callbacks(compiler_args, &mut callback)?;
303 let ctx = callback.transform_ctx.ok_or(CharonFailure::RustcError)?;
305 Some((ctx, options))
306 };
307 Ok(output)
308}
309
310pub struct CharonCallbacks<'a> {
312 options: &'a CliOpts,
313 emit_artifacts: bool,
316 codegen: bool,
318 error_ctx: Option<ErrorCtx>,
320 transform_ctx: Option<TransformCtx>,
323}
324impl<'a> Callbacks for CharonCallbacks<'a> {
325 fn config(&mut self, config: &mut Config) {
326 setup_compiler(
327 config,
328 self.options,
329 true,
330 self.emit_artifacts,
331 self.codegen,
332 );
333 }
334
335 fn after_expansion<'tcx>(&mut self, compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
340 rustc_hir::def_id::DEF_ID_DEBUG
342 .swap(&(def_id_debug as fn(_, &mut fmt::Formatter<'_>) -> _));
343
344 if precheck_rustc_errors(tcx) {
345 return Compilation::Continue;
346 }
347
348 self.transform_ctx = translate_crate::translate(
349 tcx,
350 self.options,
351 self.error_ctx.take().unwrap(),
352 compiler.sess.opts.sysroot.path().to_owned(),
353 )
354 .ok();
355
356 Compilation::Continue
357 }
358 fn after_analysis<'tcx>(&mut self, _compiler: &Compiler, tcx: TyCtxt<'tcx>) -> Compilation {
359 if !self.emit_artifacts {
360 check_late_rustc_errors(tcx);
361 }
362 Compilation::Continue
363 }
364}
365
366pub struct RunCompilerNormallyCallbacks;
368
369impl Callbacks for RunCompilerNormallyCallbacks {
370 fn config(&mut self, config: &mut Config) {
371 setup_compiler(config, &Default::default(), false, true, true);
372 }
373}
374
375fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377 rustc_middle::ty::tls::with_opt(|opt_tcx| {
378 if let Some(tcx) = opt_tcx {
379 let crate_name = if def_id.is_local() {
380 tcx.crate_name(rustc_hir::def_id::LOCAL_CRATE)
381 } else {
382 tcx.cstore_untracked().crate_name(def_id.krate)
383 };
384 write!(
385 f,
386 "{}{}",
387 crate_name,
388 tcx.def_path(def_id).to_string_no_crate_verbose()
389 )?;
390 } else {
391 write!(f, "<can't access `tcx` to print `DefId` path>")?;
392 }
393 Ok(())
394 })
395}