rustc_interface/
interface.rs

1use std::path::PathBuf;
2use std::result;
3use std::sync::Arc;
4
5use rustc_ast::{LitKind, MetaItemKind, token};
6use rustc_codegen_ssa::traits::CodegenBackend;
7use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8use rustc_data_structures::jobserver::{self, Proxy};
9use rustc_data_structures::stable_hasher::StableHasher;
10use rustc_errors::registry::Registry;
11use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed};
12use rustc_lint::LintStore;
13use rustc_middle::ty;
14use rustc_middle::ty::CurrentGcx;
15use rustc_middle::util::Providers;
16use rustc_parse::lexer::StripTokens;
17use rustc_parse::new_parser_from_source_str;
18use rustc_parse::parser::Recovery;
19use rustc_parse::parser::attr::AllowLeadingUnsafe;
20use rustc_query_impl::QueryCtxt;
21use rustc_query_system::query::print_query_stack;
22use rustc_session::config::{self, Cfg, CheckCfg, ExpectedValues, Input, OutFileName};
23use rustc_session::parse::ParseSess;
24use rustc_session::{CompilerIO, EarlyDiagCtxt, Session, lint};
25use rustc_span::source_map::{FileLoader, RealFileLoader, SourceMapInputs};
26use rustc_span::{FileName, sym};
27use tracing::trace;
28
29use crate::util;
30
31pub type Result<T> = result::Result<T, ErrorGuaranteed>;
32
33/// Represents a compiler session. Note that every `Compiler` contains a
34/// `Session`, but `Compiler` also contains some things that cannot be in
35/// `Session`, due to `Session` being in a crate that has many fewer
36/// dependencies than this crate.
37///
38/// Can be used to run `rustc_interface` queries.
39/// Created by passing [`Config`] to [`run_compiler`].
40pub struct Compiler {
41    pub sess: Session,
42    pub codegen_backend: Box<dyn CodegenBackend>,
43    pub(crate) override_queries: Option<fn(&Session, &mut Providers)>,
44
45    /// A reference to the current `GlobalCtxt` which we pass on to `GlobalCtxt`.
46    pub(crate) current_gcx: CurrentGcx,
47
48    /// A jobserver reference which we pass on to `GlobalCtxt`.
49    pub(crate) jobserver_proxy: Arc<Proxy>,
50}
51
52/// Converts strings provided as `--cfg [cfgspec]` into a `Cfg`.
53pub(crate) fn parse_cfg(dcx: DiagCtxtHandle<'_>, cfgs: Vec<String>) -> Cfg {
54    cfgs.into_iter()
55        .map(|s| {
56            let psess = ParseSess::emitter_with_note(
57                vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
58                format!("this occurred on the command line: `--cfg={s}`"),
59            );
60            let filename = FileName::cfg_spec_source_code(&s);
61
62            macro_rules! error {
63                ($reason: expr) => {
64                    #[allow(rustc::untranslatable_diagnostic)]
65                    #[allow(rustc::diagnostic_outside_of_impl)]
66                    dcx.fatal(format!("invalid `--cfg` argument: `{s}` ({})", $reason));
67                };
68            }
69
70            match new_parser_from_source_str(&psess, filename, s.to_string(), StripTokens::Nothing)
71            {
72                Ok(mut parser) => {
73                    parser = parser.recovery(Recovery::Forbidden);
74                    match parser.parse_meta_item(AllowLeadingUnsafe::No) {
75                        Ok(meta_item)
76                            if parser.token == token::Eof
77                                && parser.dcx().has_errors().is_none() =>
78                        {
79                            if meta_item.path.segments.len() != 1 {
80                                error!("argument key must be an identifier");
81                            }
82                            match &meta_item.kind {
83                                MetaItemKind::List(..) => {}
84                                MetaItemKind::NameValue(lit) if !lit.kind.is_str() => {
85                                    error!("argument value must be a string");
86                                }
87                                MetaItemKind::NameValue(..) | MetaItemKind::Word => {
88                                    let ident = meta_item.ident().expect("multi-segment cfg key");
89
90                                    if ident.is_path_segment_keyword() {
91                                        error!(
92                                            "malformed `cfg` input, expected a valid identifier"
93                                        );
94                                    }
95
96                                    return (ident.name, meta_item.value_str());
97                                }
98                            }
99                        }
100                        Ok(..) => {}
101                        Err(err) => err.cancel(),
102                    }
103                }
104                Err(errs) => errs.into_iter().for_each(|err| err.cancel()),
105            };
106
107            // If the user tried to use a key="value" flag, but is missing the quotes, provide
108            // a hint about how to resolve this.
109            if s.contains('=') && !s.contains("=\"") && !s.ends_with('"') {
110                error!(concat!(
111                    r#"expected `key` or `key="value"`, ensure escaping is appropriate"#,
112                    r#" for your shell, try 'key="value"' or key=\"value\""#
113                ));
114            } else {
115                error!(r#"expected `key` or `key="value"`"#);
116            }
117        })
118        .collect::<Cfg>()
119}
120
121/// Converts strings provided as `--check-cfg [specs]` into a `CheckCfg`.
122pub(crate) fn parse_check_cfg(dcx: DiagCtxtHandle<'_>, specs: Vec<String>) -> CheckCfg {
123    // If any --check-cfg is passed then exhaustive_values and exhaustive_names
124    // are enabled by default.
125    let exhaustive_names = !specs.is_empty();
126    let exhaustive_values = !specs.is_empty();
127    let mut check_cfg = CheckCfg { exhaustive_names, exhaustive_values, ..CheckCfg::default() };
128
129    for s in specs {
130        let psess = ParseSess::emitter_with_note(
131            vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
132            format!("this occurred on the command line: `--check-cfg={s}`"),
133        );
134        let filename = FileName::cfg_spec_source_code(&s);
135
136        const VISIT: &str =
137            "visit <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more details";
138
139        macro_rules! error {
140            ($reason:expr) => {
141                #[allow(rustc::untranslatable_diagnostic)]
142                #[allow(rustc::diagnostic_outside_of_impl)]
143                {
144                    let mut diag =
145                        dcx.struct_fatal(format!("invalid `--check-cfg` argument: `{s}`"));
146                    diag.note($reason);
147                    diag.note(VISIT);
148                    diag.emit()
149                }
150            };
151            (in $arg:expr, $reason:expr) => {
152                #[allow(rustc::untranslatable_diagnostic)]
153                #[allow(rustc::diagnostic_outside_of_impl)]
154                {
155                    let mut diag =
156                        dcx.struct_fatal(format!("invalid `--check-cfg` argument: `{s}`"));
157
158                    let pparg = rustc_ast_pretty::pprust::meta_list_item_to_string($arg);
159                    if let Some(lit) = $arg.lit() {
160                        let (lit_kind_article, lit_kind_descr) = {
161                            let lit_kind = lit.as_token_lit().kind;
162                            (lit_kind.article(), lit_kind.descr())
163                        };
164                        diag.note(format!(
165                            "`{pparg}` is {lit_kind_article} {lit_kind_descr} literal"
166                        ));
167                    } else {
168                        diag.note(format!("`{pparg}` is invalid"));
169                    }
170
171                    diag.note($reason);
172                    diag.note(VISIT);
173                    diag.emit()
174                }
175            };
176        }
177
178        let expected_error = || -> ! {
179            error!("expected `cfg(name, values(\"value1\", \"value2\", ... \"valueN\"))`")
180        };
181
182        let mut parser =
183            match new_parser_from_source_str(&psess, filename, s.to_string(), StripTokens::Nothing)
184            {
185                Ok(parser) => parser.recovery(Recovery::Forbidden),
186                Err(errs) => {
187                    errs.into_iter().for_each(|err| err.cancel());
188                    expected_error();
189                }
190            };
191
192        let meta_item = match parser.parse_meta_item(AllowLeadingUnsafe::No) {
193            Ok(meta_item) if parser.token == token::Eof && parser.dcx().has_errors().is_none() => {
194                meta_item
195            }
196            Ok(..) => expected_error(),
197            Err(err) => {
198                err.cancel();
199                expected_error();
200            }
201        };
202
203        let Some(args) = meta_item.meta_item_list() else {
204            expected_error();
205        };
206
207        if !meta_item.has_name(sym::cfg) {
208            expected_error();
209        }
210
211        let mut names = Vec::new();
212        let mut values: FxHashSet<_> = Default::default();
213
214        let mut any_specified = false;
215        let mut values_specified = false;
216        let mut values_any_specified = false;
217
218        for arg in args {
219            if arg.is_word()
220                && let Some(ident) = arg.ident()
221            {
222                if values_specified {
223                    error!("`cfg()` names cannot be after values");
224                }
225
226                if ident.is_path_segment_keyword() {
227                    error!("malformed `cfg` input, expected a valid identifier");
228                }
229
230                names.push(ident);
231            } else if let Some(boolean) = arg.boolean_literal() {
232                if values_specified {
233                    error!("`cfg()` names cannot be after values");
234                }
235                names.push(rustc_span::Ident::new(
236                    if boolean { rustc_span::kw::True } else { rustc_span::kw::False },
237                    arg.span(),
238                ));
239            } else if arg.has_name(sym::any)
240                && let Some(args) = arg.meta_item_list()
241            {
242                if any_specified {
243                    error!("`any()` cannot be specified multiple times");
244                }
245                any_specified = true;
246                if !args.is_empty() {
247                    error!(in arg, "`any()` takes no argument");
248                }
249            } else if arg.has_name(sym::values)
250                && let Some(args) = arg.meta_item_list()
251            {
252                if names.is_empty() {
253                    error!("`values()` cannot be specified before the names");
254                } else if values_specified {
255                    error!("`values()` cannot be specified multiple times");
256                }
257                values_specified = true;
258
259                for arg in args {
260                    if let Some(LitKind::Str(s, _)) = arg.lit().map(|lit| &lit.kind) {
261                        values.insert(Some(*s));
262                    } else if arg.has_name(sym::any)
263                        && let Some(args) = arg.meta_item_list()
264                    {
265                        if values_any_specified {
266                            error!(in arg, "`any()` in `values()` cannot be specified multiple times");
267                        }
268                        values_any_specified = true;
269                        if !args.is_empty() {
270                            error!(in arg, "`any()` in `values()` takes no argument");
271                        }
272                    } else if arg.has_name(sym::none)
273                        && let Some(args) = arg.meta_item_list()
274                    {
275                        values.insert(None);
276                        if !args.is_empty() {
277                            error!(in arg, "`none()` in `values()` takes no argument");
278                        }
279                    } else {
280                        error!(in arg, "`values()` arguments must be string literals, `none()` or `any()`");
281                    }
282                }
283            } else {
284                error!(in arg, "`cfg()` arguments must be simple identifiers, `any()` or `values(...)`");
285            }
286        }
287
288        if !values_specified && !any_specified {
289            // `cfg(name)` is equivalent to `cfg(name, values(none()))` so add
290            // an implicit `none()`
291            values.insert(None);
292        } else if !values.is_empty() && values_any_specified {
293            error!(
294                "`values()` arguments cannot specify string literals and `any()` at the same time"
295            );
296        }
297
298        if any_specified {
299            if names.is_empty() && values.is_empty() && !values_specified && !values_any_specified {
300                check_cfg.exhaustive_names = false;
301            } else {
302                error!("`cfg(any())` can only be provided in isolation");
303            }
304        } else {
305            for name in names {
306                check_cfg
307                    .expecteds
308                    .entry(name.name)
309                    .and_modify(|v| match v {
310                        ExpectedValues::Some(v) if !values_any_specified =>
311                        {
312                            #[allow(rustc::potential_query_instability)]
313                            v.extend(values.clone())
314                        }
315                        ExpectedValues::Some(_) => *v = ExpectedValues::Any,
316                        ExpectedValues::Any => {}
317                    })
318                    .or_insert_with(|| {
319                        if values_any_specified {
320                            ExpectedValues::Any
321                        } else {
322                            ExpectedValues::Some(values.clone())
323                        }
324                    });
325            }
326        }
327    }
328
329    check_cfg
330}
331
332/// The compiler configuration
333pub struct Config {
334    /// Command line options
335    pub opts: config::Options,
336
337    /// Unparsed cfg! configuration in addition to the default ones.
338    pub crate_cfg: Vec<String>,
339    pub crate_check_cfg: Vec<String>,
340
341    pub input: Input,
342    pub output_dir: Option<PathBuf>,
343    pub output_file: Option<OutFileName>,
344    pub ice_file: Option<PathBuf>,
345    /// Load files from sources other than the file system.
346    ///
347    /// Has no uses within this repository, but may be used in the future by
348    /// bjorn3 for "hooking rust-analyzer's VFS into rustc at some point for
349    /// running rustc without having to save". (See #102759.)
350    pub file_loader: Option<Box<dyn FileLoader + Send + Sync>>,
351    /// The list of fluent resources, used for lints declared with
352    /// [`Diagnostic`](rustc_errors::Diagnostic) and [`LintDiagnostic`](rustc_errors::LintDiagnostic).
353    pub locale_resources: Vec<&'static str>,
354
355    pub lint_caps: FxHashMap<lint::LintId, lint::Level>,
356
357    /// This is a callback from the driver that is called when [`ParseSess`] is created.
358    pub psess_created: Option<Box<dyn FnOnce(&mut ParseSess) + Send>>,
359
360    /// This is a callback to hash otherwise untracked state used by the caller, if the
361    /// hash changes between runs the incremental cache will be cleared.
362    ///
363    /// e.g. used by Clippy to hash its config file
364    pub hash_untracked_state: Option<Box<dyn FnOnce(&Session, &mut StableHasher) + Send>>,
365
366    /// This is a callback from the driver that is called when we're registering lints;
367    /// it is called during lint loading when we have the LintStore in a non-shared state.
368    ///
369    /// Note that if you find a Some here you probably want to call that function in the new
370    /// function being registered.
371    pub register_lints: Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>>,
372
373    /// This is a callback from the driver that is called just after we have populated
374    /// the list of queries.
375    pub override_queries: Option<fn(&Session, &mut Providers)>,
376
377    /// An extra set of symbols to add to the symbol interner, the symbol indices
378    /// will start at [`PREDEFINED_SYMBOLS_COUNT`](rustc_span::symbol::PREDEFINED_SYMBOLS_COUNT)
379    pub extra_symbols: Vec<&'static str>,
380
381    /// This is a callback from the driver that is called to create a codegen backend.
382    ///
383    /// Has no uses within this repository, but is used by bjorn3 for "the
384    /// hotswapping branch of cg_clif" for "setting the codegen backend from a
385    /// custom driver where the custom codegen backend has arbitrary data."
386    /// (See #102759.)
387    pub make_codegen_backend:
388        Option<Box<dyn FnOnce(&config::Options) -> Box<dyn CodegenBackend> + Send>>,
389
390    /// Registry of diagnostics codes.
391    pub registry: Registry,
392
393    /// The inner atomic value is set to true when a feature marked as `internal` is
394    /// enabled. Makes it so that "please report a bug" is hidden, as ICEs with
395    /// internal features are wontfix, and they are usually the cause of the ICEs.
396    pub using_internal_features: &'static std::sync::atomic::AtomicBool,
397}
398
399/// Initialize jobserver before getting `jobserver::client` and `build_session`.
400pub(crate) fn initialize_checked_jobserver(early_dcx: &EarlyDiagCtxt) {
401    jobserver::initialize_checked(|err| {
402        #[allow(rustc::untranslatable_diagnostic)]
403        #[allow(rustc::diagnostic_outside_of_impl)]
404        early_dcx
405            .early_struct_warn(err)
406            .with_note("the build environment is likely misconfigured")
407            .emit()
408    });
409}
410
411// JUSTIFICATION: before session exists, only config
412#[allow(rustc::bad_opt_access)]
413pub fn run_compiler<R: Send>(config: Config, f: impl FnOnce(&Compiler) -> R + Send) -> R {
414    trace!("run_compiler");
415
416    // Set parallel mode before thread pool creation, which will create `Lock`s.
417    rustc_data_structures::sync::set_dyn_thread_safe_mode(config.opts.unstable_opts.threads > 1);
418
419    // Check jobserver before run_in_thread_pool_with_globals, which call jobserver::acquire_thread
420    let early_dcx = EarlyDiagCtxt::new(config.opts.error_format);
421    initialize_checked_jobserver(&early_dcx);
422
423    crate::callbacks::setup_callbacks();
424
425    let target = config::build_target_config(
426        &early_dcx,
427        &config.opts.target_triple,
428        config.opts.sysroot.path(),
429    );
430    let file_loader = config.file_loader.unwrap_or_else(|| Box::new(RealFileLoader));
431    let path_mapping = config.opts.file_path_mapping();
432    let hash_kind = config.opts.unstable_opts.src_hash_algorithm(&target);
433    let checksum_hash_kind = config.opts.unstable_opts.checksum_hash_algorithm();
434
435    util::run_in_thread_pool_with_globals(
436        &early_dcx,
437        config.opts.edition,
438        config.opts.unstable_opts.threads,
439        &config.extra_symbols,
440        SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind },
441        |current_gcx, jobserver_proxy| {
442            // The previous `early_dcx` can't be reused here because it doesn't
443            // impl `Send`. Creating a new one is fine.
444            let early_dcx = EarlyDiagCtxt::new(config.opts.error_format);
445
446            let codegen_backend = match config.make_codegen_backend {
447                None => util::get_codegen_backend(
448                    &early_dcx,
449                    &config.opts.sysroot,
450                    config.opts.unstable_opts.codegen_backend.as_deref(),
451                    &target,
452                ),
453                Some(make_codegen_backend) => {
454                    // N.B. `make_codegen_backend` takes precedence over
455                    // `target.default_codegen_backend`, which is ignored in this case.
456                    make_codegen_backend(&config.opts)
457                }
458            };
459
460            let temps_dir = config.opts.unstable_opts.temps_dir.as_deref().map(PathBuf::from);
461
462            let bundle = match rustc_errors::fluent_bundle(
463                &config.opts.sysroot.all_paths().collect::<Vec<_>>(),
464                config.opts.unstable_opts.translate_lang.clone(),
465                config.opts.unstable_opts.translate_additional_ftl.as_deref(),
466                config.opts.unstable_opts.translate_directionality_markers,
467            ) {
468                Ok(bundle) => bundle,
469                Err(e) => {
470                    // We can't translate anything if we failed to load translations
471                    #[allow(rustc::untranslatable_diagnostic)]
472                    early_dcx.early_fatal(format!("failed to load fluent bundle: {e}"))
473                }
474            };
475
476            let mut locale_resources = config.locale_resources;
477            locale_resources.push(codegen_backend.locale_resource());
478
479            let mut sess = rustc_session::build_session(
480                config.opts,
481                CompilerIO {
482                    input: config.input,
483                    output_dir: config.output_dir,
484                    output_file: config.output_file,
485                    temps_dir,
486                },
487                bundle,
488                config.registry,
489                locale_resources,
490                config.lint_caps,
491                target,
492                util::rustc_version_str().unwrap_or("unknown"),
493                config.ice_file,
494                config.using_internal_features,
495            );
496
497            codegen_backend.init(&sess);
498
499            let cfg = parse_cfg(sess.dcx(), config.crate_cfg);
500            let mut cfg = config::build_configuration(&sess, cfg);
501            util::add_configuration(&mut cfg, &mut sess, &*codegen_backend);
502            sess.psess.config = cfg;
503
504            let mut check_cfg = parse_check_cfg(sess.dcx(), config.crate_check_cfg);
505            check_cfg.fill_well_known(&sess.target);
506            sess.psess.check_config = check_cfg;
507
508            if let Some(psess_created) = config.psess_created {
509                psess_created(&mut sess.psess);
510            }
511
512            if let Some(hash_untracked_state) = config.hash_untracked_state {
513                let mut hasher = StableHasher::new();
514                hash_untracked_state(&sess, &mut hasher);
515                sess.opts.untracked_state_hash = hasher.finish()
516            }
517
518            // Even though the session holds the lint store, we can't build the
519            // lint store until after the session exists. And we wait until now
520            // so that `register_lints` sees the fully initialized session.
521            let mut lint_store = rustc_lint::new_lint_store(sess.enable_internal_lints());
522            if let Some(register_lints) = config.register_lints.as_deref() {
523                register_lints(&sess, &mut lint_store);
524            }
525            sess.lint_store = Some(Arc::new(lint_store));
526
527            util::check_abi_required_features(&sess);
528
529            let compiler = Compiler {
530                sess,
531                codegen_backend,
532                override_queries: config.override_queries,
533                current_gcx,
534                jobserver_proxy,
535            };
536
537            // There are two paths out of `f`.
538            // - Normal exit.
539            // - Panic, e.g. triggered by `abort_if_errors` or a fatal error.
540            //
541            // We must run `finish_diagnostics` in both cases.
542            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&compiler)));
543
544            compiler.sess.finish_diagnostics();
545
546            // If error diagnostics have been emitted, we can't return an
547            // error directly, because the return type of this function
548            // is `R`, not `Result<R, E>`. But we need to communicate the
549            // errors' existence to the caller, otherwise the caller might
550            // mistakenly think that no errors occurred and return a zero
551            // exit code. So we abort (panic) instead, similar to if `f`
552            // had panicked.
553            if res.is_ok() {
554                compiler.sess.dcx().abort_if_errors();
555            }
556
557            // Also make sure to flush delayed bugs as if we panicked, the
558            // bugs would be flushed by the Drop impl of DiagCtxt while
559            // unwinding, which would result in an abort with
560            // "panic in a destructor during cleanup".
561            compiler.sess.dcx().flush_delayed();
562
563            let res = match res {
564                Ok(res) => res,
565                // Resume unwinding if a panic happened.
566                Err(err) => std::panic::resume_unwind(err),
567            };
568
569            let prof = compiler.sess.prof.clone();
570            prof.generic_activity("drop_compiler").run(move || drop(compiler));
571
572            res
573        },
574    )
575}
576
577pub fn try_print_query_stack(
578    dcx: DiagCtxtHandle<'_>,
579    limit_frames: Option<usize>,
580    file: Option<std::fs::File>,
581) {
582    eprintln!("query stack during panic:");
583
584    // Be careful relying on global state here: this code is called from
585    // a panic hook, which means that the global `DiagCtxt` may be in a weird
586    // state if it was responsible for triggering the panic.
587    let all_frames = ty::tls::with_context_opt(|icx| {
588        if let Some(icx) = icx {
589            ty::print::with_no_queries!(print_query_stack(
590                QueryCtxt::new(icx.tcx),
591                icx.query,
592                dcx,
593                limit_frames,
594                file,
595            ))
596        } else {
597            0
598        }
599    });
600
601    if let Some(limit_frames) = limit_frames
602        && all_frames > limit_frames
603    {
604        eprintln!(
605            "... and {} other queries... use `env RUST_BACKTRACE=1` to see the full query stack",
606            all_frames - limit_frames
607        );
608    } else {
609        eprintln!("end of query stack");
610    }
611}