| use rustc_attr as attr; |
| use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
| use rustc_data_structures::sync::{self, Lrc}; |
| use rustc_driver::abort_on_err; |
| use rustc_errors::emitter::{Emitter, EmitterWriter}; |
| use rustc_errors::json::JsonEmitter; |
| use rustc_feature::UnstableFeatures; |
| use rustc_hir::def::Namespace::TypeNS; |
| use rustc_hir::def_id::{CrateNum, DefId, DefIndex, LocalDefId, CRATE_DEF_INDEX, LOCAL_CRATE}; |
| use rustc_hir::HirId; |
| use rustc_interface::interface; |
| use rustc_middle::middle::cstore::CrateStore; |
| use rustc_middle::middle::privacy::AccessLevels; |
| use rustc_middle::ty::{Ty, TyCtxt}; |
| use rustc_resolve as resolve; |
| use rustc_session::config::{self, CrateType, ErrorOutputType}; |
| use rustc_session::lint; |
| use rustc_session::DiagnosticOutput; |
| use rustc_session::Session; |
| use rustc_span::source_map; |
| use rustc_span::symbol::sym; |
| use rustc_span::DUMMY_SP; |
| |
| use std::cell::RefCell; |
| use std::mem; |
| use std::rc::Rc; |
| |
| use crate::clean; |
| use crate::clean::{AttributesExt, MAX_DEF_ID}; |
| use crate::config::{Options as RustdocOptions, RenderOptions}; |
| use crate::html::render::RenderInfo; |
| use crate::passes::{self, Condition::*, ConditionalPass}; |
| |
| pub use rustc_session::config::{CodegenOptions, DebuggingOptions, Input, Options}; |
| pub use rustc_session::search_paths::SearchPath; |
| |
| pub type ExternalPaths = FxHashMap<DefId, (Vec<String>, clean::TypeKind)>; |
| |
| pub struct DocContext<'tcx> { |
| pub tcx: TyCtxt<'tcx>, |
| pub resolver: Rc<RefCell<interface::BoxedResolver>>, |
| /// Later on moved into `html::render::CACHE_KEY` |
| pub renderinfo: RefCell<RenderInfo>, |
| /// Later on moved through `clean::Crate` into `html::render::CACHE_KEY` |
| pub external_traits: Rc<RefCell<FxHashMap<DefId, clean::Trait>>>, |
| /// Used while populating `external_traits` to ensure we don't process the same trait twice at |
| /// the same time. |
| pub active_extern_traits: RefCell<FxHashSet<DefId>>, |
| // The current set of type and lifetime substitutions, |
| // for expanding type aliases at the HIR level: |
| /// Table `DefId` of type parameter -> substituted type |
| pub ty_substs: RefCell<FxHashMap<DefId, clean::Type>>, |
| /// Table `DefId` of lifetime parameter -> substituted lifetime |
| pub lt_substs: RefCell<FxHashMap<DefId, clean::Lifetime>>, |
| /// Table `DefId` of const parameter -> substituted const |
| pub ct_substs: RefCell<FxHashMap<DefId, clean::Constant>>, |
| /// Table synthetic type parameter for `impl Trait` in argument position -> bounds |
| pub impl_trait_bounds: RefCell<FxHashMap<ImplTraitParam, Vec<clean::GenericBound>>>, |
| pub fake_def_ids: RefCell<FxHashMap<CrateNum, DefId>>, |
| pub all_fake_def_ids: RefCell<FxHashSet<DefId>>, |
| /// Auto-trait or blanket impls processed so far, as `(self_ty, trait_def_id)`. |
| // FIXME(eddyb) make this a `ty::TraitRef<'tcx>` set. |
| pub generated_synthetics: RefCell<FxHashSet<(Ty<'tcx>, DefId)>>, |
| pub auto_traits: Vec<DefId>, |
| /// The options given to rustdoc that could be relevant to a pass. |
| pub render_options: RenderOptions, |
| } |
| |
| impl<'tcx> DocContext<'tcx> { |
| pub fn sess(&self) -> &Session { |
| &self.tcx.sess |
| } |
| |
| pub fn enter_resolver<F, R>(&self, f: F) -> R |
| where |
| F: FnOnce(&mut resolve::Resolver<'_>) -> R, |
| { |
| self.resolver.borrow_mut().access(f) |
| } |
| |
| /// Call the closure with the given parameters set as |
| /// the substitutions for a type alias' RHS. |
| pub fn enter_alias<F, R>( |
| &self, |
| ty_substs: FxHashMap<DefId, clean::Type>, |
| lt_substs: FxHashMap<DefId, clean::Lifetime>, |
| ct_substs: FxHashMap<DefId, clean::Constant>, |
| f: F, |
| ) -> R |
| where |
| F: FnOnce() -> R, |
| { |
| let (old_tys, old_lts, old_cts) = ( |
| mem::replace(&mut *self.ty_substs.borrow_mut(), ty_substs), |
| mem::replace(&mut *self.lt_substs.borrow_mut(), lt_substs), |
| mem::replace(&mut *self.ct_substs.borrow_mut(), ct_substs), |
| ); |
| let r = f(); |
| *self.ty_substs.borrow_mut() = old_tys; |
| *self.lt_substs.borrow_mut() = old_lts; |
| *self.ct_substs.borrow_mut() = old_cts; |
| r |
| } |
| |
| // This is an ugly hack, but it's the simplest way to handle synthetic impls without greatly |
| // refactoring either librustdoc or librustc_middle. In particular, allowing new DefIds to be |
| // registered after the AST is constructed would require storing the defid mapping in a |
| // RefCell, decreasing the performance for normal compilation for very little gain. |
| // |
| // Instead, we construct 'fake' def ids, which start immediately after the last DefId. |
| // In the Debug impl for clean::Item, we explicitly check for fake |
| // def ids, as we'll end up with a panic if we use the DefId Debug impl for fake DefIds |
| pub fn next_def_id(&self, crate_num: CrateNum) -> DefId { |
| let start_def_id = { |
| let next_id = if crate_num == LOCAL_CRATE { |
| self.tcx.hir().definitions().def_path_table().next_id() |
| } else { |
| self.enter_resolver(|r| r.cstore().def_path_table(crate_num).next_id()) |
| }; |
| |
| DefId { krate: crate_num, index: next_id } |
| }; |
| |
| let mut fake_ids = self.fake_def_ids.borrow_mut(); |
| |
| let def_id = *fake_ids.entry(crate_num).or_insert(start_def_id); |
| fake_ids.insert( |
| crate_num, |
| DefId { krate: crate_num, index: DefIndex::from(def_id.index.index() + 1) }, |
| ); |
| |
| MAX_DEF_ID.with(|m| { |
| m.borrow_mut().entry(def_id.krate).or_insert(start_def_id); |
| }); |
| |
| self.all_fake_def_ids.borrow_mut().insert(def_id); |
| |
| def_id |
| } |
| |
| /// Like the function of the same name on the HIR map, but skips calling it on fake DefIds. |
| /// (This avoids a slice-index-out-of-bounds panic.) |
| pub fn as_local_hir_id(&self, def_id: DefId) -> Option<HirId> { |
| if self.all_fake_def_ids.borrow().contains(&def_id) { |
| None |
| } else { |
| def_id.as_local().map(|def_id| self.tcx.hir().as_local_hir_id(def_id)) |
| } |
| } |
| |
| pub fn stability(&self, id: HirId) -> Option<attr::Stability> { |
| self.tcx |
| .hir() |
| .opt_local_def_id(id) |
| .and_then(|def_id| self.tcx.lookup_stability(def_id.to_def_id())) |
| .cloned() |
| } |
| |
| pub fn deprecation(&self, id: HirId) -> Option<attr::Deprecation> { |
| self.tcx |
| .hir() |
| .opt_local_def_id(id) |
| .and_then(|def_id| self.tcx.lookup_deprecation(def_id.to_def_id())) |
| } |
| } |
| |
| /// Creates a new diagnostic `Handler` that can be used to emit warnings and errors. |
| /// |
| /// If the given `error_format` is `ErrorOutputType::Json` and no `SourceMap` is given, a new one |
| /// will be created for the handler. |
| pub fn new_handler( |
| error_format: ErrorOutputType, |
| source_map: Option<Lrc<source_map::SourceMap>>, |
| debugging_opts: &DebuggingOptions, |
| ) -> rustc_errors::Handler { |
| let emitter: Box<dyn Emitter + sync::Send> = match error_format { |
| ErrorOutputType::HumanReadable(kind) => { |
| let (short, color_config) = kind.unzip(); |
| Box::new( |
| EmitterWriter::stderr( |
| color_config, |
| source_map.map(|sm| sm as _), |
| short, |
| debugging_opts.teach, |
| debugging_opts.terminal_width, |
| false, |
| ) |
| .ui_testing(debugging_opts.ui_testing), |
| ) |
| } |
| ErrorOutputType::Json { pretty, json_rendered } => { |
| let source_map = source_map.unwrap_or_else(|| { |
| Lrc::new(source_map::SourceMap::new(source_map::FilePathMapping::empty())) |
| }); |
| Box::new( |
| JsonEmitter::stderr( |
| None, |
| source_map, |
| pretty, |
| json_rendered, |
| debugging_opts.terminal_width, |
| false, |
| ) |
| .ui_testing(debugging_opts.ui_testing), |
| ) |
| } |
| }; |
| |
| rustc_errors::Handler::with_emitter_and_flags( |
| emitter, |
| debugging_opts.diagnostic_handler_flags(true), |
| ) |
| } |
| |
| /// This function is used to setup the lint initialization. By default, in rustdoc, everything |
| /// is "allowed". Depending if we run in test mode or not, we want some of them to be at their |
| /// default level. For example, the "INVALID_CODEBLOCK_ATTRIBUTES" lint is activated in both |
| /// modes. |
| /// |
| /// A little detail easy to forget is that there is a way to set the lint level for all lints |
| /// through the "WARNINGS" lint. To prevent this to happen, we set it back to its "normal" level |
| /// inside this function. |
| /// |
| /// It returns a tuple containing: |
| /// * Vector of tuples of lints' name and their associated "max" level |
| /// * HashMap of lint id with their associated "max" level |
| pub fn init_lints<F>( |
| mut allowed_lints: Vec<String>, |
| lint_opts: Vec<(String, lint::Level)>, |
| filter_call: F, |
| ) -> (Vec<(String, lint::Level)>, FxHashMap<lint::LintId, lint::Level>) |
| where |
| F: Fn(&lint::Lint) -> Option<(String, lint::Level)>, |
| { |
| let warnings_lint_name = lint::builtin::WARNINGS.name; |
| |
| allowed_lints.push(warnings_lint_name.to_owned()); |
| allowed_lints.extend(lint_opts.iter().map(|(lint, _)| lint).cloned()); |
| |
| let lints = || { |
| lint::builtin::HardwiredLints::get_lints() |
| .into_iter() |
| .chain(rustc_lint::SoftLints::get_lints().into_iter()) |
| }; |
| |
| let lint_opts = lints() |
| .filter_map(|lint| { |
| // Permit feature-gated lints to avoid feature errors when trying to |
| // allow all lints. |
| if lint.name == warnings_lint_name || lint.feature_gate.is_some() { |
| None |
| } else { |
| filter_call(lint) |
| } |
| }) |
| .chain(lint_opts.into_iter()) |
| .collect::<Vec<_>>(); |
| |
| let lint_caps = lints() |
| .filter_map(|lint| { |
| // We don't want to allow *all* lints so let's ignore |
| // those ones. |
| if allowed_lints.iter().any(|l| lint.name == l) { |
| None |
| } else { |
| Some((lint::LintId::of(lint), lint::Allow)) |
| } |
| }) |
| .collect(); |
| (lint_opts, lint_caps) |
| } |
| |
| pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOptions) { |
| // Parse, resolve, and typecheck the given crate. |
| |
| let RustdocOptions { |
| input, |
| crate_name, |
| proc_macro_crate, |
| error_format, |
| libs, |
| externs, |
| mut cfgs, |
| codegen_options, |
| debugging_options, |
| target, |
| edition, |
| maybe_sysroot, |
| lint_opts, |
| describe_lints, |
| lint_cap, |
| mut default_passes, |
| mut manual_passes, |
| display_warnings, |
| render_options, |
| output_format, |
| .. |
| } = options; |
| |
| let extern_names: Vec<String> = externs |
| .iter() |
| .filter(|(_, entry)| entry.add_prelude) |
| .map(|(name, _)| name) |
| .cloned() |
| .collect(); |
| |
| // Add the doc cfg into the doc build. |
| cfgs.push("doc".to_string()); |
| |
| let cpath = Some(input.clone()); |
| let input = Input::File(input); |
| |
| let intra_link_resolution_failure_name = lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE.name; |
| let missing_docs = rustc_lint::builtin::MISSING_DOCS.name; |
| let missing_doc_example = rustc_lint::builtin::MISSING_DOC_CODE_EXAMPLES.name; |
| let private_doc_tests = rustc_lint::builtin::PRIVATE_DOC_TESTS.name; |
| let no_crate_level_docs = rustc_lint::builtin::MISSING_CRATE_LEVEL_DOCS.name; |
| let invalid_codeblock_attribute_name = rustc_lint::builtin::INVALID_CODEBLOCK_ATTRIBUTES.name; |
| |
| // In addition to those specific lints, we also need to allow those given through |
| // command line, otherwise they'll get ignored and we don't want that. |
| let allowed_lints = vec![ |
| intra_link_resolution_failure_name.to_owned(), |
| missing_docs.to_owned(), |
| missing_doc_example.to_owned(), |
| private_doc_tests.to_owned(), |
| no_crate_level_docs.to_owned(), |
| invalid_codeblock_attribute_name.to_owned(), |
| ]; |
| |
| let (lint_opts, lint_caps) = init_lints(allowed_lints, lint_opts, |lint| { |
| if lint.name == intra_link_resolution_failure_name |
| || lint.name == invalid_codeblock_attribute_name |
| { |
| None |
| } else { |
| Some((lint.name_lower(), lint::Allow)) |
| } |
| }); |
| |
| let crate_types = |
| if proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] }; |
| // plays with error output here! |
| let sessopts = config::Options { |
| maybe_sysroot, |
| search_paths: libs, |
| crate_types, |
| lint_opts: if !display_warnings { lint_opts } else { vec![] }, |
| lint_cap: Some(lint_cap.unwrap_or_else(|| lint::Forbid)), |
| cg: codegen_options, |
| externs, |
| target_triple: target, |
| unstable_features: UnstableFeatures::from_environment(), |
| actually_rustdoc: true, |
| debugging_opts: debugging_options, |
| error_format, |
| edition, |
| describe_lints, |
| ..Options::default() |
| }; |
| |
| let config = interface::Config { |
| opts: sessopts, |
| crate_cfg: interface::parse_cfgspecs(cfgs), |
| input, |
| input_path: cpath, |
| output_file: None, |
| output_dir: None, |
| file_loader: None, |
| diagnostic_output: DiagnosticOutput::Default, |
| stderr: None, |
| crate_name, |
| lint_caps, |
| register_lints: None, |
| override_queries: None, |
| registry: rustc_driver::diagnostics_registry(), |
| }; |
| |
| interface::create_compiler_and_run(config, |compiler| { |
| compiler.enter(|queries| { |
| let sess = compiler.session(); |
| |
| // We need to hold on to the complete resolver, so we cause everything to be |
| // cloned for the analysis passes to use. Suboptimal, but necessary in the |
| // current architecture. |
| let resolver = { |
| let parts = abort_on_err(queries.expansion(), sess).peek(); |
| let resolver = parts.1.borrow(); |
| |
| // Before we actually clone it, let's force all the extern'd crates to |
| // actually be loaded, just in case they're only referred to inside |
| // intra-doc-links |
| resolver.borrow_mut().access(|resolver| { |
| for extern_name in &extern_names { |
| resolver |
| .resolve_str_path_error( |
| DUMMY_SP, |
| extern_name, |
| TypeNS, |
| LocalDefId { local_def_index: CRATE_DEF_INDEX }, |
| ) |
| .unwrap_or_else(|()| { |
| panic!("Unable to resolve external crate {}", extern_name) |
| }); |
| } |
| }); |
| |
| // Now we're good to clone the resolver because everything should be loaded |
| resolver.clone() |
| }; |
| |
| if sess.has_errors() { |
| sess.fatal("Compilation failed, aborting rustdoc"); |
| } |
| |
| let mut global_ctxt = abort_on_err(queries.global_ctxt(), sess).take(); |
| |
| global_ctxt.enter(|tcx| { |
| tcx.analysis(LOCAL_CRATE).ok(); |
| |
| // Abort if there were any errors so far |
| sess.abort_if_errors(); |
| |
| let access_levels = tcx.privacy_access_levels(LOCAL_CRATE); |
| // Convert from a HirId set to a DefId set since we don't always have easy access |
| // to the map from defid -> hirid |
| let access_levels = AccessLevels { |
| map: access_levels |
| .map |
| .iter() |
| .map(|(&k, &v)| (tcx.hir().local_def_id(k).to_def_id(), v)) |
| .collect(), |
| }; |
| |
| let mut renderinfo = RenderInfo::default(); |
| renderinfo.access_levels = access_levels; |
| renderinfo.output_format = output_format; |
| |
| let mut ctxt = DocContext { |
| tcx, |
| resolver, |
| external_traits: Default::default(), |
| active_extern_traits: Default::default(), |
| renderinfo: RefCell::new(renderinfo), |
| ty_substs: Default::default(), |
| lt_substs: Default::default(), |
| ct_substs: Default::default(), |
| impl_trait_bounds: Default::default(), |
| fake_def_ids: Default::default(), |
| all_fake_def_ids: Default::default(), |
| generated_synthetics: Default::default(), |
| auto_traits: tcx |
| .all_traits(LOCAL_CRATE) |
| .iter() |
| .cloned() |
| .filter(|trait_def_id| tcx.trait_is_auto(*trait_def_id)) |
| .collect(), |
| render_options, |
| }; |
| debug!("crate: {:?}", tcx.hir().krate()); |
| |
| let mut krate = clean::krate(&mut ctxt); |
| |
| if let Some(ref m) = krate.module { |
| if let None | Some("") = m.doc_value() { |
| let help = "The following guide may be of use:\n\ |
| https://doc.rust-lang.org/nightly/rustdoc/how-to-write-documentation\ |
| .html"; |
| tcx.struct_lint_node( |
| rustc_lint::builtin::MISSING_CRATE_LEVEL_DOCS, |
| ctxt.as_local_hir_id(m.def_id).unwrap(), |
| |lint| { |
| let mut diag = lint.build( |
| "no documentation found for this crate's top-level module", |
| ); |
| diag.help(help); |
| diag.emit(); |
| }, |
| ); |
| } |
| } |
| |
| fn report_deprecated_attr(name: &str, diag: &rustc_errors::Handler) { |
| let mut msg = diag.struct_warn(&format!( |
| "the `#![doc({})]` attribute is considered deprecated", |
| name |
| )); |
| msg.warn( |
| "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \ |
| for more information", |
| ); |
| |
| if name == "no_default_passes" { |
| msg.help("you may want to use `#![doc(document_private_items)]`"); |
| } |
| |
| msg.emit(); |
| } |
| |
| // Process all of the crate attributes, extracting plugin metadata along |
| // with the passes which we are supposed to run. |
| for attr in krate.module.as_ref().unwrap().attrs.lists(sym::doc) { |
| let diag = ctxt.sess().diagnostic(); |
| |
| let name = attr.name_or_empty(); |
| if attr.is_word() { |
| if name == sym::no_default_passes { |
| report_deprecated_attr("no_default_passes", diag); |
| if default_passes == passes::DefaultPassOption::Default { |
| default_passes = passes::DefaultPassOption::None; |
| } |
| } |
| } else if let Some(value) = attr.value_str() { |
| let sink = match name { |
| sym::passes => { |
| report_deprecated_attr("passes = \"...\"", diag); |
| &mut manual_passes |
| } |
| sym::plugins => { |
| report_deprecated_attr("plugins = \"...\"", diag); |
| eprintln!( |
| "WARNING: `#![doc(plugins = \"...\")]` \ |
| no longer functions; see CVE-2018-1000622" |
| ); |
| continue; |
| } |
| _ => continue, |
| }; |
| for name in value.as_str().split_whitespace() { |
| sink.push(name.to_string()); |
| } |
| } |
| |
| if attr.is_word() && name == sym::document_private_items { |
| ctxt.render_options.document_private = true; |
| } |
| } |
| |
| let passes = passes::defaults(default_passes).iter().copied().chain( |
| manual_passes.into_iter().flat_map(|name| { |
| if let Some(pass) = passes::find_pass(&name) { |
| Some(ConditionalPass::always(pass)) |
| } else { |
| error!("unknown pass {}, skipping", name); |
| None |
| } |
| }), |
| ); |
| |
| info!("Executing passes"); |
| |
| for p in passes { |
| let run = match p.condition { |
| Always => true, |
| WhenDocumentPrivate => ctxt.render_options.document_private, |
| WhenNotDocumentPrivate => !ctxt.render_options.document_private, |
| WhenNotDocumentHidden => !ctxt.render_options.document_hidden, |
| }; |
| if run { |
| debug!("running pass {}", p.pass.name); |
| krate = (p.pass.run)(krate, &ctxt); |
| } |
| } |
| |
| ctxt.sess().abort_if_errors(); |
| |
| (krate, ctxt.renderinfo.into_inner(), ctxt.render_options) |
| }) |
| }) |
| }) |
| } |
| |
| /// `DefId` or parameter index (`ty::ParamTy.index`) of a synthetic type parameter |
| /// for `impl Trait` in argument position. |
| #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] |
| pub enum ImplTraitParam { |
| DefId(DefId), |
| ParamIndex(u32), |
| } |
| |
| impl From<DefId> for ImplTraitParam { |
| fn from(did: DefId) -> Self { |
| ImplTraitParam::DefId(did) |
| } |
| } |
| |
| impl From<u32> for ImplTraitParam { |
| fn from(idx: u32) -> Self { |
| ImplTraitParam::ParamIndex(idx) |
| } |
| } |