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