| use std::path::PathBuf; | |
| use std::result; | |
| use std::sync::Arc; | |
| use rustc_ast::{LitKind, MetaItemKind, token}; | |
| use rustc_codegen_ssa::traits::CodegenBackend; | |
| use rustc_data_structures::fx::{FxHashMap, FxHashSet}; | |
| use rustc_data_structures::jobserver; | |
| use rustc_errors::{DiagCtxtHandle, ErrorGuaranteed}; | |
| use rustc_lint::LintStore; | |
| use rustc_middle::ty; | |
| use rustc_middle::ty::CurrentGcx; | |
| use rustc_middle::util::Providers; | |
| use rustc_parse::lexer::StripTokens; | |
| use rustc_parse::new_parser_from_source_str; | |
| use rustc_parse::parser::Recovery; | |
| use rustc_query_impl::print_query_stack; | |
| use rustc_session::config::{self, Cfg, CheckCfg, ExpectedValues, Input, OutFileName}; | |
| use rustc_session::parse::ParseSess; | |
| use rustc_session::{CompilerIO, EarlyDiagCtxt, Session, lint}; | |
| use rustc_span::source_map::{FileLoader, RealFileLoader, SourceMapInputs}; | |
| use rustc_span::{FileName, sym}; | |
| use tracing::trace; | |
| use crate::util; | |
| pub type Result<T> = result::Result<T, ErrorGuaranteed>; | |
| /// Represents a compiler session. Note that every `Compiler` contains a | |
| /// `Session`, but `Compiler` also contains some things that cannot be in | |
| /// `Session`, due to `Session` being in a crate that has many fewer | |
| /// dependencies than this crate. | |
| /// | |
| /// Can be used to run `rustc_interface` queries. | |
| /// Created by passing [`Config`] to [`run_compiler`]. | |
| pub struct Compiler { | |
| pub sess: Session, | |
| pub codegen_backend: Box<dyn CodegenBackend>, | |
| pub(crate) override_queries: Option<fn(&Session, &mut Providers)>, | |
| /// A reference to the current `GlobalCtxt` which we pass on to `GlobalCtxt`. | |
| pub(crate) current_gcx: CurrentGcx, | |
| } | |
| /// Converts strings provided as `--cfg [cfgspec]` into a `Cfg`. | |
| pub(crate) fn parse_cfg(dcx: DiagCtxtHandle<'_>, cfgs: Vec<String>) -> Cfg { | |
| cfgs.into_iter() | |
| .map(|s| { | |
| let psess = ParseSess::emitter_with_note(format!( | |
| "this occurred on the command line: `--cfg={s}`" | |
| )); | |
| let filename = FileName::cfg_spec_source_code(&s); | |
| macro_rules! error { | |
| ($reason: expr) => { | |
| dcx.fatal(format!("invalid `--cfg` argument: `{s}` ({})", $reason)); | |
| }; | |
| } | |
| match new_parser_from_source_str(&psess, filename, s.to_string(), StripTokens::Nothing) | |
| { | |
| Ok(mut parser) => { | |
| parser = parser.recovery(Recovery::Forbidden); | |
| match parser.parse_meta_item() { | |
| Ok(meta_item) | |
| if parser.token == token::Eof | |
| && parser.dcx().has_errors().is_none() => | |
| { | |
| if meta_item.path.segments.len() != 1 { | |
| error!("argument key must be an identifier"); | |
| } | |
| match &meta_item.kind { | |
| MetaItemKind::List(..) => {} | |
| MetaItemKind::NameValue(lit) if !lit.kind.is_str() => { | |
| error!("argument value must be a string"); | |
| } | |
| MetaItemKind::NameValue(..) | MetaItemKind::Word => { | |
| let ident = meta_item.ident().expect("multi-segment cfg key"); | |
| if ident.is_path_segment_keyword() { | |
| error!( | |
| "malformed `cfg` input, expected a valid identifier" | |
| ); | |
| } | |
| return (ident.name, meta_item.value_str()); | |
| } | |
| } | |
| } | |
| Ok(..) => {} | |
| Err(err) => err.cancel(), | |
| } | |
| } | |
| Err(errs) => errs.into_iter().for_each(|err| err.cancel()), | |
| }; | |
| // If the user tried to use a key="value" flag, but is missing the quotes, provide | |
| // a hint about how to resolve this. | |
| if s.contains('=') && !s.contains("=\"") && !s.ends_with('"') { | |
| error!(concat!( | |
| r#"expected `key` or `key="value"`, ensure escaping is appropriate"#, | |
| r#" for your shell, try 'key="value"' or key=\"value\""# | |
| )); | |
| } else { | |
| error!(r#"expected `key` or `key="value"`"#); | |
| } | |
| }) | |
| .collect::<Cfg>() | |
| } | |
| /// Converts strings provided as `--check-cfg [specs]` into a `CheckCfg`. | |
| pub(crate) fn parse_check_cfg(dcx: DiagCtxtHandle<'_>, specs: Vec<String>) -> CheckCfg { | |
| // If any --check-cfg is passed then exhaustive_values and exhaustive_names | |
| // are enabled by default. | |
| let exhaustive_names = !specs.is_empty(); | |
| let exhaustive_values = !specs.is_empty(); | |
| let mut check_cfg = CheckCfg { exhaustive_names, exhaustive_values, ..CheckCfg::default() }; | |
| for s in specs { | |
| let psess = ParseSess::emitter_with_note(format!( | |
| "this occurred on the command line: `--check-cfg={s}`" | |
| )); | |
| let filename = FileName::cfg_spec_source_code(&s); | |
| const VISIT: &str = | |
| "visit <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more details"; | |
| macro_rules! error { | |
| ($reason:expr) => {{ | |
| let mut diag = dcx.struct_fatal(format!("invalid `--check-cfg` argument: `{s}`")); | |
| diag.note($reason); | |
| diag.note(VISIT); | |
| diag.emit() | |
| }}; | |
| (in $arg:expr, $reason:expr) => {{ | |
| let mut diag = dcx.struct_fatal(format!("invalid `--check-cfg` argument: `{s}`")); | |
| let pparg = rustc_ast_pretty::pprust::meta_list_item_to_string($arg); | |
| if let Some(lit) = $arg.lit() { | |
| let (lit_kind_article, lit_kind_descr) = { | |
| let lit_kind = lit.as_token_lit().kind; | |
| (lit_kind.article(), lit_kind.descr()) | |
| }; | |
| diag.note(format!("`{pparg}` is {lit_kind_article} {lit_kind_descr} literal")); | |
| } else { | |
| diag.note(format!("`{pparg}` is invalid")); | |
| } | |
| diag.note($reason); | |
| diag.note(VISIT); | |
| diag.emit() | |
| }}; | |
| } | |
| let expected_error = || -> ! { | |
| error!("expected `cfg(name, values(\"value1\", \"value2\", ... \"valueN\"))`") | |
| }; | |
| let mut parser = | |
| match new_parser_from_source_str(&psess, filename, s.to_string(), StripTokens::Nothing) | |
| { | |
| Ok(parser) => parser.recovery(Recovery::Forbidden), | |
| Err(errs) => { | |
| errs.into_iter().for_each(|err| err.cancel()); | |
| expected_error(); | |
| } | |
| }; | |
| let meta_item = match parser.parse_meta_item() { | |
| Ok(meta_item) if parser.token == token::Eof && parser.dcx().has_errors().is_none() => { | |
| meta_item | |
| } | |
| Ok(..) => expected_error(), | |
| Err(err) => { | |
| err.cancel(); | |
| expected_error(); | |
| } | |
| }; | |
| let Some(args) = meta_item.meta_item_list() else { | |
| expected_error(); | |
| }; | |
| if !meta_item.has_name(sym::cfg) { | |
| expected_error(); | |
| } | |
| let mut names = Vec::new(); | |
| let mut values: FxHashSet<_> = Default::default(); | |
| let mut any_specified = false; | |
| let mut values_specified = false; | |
| let mut values_any_specified = false; | |
| for arg in args { | |
| if arg.is_word() | |
| && let Some(ident) = arg.ident() | |
| { | |
| if values_specified { | |
| error!("`cfg()` names cannot be after values"); | |
| } | |
| if ident.is_path_segment_keyword() { | |
| error!("malformed `cfg` input, expected a valid identifier"); | |
| } | |
| names.push(ident); | |
| } else if let Some(boolean) = arg.boolean_literal() { | |
| if values_specified { | |
| error!("`cfg()` names cannot be after values"); | |
| } | |
| names.push(rustc_span::Ident::new( | |
| if boolean { rustc_span::kw::True } else { rustc_span::kw::False }, | |
| arg.span(), | |
| )); | |
| } else if arg.has_name(sym::any) | |
| && let Some(args) = arg.meta_item_list() | |
| { | |
| if any_specified { | |
| error!("`any()` cannot be specified multiple times"); | |
| } | |
| any_specified = true; | |
| if !args.is_empty() { | |
| error!(in arg, "`any()` takes no argument"); | |
| } | |
| } else if arg.has_name(sym::values) | |
| && let Some(args) = arg.meta_item_list() | |
| { | |
| if names.is_empty() { | |
| error!("`values()` cannot be specified before the names"); | |
| } else if values_specified { | |
| error!("`values()` cannot be specified multiple times"); | |
| } | |
| values_specified = true; | |
| for arg in args { | |
| if let Some(LitKind::Str(s, _)) = arg.lit().map(|lit| &lit.kind) { | |
| values.insert(Some(*s)); | |
| } else if arg.has_name(sym::any) | |
| && let Some(args) = arg.meta_item_list() | |
| { | |
| if values_any_specified { | |
| error!(in arg, "`any()` in `values()` cannot be specified multiple times"); | |
| } | |
| values_any_specified = true; | |
| if !args.is_empty() { | |
| error!(in arg, "`any()` in `values()` takes no argument"); | |
| } | |
| } else if arg.has_name(sym::none) | |
| && let Some(args) = arg.meta_item_list() | |
| { | |
| values.insert(None); | |
| if !args.is_empty() { | |
| error!(in arg, "`none()` in `values()` takes no argument"); | |
| } | |
| } else { | |
| error!(in arg, "`values()` arguments must be string literals, `none()` or `any()`"); | |
| } | |
| } | |
| } else { | |
| error!(in arg, "`cfg()` arguments must be simple identifiers, `any()` or `values(...)`"); | |
| } | |
| } | |
| if !values_specified && !any_specified { | |
| // `cfg(name)` is equivalent to `cfg(name, values(none()))` so add | |
| // an implicit `none()` | |
| values.insert(None); | |
| } else if !values.is_empty() && values_any_specified { | |
| error!( | |
| "`values()` arguments cannot specify string literals and `any()` at the same time" | |
| ); | |
| } | |
| if any_specified { | |
| if names.is_empty() && values.is_empty() && !values_specified && !values_any_specified { | |
| check_cfg.exhaustive_names = false; | |
| } else { | |
| error!("`cfg(any())` can only be provided in isolation"); | |
| } | |
| } else { | |
| for name in names { | |
| check_cfg | |
| .expecteds | |
| .entry(name.name) | |
| .and_modify(|v| match v { | |
| ExpectedValues::Some(v) if !values_any_specified => | |
| { | |
| #[allow(rustc::potential_query_instability)] | |
| v.extend(values.clone()) | |
| } | |
| ExpectedValues::Some(_) => *v = ExpectedValues::Any, | |
| ExpectedValues::Any => {} | |
| }) | |
| .or_insert_with(|| { | |
| if values_any_specified { | |
| ExpectedValues::Any | |
| } else { | |
| ExpectedValues::Some(values.clone()) | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| check_cfg | |
| } | |
| /// The compiler configuration | |
| pub struct Config { | |
| /// Command line options | |
| pub opts: config::Options, | |
| /// Unparsed cfg! configuration in addition to the default ones. | |
| pub crate_cfg: Vec<String>, | |
| pub crate_check_cfg: Vec<String>, | |
| pub input: Input, | |
| pub output_dir: Option<PathBuf>, | |
| pub output_file: Option<OutFileName>, | |
| pub ice_file: Option<PathBuf>, | |
| /// Load files from sources other than the file system. | |
| /// | |
| /// Has no uses within this repository, but may be used in the future by | |
| /// bjorn3 for "hooking rust-analyzer's VFS into rustc at some point for | |
| /// running rustc without having to save". (See #102759.) | |
| pub file_loader: Option<Box<dyn FileLoader + Send + Sync>>, | |
| pub lint_caps: FxHashMap<lint::LintId, lint::Level>, | |
| /// This is a callback from the driver that is called when [`ParseSess`] is created. | |
| pub psess_created: Option<Box<dyn FnOnce(&mut ParseSess) + Send>>, | |
| /// This is a callback to track otherwise untracked state used by the caller. | |
| /// | |
| /// You can write to `sess.env_depinfo` and `sess.file_depinfo` to track env vars and files. | |
| pub track_state: Option<Box<dyn FnOnce(&Session) + Send>>, | |
| /// This is a callback from the driver that is called when we're registering lints; | |
| /// it is called during lint loading when we have the LintStore in a non-shared state. | |
| /// | |
| /// Note that if you find a Some here you probably want to call that function in the new | |
| /// function being registered. | |
| pub register_lints: Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>>, | |
| /// This is a callback from the driver that is called just after we have populated | |
| /// the list of queries. | |
| pub override_queries: Option<fn(&Session, &mut Providers)>, | |
| /// An extra set of symbols to add to the symbol interner, the symbol indices | |
| /// will start at [`PREDEFINED_SYMBOLS_COUNT`](rustc_span::symbol::PREDEFINED_SYMBOLS_COUNT) | |
| pub extra_symbols: Vec<&'static str>, | |
| /// This is a callback from the driver that is called to create a codegen backend. | |
| /// | |
| /// Has no uses within this repository, but is used by bjorn3 for "the | |
| /// hotswapping branch of cg_clif" for "setting the codegen backend from a | |
| /// custom driver where the custom codegen backend has arbitrary data." | |
| /// (See #102759.) | |
| pub make_codegen_backend: Option<Box<dyn FnOnce(&Session) -> Box<dyn CodegenBackend> + Send>>, | |
| /// The inner atomic value is set to true when a feature marked as `internal` is | |
| /// enabled. Makes it so that "please report a bug" is hidden, as ICEs with | |
| /// internal features are wontfix, and they are usually the cause of the ICEs. | |
| pub using_internal_features: &'static std::sync::atomic::AtomicBool, | |
| } | |
| // JUSTIFICATION: before session exists, only config | |
| #[allow(rustc::bad_opt_access)] | |
| pub fn run_compiler<R: Send>(config: Config, f: impl FnOnce(&Compiler) -> R + Send) -> R { | |
| trace!("run_compiler"); | |
| // Set parallel mode before thread pool creation, which will create `Lock`s. | |
| rustc_data_structures::sync::set_dyn_thread_safe_mode(config.opts.jobs.frontend.is_some()); | |
| // Initialize jobserver as early as possible. | |
| let early_dcx = EarlyDiagCtxt::new(config.opts.error_format); | |
| let jobs = config.opts.jobs; | |
| if let Some(limit) = jobs.frontend.max(jobs.backend).max(jobs.linker.limit()) { | |
| jobserver::initialize(limit.get(), |err| { | |
| let note = "the build environment is likely misconfigured"; | |
| early_dcx.early_struct_warn(err).with_note(note).emit() | |
| }); | |
| } | |
| crate::callbacks::setup_callbacks(); | |
| let target = config::build_target_config( | |
| &early_dcx, | |
| &config.opts.target_triple, | |
| config.opts.sysroot.path(), | |
| config.opts.unstable_opts.unstable_options, | |
| ); | |
| let file_loader = config.file_loader.unwrap_or_else(|| Box::new(RealFileLoader)); | |
| let path_mapping = config.opts.file_path_mapping(); | |
| let hash_kind = config.opts.unstable_opts.src_hash_algorithm(&target); | |
| let checksum_hash_kind = config.opts.unstable_opts.checksum_hash_algorithm(); | |
| util::run_in_thread_pool_with_globals( | |
| &early_dcx, | |
| config.opts.edition, | |
| jobs, | |
| &config.extra_symbols, | |
| SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind }, | |
| |current_gcx| { | |
| // The previous `early_dcx` can't be reused here because it doesn't | |
| // impl `Send`. Creating a new one is fine. | |
| let early_dcx = EarlyDiagCtxt::new(config.opts.error_format); | |
| let temps_dir = config.opts.unstable_opts.temps_dir.as_deref().map(PathBuf::from); | |
| let mut sess = rustc_session::build_session( | |
| config.opts, | |
| CompilerIO { | |
| input: config.input, | |
| output_dir: config.output_dir, | |
| output_file: config.output_file, | |
| temps_dir, | |
| }, | |
| config.lint_caps, | |
| target, | |
| util::rustc_version_str().unwrap_or("unknown"), | |
| config.ice_file, | |
| config.using_internal_features, | |
| ); | |
| let codegen_backend = match config.make_codegen_backend { | |
| None => util::get_codegen_backend( | |
| &early_dcx, | |
| &sess.opts.sysroot, | |
| sess.opts.unstable_opts.codegen_backend.as_deref(), | |
| &sess.target, | |
| ), | |
| Some(make_codegen_backend) => { | |
| // N.B. `make_codegen_backend` takes precedence over | |
| // `target.default_codegen_backend`, which is ignored in this case. | |
| make_codegen_backend(&sess) | |
| } | |
| }; | |
| codegen_backend.init(&sess); | |
| sess.replaced_intrinsics = FxHashSet::from_iter(codegen_backend.replaced_intrinsics()); | |
| sess.fallback_intrinsics = FxHashSet::from_iter(codegen_backend.fallback_intrinsics()); | |
| sess.thin_lto_supported = codegen_backend.thin_lto_supported(); | |
| let cfg = parse_cfg(sess.dcx(), config.crate_cfg); | |
| let mut cfg = config::build_configuration(&sess, cfg); | |
| util::add_configuration(&mut cfg, &mut sess, &*codegen_backend); | |
| sess.config = cfg; | |
| let mut check_cfg = parse_check_cfg(sess.dcx(), config.crate_check_cfg); | |
| check_cfg.fill_well_known(&sess.target); | |
| sess.check_config = check_cfg; | |
| if let Some(psess_created) = config.psess_created { | |
| psess_created(&mut sess.psess); | |
| } | |
| if let Some(track_state) = config.track_state { | |
| track_state(&sess); | |
| } | |
| // Even though the session holds the lint store, we can't build the | |
| // lint store until after the session exists. And we wait until now | |
| // so that `register_lints` sees the fully initialized session. | |
| let mut lint_store = rustc_lint::new_lint_store(sess.enable_internal_lints()); | |
| if let Some(register_lints) = config.register_lints.as_deref() { | |
| register_lints(&sess, &mut lint_store); | |
| } | |
| sess.lint_store = Some(Arc::new(lint_store)); | |
| util::check_abi_required_features(&sess); | |
| let compiler = Compiler { | |
| sess, | |
| codegen_backend, | |
| override_queries: config.override_queries, | |
| current_gcx, | |
| }; | |
| // There are two paths out of `f`. | |
| // - Normal exit. | |
| // - Panic, e.g. triggered by `abort_if_errors` or a fatal error. | |
| // | |
| // We must run `finish_diagnostics` in both cases. | |
| let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&compiler))); | |
| compiler.sess.finish_diagnostics(); | |
| // If error diagnostics have been emitted, we can't return an | |
| // error directly, because the return type of this function | |
| // is `R`, not `Result<R, E>`. But we need to communicate the | |
| // errors' existence to the caller, otherwise the caller might | |
| // mistakenly think that no errors occurred and return a zero | |
| // exit code. So we abort (panic) instead, similar to if `f` | |
| // had panicked. | |
| if res.is_ok() { | |
| compiler.sess.dcx().abort_if_errors(); | |
| } | |
| // Also make sure to flush delayed bugs as if we panicked, the | |
| // bugs would be flushed by the Drop impl of DiagCtxt while | |
| // unwinding, which would result in an abort with | |
| // "panic in a destructor during cleanup". | |
| compiler.sess.dcx().flush_delayed(); | |
| let res = match res { | |
| Ok(res) => res, | |
| // Resume unwinding if a panic happened. | |
| Err(err) => std::panic::resume_unwind(err), | |
| }; | |
| let prof = compiler.sess.prof.clone(); | |
| prof.generic_activity("drop_compiler").run(move || drop(compiler)); | |
| res | |
| }, | |
| ) | |
| } | |
| pub fn try_print_query_stack( | |
| dcx: DiagCtxtHandle<'_>, | |
| limit_frames: Option<usize>, | |
| file: Option<std::fs::File>, | |
| ) { | |
| eprintln!("query stack during panic:"); | |
| // Be careful relying on global state here: this code is called from | |
| // a panic hook, which means that the global `DiagCtxt` may be in a weird | |
| // state if it was responsible for triggering the panic. | |
| let all_frames = ty::tls::with_context_opt(|icx| { | |
| if let Some(icx) = icx { | |
| ty::print::with_no_queries!(print_query_stack( | |
| icx.tcx, | |
| icx.query, | |
| dcx, | |
| limit_frames, | |
| file, | |
| )) | |
| } else { | |
| 0 | |
| } | |
| }); | |
| if let Some(limit_frames) = limit_frames | |
| && all_frames > limit_frames | |
| { | |
| eprintln!( | |
| "... and {} other queries... use `env RUST_BACKTRACE=1` to see the full query stack", | |
| all_frames - limit_frames | |
| ); | |
| } else { | |
| eprintln!("end of query stack"); | |
| } | |
| } |