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

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy