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
32pub 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 pub(crate) current_gcx: CurrentGcx,
46
47 pub(crate) jobserver_proxy: Arc<Proxy>,
49}
50
51pub(crate) fn parse_cfg(dcx: DiagCtxtHandle<'_>, cfgs: Vec<String>) -> Cfg {
53 cfgs.into_iter()
54 .map(|s| {
55 let psess = ParseSess::with_fatal_emitter(
56 vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
57 format!("this error occurred on the command line: `--cfg={s}`"),
58 );
59 let filename = FileName::cfg_spec_source_code(&s);
60
61 macro_rules! error {
62 ($reason: expr) => {
63 #[allow(rustc::untranslatable_diagnostic)]
64 #[allow(rustc::diagnostic_outside_of_impl)]
65 dcx.fatal(format!(
66 concat!("invalid `--cfg` argument: `{}` (", $reason, ")"),
67 s
68 ));
69 };
70 }
71
72 match new_parser_from_source_str(&psess, filename, s.to_string()) {
73 Ok(mut parser) => match parser.parse_meta_item(AllowLeadingUnsafe::No) {
74 Ok(meta_item) if parser.token == token::Eof => {
75 if meta_item.path.segments.len() != 1 {
76 error!("argument key must be an identifier");
77 }
78 match &meta_item.kind {
79 MetaItemKind::List(..) => {}
80 MetaItemKind::NameValue(lit) if !lit.kind.is_str() => {
81 error!("argument value must be a string");
82 }
83 MetaItemKind::NameValue(..) | MetaItemKind::Word => {
84 let ident = meta_item.ident().expect("multi-segment cfg key");
85 return (ident.name, meta_item.value_str());
86 }
87 }
88 }
89 Ok(..) => {}
90 Err(err) => err.cancel(),
91 },
92 Err(errs) => errs.into_iter().for_each(|err| err.cancel()),
93 }
94
95 if s.contains('=') && !s.contains("=\"") && !s.ends_with('"') {
98 error!(concat!(
99 r#"expected `key` or `key="value"`, ensure escaping is appropriate"#,
100 r#" for your shell, try 'key="value"' or key=\"value\""#
101 ));
102 } else {
103 error!(r#"expected `key` or `key="value"`"#);
104 }
105 })
106 .collect::<Cfg>()
107}
108
109pub(crate) fn parse_check_cfg(dcx: DiagCtxtHandle<'_>, specs: Vec<String>) -> CheckCfg {
111 let exhaustive_names = !specs.is_empty();
114 let exhaustive_values = !specs.is_empty();
115 let mut check_cfg = CheckCfg { exhaustive_names, exhaustive_values, ..CheckCfg::default() };
116
117 for s in specs {
118 let psess = ParseSess::with_fatal_emitter(
119 vec![crate::DEFAULT_LOCALE_RESOURCE, rustc_parse::DEFAULT_LOCALE_RESOURCE],
120 format!("this error occurred on the command line: `--check-cfg={s}`"),
121 );
122 let filename = FileName::cfg_spec_source_code(&s);
123
124 const VISIT: &str =
125 "visit <https://doc.rust-lang.org/nightly/rustc/check-cfg.html> for more details";
126
127 macro_rules! error {
128 ($reason:expr) => {
129 #[allow(rustc::untranslatable_diagnostic)]
130 #[allow(rustc::diagnostic_outside_of_impl)]
131 {
132 let mut diag =
133 dcx.struct_fatal(format!("invalid `--check-cfg` argument: `{s}`"));
134 diag.note($reason);
135 diag.note(VISIT);
136 diag.emit()
137 }
138 };
139 (in $arg:expr, $reason:expr) => {
140 #[allow(rustc::untranslatable_diagnostic)]
141 #[allow(rustc::diagnostic_outside_of_impl)]
142 {
143 let mut diag =
144 dcx.struct_fatal(format!("invalid `--check-cfg` argument: `{s}`"));
145
146 let pparg = rustc_ast_pretty::pprust::meta_list_item_to_string($arg);
147 if let Some(lit) = $arg.lit() {
148 let (lit_kind_article, lit_kind_descr) = {
149 let lit_kind = lit.as_token_lit().kind;
150 (lit_kind.article(), lit_kind.descr())
151 };
152 diag.note(format!(
153 "`{pparg}` is {lit_kind_article} {lit_kind_descr} literal"
154 ));
155 } else {
156 diag.note(format!("`{pparg}` is invalid"));
157 }
158
159 diag.note($reason);
160 diag.note(VISIT);
161 diag.emit()
162 }
163 };
164 }
165
166 let expected_error = || -> ! {
167 error!("expected `cfg(name, values(\"value1\", \"value2\", ... \"valueN\"))`")
168 };
169
170 let mut parser = match new_parser_from_source_str(&psess, filename, s.to_string()) {
171 Ok(parser) => parser,
172 Err(errs) => {
173 errs.into_iter().for_each(|err| err.cancel());
174 expected_error();
175 }
176 };
177
178 let meta_item = match parser.parse_meta_item(AllowLeadingUnsafe::No) {
179 Ok(meta_item) if parser.token == token::Eof => meta_item,
180 Ok(..) => expected_error(),
181 Err(err) => {
182 err.cancel();
183 expected_error();
184 }
185 };
186
187 let Some(args) = meta_item.meta_item_list() else {
188 expected_error();
189 };
190
191 if !meta_item.has_name(sym::cfg) {
192 expected_error();
193 }
194
195 let mut names = Vec::new();
196 let mut values: FxHashSet<_> = Default::default();
197
198 let mut any_specified = false;
199 let mut values_specified = false;
200 let mut values_any_specified = false;
201
202 for arg in args {
203 if arg.is_word()
204 && let Some(ident) = arg.ident()
205 {
206 if values_specified {
207 error!("`cfg()` names cannot be after values");
208 }
209 names.push(ident);
210 } else if let Some(boolean) = arg.boolean_literal() {
211 if values_specified {
212 error!("`cfg()` names cannot be after values");
213 }
214 names.push(rustc_span::Ident::new(
215 if boolean { rustc_span::kw::True } else { rustc_span::kw::False },
216 arg.span(),
217 ));
218 } else if arg.has_name(sym::any)
219 && let Some(args) = arg.meta_item_list()
220 {
221 if any_specified {
222 error!("`any()` cannot be specified multiple times");
223 }
224 any_specified = true;
225 if !args.is_empty() {
226 error!(in arg, "`any()` takes no argument");
227 }
228 } else if arg.has_name(sym::values)
229 && let Some(args) = arg.meta_item_list()
230 {
231 if names.is_empty() {
232 error!("`values()` cannot be specified before the names");
233 } else if values_specified {
234 error!("`values()` cannot be specified multiple times");
235 }
236 values_specified = true;
237
238 for arg in args {
239 if let Some(LitKind::Str(s, _)) = arg.lit().map(|lit| &lit.kind) {
240 values.insert(Some(*s));
241 } else if arg.has_name(sym::any)
242 && let Some(args) = arg.meta_item_list()
243 {
244 if values_any_specified {
245 error!(in arg, "`any()` in `values()` cannot be specified multiple times");
246 }
247 values_any_specified = true;
248 if !args.is_empty() {
249 error!(in arg, "`any()` in `values()` takes no argument");
250 }
251 } else if arg.has_name(sym::none)
252 && let Some(args) = arg.meta_item_list()
253 {
254 values.insert(None);
255 if !args.is_empty() {
256 error!(in arg, "`none()` in `values()` takes no argument");
257 }
258 } else {
259 error!(in arg, "`values()` arguments must be string literals, `none()` or `any()`");
260 }
261 }
262 } else {
263 error!(in arg, "`cfg()` arguments must be simple identifiers, `any()` or `values(...)`");
264 }
265 }
266
267 if !values_specified && !any_specified {
268 values.insert(None);
271 } else if !values.is_empty() && values_any_specified {
272 error!(
273 "`values()` arguments cannot specify string literals and `any()` at the same time"
274 );
275 }
276
277 if any_specified {
278 if names.is_empty() && values.is_empty() && !values_specified && !values_any_specified {
279 check_cfg.exhaustive_names = false;
280 } else {
281 error!("`cfg(any())` can only be provided in isolation");
282 }
283 } else {
284 for name in names {
285 check_cfg
286 .expecteds
287 .entry(name.name)
288 .and_modify(|v| match v {
289 ExpectedValues::Some(v) if !values_any_specified => {
290 v.extend(values.clone())
291 }
292 ExpectedValues::Some(_) => *v = ExpectedValues::Any,
293 ExpectedValues::Any => {}
294 })
295 .or_insert_with(|| {
296 if values_any_specified {
297 ExpectedValues::Any
298 } else {
299 ExpectedValues::Some(values.clone())
300 }
301 });
302 }
303 }
304 }
305
306 check_cfg
307}
308
309pub struct Config {
311 pub opts: config::Options,
313
314 pub crate_cfg: Vec<String>,
316 pub crate_check_cfg: Vec<String>,
317
318 pub input: Input,
319 pub output_dir: Option<PathBuf>,
320 pub output_file: Option<OutFileName>,
321 pub ice_file: Option<PathBuf>,
322 pub file_loader: Option<Box<dyn FileLoader + Send + Sync>>,
328 pub locale_resources: Vec<&'static str>,
331
332 pub lint_caps: FxHashMap<lint::LintId, lint::Level>,
333
334 pub psess_created: Option<Box<dyn FnOnce(&mut ParseSess) + Send>>,
336
337 pub hash_untracked_state: Option<Box<dyn FnOnce(&Session, &mut StableHasher) + Send>>,
342
343 pub register_lints: Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>>,
349
350 pub override_queries: Option<fn(&Session, &mut Providers)>,
353
354 pub extra_symbols: Vec<&'static str>,
357
358 pub make_codegen_backend:
365 Option<Box<dyn FnOnce(&config::Options) -> Box<dyn CodegenBackend> + Send>>,
366
367 pub registry: Registry,
369
370 pub using_internal_features: &'static std::sync::atomic::AtomicBool,
374
375 pub expanded_args: Vec<String>,
380}
381
382pub(crate) fn initialize_checked_jobserver(early_dcx: &EarlyDiagCtxt) {
384 jobserver::initialize_checked(|err| {
385 #[allow(rustc::untranslatable_diagnostic)]
386 #[allow(rustc::diagnostic_outside_of_impl)]
387 early_dcx
388 .early_struct_warn(err)
389 .with_note("the build environment is likely misconfigured")
390 .emit()
391 });
392}
393
394#[allow(rustc::bad_opt_access)]
396pub fn run_compiler<R: Send>(config: Config, f: impl FnOnce(&Compiler) -> R + Send) -> R {
397 trace!("run_compiler");
398
399 rustc_data_structures::sync::set_dyn_thread_safe_mode(config.opts.unstable_opts.threads > 1);
401
402 let early_dcx = EarlyDiagCtxt::new(config.opts.error_format);
404 initialize_checked_jobserver(&early_dcx);
405
406 crate::callbacks::setup_callbacks();
407
408 let sysroot = config.opts.sysroot.clone();
409 let target = config::build_target_config(&early_dcx, &config.opts.target_triple, &sysroot);
410 let file_loader = config.file_loader.unwrap_or_else(|| Box::new(RealFileLoader));
411 let path_mapping = config.opts.file_path_mapping();
412 let hash_kind = config.opts.unstable_opts.src_hash_algorithm(&target);
413 let checksum_hash_kind = config.opts.unstable_opts.checksum_hash_algorithm();
414
415 util::run_in_thread_pool_with_globals(
416 &early_dcx,
417 config.opts.edition,
418 config.opts.unstable_opts.threads,
419 &config.extra_symbols,
420 SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind },
421 |current_gcx, jobserver_proxy| {
422 let early_dcx = EarlyDiagCtxt::new(config.opts.error_format);
425
426 let codegen_backend = match config.make_codegen_backend {
427 None => util::get_codegen_backend(
428 &early_dcx,
429 &sysroot,
430 config.opts.unstable_opts.codegen_backend.as_deref(),
431 &target,
432 ),
433 Some(make_codegen_backend) => {
434 make_codegen_backend(&config.opts)
437 }
438 };
439
440 let temps_dir = config.opts.unstable_opts.temps_dir.as_deref().map(PathBuf::from);
441
442 let bundle = match rustc_errors::fluent_bundle(
443 sysroot_with_fallback(&config.opts.sysroot),
444 config.opts.unstable_opts.translate_lang.clone(),
445 config.opts.unstable_opts.translate_additional_ftl.as_deref(),
446 config.opts.unstable_opts.translate_directionality_markers,
447 ) {
448 Ok(bundle) => bundle,
449 Err(e) => {
450 #[allow(rustc::untranslatable_diagnostic)]
452 early_dcx.early_fatal(format!("failed to load fluent bundle: {e}"))
453 }
454 };
455
456 let mut locale_resources = config.locale_resources;
457 locale_resources.push(codegen_backend.locale_resource());
458
459 let mut sess = rustc_session::build_session(
460 config.opts,
461 CompilerIO {
462 input: config.input,
463 output_dir: config.output_dir,
464 output_file: config.output_file,
465 temps_dir,
466 },
467 bundle,
468 config.registry,
469 locale_resources,
470 config.lint_caps,
471 target,
472 sysroot,
473 util::rustc_version_str().unwrap_or("unknown"),
474 config.ice_file,
475 config.using_internal_features,
476 config.expanded_args,
477 );
478
479 codegen_backend.init(&sess);
480
481 let cfg = parse_cfg(sess.dcx(), config.crate_cfg);
482 let mut cfg = config::build_configuration(&sess, cfg);
483 util::add_configuration(&mut cfg, &mut sess, &*codegen_backend);
484 sess.psess.config = cfg;
485
486 let mut check_cfg = parse_check_cfg(sess.dcx(), config.crate_check_cfg);
487 check_cfg.fill_well_known(&sess.target);
488 sess.psess.check_config = check_cfg;
489
490 if let Some(psess_created) = config.psess_created {
491 psess_created(&mut sess.psess);
492 }
493
494 if let Some(hash_untracked_state) = config.hash_untracked_state {
495 let mut hasher = StableHasher::new();
496 hash_untracked_state(&sess, &mut hasher);
497 sess.opts.untracked_state_hash = hasher.finish()
498 }
499
500 let mut lint_store = rustc_lint::new_lint_store(sess.enable_internal_lints());
504 if let Some(register_lints) = config.register_lints.as_deref() {
505 register_lints(&sess, &mut lint_store);
506 }
507 sess.lint_store = Some(Arc::new(lint_store));
508
509 util::check_abi_required_features(&sess);
510
511 let compiler = Compiler {
512 sess,
513 codegen_backend,
514 override_queries: config.override_queries,
515 current_gcx,
516 jobserver_proxy,
517 };
518
519 let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&compiler)));
525
526 compiler.sess.finish_diagnostics();
527
528 if res.is_ok() {
536 compiler.sess.dcx().abort_if_errors();
537 }
538
539 compiler.sess.dcx().flush_delayed();
544
545 let res = match res {
546 Ok(res) => res,
547 Err(err) => std::panic::resume_unwind(err),
549 };
550
551 let prof = compiler.sess.prof.clone();
552 prof.generic_activity("drop_compiler").run(move || drop(compiler));
553
554 res
555 },
556 )
557}
558
559pub fn try_print_query_stack(
560 dcx: DiagCtxtHandle<'_>,
561 limit_frames: Option<usize>,
562 file: Option<std::fs::File>,
563) {
564 eprintln!("query stack during panic:");
565
566 let all_frames = ty::tls::with_context_opt(|icx| {
570 if let Some(icx) = icx {
571 ty::print::with_no_queries!(print_query_stack(
572 QueryCtxt::new(icx.tcx),
573 icx.query,
574 dcx,
575 limit_frames,
576 file,
577 ))
578 } else {
579 0
580 }
581 });
582
583 if let Some(limit_frames) = limit_frames
584 && all_frames > limit_frames
585 {
586 eprintln!(
587 "... and {} other queries... use `env RUST_BACKTRACE=1` to see the full query stack",
588 all_frames - limit_frames
589 );
590 } else {
591 eprintln!("end of query stack");
592 }
593}