diff options
Diffstat (limited to 'compiler/rustc_passes')
22 files changed, 9762 insertions, 0 deletions
diff --git a/compiler/rustc_passes/Cargo.toml b/compiler/rustc_passes/Cargo.toml new file mode 100644 index 000000000..faa9c493d --- /dev/null +++ b/compiler/rustc_passes/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "rustc_passes" +version = "0.0.0" +edition = "2021" + +[dependencies] +tracing = "0.1" +itertools = "0.10.1" +rustc_middle = { path = "../rustc_middle" } +rustc_attr = { path = "../rustc_attr" } +rustc_data_structures = { path = "../rustc_data_structures" } +rustc_errors = { path = "../rustc_errors" } +rustc_expand = { path = "../rustc_expand" } +rustc_hir = { path = "../rustc_hir" } +rustc_index = { path = "../rustc_index" } +rustc_session = { path = "../rustc_session" } +rustc_target = { path = "../rustc_target" } +rustc_macros = { path = "../rustc_macros" } +rustc_ast = { path = "../rustc_ast" } +rustc_serialize = { path = "../rustc_serialize" } +rustc_span = { path = "../rustc_span" } +rustc_lexer = { path = "../rustc_lexer" } +rustc_ast_pretty = { path = "../rustc_ast_pretty" } +rustc_feature = { path = "../rustc_feature" } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs new file mode 100644 index 000000000..a2ac329f2 --- /dev/null +++ b/compiler/rustc_passes/src/check_attr.rs @@ -0,0 +1,2217 @@ +//! This module implements some validity checks for attributes. +//! In particular it verifies that `#[inline]` and `#[repr]` attributes are +//! attached to items that actually support them and if there are +//! conflicts between multiple such attributes attached to the same +//! item. + +use crate::errors; +use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem}; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::{fluent, struct_span_err, Applicability, MultiSpan}; +use rustc_expand::base::resolve_path; +use rustc_feature::{AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP}; +use rustc_hir as hir; +use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID}; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{self, FnSig, ForeignItem, HirId, Item, ItemKind, TraitItem, CRATE_HIR_ID}; +use rustc_hir::{MethodKind, Target}; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_session::lint::builtin::{ + CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, UNUSED_ATTRIBUTES, +}; +use rustc_session::parse::feature_err; +use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::{Span, DUMMY_SP}; +use rustc_target::spec::abi::Abi; +use std::collections::hash_map::Entry; + +pub(crate) fn target_from_impl_item<'tcx>( + tcx: TyCtxt<'tcx>, + impl_item: &hir::ImplItem<'_>, +) -> Target { + match impl_item.kind { + hir::ImplItemKind::Const(..) => Target::AssocConst, + hir::ImplItemKind::Fn(..) => { + let parent_hir_id = tcx.hir().get_parent_item(impl_item.hir_id()); + let containing_item = tcx.hir().expect_item(parent_hir_id); + let containing_impl_is_for_trait = match &containing_item.kind { + hir::ItemKind::Impl(impl_) => impl_.of_trait.is_some(), + _ => bug!("parent of an ImplItem must be an Impl"), + }; + if containing_impl_is_for_trait { + Target::Method(MethodKind::Trait { body: true }) + } else { + Target::Method(MethodKind::Inherent) + } + } + hir::ImplItemKind::TyAlias(..) => Target::AssocTy, + } +} + +#[derive(Clone, Copy)] +enum ItemLike<'tcx> { + Item(&'tcx Item<'tcx>), + ForeignItem, +} + +struct CheckAttrVisitor<'tcx> { + tcx: TyCtxt<'tcx>, +} + +impl CheckAttrVisitor<'_> { + /// Checks any attribute. + fn check_attributes( + &self, + hir_id: HirId, + span: Span, + target: Target, + item: Option<ItemLike<'_>>, + ) { + let mut doc_aliases = FxHashMap::default(); + let mut is_valid = true; + let mut specified_inline = None; + let mut seen = FxHashMap::default(); + let attrs = self.tcx.hir().attrs(hir_id); + for attr in attrs { + let attr_is_valid = match attr.name_or_empty() { + sym::inline => self.check_inline(hir_id, attr, span, target), + sym::no_coverage => self.check_no_coverage(hir_id, attr, span, target), + sym::non_exhaustive => self.check_non_exhaustive(hir_id, attr, span, target), + sym::marker => self.check_marker(hir_id, attr, span, target), + sym::rustc_must_implement_one_of => { + self.check_rustc_must_implement_one_of(attr, span, target) + } + sym::target_feature => self.check_target_feature(hir_id, attr, span, target), + sym::thread_local => self.check_thread_local(attr, span, target), + sym::track_caller => { + self.check_track_caller(hir_id, attr.span, attrs, span, target) + } + sym::doc => self.check_doc_attrs( + attr, + hir_id, + target, + &mut specified_inline, + &mut doc_aliases, + ), + sym::no_link => self.check_no_link(hir_id, &attr, span, target), + sym::export_name => self.check_export_name(hir_id, &attr, span, target), + sym::rustc_layout_scalar_valid_range_start + | sym::rustc_layout_scalar_valid_range_end => { + self.check_rustc_layout_scalar_valid_range(&attr, span, target) + } + sym::allow_internal_unstable => { + self.check_allow_internal_unstable(hir_id, &attr, span, target, &attrs) + } + sym::debugger_visualizer => self.check_debugger_visualizer(&attr, target), + sym::rustc_allow_const_fn_unstable => { + self.check_rustc_allow_const_fn_unstable(hir_id, &attr, span, target) + } + sym::rustc_std_internal_symbol => { + self.check_rustc_std_internal_symbol(&attr, span, target) + } + sym::naked => self.check_naked(hir_id, attr, span, target), + sym::rustc_legacy_const_generics => { + self.check_rustc_legacy_const_generics(&attr, span, target, item) + } + sym::rustc_lint_query_instability => { + self.check_rustc_lint_query_instability(&attr, span, target) + } + sym::rustc_lint_diagnostics => { + self.check_rustc_lint_diagnostics(&attr, span, target) + } + sym::rustc_lint_opt_ty => self.check_rustc_lint_opt_ty(&attr, span, target), + sym::rustc_lint_opt_deny_field_access => { + self.check_rustc_lint_opt_deny_field_access(&attr, span, target) + } + sym::rustc_clean + | sym::rustc_dirty + | sym::rustc_if_this_changed + | sym::rustc_then_this_would_need => self.check_rustc_dirty_clean(&attr), + sym::cmse_nonsecure_entry => self.check_cmse_nonsecure_entry(attr, span, target), + sym::const_trait => self.check_const_trait(attr, span, target), + sym::must_not_suspend => self.check_must_not_suspend(&attr, span, target), + sym::must_use => self.check_must_use(hir_id, &attr, span, target), + sym::rustc_pass_by_value => self.check_pass_by_value(&attr, span, target), + sym::rustc_allow_incoherent_impl => { + self.check_allow_incoherent_impl(&attr, span, target) + } + sym::rustc_has_incoherent_inherent_impls => { + self.check_has_incoherent_inherent_impls(&attr, span, target) + } + sym::rustc_const_unstable + | sym::rustc_const_stable + | sym::unstable + | sym::stable + | sym::rustc_allowed_through_unstable_modules + | sym::rustc_promotable => self.check_stability_promotable(&attr, span, target), + _ => true, + }; + is_valid &= attr_is_valid; + + // lint-only checks + match attr.name_or_empty() { + sym::cold => self.check_cold(hir_id, attr, span, target), + sym::link => self.check_link(hir_id, attr, span, target), + sym::link_name => self.check_link_name(hir_id, attr, span, target), + sym::link_section => self.check_link_section(hir_id, attr, span, target), + sym::no_mangle => self.check_no_mangle(hir_id, attr, span, target), + sym::deprecated => self.check_deprecated(hir_id, attr, span, target), + sym::macro_use | sym::macro_escape => self.check_macro_use(hir_id, attr, target), + sym::path => self.check_generic_attr(hir_id, attr, target, &[Target::Mod]), + sym::plugin_registrar => self.check_plugin_registrar(hir_id, attr, target), + sym::macro_export => self.check_macro_export(hir_id, attr, target), + sym::ignore | sym::should_panic | sym::proc_macro_derive => { + self.check_generic_attr(hir_id, attr, target, &[Target::Fn]) + } + sym::automatically_derived => { + self.check_generic_attr(hir_id, attr, target, &[Target::Impl]) + } + sym::no_implicit_prelude => { + self.check_generic_attr(hir_id, attr, target, &[Target::Mod]) + } + _ => {} + } + + let builtin = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name)); + + if hir_id != CRATE_HIR_ID { + if let Some(BuiltinAttribute { type_: AttributeType::CrateLevel, .. }) = + attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name)) + { + match attr.style { + ast::AttrStyle::Outer => self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::OuterCrateLevelAttr, + ), + ast::AttrStyle::Inner => self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::InnerCrateLevelAttr, + ), + } + } + } + + if let Some(BuiltinAttribute { duplicates, .. }) = builtin { + check_duplicates(self.tcx, attr, hir_id, *duplicates, &mut seen); + } + + self.check_unused_attribute(hir_id, attr) + } + + if !is_valid { + return; + } + + // FIXME(@lcnr): this doesn't belong here. + if matches!(target, Target::Closure | Target::Fn | Target::Method(_) | Target::ForeignFn) { + self.tcx.ensure().codegen_fn_attrs(self.tcx.hir().local_def_id(hir_id)); + } + + self.check_repr(attrs, span, target, item, hir_id); + self.check_used(attrs, target); + } + + fn inline_attr_str_error_with_macro_def(&self, hir_id: HirId, attr: &Attribute, sym: &str) { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::IgnoredAttrWithMacro { sym }, + ); + } + + fn inline_attr_str_error_without_macro_def(&self, hir_id: HirId, attr: &Attribute, sym: &str) { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::IgnoredAttr { sym }, + ); + } + + /// Checks if an `#[inline]` is applied to a function or a closure. Returns `true` if valid. + fn check_inline(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool { + match target { + Target::Fn + | Target::Closure + | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true, + Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::IgnoredInlineAttrFnProto, + ); + true + } + // FIXME(#65833): We permit associated consts to have an `#[inline]` attribute with + // just a lint, because we previously erroneously allowed it and some crates used it + // accidentally, to to be compatible with crates depending on them, we can't throw an + // error here. + Target::AssocConst => { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::IgnoredInlineAttrConstants, + ); + true + } + // FIXME(#80564): Same for fields, arms, and macro defs + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "inline"); + true + } + _ => { + self.tcx.sess.emit_err(errors::InlineNotFnOrClosure { + attr_span: attr.span, + defn_span: span, + }); + false + } + } + } + + /// Checks if a `#[no_coverage]` is applied directly to a function + fn check_no_coverage( + &self, + hir_id: HirId, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + match target { + // no_coverage on function is fine + Target::Fn + | Target::Closure + | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true, + + // function prototypes can't be covered + Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::IgnoredNoCoverageFnProto, + ); + true + } + + Target::Mod | Target::ForeignMod | Target::Impl | Target::Trait => { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::IgnoredNoCoveragePropagate, + ); + true + } + + Target::Expression | Target::Statement | Target::Arm => { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::IgnoredNoCoverageFnDefn, + ); + true + } + + _ => { + self.tcx.sess.emit_err(errors::IgnoredNoCoverageNotCoverable { + attr_span: attr.span, + defn_span: span, + }); + false + } + } + } + + fn check_generic_attr( + &self, + hir_id: HirId, + attr: &Attribute, + target: Target, + allowed_targets: &[Target], + ) { + if !allowed_targets.iter().any(|t| t == &target) { + let name = attr.name_or_empty(); + let mut i = allowed_targets.iter(); + // Pluralize + let b = i.next().map_or_else(String::new, |t| t.to_string() + "s"); + let supported_names = i.enumerate().fold(b, |mut b, (i, allowed_target)| { + if allowed_targets.len() > 2 && i == allowed_targets.len() - 2 { + b.push_str(", and "); + } else if allowed_targets.len() == 2 && i == allowed_targets.len() - 2 { + b.push_str(" and "); + } else { + b.push_str(", "); + } + // Pluralize + b.push_str(&(allowed_target.to_string() + "s")); + b + }); + self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| { + lint.build(&format!("`#[{name}]` only has an effect on {}", supported_names)) + .emit(); + }); + } + } + + /// Checks if `#[naked]` is applied to a function definition. + fn check_naked(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool { + match target { + Target::Fn + | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true, + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[allow_internal_unstable]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "naked"); + true + } + _ => { + self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn { + attr_span: attr.span, + defn_span: span, + }); + false + } + } + } + + /// Checks if `#[cmse_nonsecure_entry]` is applied to a function definition. + fn check_cmse_nonsecure_entry(&self, attr: &Attribute, span: Span, target: Target) -> bool { + match target { + Target::Fn + | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true, + _ => { + self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn { + attr_span: attr.span, + defn_span: span, + }); + false + } + } + } + + /// Checks if a `#[track_caller]` is applied to a non-naked function. Returns `true` if valid. + fn check_track_caller( + &self, + hir_id: HirId, + attr_span: Span, + attrs: &[Attribute], + span: Span, + target: Target, + ) -> bool { + match target { + _ if attrs.iter().any(|attr| attr.has_name(sym::naked)) => { + self.tcx.sess.emit_err(errors::NakedTrackedCaller { attr_span }); + false + } + Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => true, + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[track_caller]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + for attr in attrs { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "track_caller"); + } + true + } + _ => { + self.tcx + .sess + .emit_err(errors::TrackedCallerWrongLocation { attr_span, defn_span: span }); + false + } + } + } + + /// Checks if the `#[non_exhaustive]` attribute on an `item` is valid. Returns `true` if valid. + fn check_non_exhaustive( + &self, + hir_id: HirId, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + match target { + Target::Struct | Target::Enum | Target::Variant => true, + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[non_exhaustive]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "non_exhaustive"); + true + } + _ => { + self.tcx.sess.emit_err(errors::NonExhaustiveWrongLocation { + attr_span: attr.span, + defn_span: span, + }); + false + } + } + } + + /// Checks if the `#[marker]` attribute on an `item` is valid. Returns `true` if valid. + fn check_marker(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool { + match target { + Target::Trait => true, + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[marker]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "marker"); + true + } + _ => { + self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToTrait { + attr_span: attr.span, + defn_span: span, + }); + false + } + } + } + + /// Checks if the `#[rustc_must_implement_one_of]` attribute on a `target` is valid. Returns `true` if valid. + fn check_rustc_must_implement_one_of( + &self, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + match target { + Target::Trait => true, + _ => { + self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToTrait { + attr_span: attr.span, + defn_span: span, + }); + false + } + } + } + + /// Checks if the `#[target_feature]` attribute on `item` is valid. Returns `true` if valid. + fn check_target_feature( + &self, + hir_id: HirId, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + match target { + Target::Fn + | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true, + // FIXME: #[target_feature] was previously erroneously allowed on statements and some + // crates used this, so only emit a warning. + Target::Statement => { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::TargetFeatureOnStatement, + ); + true + } + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[target_feature]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "target_feature"); + true + } + _ => { + self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn { + attr_span: attr.span, + defn_span: span, + }); + false + } + } + } + + /// Checks if the `#[thread_local]` attribute on `item` is valid. Returns `true` if valid. + fn check_thread_local(&self, attr: &Attribute, span: Span, target: Target) -> bool { + match target { + Target::ForeignStatic | Target::Static => true, + _ => { + self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToStatic { + attr_span: attr.span, + defn_span: span, + }); + false + } + } + } + + fn doc_attr_str_error(&self, meta: &NestedMetaItem, attr_name: &str) { + self.tcx.sess.emit_err(errors::DocExpectStr { attr_span: meta.span(), attr_name }); + } + + fn check_doc_alias_value( + &self, + meta: &NestedMetaItem, + doc_alias: Symbol, + hir_id: HirId, + target: Target, + is_list: bool, + aliases: &mut FxHashMap<String, Span>, + ) -> bool { + let tcx = self.tcx; + let span = meta.name_value_literal_span().unwrap_or_else(|| meta.span()); + let attr_str = + &format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" }); + if doc_alias == kw::Empty { + tcx.sess.emit_err(errors::DocAliasEmpty { span, attr_str }); + return false; + } + + let doc_alias_str = doc_alias.as_str(); + if let Some(c) = doc_alias_str + .chars() + .find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' ')) + { + tcx.sess.emit_err(errors::DocAliasBadChar { span, attr_str, char_: c }); + return false; + } + if doc_alias_str.starts_with(' ') || doc_alias_str.ends_with(' ') { + tcx.sess.emit_err(errors::DocAliasStartEnd { span, attr_str }); + return false; + } + + let span = meta.span(); + if let Some(location) = match target { + Target::AssocTy => { + let parent_hir_id = self.tcx.hir().get_parent_item(hir_id); + let containing_item = self.tcx.hir().expect_item(parent_hir_id); + if Target::from_item(containing_item) == Target::Impl { + Some("type alias in implementation block") + } else { + None + } + } + Target::AssocConst => { + let parent_hir_id = self.tcx.hir().get_parent_item(hir_id); + let containing_item = self.tcx.hir().expect_item(parent_hir_id); + // We can't link to trait impl's consts. + let err = "associated constant in trait implementation block"; + match containing_item.kind { + ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) => Some(err), + _ => None, + } + } + // we check the validity of params elsewhere + Target::Param => return false, + Target::Expression + | Target::Statement + | Target::Arm + | Target::ForeignMod + | Target::Closure + | Target::Impl => Some(target.name()), + Target::ExternCrate + | Target::Use + | Target::Static + | Target::Const + | Target::Fn + | Target::Mod + | Target::GlobalAsm + | Target::TyAlias + | Target::OpaqueTy + | Target::Enum + | Target::Variant + | Target::Struct + | Target::Field + | Target::Union + | Target::Trait + | Target::TraitAlias + | Target::Method(..) + | Target::ForeignFn + | Target::ForeignStatic + | Target::ForeignTy + | Target::GenericParam(..) + | Target::MacroDef => None, + } { + tcx.sess.emit_err(errors::DocAliasBadLocation { span, attr_str, location }); + return false; + } + let item_name = self.tcx.hir().name(hir_id); + if item_name == doc_alias { + tcx.sess.emit_err(errors::DocAliasNotAnAlias { span, attr_str }); + return false; + } + if let Err(entry) = aliases.try_insert(doc_alias_str.to_owned(), span) { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + span, + errors::DocAliasDuplicated { first_defn: *entry.entry.get() }, + ); + } + true + } + + fn check_doc_alias( + &self, + meta: &NestedMetaItem, + hir_id: HirId, + target: Target, + aliases: &mut FxHashMap<String, Span>, + ) -> bool { + if let Some(values) = meta.meta_item_list() { + let mut errors = 0; + for v in values { + match v.literal() { + Some(l) => match l.kind { + LitKind::Str(s, _) => { + if !self.check_doc_alias_value(v, s, hir_id, target, true, aliases) { + errors += 1; + } + } + _ => { + self.tcx + .sess + .emit_err(errors::DocAliasNotStringLiteral { span: v.span() }); + errors += 1; + } + }, + None => { + self.tcx.sess.emit_err(errors::DocAliasNotStringLiteral { span: v.span() }); + errors += 1; + } + } + } + errors == 0 + } else if let Some(doc_alias) = meta.value_str() { + self.check_doc_alias_value(meta, doc_alias, hir_id, target, false, aliases) + } else { + self.tcx.sess.emit_err(errors::DocAliasMalformed { span: meta.span() }); + false + } + } + + fn check_doc_keyword(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool { + let doc_keyword = meta.value_str().unwrap_or(kw::Empty); + if doc_keyword == kw::Empty { + self.doc_attr_str_error(meta, "keyword"); + return false; + } + match self.tcx.hir().find(hir_id).and_then(|node| match node { + hir::Node::Item(item) => Some(&item.kind), + _ => None, + }) { + Some(ItemKind::Mod(ref module)) => { + if !module.item_ids.is_empty() { + self.tcx.sess.emit_err(errors::DocKeywordEmptyMod { span: meta.span() }); + return false; + } + } + _ => { + self.tcx.sess.emit_err(errors::DocKeywordNotMod { span: meta.span() }); + return false; + } + } + if !rustc_lexer::is_ident(doc_keyword.as_str()) { + self.tcx.sess.emit_err(errors::DocKeywordInvalidIdent { + span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()), + doc_keyword, + }); + return false; + } + true + } + + fn check_doc_fake_variadic(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool { + match self.tcx.hir().find(hir_id).and_then(|node| match node { + hir::Node::Item(item) => Some(&item.kind), + _ => None, + }) { + Some(ItemKind::Impl(ref i)) => { + let is_valid = matches!(&i.self_ty.kind, hir::TyKind::Tup([_])) + || if let hir::TyKind::BareFn(bare_fn_ty) = &i.self_ty.kind { + bare_fn_ty.decl.inputs.len() == 1 + } else { + false + }; + if !is_valid { + self.tcx.sess.emit_err(errors::DocFakeVariadicNotValid { span: meta.span() }); + return false; + } + } + _ => { + self.tcx.sess.emit_err(errors::DocKeywordOnlyImpl { span: meta.span() }); + return false; + } + } + true + } + + /// Checks `#[doc(inline)]`/`#[doc(no_inline)]` attributes. Returns `true` if valid. + /// + /// A doc inlining attribute is invalid if it is applied to a non-`use` item, or + /// if there are conflicting attributes for one item. + /// + /// `specified_inline` is used to keep track of whether we have + /// already seen an inlining attribute for this item. + /// If so, `specified_inline` holds the value and the span of + /// the first `inline`/`no_inline` attribute. + fn check_doc_inline( + &self, + attr: &Attribute, + meta: &NestedMetaItem, + hir_id: HirId, + target: Target, + specified_inline: &mut Option<(bool, Span)>, + ) -> bool { + if target == Target::Use || target == Target::ExternCrate { + let do_inline = meta.name_or_empty() == sym::inline; + if let Some((prev_inline, prev_span)) = *specified_inline { + if do_inline != prev_inline { + let mut spans = MultiSpan::from_spans(vec![prev_span, meta.span()]); + spans.push_span_label(prev_span, fluent::passes::doc_inline_conflict_first); + spans.push_span_label(meta.span(), fluent::passes::doc_inline_conflict_second); + self.tcx.sess.emit_err(errors::DocKeywordConflict { spans }); + return false; + } + true + } else { + *specified_inline = Some((do_inline, meta.span())); + true + } + } else { + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + meta.span(), + errors::DocInlineOnlyUse { + attr_span: meta.span(), + item_span: (attr.style == AttrStyle::Outer) + .then(|| self.tcx.hir().span(hir_id)), + }, + ); + false + } + } + + /// Checks that an attribute is *not* used at the crate level. Returns `true` if valid. + fn check_attr_not_crate_level( + &self, + meta: &NestedMetaItem, + hir_id: HirId, + attr_name: &str, + ) -> bool { + if CRATE_HIR_ID == hir_id { + self.tcx.sess.emit_err(errors::DocAttrNotCrateLevel { span: meta.span(), attr_name }); + return false; + } + true + } + + /// Checks that an attribute is used at the crate level. Returns `true` if valid. + fn check_attr_crate_level( + &self, + attr: &Attribute, + meta: &NestedMetaItem, + hir_id: HirId, + ) -> bool { + if hir_id != CRATE_HIR_ID { + self.tcx.struct_span_lint_hir(INVALID_DOC_ATTRIBUTES, hir_id, meta.span(), |lint| { + let mut err = lint.build(fluent::passes::attr_crate_level); + if attr.style == AttrStyle::Outer + && self.tcx.hir().get_parent_item(hir_id) == CRATE_DEF_ID + { + if let Ok(mut src) = self.tcx.sess.source_map().span_to_snippet(attr.span) { + src.insert(1, '!'); + err.span_suggestion_verbose( + attr.span, + fluent::passes::suggestion, + src, + Applicability::MaybeIncorrect, + ); + } else { + err.span_help(attr.span, fluent::passes::help); + } + } + err.note(fluent::passes::note).emit(); + }); + return false; + } + true + } + + /// Checks that `doc(test(...))` attribute contains only valid attributes. Returns `true` if + /// valid. + fn check_test_attr(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool { + let mut is_valid = true; + if let Some(metas) = meta.meta_item_list() { + for i_meta in metas { + match i_meta.name_or_empty() { + sym::attr | sym::no_crate_inject => {} + _ => { + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + i_meta.span(), + errors::DocTestUnknown { + path: rustc_ast_pretty::pprust::path_to_string( + &i_meta.meta_item().unwrap().path, + ), + }, + ); + is_valid = false; + } + } + } + } else { + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + meta.span(), + errors::DocTestTakesList, + ); + is_valid = false; + } + is_valid + } + + /// Runs various checks on `#[doc]` attributes. Returns `true` if valid. + /// + /// `specified_inline` should be initialized to `None` and kept for the scope + /// of one item. Read the documentation of [`check_doc_inline`] for more information. + /// + /// [`check_doc_inline`]: Self::check_doc_inline + fn check_doc_attrs( + &self, + attr: &Attribute, + hir_id: HirId, + target: Target, + specified_inline: &mut Option<(bool, Span)>, + aliases: &mut FxHashMap<String, Span>, + ) -> bool { + let mut is_valid = true; + + if let Some(mi) = attr.meta() && let Some(list) = mi.meta_item_list() { + for meta in list { + if let Some(i_meta) = meta.meta_item() { + match i_meta.name_or_empty() { + sym::alias + if !self.check_attr_not_crate_level(meta, hir_id, "alias") + || !self.check_doc_alias(meta, hir_id, target, aliases) => + { + is_valid = false + } + + sym::keyword + if !self.check_attr_not_crate_level(meta, hir_id, "keyword") + || !self.check_doc_keyword(meta, hir_id) => + { + is_valid = false + } + + sym::fake_variadic + if !self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") + || !self.check_doc_fake_variadic(meta, hir_id) => + { + is_valid = false + } + + sym::html_favicon_url + | sym::html_logo_url + | sym::html_playground_url + | sym::issue_tracker_base_url + | sym::html_root_url + | sym::html_no_source + | sym::test + if !self.check_attr_crate_level(attr, meta, hir_id) => + { + is_valid = false; + } + + sym::inline | sym::no_inline + if !self.check_doc_inline( + attr, + meta, + hir_id, + target, + specified_inline, + ) => + { + is_valid = false; + } + + // no_default_passes: deprecated + // passes: deprecated + // plugins: removed, but rustdoc warns about it itself + sym::alias + | sym::cfg + | sym::cfg_hide + | sym::hidden + | sym::html_favicon_url + | sym::html_logo_url + | sym::html_no_source + | sym::html_playground_url + | sym::html_root_url + | sym::inline + | sym::issue_tracker_base_url + | sym::keyword + | sym::masked + | sym::no_default_passes + | sym::no_inline + | sym::notable_trait + | sym::passes + | sym::plugins + | sym::fake_variadic => {} + + sym::test => { + if !self.check_test_attr(meta, hir_id) { + is_valid = false; + } + } + + sym::primitive => { + if !self.tcx.features().rustdoc_internals { + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + i_meta.span, + errors::DocPrimitive, + ); + } + } + + _ => { + let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path); + if i_meta.has_name(sym::spotlight) { + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + i_meta.span, + errors::DocTestUnknownSpotlight { + path, + span: i_meta.span + } + ); + } else if i_meta.has_name(sym::include) && + let Some(value) = i_meta.value_str() { + let applicability = if list.len() == 1 { + Applicability::MachineApplicable + } else { + Applicability::MaybeIncorrect + }; + // If there are multiple attributes, the suggestion would suggest + // deleting all of them, which is incorrect. + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + i_meta.span, + errors::DocTestUnknownInclude { + path, + value: value.to_string(), + inner: (attr.style == AttrStyle::Inner) + .then_some("!") + .unwrap_or(""), + sugg: (attr.meta().unwrap().span, applicability), + } + ); + } else { + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + i_meta.span, + errors::DocTestUnknownAny { path } + ); + } + is_valid = false; + } + } + } else { + self.tcx.emit_spanned_lint( + INVALID_DOC_ATTRIBUTES, + hir_id, + meta.span(), + errors::DocInvalid, + ); + is_valid = false; + } + } + } + + is_valid + } + + /// Warns against some misuses of `#[pass_by_value]` + fn check_pass_by_value(&self, attr: &Attribute, span: Span, target: Target) -> bool { + match target { + Target::Struct | Target::Enum | Target::TyAlias => true, + _ => { + self.tcx.sess.emit_err(errors::PassByValue { attr_span: attr.span, span }); + false + } + } + } + + fn check_allow_incoherent_impl(&self, attr: &Attribute, span: Span, target: Target) -> bool { + match target { + Target::Method(MethodKind::Inherent) => true, + _ => { + self.tcx.sess.emit_err(errors::AllowIncoherentImpl { attr_span: attr.span, span }); + false + } + } + } + + fn check_has_incoherent_inherent_impls( + &self, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + match target { + Target::Trait | Target::Struct | Target::Enum | Target::Union | Target::ForeignTy => { + true + } + _ => { + self.tcx + .sess + .emit_err(errors::HasIncoherentInherentImpl { attr_span: attr.span, span }); + false + } + } + } + + /// Warns against some misuses of `#[must_use]` + fn check_must_use(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool { + let node = self.tcx.hir().get(hir_id); + if let Some(kind) = node.fn_kind() && let rustc_hir::IsAsync::Async = kind.asyncness() { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::MustUseAsync { span } + ); + } + + if !matches!( + target, + Target::Fn + | Target::Enum + | Target::Struct + | Target::Union + | Target::Method(_) + | Target::ForeignFn + // `impl Trait` in return position can trip + // `unused_must_use` if `Trait` is marked as + // `#[must_use]` + | Target::Trait + ) { + let article = match target { + Target::ExternCrate + | Target::OpaqueTy + | Target::Enum + | Target::Impl + | Target::Expression + | Target::Arm + | Target::AssocConst + | Target::AssocTy => "an", + _ => "a", + }; + + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::MustUseNoEffect { article, target }, + ); + } + + // For now, its always valid + true + } + + /// Checks if `#[must_not_suspend]` is applied to a function. Returns `true` if valid. + fn check_must_not_suspend(&self, attr: &Attribute, span: Span, target: Target) -> bool { + match target { + Target::Struct | Target::Enum | Target::Union | Target::Trait => true, + _ => { + self.tcx.sess.emit_err(errors::MustNotSuspend { attr_span: attr.span, span }); + false + } + } + } + + /// Checks if `#[cold]` is applied to a non-function. Returns `true` if valid. + fn check_cold(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) { + match target { + Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => {} + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[cold]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "cold"); + } + _ => { + // FIXME: #[cold] was previously allowed on non-functions and some crates used + // this, so only emit a warning. + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::Cold { span }, + ); + } + } + } + + /// Checks if `#[link]` is applied to an item other than a foreign module. + fn check_link(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) { + if target == Target::ForeignMod + && let hir::Node::Item(item) = self.tcx.hir().get(hir_id) + && let Item { kind: ItemKind::ForeignMod { abi, .. }, .. } = item + && !matches!(abi, Abi::Rust | Abi::RustIntrinsic | Abi::PlatformIntrinsic) + { + return; + } + + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::Link { span: (target != Target::ForeignMod).then_some(span) }, + ); + } + + /// Checks if `#[link_name]` is applied to an item other than a foreign function or static. + fn check_link_name(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) { + match target { + Target::ForeignFn | Target::ForeignStatic => {} + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[link_name]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "link_name"); + } + _ => { + // FIXME: #[cold] was previously allowed on non-functions/statics and some crates + // used this, so only emit a warning. + let attr_span = matches!(target, Target::ForeignMod).then_some(attr.span); + if let Some(s) = attr.value_str() { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::LinkName { span, attr_span, value: s.as_str() }, + ); + } else { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::LinkName { span, attr_span, value: "..." }, + ); + }; + } + } + } + + /// Checks if `#[no_link]` is applied to an `extern crate`. Returns `true` if valid. + fn check_no_link(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool { + match target { + Target::ExternCrate => true, + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[no_link]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "no_link"); + true + } + _ => { + self.tcx.sess.emit_err(errors::NoLink { attr_span: attr.span, span }); + false + } + } + } + + fn is_impl_item(&self, hir_id: HirId) -> bool { + matches!(self.tcx.hir().get(hir_id), hir::Node::ImplItem(..)) + } + + /// Checks if `#[export_name]` is applied to a function or static. Returns `true` if valid. + fn check_export_name( + &self, + hir_id: HirId, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + match target { + Target::Static | Target::Fn => true, + Target::Method(..) if self.is_impl_item(hir_id) => true, + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[export_name]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "export_name"); + true + } + _ => { + self.tcx.sess.emit_err(errors::ExportName { attr_span: attr.span, span }); + false + } + } + } + + fn check_rustc_layout_scalar_valid_range( + &self, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + if target != Target::Struct { + self.tcx.sess.emit_err(errors::RustcLayoutScalarValidRangeNotStruct { + attr_span: attr.span, + span, + }); + return false; + } + + let Some(list) = attr.meta_item_list() else { + return false; + }; + + if matches!(&list[..], &[NestedMetaItem::Literal(Lit { kind: LitKind::Int(..), .. })]) { + true + } else { + self.tcx.sess.emit_err(errors::RustcLayoutScalarValidRangeArg { attr_span: attr.span }); + false + } + } + + /// Checks if `#[rustc_legacy_const_generics]` is applied to a function and has a valid argument. + fn check_rustc_legacy_const_generics( + &self, + attr: &Attribute, + span: Span, + target: Target, + item: Option<ItemLike<'_>>, + ) -> bool { + let is_function = matches!(target, Target::Fn); + if !is_function { + self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn { + attr_span: attr.span, + defn_span: span, + }); + return false; + } + + let Some(list) = attr.meta_item_list() else { + // The attribute form is validated on AST. + return false; + }; + + let Some(ItemLike::Item(Item { + kind: ItemKind::Fn(FnSig { decl, .. }, generics, _), + .. + })) = item else { + bug!("should be a function item"); + }; + + for param in generics.params { + match param.kind { + hir::GenericParamKind::Const { .. } => {} + _ => { + self.tcx.sess.emit_err(errors::RustcLegacyConstGenericsOnly { + attr_span: attr.span, + param_span: param.span, + }); + return false; + } + } + } + + if list.len() != generics.params.len() { + self.tcx.sess.emit_err(errors::RustcLegacyConstGenericsIndex { + attr_span: attr.span, + generics_span: generics.span, + }); + return false; + } + + let arg_count = decl.inputs.len() as u128 + generics.params.len() as u128; + let mut invalid_args = vec![]; + for meta in list { + if let Some(LitKind::Int(val, _)) = meta.literal().map(|lit| &lit.kind) { + if *val >= arg_count { + let span = meta.span(); + self.tcx.sess.emit_err(errors::RustcLegacyConstGenericsIndexExceed { + span, + arg_count: arg_count as usize, + }); + return false; + } + } else { + invalid_args.push(meta.span()); + } + } + + if !invalid_args.is_empty() { + self.tcx.sess.emit_err(errors::RustcLegacyConstGenericsIndexNegative { invalid_args }); + false + } else { + true + } + } + + /// Helper function for checking that the provided attribute is only applied to a function or + /// method. + fn check_applied_to_fn_or_method(&self, attr: &Attribute, span: Span, target: Target) -> bool { + let is_function = matches!(target, Target::Fn | Target::Method(..)); + if !is_function { + self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn { + attr_span: attr.span, + defn_span: span, + }); + false + } else { + true + } + } + + /// Checks that the `#[rustc_lint_query_instability]` attribute is only applied to a function + /// or method. + fn check_rustc_lint_query_instability( + &self, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + self.check_applied_to_fn_or_method(attr, span, target) + } + + /// Checks that the `#[rustc_lint_diagnostics]` attribute is only applied to a function or + /// method. + fn check_rustc_lint_diagnostics(&self, attr: &Attribute, span: Span, target: Target) -> bool { + self.check_applied_to_fn_or_method(attr, span, target) + } + + /// Checks that the `#[rustc_lint_opt_ty]` attribute is only applied to a struct. + fn check_rustc_lint_opt_ty(&self, attr: &Attribute, span: Span, target: Target) -> bool { + match target { + Target::Struct => true, + _ => { + self.tcx.sess.emit_err(errors::RustcLintOptTy { attr_span: attr.span, span }); + false + } + } + } + + /// Checks that the `#[rustc_lint_opt_deny_field_access]` attribute is only applied to a field. + fn check_rustc_lint_opt_deny_field_access( + &self, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + match target { + Target::Field => true, + _ => { + self.tcx + .sess + .emit_err(errors::RustcLintOptDenyFieldAccess { attr_span: attr.span, span }); + false + } + } + } + + /// Checks that the dep-graph debugging attributes are only present when the query-dep-graph + /// option is passed to the compiler. + fn check_rustc_dirty_clean(&self, attr: &Attribute) -> bool { + if self.tcx.sess.opts.unstable_opts.query_dep_graph { + true + } else { + self.tcx.sess.emit_err(errors::RustcDirtyClean { span: attr.span }); + false + } + } + + /// Checks if `#[link_section]` is applied to a function or static. + fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) { + match target { + Target::Static | Target::Fn | Target::Method(..) => {} + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[link_section]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "link_section"); + } + _ => { + // FIXME: #[link_section] was previously allowed on non-functions/statics and some + // crates used this, so only emit a warning. + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::LinkSection { span }, + ); + } + } + } + + /// Checks if `#[no_mangle]` is applied to a function or static. + fn check_no_mangle(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) { + match target { + Target::Static | Target::Fn => {} + Target::Method(..) if self.is_impl_item(hir_id) => {} + // FIXME(#80564): We permit struct fields, match arms and macro defs to have an + // `#[no_mangle]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "no_mangle"); + } + // FIXME: #[no_mangle] was previously allowed on non-functions/statics, this should be an error + // The error should specify that the item that is wrong is specifically a *foreign* fn/static + // otherwise the error seems odd + Target::ForeignFn | Target::ForeignStatic => { + let foreign_item_kind = match target { + Target::ForeignFn => "function", + Target::ForeignStatic => "static", + _ => unreachable!(), + }; + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::NoMangleForeign { span, attr_span: attr.span, foreign_item_kind }, + ); + } + _ => { + // FIXME: #[no_mangle] was previously allowed on non-functions/statics and some + // crates used this, so only emit a warning. + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::NoMangle { span }, + ); + } + } + } + + /// Checks if the `#[repr]` attributes on `item` are valid. + fn check_repr( + &self, + attrs: &[Attribute], + span: Span, + target: Target, + item: Option<ItemLike<'_>>, + hir_id: HirId, + ) { + // Extract the names of all repr hints, e.g., [foo, bar, align] for: + // ``` + // #[repr(foo)] + // #[repr(bar, align(8))] + // ``` + let hints: Vec<_> = attrs + .iter() + .filter(|attr| attr.has_name(sym::repr)) + .filter_map(|attr| attr.meta_item_list()) + .flatten() + .collect(); + + let mut int_reprs = 0; + let mut is_c = false; + let mut is_simd = false; + let mut is_transparent = false; + + for hint in &hints { + if !hint.is_meta_item() { + self.tcx.sess.emit_err(errors::ReprIdent { span: hint.span() }); + continue; + } + + let (article, allowed_targets) = match hint.name_or_empty() { + sym::C => { + is_c = true; + match target { + Target::Struct | Target::Union | Target::Enum => continue, + _ => ("a", "struct, enum, or union"), + } + } + sym::align => { + if let (Target::Fn, false) = (target, self.tcx.features().fn_align) { + feature_err( + &self.tcx.sess.parse_sess, + sym::fn_align, + hint.span(), + "`repr(align)` attributes on functions are unstable", + ) + .emit(); + } + + match target { + Target::Struct | Target::Union | Target::Enum | Target::Fn => continue, + _ => ("a", "struct, enum, function, or union"), + } + } + sym::packed => { + if target != Target::Struct && target != Target::Union { + ("a", "struct or union") + } else { + continue; + } + } + sym::simd => { + is_simd = true; + if target != Target::Struct { + ("a", "struct") + } else { + continue; + } + } + sym::transparent => { + is_transparent = true; + match target { + Target::Struct | Target::Union | Target::Enum => continue, + _ => ("a", "struct, enum, or union"), + } + } + sym::i8 + | sym::u8 + | sym::i16 + | sym::u16 + | sym::i32 + | sym::u32 + | sym::i64 + | sym::u64 + | sym::i128 + | sym::u128 + | sym::isize + | sym::usize => { + int_reprs += 1; + if target != Target::Enum { + ("an", "enum") + } else { + continue; + } + } + _ => { + struct_span_err!( + self.tcx.sess, + hint.span(), + E0552, + "unrecognized representation hint" + ) + .emit(); + + continue; + } + }; + + struct_span_err!( + self.tcx.sess, + hint.span(), + E0517, + "{}", + &format!("attribute should be applied to {article} {allowed_targets}") + ) + .span_label(span, &format!("not {article} {allowed_targets}")) + .emit(); + } + + // Just point at all repr hints if there are any incompatibilities. + // This is not ideal, but tracking precisely which ones are at fault is a huge hassle. + let hint_spans = hints.iter().map(|hint| hint.span()); + + // Error on repr(transparent, <anything else>). + if is_transparent && hints.len() > 1 { + let hint_spans: Vec<_> = hint_spans.clone().collect(); + struct_span_err!( + self.tcx.sess, + hint_spans, + E0692, + "transparent {} cannot have other repr hints", + target + ) + .emit(); + } + // Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8) + if (int_reprs > 1) + || (is_simd && is_c) + || (int_reprs == 1 + && is_c + && item.map_or(false, |item| { + if let ItemLike::Item(item) = item { + return is_c_like_enum(item); + } + return false; + })) + { + self.tcx.emit_spanned_lint( + CONFLICTING_REPR_HINTS, + hir_id, + hint_spans.collect::<Vec<Span>>(), + errors::ReprConflicting, + ); + } + } + + fn check_used(&self, attrs: &[Attribute], target: Target) { + let mut used_linker_span = None; + let mut used_compiler_span = None; + for attr in attrs.iter().filter(|attr| attr.has_name(sym::used)) { + if target != Target::Static { + self.tcx.sess.emit_err(errors::UsedStatic { span: attr.span }); + } + let inner = attr.meta_item_list(); + match inner.as_deref() { + Some([item]) if item.has_name(sym::linker) => { + if used_linker_span.is_none() { + used_linker_span = Some(attr.span); + } + } + Some([item]) if item.has_name(sym::compiler) => { + if used_compiler_span.is_none() { + used_compiler_span = Some(attr.span); + } + } + Some(_) => { + // This error case is handled in rustc_typeck::collect. + } + None => { + // Default case (compiler) when arg isn't defined. + if used_compiler_span.is_none() { + used_compiler_span = Some(attr.span); + } + } + } + } + if let (Some(linker_span), Some(compiler_span)) = (used_linker_span, used_compiler_span) { + self.tcx + .sess + .emit_err(errors::UsedCompilerLinker { spans: vec![linker_span, compiler_span] }); + } + } + + /// Outputs an error for `#[allow_internal_unstable]` which can only be applied to macros. + /// (Allows proc_macro functions) + fn check_allow_internal_unstable( + &self, + hir_id: HirId, + attr: &Attribute, + span: Span, + target: Target, + attrs: &[Attribute], + ) -> bool { + debug!("Checking target: {:?}", target); + match target { + Target::Fn => { + for attr in attrs { + if self.tcx.sess.is_proc_macro_attr(attr) { + debug!("Is proc macro attr"); + return true; + } + } + debug!("Is not proc macro attr"); + false + } + Target::MacroDef => true, + // FIXME(#80564): We permit struct fields and match arms to have an + // `#[allow_internal_unstable]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm => { + self.inline_attr_str_error_without_macro_def( + hir_id, + attr, + "allow_internal_unstable", + ); + true + } + _ => { + self.tcx + .sess + .emit_err(errors::AllowInternalUnstable { attr_span: attr.span, span }); + false + } + } + } + + /// Checks if the items on the `#[debugger_visualizer]` attribute are valid. + fn check_debugger_visualizer(&self, attr: &Attribute, target: Target) -> bool { + match target { + Target::Mod => {} + _ => { + self.tcx.sess.emit_err(errors::DebugVisualizerPlacement { span: attr.span }); + return false; + } + } + + let Some(hints) = attr.meta_item_list() else { + self.tcx.sess.emit_err(errors::DebugVisualizerInvalid { span: attr.span }); + return false; + }; + + let hint = match hints.len() { + 1 => &hints[0], + _ => { + self.tcx.sess.emit_err(errors::DebugVisualizerInvalid { span: attr.span }); + return false; + } + }; + + let Some(meta_item) = hint.meta_item() else { + self.tcx.sess.emit_err(errors::DebugVisualizerInvalid { span: attr.span }); + return false; + }; + + let visualizer_path = match (meta_item.name_or_empty(), meta_item.value_str()) { + (sym::natvis_file, Some(value)) => value, + (sym::gdb_script_file, Some(value)) => value, + (_, _) => { + self.tcx.sess.emit_err(errors::DebugVisualizerInvalid { span: meta_item.span }); + return false; + } + }; + + let file = + match resolve_path(&self.tcx.sess.parse_sess, visualizer_path.as_str(), attr.span) { + Ok(file) => file, + Err(mut err) => { + err.emit(); + return false; + } + }; + + match std::fs::File::open(&file) { + Ok(_) => true, + Err(err) => { + self.tcx + .sess + .struct_span_err( + meta_item.span, + &format!("couldn't read {}: {}", file.display(), err), + ) + .emit(); + false + } + } + } + + /// Outputs an error for `#[allow_internal_unstable]` which can only be applied to macros. + /// (Allows proc_macro functions) + fn check_rustc_allow_const_fn_unstable( + &self, + hir_id: HirId, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + match target { + Target::Fn | Target::Method(_) + if self.tcx.is_const_fn_raw(self.tcx.hir().local_def_id(hir_id).to_def_id()) => + { + true + } + // FIXME(#80564): We permit struct fields and match arms to have an + // `#[allow_internal_unstable]` attribute with just a lint, because we previously + // erroneously allowed it and some crates used it accidentally, to to be compatible + // with crates depending on them, we can't throw an error here. + Target::Field | Target::Arm | Target::MacroDef => { + self.inline_attr_str_error_with_macro_def(hir_id, attr, "allow_internal_unstable"); + true + } + _ => { + self.tcx + .sess + .emit_err(errors::RustcAllowConstFnUnstable { attr_span: attr.span, span }); + false + } + } + } + + fn check_rustc_std_internal_symbol( + &self, + attr: &Attribute, + span: Span, + target: Target, + ) -> bool { + match target { + Target::Fn | Target::Static => true, + _ => { + self.tcx + .sess + .emit_err(errors::RustcStdInternalSymbol { attr_span: attr.span, span }); + false + } + } + } + + /// `#[const_trait]` only applies to traits. + fn check_const_trait(&self, attr: &Attribute, _span: Span, target: Target) -> bool { + match target { + Target::Trait => true, + _ => { + self.tcx.sess.emit_err(errors::ConstTrait { attr_span: attr.span }); + false + } + } + } + + fn check_stability_promotable(&self, attr: &Attribute, _span: Span, target: Target) -> bool { + match target { + Target::Expression => { + self.tcx.sess.emit_err(errors::StabilityPromotable { attr_span: attr.span }); + false + } + _ => true, + } + } + + fn check_deprecated(&self, hir_id: HirId, attr: &Attribute, _span: Span, target: Target) { + match target { + Target::Closure | Target::Expression | Target::Statement | Target::Arm => { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::Deprecated, + ); + } + _ => {} + } + } + + fn check_macro_use(&self, hir_id: HirId, attr: &Attribute, target: Target) { + let name = attr.name_or_empty(); + match target { + Target::ExternCrate | Target::Mod => {} + _ => { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::MacroUse { name }, + ); + } + } + } + + fn check_macro_export(&self, hir_id: HirId, attr: &Attribute, target: Target) { + if target != Target::MacroDef { + self.tcx.emit_spanned_lint(UNUSED_ATTRIBUTES, hir_id, attr.span, errors::MacroExport); + } + } + + fn check_plugin_registrar(&self, hir_id: HirId, attr: &Attribute, target: Target) { + if target != Target::Fn { + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::PluginRegistrar, + ); + } + } + + fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute) { + // Warn on useless empty attributes. + let note = if matches!( + attr.name_or_empty(), + sym::macro_use + | sym::allow + | sym::expect + | sym::warn + | sym::deny + | sym::forbid + | sym::feature + | sym::repr + | sym::target_feature + ) && attr.meta_item_list().map_or(false, |list| list.is_empty()) + { + errors::UnusedNote::EmptyList { name: attr.name_or_empty() } + } else if matches!( + attr.name_or_empty(), + sym::allow | sym::warn | sym::deny | sym::forbid | sym::expect + ) && let Some(meta) = attr.meta_item_list() + && meta.len() == 1 + && let Some(item) = meta[0].meta_item() + && let MetaItemKind::NameValue(_) = &item.kind + && item.path == sym::reason + { + errors::UnusedNote::NoLints { name: attr.name_or_empty() } + } else if attr.name_or_empty() == sym::default_method_body_is_const { + errors::UnusedNote::DefaultMethodBodyConst + } else { + return; + }; + + self.tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::Unused { attr_span: attr.span, note }, + ); + } +} + +impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + + fn visit_item(&mut self, item: &'tcx Item<'tcx>) { + // Historically we've run more checks on non-exported than exported macros, + // so this lets us continue to run them while maintaining backwards compatibility. + // In the long run, the checks should be harmonized. + if let ItemKind::Macro(ref macro_def, _) = item.kind { + let def_id = item.def_id.to_def_id(); + if macro_def.macro_rules && !self.tcx.has_attr(def_id, sym::macro_export) { + check_non_exported_macro_for_invalid_attrs(self.tcx, item); + } + } + + let target = Target::from_item(item); + self.check_attributes(item.hir_id(), item.span, target, Some(ItemLike::Item(item))); + intravisit::walk_item(self, item) + } + + fn visit_generic_param(&mut self, generic_param: &'tcx hir::GenericParam<'tcx>) { + let target = Target::from_generic_param(generic_param); + self.check_attributes(generic_param.hir_id, generic_param.span, target, None); + intravisit::walk_generic_param(self, generic_param) + } + + fn visit_trait_item(&mut self, trait_item: &'tcx TraitItem<'tcx>) { + let target = Target::from_trait_item(trait_item); + self.check_attributes(trait_item.hir_id(), trait_item.span, target, None); + intravisit::walk_trait_item(self, trait_item) + } + + fn visit_field_def(&mut self, struct_field: &'tcx hir::FieldDef<'tcx>) { + self.check_attributes(struct_field.hir_id, struct_field.span, Target::Field, None); + intravisit::walk_field_def(self, struct_field); + } + + fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { + self.check_attributes(arm.hir_id, arm.span, Target::Arm, None); + intravisit::walk_arm(self, arm); + } + + fn visit_foreign_item(&mut self, f_item: &'tcx ForeignItem<'tcx>) { + let target = Target::from_foreign_item(f_item); + self.check_attributes(f_item.hir_id(), f_item.span, target, Some(ItemLike::ForeignItem)); + intravisit::walk_foreign_item(self, f_item) + } + + fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) { + let target = target_from_impl_item(self.tcx, impl_item); + self.check_attributes(impl_item.hir_id(), impl_item.span, target, None); + intravisit::walk_impl_item(self, impl_item) + } + + fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) { + // When checking statements ignore expressions, they will be checked later. + if let hir::StmtKind::Local(ref l) = stmt.kind { + self.check_attributes(l.hir_id, stmt.span, Target::Statement, None); + } + intravisit::walk_stmt(self, stmt) + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + let target = match expr.kind { + hir::ExprKind::Closure { .. } => Target::Closure, + _ => Target::Expression, + }; + + self.check_attributes(expr.hir_id, expr.span, target, None); + intravisit::walk_expr(self, expr) + } + + fn visit_variant( + &mut self, + variant: &'tcx hir::Variant<'tcx>, + generics: &'tcx hir::Generics<'tcx>, + item_id: HirId, + ) { + self.check_attributes(variant.id, variant.span, Target::Variant, None); + intravisit::walk_variant(self, variant, generics, item_id) + } + + fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { + self.check_attributes(param.hir_id, param.span, Target::Param, None); + + intravisit::walk_param(self, param); + } +} + +fn is_c_like_enum(item: &Item<'_>) -> bool { + if let ItemKind::Enum(ref def, _) = item.kind { + for variant in def.variants { + match variant.data { + hir::VariantData::Unit(..) => { /* continue */ } + _ => return false, + } + } + true + } else { + false + } +} + +// FIXME: Fix "Cannot determine resolution" error and remove built-in macros +// from this check. +fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) { + // Check for builtin attributes at the crate level + // which were unsuccessfully resolved due to cannot determine + // resolution for the attribute macro error. + const ATTRS_TO_CHECK: &[Symbol] = &[ + sym::macro_export, + sym::repr, + sym::path, + sym::automatically_derived, + sym::start, + sym::rustc_main, + sym::derive, + sym::test, + sym::test_case, + sym::global_allocator, + sym::bench, + ]; + + for attr in attrs { + // This function should only be called with crate attributes + // which are inner attributes always but lets check to make sure + if attr.style == AttrStyle::Inner { + for attr_to_check in ATTRS_TO_CHECK { + if attr.has_name(*attr_to_check) { + let mut err = tcx.sess.struct_span_err( + attr.span, + &format!( + "`{}` attribute cannot be used at crate level", + attr_to_check.to_ident_string() + ), + ); + // Only emit an error with a suggestion if we can create a + // string out of the attribute span + if let Ok(src) = tcx.sess.source_map().span_to_snippet(attr.span) { + let replacement = src.replace("#!", "#"); + err.span_suggestion_verbose( + attr.span, + "perhaps you meant to use an outer attribute", + replacement, + rustc_errors::Applicability::MachineApplicable, + ); + } + err.emit(); + } + } + } + } +} + +fn check_non_exported_macro_for_invalid_attrs(tcx: TyCtxt<'_>, item: &Item<'_>) { + let attrs = tcx.hir().attrs(item.hir_id()); + + for attr in attrs { + if attr.has_name(sym::inline) { + tcx.sess.emit_err(errors::NonExportedMacroInvalidAttrs { attr_span: attr.span }); + } + } +} + +fn check_mod_attrs(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { + let check_attr_visitor = &mut CheckAttrVisitor { tcx }; + tcx.hir().visit_item_likes_in_module(module_def_id, check_attr_visitor); + if module_def_id.is_top_level_module() { + check_attr_visitor.check_attributes(CRATE_HIR_ID, DUMMY_SP, Target::Mod, None); + check_invalid_crate_level_attr(tcx, tcx.hir().krate_attrs()); + } +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { check_mod_attrs, ..*providers }; +} + +fn check_duplicates( + tcx: TyCtxt<'_>, + attr: &Attribute, + hir_id: HirId, + duplicates: AttributeDuplicates, + seen: &mut FxHashMap<Symbol, Span>, +) { + use AttributeDuplicates::*; + if matches!(duplicates, WarnFollowingWordOnly) && !attr.is_word() { + return; + } + match duplicates { + DuplicatesOk => {} + WarnFollowing | FutureWarnFollowing | WarnFollowingWordOnly | FutureWarnPreceding => { + match seen.entry(attr.name_or_empty()) { + Entry::Occupied(mut entry) => { + let (this, other) = if matches!(duplicates, FutureWarnPreceding) { + let to_remove = entry.insert(attr.span); + (to_remove, attr.span) + } else { + (attr.span, *entry.get()) + }; + tcx.emit_spanned_lint( + UNUSED_ATTRIBUTES, + hir_id, + this, + errors::UnusedDuplicate { + this, + other, + warning: matches!( + duplicates, + FutureWarnFollowing | FutureWarnPreceding + ) + .then_some(()), + }, + ); + } + Entry::Vacant(entry) => { + entry.insert(attr.span); + } + } + } + ErrorFollowing | ErrorPreceding => match seen.entry(attr.name_or_empty()) { + Entry::Occupied(mut entry) => { + let (this, other) = if matches!(duplicates, ErrorPreceding) { + let to_remove = entry.insert(attr.span); + (to_remove, attr.span) + } else { + (attr.span, *entry.get()) + }; + tcx.sess.emit_err(errors::UnusedMultiple { + this, + other, + name: attr.name_or_empty(), + }); + } + Entry::Vacant(entry) => { + entry.insert(attr.span); + } + }, + } +} diff --git a/compiler/rustc_passes/src/check_const.rs b/compiler/rustc_passes/src/check_const.rs new file mode 100644 index 000000000..70518284c --- /dev/null +++ b/compiler/rustc_passes/src/check_const.rs @@ -0,0 +1,236 @@ +//! This pass checks HIR bodies that may be evaluated at compile-time (e.g., `const`, `static`, +//! `const fn`) for structured control flow (e.g. `if`, `while`), which is forbidden in a const +//! context. +//! +//! By the time the MIR const-checker runs, these high-level constructs have been lowered to +//! control-flow primitives (e.g., `Goto`, `SwitchInt`), making it tough to properly attribute +//! errors. We still look for those primitives in the MIR const-checker to ensure nothing slips +//! through, but errors for structured control flow in a `const` should be emitted here. + +use rustc_attr as attr; +use rustc_errors::struct_span_err; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_session::parse::feature_err; +use rustc_span::{sym, Span, Symbol}; + +/// An expression that is not *always* legal in a const context. +#[derive(Clone, Copy)] +enum NonConstExpr { + Loop(hir::LoopSource), + Match(hir::MatchSource), +} + +impl NonConstExpr { + fn name(self) -> String { + match self { + Self::Loop(src) => format!("`{}`", src.name()), + Self::Match(src) => format!("`{}`", src.name()), + } + } + + fn required_feature_gates(self) -> Option<&'static [Symbol]> { + use hir::LoopSource::*; + use hir::MatchSource::*; + + let gates: &[_] = match self { + Self::Match(AwaitDesugar) => { + return None; + } + + Self::Loop(ForLoop) | Self::Match(ForLoopDesugar) => &[sym::const_for], + + Self::Match(TryDesugar) => &[sym::const_try], + + // All other expressions are allowed. + Self::Loop(Loop | While) | Self::Match(Normal) => &[], + }; + + Some(gates) + } +} + +fn check_mod_const_bodies(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { + let mut vis = CheckConstVisitor::new(tcx); + tcx.hir().visit_item_likes_in_module(module_def_id, &mut vis); +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { check_mod_const_bodies, ..*providers }; +} + +#[derive(Copy, Clone)] +struct CheckConstVisitor<'tcx> { + tcx: TyCtxt<'tcx>, + const_kind: Option<hir::ConstContext>, + def_id: Option<LocalDefId>, +} + +impl<'tcx> CheckConstVisitor<'tcx> { + fn new(tcx: TyCtxt<'tcx>) -> Self { + CheckConstVisitor { tcx, const_kind: None, def_id: None } + } + + /// Emits an error when an unsupported expression is found in a const context. + fn const_check_violated(&self, expr: NonConstExpr, span: Span) { + let Self { tcx, def_id, const_kind } = *self; + + let features = tcx.features(); + let required_gates = expr.required_feature_gates(); + + let is_feature_allowed = |feature_gate| { + // All features require that the corresponding gate be enabled, + // even if the function has `#[rustc_allow_const_fn_unstable(the_gate)]`. + if !tcx.features().enabled(feature_gate) { + return false; + } + + // If `def_id` is `None`, we don't need to consider stability attributes. + let def_id = match def_id { + Some(x) => x, + None => return true, + }; + + // If the function belongs to a trait, then it must enable the const_trait_impl + // feature to use that trait function (with a const default body). + if tcx.trait_of_item(def_id.to_def_id()).is_some() { + return true; + } + + // If this crate is not using stability attributes, or this function is not claiming to be a + // stable `const fn`, that is all that is required. + if !tcx.features().staged_api + || tcx.has_attr(def_id.to_def_id(), sym::rustc_const_unstable) + { + return true; + } + + // However, we cannot allow stable `const fn`s to use unstable features without an explicit + // opt-in via `rustc_allow_const_fn_unstable`. + let attrs = tcx.hir().attrs(tcx.hir().local_def_id_to_hir_id(def_id)); + attr::rustc_allow_const_fn_unstable(&tcx.sess, attrs).any(|name| name == feature_gate) + }; + + match required_gates { + // Don't emit an error if the user has enabled the requisite feature gates. + Some(gates) if gates.iter().copied().all(is_feature_allowed) => return, + + // `-Zunleash-the-miri-inside-of-you` only works for expressions that don't have a + // corresponding feature gate. This encourages nightly users to use feature gates when + // possible. + None if tcx.sess.opts.unstable_opts.unleash_the_miri_inside_of_you => { + tcx.sess.span_warn(span, "skipping const checks"); + return; + } + + _ => {} + } + + let const_kind = + const_kind.expect("`const_check_violated` may only be called inside a const context"); + + let msg = format!("{} is not allowed in a `{}`", expr.name(), const_kind.keyword_name()); + + let required_gates = required_gates.unwrap_or(&[]); + let missing_gates: Vec<_> = + required_gates.iter().copied().filter(|&g| !features.enabled(g)).collect(); + + match missing_gates.as_slice() { + [] => { + struct_span_err!(tcx.sess, span, E0744, "{}", msg).emit(); + } + + [missing_primary, ref missing_secondary @ ..] => { + let mut err = feature_err(&tcx.sess.parse_sess, *missing_primary, span, &msg); + + // If multiple feature gates would be required to enable this expression, include + // them as help messages. Don't emit a separate error for each missing feature gate. + // + // FIXME(ecstaticmorse): Maybe this could be incorporated into `feature_err`? This + // is a pretty narrow case, however. + if tcx.sess.is_nightly_build() { + for gate in missing_secondary { + let note = format!( + "add `#![feature({})]` to the crate attributes to enable", + gate, + ); + err.help(¬e); + } + } + + err.emit(); + } + } + } + + /// Saves the parent `const_kind` before calling `f` and restores it afterwards. + fn recurse_into( + &mut self, + kind: Option<hir::ConstContext>, + def_id: Option<LocalDefId>, + f: impl FnOnce(&mut Self), + ) { + let parent_def_id = self.def_id; + let parent_kind = self.const_kind; + self.def_id = def_id; + self.const_kind = kind; + f(self); + self.def_id = parent_def_id; + self.const_kind = parent_kind; + } +} + +impl<'tcx> Visitor<'tcx> for CheckConstVisitor<'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + + fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) { + intravisit::walk_item(self, item); + } + + fn visit_anon_const(&mut self, anon: &'tcx hir::AnonConst) { + let kind = Some(hir::ConstContext::Const); + self.recurse_into(kind, None, |this| intravisit::walk_anon_const(this, anon)); + } + + fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) { + let owner = self.tcx.hir().body_owner_def_id(body.id()); + let kind = self.tcx.hir().body_const_context(owner); + self.recurse_into(kind, Some(owner), |this| intravisit::walk_body(this, body)); + } + + fn visit_expr(&mut self, e: &'tcx hir::Expr<'tcx>) { + match &e.kind { + // Skip the following checks if we are not currently in a const context. + _ if self.const_kind.is_none() => {} + + hir::ExprKind::Loop(_, _, source, _) => { + self.const_check_violated(NonConstExpr::Loop(*source), e.span); + } + + hir::ExprKind::Match(_, _, source) => { + let non_const_expr = match source { + // These are handled by `ExprKind::Loop` above. + hir::MatchSource::ForLoopDesugar => None, + + _ => Some(NonConstExpr::Match(*source)), + }; + + if let Some(expr) = non_const_expr { + self.const_check_violated(expr, e.span); + } + } + + _ => {} + } + + intravisit::walk_expr(self, e); + } +} diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs new file mode 100644 index 000000000..1e2fbeb38 --- /dev/null +++ b/compiler/rustc_passes/src/dead.rs @@ -0,0 +1,960 @@ +// This implements the dead-code warning pass. It follows middle::reachable +// closely. The idea is that all reachable symbols are live, codes called +// from live codes are live, and everything else is dead. + +use itertools::Itertools; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::{pluralize, Applicability, MultiSpan}; +use rustc_hir as hir; +use rustc_hir::def::{CtorOf, DefKind, Res}; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{Node, PatKind, TyKind}; +use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; +use rustc_middle::middle::privacy; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::{self, DefIdTree, TyCtxt}; +use rustc_session::lint; +use rustc_span::symbol::{sym, Symbol}; +use std::mem; + +// Any local node that may call something in its body block should be +// explored. For example, if it's a live Node::Item that is a +// function, then we should explore its block to check for codes that +// may need to be marked as live. +fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { + matches!( + tcx.hir().find_by_def_id(def_id), + Some( + Node::Item(..) + | Node::ImplItem(..) + | Node::ForeignItem(..) + | Node::TraitItem(..) + | Node::Variant(..) + | Node::AnonConst(..) + ) + ) +} + +struct MarkSymbolVisitor<'tcx> { + worklist: Vec<LocalDefId>, + tcx: TyCtxt<'tcx>, + maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>, + live_symbols: FxHashSet<LocalDefId>, + repr_has_repr_c: bool, + repr_has_repr_simd: bool, + in_pat: bool, + ignore_variant_stack: Vec<DefId>, + // maps from tuple struct constructors to tuple struct items + struct_constructors: FxHashMap<LocalDefId, LocalDefId>, + // maps from ADTs to ignored derived traits (e.g. Debug and Clone) + // and the span of their respective impl (i.e., part of the derive + // macro) + ignored_derived_traits: FxHashMap<LocalDefId, Vec<(DefId, DefId)>>, +} + +impl<'tcx> MarkSymbolVisitor<'tcx> { + /// Gets the type-checking results for the current body. + /// As this will ICE if called outside bodies, only call when working with + /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies). + #[track_caller] + fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> { + self.maybe_typeck_results + .expect("`MarkSymbolVisitor::typeck_results` called outside of body") + } + + fn check_def_id(&mut self, def_id: DefId) { + if let Some(def_id) = def_id.as_local() { + if should_explore(self.tcx, def_id) || self.struct_constructors.contains_key(&def_id) { + self.worklist.push(def_id); + } + self.live_symbols.insert(def_id); + } + } + + fn insert_def_id(&mut self, def_id: DefId) { + if let Some(def_id) = def_id.as_local() { + debug_assert!(!should_explore(self.tcx, def_id)); + self.live_symbols.insert(def_id); + } + } + + fn handle_res(&mut self, res: Res) { + match res { + Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::TyAlias, def_id) => { + self.check_def_id(def_id); + } + _ if self.in_pat => {} + Res::PrimTy(..) | Res::SelfCtor(..) | Res::Local(..) => {} + Res::Def(DefKind::Ctor(CtorOf::Variant, ..), ctor_def_id) => { + let variant_id = self.tcx.parent(ctor_def_id); + let enum_id = self.tcx.parent(variant_id); + self.check_def_id(enum_id); + if !self.ignore_variant_stack.contains(&ctor_def_id) { + self.check_def_id(variant_id); + } + } + Res::Def(DefKind::Variant, variant_id) => { + let enum_id = self.tcx.parent(variant_id); + self.check_def_id(enum_id); + if !self.ignore_variant_stack.contains(&variant_id) { + self.check_def_id(variant_id); + } + } + Res::Def(_, def_id) => self.check_def_id(def_id), + Res::SelfTy { trait_: t, alias_to: i } => { + if let Some(t) = t { + self.check_def_id(t); + } + if let Some((i, _)) = i { + self.check_def_id(i); + } + } + Res::ToolMod | Res::NonMacroAttr(..) | Res::Err => {} + } + } + + fn lookup_and_handle_method(&mut self, id: hir::HirId) { + if let Some(def_id) = self.typeck_results().type_dependent_def_id(id) { + self.check_def_id(def_id); + } else { + bug!("no type-dependent def for method"); + } + } + + fn handle_field_access(&mut self, lhs: &hir::Expr<'_>, hir_id: hir::HirId) { + match self.typeck_results().expr_ty_adjusted(lhs).kind() { + ty::Adt(def, _) => { + let index = self.tcx.field_index(hir_id, self.typeck_results()); + self.insert_def_id(def.non_enum_variant().fields[index].did); + } + ty::Tuple(..) => {} + _ => span_bug!(lhs.span, "named field access on non-ADT"), + } + } + + #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands. + fn handle_assign(&mut self, expr: &'tcx hir::Expr<'tcx>) { + if self + .typeck_results() + .expr_adjustments(expr) + .iter() + .any(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_))) + { + self.visit_expr(expr); + } else if let hir::ExprKind::Field(base, ..) = expr.kind { + // Ignore write to field + self.handle_assign(base); + } else { + self.visit_expr(expr); + } + } + + #[allow(dead_code)] // FIXME(81658): should be used + lint reinstated after #83171 relands. + fn check_for_self_assign(&mut self, assign: &'tcx hir::Expr<'tcx>) { + fn check_for_self_assign_helper<'tcx>( + typeck_results: &'tcx ty::TypeckResults<'tcx>, + lhs: &'tcx hir::Expr<'tcx>, + rhs: &'tcx hir::Expr<'tcx>, + ) -> bool { + match (&lhs.kind, &rhs.kind) { + (hir::ExprKind::Path(ref qpath_l), hir::ExprKind::Path(ref qpath_r)) => { + if let (Res::Local(id_l), Res::Local(id_r)) = ( + typeck_results.qpath_res(qpath_l, lhs.hir_id), + typeck_results.qpath_res(qpath_r, rhs.hir_id), + ) { + if id_l == id_r { + return true; + } + } + return false; + } + (hir::ExprKind::Field(lhs_l, ident_l), hir::ExprKind::Field(lhs_r, ident_r)) => { + if ident_l == ident_r { + return check_for_self_assign_helper(typeck_results, lhs_l, lhs_r); + } + return false; + } + _ => { + return false; + } + } + } + + if let hir::ExprKind::Assign(lhs, rhs, _) = assign.kind + && check_for_self_assign_helper(self.typeck_results(), lhs, rhs) + && !assign.span.from_expansion() + { + let is_field_assign = matches!(lhs.kind, hir::ExprKind::Field(..)); + self.tcx.struct_span_lint_hir( + lint::builtin::DEAD_CODE, + assign.hir_id, + assign.span, + |lint| { + lint.build(&format!( + "useless assignment of {} of type `{}` to itself", + if is_field_assign { "field" } else { "variable" }, + self.typeck_results().expr_ty(lhs), + )) + .emit(); + }, + ) + } + } + + fn handle_field_pattern_match( + &mut self, + lhs: &hir::Pat<'_>, + res: Res, + pats: &[hir::PatField<'_>], + ) { + let variant = match self.typeck_results().node_type(lhs.hir_id).kind() { + ty::Adt(adt, _) => adt.variant_of_res(res), + _ => span_bug!(lhs.span, "non-ADT in struct pattern"), + }; + for pat in pats { + if let PatKind::Wild = pat.pat.kind { + continue; + } + let index = self.tcx.field_index(pat.hir_id, self.typeck_results()); + self.insert_def_id(variant.fields[index].did); + } + } + + fn handle_tuple_field_pattern_match( + &mut self, + lhs: &hir::Pat<'_>, + res: Res, + pats: &[hir::Pat<'_>], + dotdot: Option<usize>, + ) { + let variant = match self.typeck_results().node_type(lhs.hir_id).kind() { + ty::Adt(adt, _) => adt.variant_of_res(res), + _ => span_bug!(lhs.span, "non-ADT in tuple struct pattern"), + }; + let first_n = pats.iter().enumerate().take(dotdot.unwrap_or(pats.len())); + let missing = variant.fields.len() - pats.len(); + let last_n = pats + .iter() + .enumerate() + .skip(dotdot.unwrap_or(pats.len())) + .map(|(idx, pat)| (idx + missing, pat)); + for (idx, pat) in first_n.chain(last_n) { + if let PatKind::Wild = pat.kind { + continue; + } + self.insert_def_id(variant.fields[idx].did); + } + } + + fn mark_live_symbols(&mut self) { + let mut scanned = FxHashSet::default(); + while let Some(id) = self.worklist.pop() { + if !scanned.insert(id) { + continue; + } + + // in the case of tuple struct constructors we want to check the item, not the generated + // tuple struct constructor function + let id = self.struct_constructors.get(&id).copied().unwrap_or(id); + + if let Some(node) = self.tcx.hir().find_by_def_id(id) { + self.live_symbols.insert(id); + self.visit_node(node); + } + } + } + + /// Automatically generated items marked with `rustc_trivial_field_reads` + /// will be ignored for the purposes of dead code analysis (see PR #85200 + /// for discussion). + fn should_ignore_item(&mut self, def_id: DefId) -> bool { + if let Some(impl_of) = self.tcx.impl_of_method(def_id) { + if !self.tcx.has_attr(impl_of, sym::automatically_derived) { + return false; + } + + if let Some(trait_of) = self.tcx.trait_id_of_impl(impl_of) + && self.tcx.has_attr(trait_of, sym::rustc_trivial_field_reads) + { + let trait_ref = self.tcx.impl_trait_ref(impl_of).unwrap(); + if let ty::Adt(adt_def, _) = trait_ref.self_ty().kind() + && let Some(adt_def_id) = adt_def.did().as_local() + { + self.ignored_derived_traits + .entry(adt_def_id) + .or_default() + .push((trait_of, impl_of)); + } + return true; + } + } + + return false; + } + + fn visit_node(&mut self, node: Node<'tcx>) { + if let Node::ImplItem(hir::ImplItem { def_id, .. }) = node + && self.should_ignore_item(def_id.to_def_id()) + { + return; + } + + let had_repr_c = self.repr_has_repr_c; + let had_repr_simd = self.repr_has_repr_simd; + self.repr_has_repr_c = false; + self.repr_has_repr_simd = false; + match node { + Node::Item(item) => match item.kind { + hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) => { + let def = self.tcx.adt_def(item.def_id); + self.repr_has_repr_c = def.repr().c(); + self.repr_has_repr_simd = def.repr().simd(); + + intravisit::walk_item(self, &item) + } + hir::ItemKind::ForeignMod { .. } => {} + _ => intravisit::walk_item(self, &item), + }, + Node::TraitItem(trait_item) => { + intravisit::walk_trait_item(self, trait_item); + } + Node::ImplItem(impl_item) => { + let item = self.tcx.local_parent(impl_item.def_id); + if self.tcx.impl_trait_ref(item).is_none() { + //// If it's a type whose items are live, then it's live, too. + //// This is done to handle the case where, for example, the static + //// method of a private type is used, but the type itself is never + //// called directly. + let self_ty = self.tcx.type_of(item); + match *self_ty.kind() { + ty::Adt(def, _) => self.check_def_id(def.did()), + ty::Foreign(did) => self.check_def_id(did), + ty::Dynamic(data, ..) => { + if let Some(def_id) = data.principal_def_id() { + self.check_def_id(def_id) + } + } + _ => {} + } + } + intravisit::walk_impl_item(self, impl_item); + } + Node::ForeignItem(foreign_item) => { + intravisit::walk_foreign_item(self, &foreign_item); + } + _ => {} + } + self.repr_has_repr_simd = had_repr_simd; + self.repr_has_repr_c = had_repr_c; + } + + fn mark_as_used_if_union(&mut self, adt: ty::AdtDef<'tcx>, fields: &[hir::ExprField<'_>]) { + if adt.is_union() && adt.non_enum_variant().fields.len() > 1 && adt.did().is_local() { + for field in fields { + let index = self.tcx.field_index(field.hir_id, self.typeck_results()); + self.insert_def_id(adt.non_enum_variant().fields[index].did); + } + } + } +} + +impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> { + fn visit_nested_body(&mut self, body: hir::BodyId) { + let old_maybe_typeck_results = + self.maybe_typeck_results.replace(self.tcx.typeck_body(body)); + let body = self.tcx.hir().body(body); + self.visit_body(body); + self.maybe_typeck_results = old_maybe_typeck_results; + } + + fn visit_variant_data( + &mut self, + def: &'tcx hir::VariantData<'tcx>, + _: Symbol, + _: &hir::Generics<'_>, + _: hir::HirId, + _: rustc_span::Span, + ) { + let tcx = self.tcx; + let has_repr_c = self.repr_has_repr_c; + let has_repr_simd = self.repr_has_repr_simd; + let live_fields = def.fields().iter().filter_map(|f| { + let def_id = tcx.hir().local_def_id(f.hir_id); + if has_repr_c || (f.is_positional() && has_repr_simd) { + return Some(def_id); + } + if !tcx.visibility(f.hir_id.owner).is_public() { + return None; + } + if tcx.visibility(def_id).is_public() { Some(def_id) } else { None } + }); + self.live_symbols.extend(live_fields); + + intravisit::walk_struct_def(self, def); + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + match expr.kind { + hir::ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(..)) => { + let res = self.typeck_results().qpath_res(qpath, expr.hir_id); + self.handle_res(res); + } + hir::ExprKind::MethodCall(..) => { + self.lookup_and_handle_method(expr.hir_id); + } + hir::ExprKind::Field(ref lhs, ..) => { + self.handle_field_access(&lhs, expr.hir_id); + } + hir::ExprKind::Struct(ref qpath, ref fields, _) => { + let res = self.typeck_results().qpath_res(qpath, expr.hir_id); + self.handle_res(res); + if let ty::Adt(adt, _) = self.typeck_results().expr_ty(expr).kind() { + self.mark_as_used_if_union(*adt, fields); + } + } + _ => (), + } + + intravisit::walk_expr(self, expr); + } + + fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { + // Inside the body, ignore constructions of variants + // necessary for the pattern to match. Those construction sites + // can't be reached unless the variant is constructed elsewhere. + let len = self.ignore_variant_stack.len(); + self.ignore_variant_stack.extend(arm.pat.necessary_variants()); + intravisit::walk_arm(self, arm); + self.ignore_variant_stack.truncate(len); + } + + fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { + self.in_pat = true; + match pat.kind { + PatKind::Struct(ref path, ref fields, _) => { + let res = self.typeck_results().qpath_res(path, pat.hir_id); + self.handle_field_pattern_match(pat, res, fields); + } + PatKind::Path(ref qpath) => { + let res = self.typeck_results().qpath_res(qpath, pat.hir_id); + self.handle_res(res); + } + PatKind::TupleStruct(ref qpath, ref fields, dotdot) => { + let res = self.typeck_results().qpath_res(qpath, pat.hir_id); + self.handle_tuple_field_pattern_match(pat, res, fields, dotdot); + } + _ => (), + } + + intravisit::walk_pat(self, pat); + self.in_pat = false; + } + + fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) { + self.handle_res(path.res); + intravisit::walk_path(self, path); + } + + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx>) { + if let TyKind::OpaqueDef(item_id, _) = ty.kind { + let item = self.tcx.hir().item(item_id); + intravisit::walk_item(self, item); + } + intravisit::walk_ty(self, ty); + } + + fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) { + // When inline const blocks are used in pattern position, paths + // referenced by it should be considered as used. + let in_pat = mem::replace(&mut self.in_pat, false); + + self.live_symbols.insert(self.tcx.hir().local_def_id(c.hir_id)); + intravisit::walk_anon_const(self, c); + + self.in_pat = in_pat; + } +} + +fn has_allow_dead_code_or_lang_attr_helper( + tcx: TyCtxt<'_>, + id: hir::HirId, + lint: &'static lint::Lint, +) -> bool { + let attrs = tcx.hir().attrs(id); + if tcx.sess.contains_name(attrs, sym::lang) { + return true; + } + + // Stable attribute for #[lang = "panic_impl"] + if tcx.sess.contains_name(attrs, sym::panic_handler) { + return true; + } + + // (To be) stable attribute for #[lang = "oom"] + if tcx.sess.contains_name(attrs, sym::alloc_error_handler) { + return true; + } + + let def_id = tcx.hir().local_def_id(id); + if tcx.def_kind(def_id).has_codegen_attrs() { + let cg_attrs = tcx.codegen_fn_attrs(def_id); + + // #[used], #[no_mangle], #[export_name], etc also keeps the item alive + // forcefully, e.g., for placing it in a specific section. + if cg_attrs.contains_extern_indicator() + || cg_attrs.flags.contains(CodegenFnAttrFlags::USED) + || cg_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) + { + return true; + } + } + + tcx.lint_level_at_node(lint, id).0 == lint::Allow +} + +fn has_allow_dead_code_or_lang_attr(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { + has_allow_dead_code_or_lang_attr_helper(tcx, id, lint::builtin::DEAD_CODE) +} + +// These check_* functions seeds items that +// 1) We want to explicitly consider as live: +// * Item annotated with #[allow(dead_code)] +// - This is done so that if we want to suppress warnings for a +// group of dead functions, we only have to annotate the "root". +// For example, if both `f` and `g` are dead and `f` calls `g`, +// then annotating `f` with `#[allow(dead_code)]` will suppress +// warning for both `f` and `g`. +// * Item annotated with #[lang=".."] +// - This is because lang items are always callable from elsewhere. +// or +// 2) We are not sure to be live or not +// * Implementations of traits and trait methods +fn check_item<'tcx>( + tcx: TyCtxt<'tcx>, + worklist: &mut Vec<LocalDefId>, + struct_constructors: &mut FxHashMap<LocalDefId, LocalDefId>, + id: hir::ItemId, +) { + let allow_dead_code = has_allow_dead_code_or_lang_attr(tcx, id.hir_id()); + if allow_dead_code { + worklist.push(id.def_id); + } + + match tcx.def_kind(id.def_id) { + DefKind::Enum => { + let item = tcx.hir().item(id); + if let hir::ItemKind::Enum(ref enum_def, _) = item.kind { + let hir = tcx.hir(); + if allow_dead_code { + worklist.extend( + enum_def.variants.iter().map(|variant| hir.local_def_id(variant.id)), + ); + } + + for variant in enum_def.variants { + if let Some(ctor_hir_id) = variant.data.ctor_hir_id() { + struct_constructors + .insert(hir.local_def_id(ctor_hir_id), hir.local_def_id(variant.id)); + } + } + } + } + DefKind::Impl => { + let of_trait = tcx.impl_trait_ref(id.def_id); + + if of_trait.is_some() { + worklist.push(id.def_id); + } + + // get DefIds from another query + let local_def_ids = tcx + .associated_item_def_ids(id.def_id) + .iter() + .filter_map(|def_id| def_id.as_local()); + + // And we access the Map here to get HirId from LocalDefId + for id in local_def_ids { + if of_trait.is_some() + || has_allow_dead_code_or_lang_attr(tcx, tcx.hir().local_def_id_to_hir_id(id)) + { + worklist.push(id); + } + } + } + DefKind::Struct => { + let item = tcx.hir().item(id); + if let hir::ItemKind::Struct(ref variant_data, _) = item.kind + && let Some(ctor_hir_id) = variant_data.ctor_hir_id() + { + struct_constructors.insert(tcx.hir().local_def_id(ctor_hir_id), item.def_id); + } + } + DefKind::GlobalAsm => { + // global_asm! is always live. + worklist.push(id.def_id); + } + _ => {} + } +} + +fn check_trait_item<'tcx>(tcx: TyCtxt<'tcx>, worklist: &mut Vec<LocalDefId>, id: hir::TraitItemId) { + use hir::TraitItemKind::{Const, Fn}; + if matches!(tcx.def_kind(id.def_id), DefKind::AssocConst | DefKind::AssocFn) { + let trait_item = tcx.hir().trait_item(id); + if matches!(trait_item.kind, Const(_, Some(_)) | Fn(_, hir::TraitFn::Provided(_))) + && has_allow_dead_code_or_lang_attr(tcx, trait_item.hir_id()) + { + worklist.push(trait_item.def_id); + } + } +} + +fn check_foreign_item<'tcx>( + tcx: TyCtxt<'tcx>, + worklist: &mut Vec<LocalDefId>, + id: hir::ForeignItemId, +) { + if matches!(tcx.def_kind(id.def_id), DefKind::Static(_) | DefKind::Fn) + && has_allow_dead_code_or_lang_attr(tcx, id.hir_id()) + { + worklist.push(id.def_id); + } +} + +fn create_and_seed_worklist<'tcx>( + tcx: TyCtxt<'tcx>, +) -> (Vec<LocalDefId>, FxHashMap<LocalDefId, LocalDefId>) { + let access_levels = &tcx.privacy_access_levels(()); + // see `MarkSymbolVisitor::struct_constructors` + let mut struct_constructors = Default::default(); + let mut worklist = access_levels + .map + .iter() + .filter_map( + |(&id, &level)| { + if level >= privacy::AccessLevel::Reachable { Some(id) } else { None } + }, + ) + // Seed entry point + .chain(tcx.entry_fn(()).and_then(|(def_id, _)| def_id.as_local())) + .collect::<Vec<_>>(); + + let crate_items = tcx.hir_crate_items(()); + for id in crate_items.items() { + check_item(tcx, &mut worklist, &mut struct_constructors, id); + } + + for id in crate_items.trait_items() { + check_trait_item(tcx, &mut worklist, id); + } + + for id in crate_items.foreign_items() { + check_foreign_item(tcx, &mut worklist, id); + } + + (worklist, struct_constructors) +} + +fn live_symbols_and_ignored_derived_traits<'tcx>( + tcx: TyCtxt<'tcx>, + (): (), +) -> (FxHashSet<LocalDefId>, FxHashMap<LocalDefId, Vec<(DefId, DefId)>>) { + let (worklist, struct_constructors) = create_and_seed_worklist(tcx); + let mut symbol_visitor = MarkSymbolVisitor { + worklist, + tcx, + maybe_typeck_results: None, + live_symbols: Default::default(), + repr_has_repr_c: false, + repr_has_repr_simd: false, + in_pat: false, + ignore_variant_stack: vec![], + struct_constructors, + ignored_derived_traits: FxHashMap::default(), + }; + symbol_visitor.mark_live_symbols(); + (symbol_visitor.live_symbols, symbol_visitor.ignored_derived_traits) +} + +struct DeadVariant { + def_id: LocalDefId, + name: Symbol, + level: lint::Level, +} + +struct DeadVisitor<'tcx> { + tcx: TyCtxt<'tcx>, + live_symbols: &'tcx FxHashSet<LocalDefId>, + ignored_derived_traits: &'tcx FxHashMap<LocalDefId, Vec<(DefId, DefId)>>, +} + +enum ShouldWarnAboutField { + Yes(bool), // positional? + No, +} + +impl<'tcx> DeadVisitor<'tcx> { + fn should_warn_about_field(&mut self, field: &ty::FieldDef) -> ShouldWarnAboutField { + if self.live_symbols.contains(&field.did.expect_local()) { + return ShouldWarnAboutField::No; + } + let field_type = self.tcx.type_of(field.did); + if field_type.is_phantom_data() { + return ShouldWarnAboutField::No; + } + let is_positional = field.name.as_str().starts_with(|c: char| c.is_ascii_digit()); + if is_positional + && self + .tcx + .layout_of(self.tcx.param_env(field.did).and(field_type)) + .map_or(true, |layout| layout.is_zst()) + { + return ShouldWarnAboutField::No; + } + ShouldWarnAboutField::Yes(is_positional) + } + + fn warn_multiple_dead_codes( + &self, + dead_codes: &[LocalDefId], + participle: &str, + parent_item: Option<LocalDefId>, + is_positional: bool, + ) { + if let Some(&first_id) = dead_codes.first() { + let tcx = self.tcx; + let names: Vec<_> = dead_codes + .iter() + .map(|&def_id| tcx.item_name(def_id.to_def_id()).to_string()) + .collect(); + let spans: Vec<_> = dead_codes + .iter() + .map(|&def_id| match tcx.def_ident_span(def_id) { + Some(s) => s.with_ctxt(tcx.def_span(def_id).ctxt()), + None => tcx.def_span(def_id), + }) + .collect(); + + tcx.struct_span_lint_hir( + if is_positional { + lint::builtin::UNUSED_TUPLE_STRUCT_FIELDS + } else { + lint::builtin::DEAD_CODE + }, + tcx.hir().local_def_id_to_hir_id(first_id), + MultiSpan::from_spans(spans.clone()), + |lint| { + let descr = tcx.def_kind(first_id).descr(first_id.to_def_id()); + let span_len = dead_codes.len(); + let names = match &names[..] { + _ if span_len > 6 => String::new(), + [name] => format!("`{name}` "), + [names @ .., last] => { + format!( + "{} and `{last}` ", + names.iter().map(|name| format!("`{name}`")).join(", ") + ) + } + [] => unreachable!(), + }; + let mut err = lint.build(&format!( + "{these}{descr}{s} {names}{are} never {participle}", + these = if span_len > 6 { "multiple " } else { "" }, + s = pluralize!(span_len), + are = pluralize!("is", span_len), + )); + + if is_positional { + err.multipart_suggestion( + &format!( + "consider changing the field{s} to be of unit type to \ + suppress this warning while preserving the field \ + numbering, or remove the field{s}", + s = pluralize!(span_len) + ), + spans.iter().map(|sp| (*sp, "()".to_string())).collect(), + // "HasPlaceholders" because applying this fix by itself isn't + // enough: All constructor calls have to be adjusted as well + Applicability::HasPlaceholders, + ); + } + + if let Some(parent_item) = parent_item { + let parent_descr = tcx.def_kind(parent_item).descr(parent_item.to_def_id()); + err.span_label( + tcx.def_ident_span(parent_item).unwrap(), + format!("{descr}{s} in this {parent_descr}", s = pluralize!(span_len)), + ); + } + + let encl_def_id = parent_item.unwrap_or(first_id); + if let Some(ign_traits) = self.ignored_derived_traits.get(&encl_def_id) { + let traits_str = ign_traits + .iter() + .map(|(trait_id, _)| format!("`{}`", self.tcx.item_name(*trait_id))) + .collect::<Vec<_>>() + .join(" and "); + let plural_s = pluralize!(ign_traits.len()); + let article = if ign_traits.len() > 1 { "" } else { "a " }; + let is_are = if ign_traits.len() > 1 { "these are" } else { "this is" }; + let msg = format!( + "`{}` has {}derived impl{} for the trait{} {}, but {} \ + intentionally ignored during dead code analysis", + self.tcx.item_name(encl_def_id.to_def_id()), + article, + plural_s, + plural_s, + traits_str, + is_are + ); + err.note(&msg); + } + err.emit(); + }, + ); + } + } + + fn warn_dead_fields_and_variants( + &self, + def_id: LocalDefId, + participle: &str, + dead_codes: Vec<DeadVariant>, + is_positional: bool, + ) { + let mut dead_codes = dead_codes + .iter() + .filter(|v| !v.name.as_str().starts_with('_')) + .map(|v| v) + .collect::<Vec<&DeadVariant>>(); + if dead_codes.is_empty() { + return; + } + dead_codes.sort_by_key(|v| v.level); + for (_, group) in &dead_codes.into_iter().group_by(|v| v.level) { + self.warn_multiple_dead_codes( + &group.map(|v| v.def_id).collect::<Vec<_>>(), + participle, + Some(def_id), + is_positional, + ); + } + } + + fn warn_dead_code(&mut self, id: LocalDefId, participle: &str) { + self.warn_multiple_dead_codes(&[id], participle, None, false); + } + + fn check_definition(&mut self, def_id: LocalDefId) { + if self.live_symbols.contains(&def_id) { + return; + } + let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id); + if has_allow_dead_code_or_lang_attr(self.tcx, hir_id) { + return; + } + let Some(name) = self.tcx.opt_item_name(def_id.to_def_id()) else { + return + }; + if name.as_str().starts_with('_') { + return; + } + match self.tcx.def_kind(def_id) { + DefKind::AssocConst + | DefKind::AssocFn + | DefKind::Fn + | DefKind::Static(_) + | DefKind::Const + | DefKind::TyAlias + | DefKind::Enum + | DefKind::Union + | DefKind::ForeignTy => self.warn_dead_code(def_id, "used"), + DefKind::Struct => self.warn_dead_code(def_id, "constructed"), + DefKind::Variant | DefKind::Field => bug!("should be handled specially"), + _ => {} + } + } +} + +fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalDefId) { + let (live_symbols, ignored_derived_traits) = tcx.live_symbols_and_ignored_derived_traits(()); + let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits }; + + let module_items = tcx.hir_module_items(module); + + for item in module_items.items() { + if !live_symbols.contains(&item.def_id) { + let parent = tcx.local_parent(item.def_id); + if parent != module && !live_symbols.contains(&parent) { + // We already have diagnosed something. + continue; + } + visitor.check_definition(item.def_id); + continue; + } + + let def_kind = tcx.def_kind(item.def_id); + if let DefKind::Struct | DefKind::Union | DefKind::Enum = def_kind { + let adt = tcx.adt_def(item.def_id); + let mut dead_variants = Vec::new(); + + for variant in adt.variants() { + let def_id = variant.def_id.expect_local(); + if !live_symbols.contains(&def_id) { + // Record to group diagnostics. + let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); + let level = tcx.lint_level_at_node(lint::builtin::DEAD_CODE, hir_id).0; + dead_variants.push(DeadVariant { def_id, name: variant.name, level }); + continue; + } + + let mut is_positional = false; + let dead_fields = variant + .fields + .iter() + .filter_map(|field| { + let def_id = field.did.expect_local(); + let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); + if let ShouldWarnAboutField::Yes(is_pos) = + visitor.should_warn_about_field(&field) + { + let level = tcx + .lint_level_at_node( + if is_pos { + is_positional = true; + lint::builtin::UNUSED_TUPLE_STRUCT_FIELDS + } else { + lint::builtin::DEAD_CODE + }, + hir_id, + ) + .0; + Some(DeadVariant { def_id, name: field.name, level }) + } else { + None + } + }) + .collect(); + visitor.warn_dead_fields_and_variants(def_id, "read", dead_fields, is_positional) + } + + visitor.warn_dead_fields_and_variants(item.def_id, "constructed", dead_variants, false); + } + } + + for impl_item in module_items.impl_items() { + visitor.check_definition(impl_item.def_id); + } + + for foreign_item in module_items.foreign_items() { + visitor.check_definition(foreign_item.def_id); + } + + // We do not warn trait items. +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = + Providers { live_symbols_and_ignored_derived_traits, check_mod_deathness, ..*providers }; +} diff --git a/compiler/rustc_passes/src/debugger_visualizer.rs b/compiler/rustc_passes/src/debugger_visualizer.rs new file mode 100644 index 000000000..e08683fe2 --- /dev/null +++ b/compiler/rustc_passes/src/debugger_visualizer.rs @@ -0,0 +1,99 @@ +//! Detecting usage of the `#[debugger_visualizer]` attribute. + +use hir::CRATE_HIR_ID; +use rustc_data_structures::fx::FxHashSet; +use rustc_expand::base::resolve_path; +use rustc_hir as hir; +use rustc_hir::def_id::CrateNum; +use rustc_hir::HirId; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_span::def_id::LOCAL_CRATE; +use rustc_span::{sym, DebuggerVisualizerFile, DebuggerVisualizerType}; + +use std::sync::Arc; + +fn check_for_debugger_visualizer<'tcx>( + tcx: TyCtxt<'tcx>, + hir_id: HirId, + debugger_visualizers: &mut FxHashSet<DebuggerVisualizerFile>, +) { + let attrs = tcx.hir().attrs(hir_id); + for attr in attrs { + if attr.has_name(sym::debugger_visualizer) { + let Some(list) = attr.meta_item_list() else { + continue + }; + + let meta_item = match list.len() { + 1 => match list[0].meta_item() { + Some(meta_item) => meta_item, + _ => continue, + }, + _ => continue, + }; + + let visualizer_type = match meta_item.name_or_empty() { + sym::natvis_file => DebuggerVisualizerType::Natvis, + sym::gdb_script_file => DebuggerVisualizerType::GdbPrettyPrinter, + _ => continue, + }; + + let file = match meta_item.value_str() { + Some(value) => { + match resolve_path(&tcx.sess.parse_sess, value.as_str(), attr.span) { + Ok(file) => file, + _ => continue, + } + } + None => continue, + }; + + match std::fs::read(&file) { + Ok(contents) => { + debugger_visualizers + .insert(DebuggerVisualizerFile::new(Arc::from(contents), visualizer_type)); + } + Err(err) => { + tcx.sess + .struct_span_err( + meta_item.span, + &format!("couldn't read {}: {}", file.display(), err), + ) + .emit(); + } + } + } + } +} + +/// Traverses and collects the debugger visualizers for a specific crate. +fn debugger_visualizers<'tcx>(tcx: TyCtxt<'tcx>, cnum: CrateNum) -> Vec<DebuggerVisualizerFile> { + assert_eq!(cnum, LOCAL_CRATE); + + // Initialize the collector. + let mut debugger_visualizers = FxHashSet::default(); + + // Collect debugger visualizers in this crate. + tcx.hir().for_each_module(|id| { + check_for_debugger_visualizer( + tcx, + tcx.hir().local_def_id_to_hir_id(id), + &mut debugger_visualizers, + ) + }); + + // Collect debugger visualizers on the crate attributes. + check_for_debugger_visualizer(tcx, CRATE_HIR_ID, &mut debugger_visualizers); + + // Extract out the found debugger_visualizer items. + let mut visualizers = debugger_visualizers.into_iter().collect::<Vec<_>>(); + + // Sort the visualizers so we always get a deterministic query result. + visualizers.sort(); + visualizers +} + +pub fn provide(providers: &mut Providers) { + providers.debugger_visualizers = debugger_visualizers; +} diff --git a/compiler/rustc_passes/src/diagnostic_items.rs b/compiler/rustc_passes/src/diagnostic_items.rs new file mode 100644 index 000000000..e6b69d898 --- /dev/null +++ b/compiler/rustc_passes/src/diagnostic_items.rs @@ -0,0 +1,113 @@ +//! Detecting diagnostic items. +//! +//! Diagnostic items are items that are not language-inherent, but can reasonably be expected to +//! exist for diagnostic purposes. This allows diagnostic authors to refer to specific items +//! directly, without having to guess module paths and crates. +//! Examples are: +//! +//! * Traits like `Debug`, that have no bearing on language semantics +//! +//! * Compiler internal types like `Ty` and `TyCtxt` + +use rustc_ast as ast; +use rustc_hir::diagnostic_items::DiagnosticItems; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_span::def_id::{CrateNum, DefId, LocalDefId, LOCAL_CRATE}; +use rustc_span::symbol::{sym, Symbol}; + +fn observe_item<'tcx>( + tcx: TyCtxt<'tcx>, + diagnostic_items: &mut DiagnosticItems, + def_id: LocalDefId, +) { + let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); + let attrs = tcx.hir().attrs(hir_id); + if let Some(name) = extract(attrs) { + // insert into our table + collect_item(tcx, diagnostic_items, name, def_id.to_def_id()); + } +} + +fn collect_item(tcx: TyCtxt<'_>, items: &mut DiagnosticItems, name: Symbol, item_def_id: DefId) { + items.id_to_name.insert(item_def_id, name); + if let Some(original_def_id) = items.name_to_id.insert(name, item_def_id) { + if original_def_id != item_def_id { + let mut err = match tcx.hir().span_if_local(item_def_id) { + Some(span) => tcx + .sess + .struct_span_err(span, &format!("duplicate diagnostic item found: `{name}`.")), + None => tcx.sess.struct_err(&format!( + "duplicate diagnostic item in crate `{}`: `{}`.", + tcx.crate_name(item_def_id.krate), + name + )), + }; + if let Some(span) = tcx.hir().span_if_local(original_def_id) { + err.span_note(span, "the diagnostic item is first defined here"); + } else { + err.note(&format!( + "the diagnostic item is first defined in crate `{}`.", + tcx.crate_name(original_def_id.krate) + )); + } + err.emit(); + } + } +} + +/// Extract the first `rustc_diagnostic_item = "$name"` out of a list of attributes. +fn extract(attrs: &[ast::Attribute]) -> Option<Symbol> { + attrs.iter().find_map(|attr| { + if attr.has_name(sym::rustc_diagnostic_item) { attr.value_str() } else { None } + }) +} + +/// Traverse and collect the diagnostic items in the current +fn diagnostic_items<'tcx>(tcx: TyCtxt<'tcx>, cnum: CrateNum) -> DiagnosticItems { + assert_eq!(cnum, LOCAL_CRATE); + + // Initialize the collector. + let mut diagnostic_items = DiagnosticItems::default(); + + // Collect diagnostic items in this crate. + let crate_items = tcx.hir_crate_items(()); + + for id in crate_items.items() { + observe_item(tcx, &mut diagnostic_items, id.def_id); + } + + for id in crate_items.trait_items() { + observe_item(tcx, &mut diagnostic_items, id.def_id); + } + + for id in crate_items.impl_items() { + observe_item(tcx, &mut diagnostic_items, id.def_id); + } + + for id in crate_items.foreign_items() { + observe_item(tcx, &mut diagnostic_items, id.def_id); + } + + diagnostic_items +} + +/// Traverse and collect all the diagnostic items in all crates. +fn all_diagnostic_items<'tcx>(tcx: TyCtxt<'tcx>, (): ()) -> DiagnosticItems { + // Initialize the collector. + let mut items = DiagnosticItems::default(); + + // Collect diagnostic items in other crates. + for &cnum in tcx.crates(()).iter().chain(std::iter::once(&LOCAL_CRATE)) { + for (&name, &def_id) in &tcx.diagnostic_items(cnum).name_to_id { + collect_item(tcx, &mut items, name, def_id); + } + } + + items +} + +pub fn provide(providers: &mut Providers) { + providers.diagnostic_items = diagnostic_items; + providers.all_diagnostic_items = all_diagnostic_items; +} diff --git a/compiler/rustc_passes/src/entry.rs b/compiler/rustc_passes/src/entry.rs new file mode 100644 index 000000000..7381019a6 --- /dev/null +++ b/compiler/rustc_passes/src/entry.rs @@ -0,0 +1,231 @@ +use rustc_ast::{entry::EntryPointType, Attribute}; +use rustc_errors::struct_span_err; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID, LOCAL_CRATE}; +use rustc_hir::{ItemId, Node, CRATE_HIR_ID}; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::{DefIdTree, TyCtxt}; +use rustc_session::config::{CrateType, EntryFnType}; +use rustc_session::parse::feature_err; +use rustc_span::symbol::sym; +use rustc_span::{Span, Symbol, DUMMY_SP}; + +struct EntryContext<'tcx> { + tcx: TyCtxt<'tcx>, + + /// The function that has attribute named `main`. + attr_main_fn: Option<(LocalDefId, Span)>, + + /// The function that has the attribute 'start' on it. + start_fn: Option<(LocalDefId, Span)>, + + /// The functions that one might think are `main` but aren't, e.g. + /// main functions not defined at the top level. For diagnostics. + non_main_fns: Vec<Span>, +} + +fn entry_fn(tcx: TyCtxt<'_>, (): ()) -> Option<(DefId, EntryFnType)> { + let any_exe = tcx.sess.crate_types().iter().any(|ty| *ty == CrateType::Executable); + if !any_exe { + // No need to find a main function. + return None; + } + + // If the user wants no main function at all, then stop here. + if tcx.sess.contains_name(&tcx.hir().attrs(CRATE_HIR_ID), sym::no_main) { + return None; + } + + let mut ctxt = + EntryContext { tcx, attr_main_fn: None, start_fn: None, non_main_fns: Vec::new() }; + + for id in tcx.hir().items() { + find_item(id, &mut ctxt); + } + + configure_main(tcx, &ctxt) +} + +// Beware, this is duplicated in `librustc_builtin_macros/test_harness.rs` +// (with `ast::Item`), so make sure to keep them in sync. +// A small optimization was added so that hir::Item is fetched only when needed. +// An equivalent optimization was not applied to the duplicated code in test_harness.rs. +fn entry_point_type(ctxt: &EntryContext<'_>, id: ItemId, at_root: bool) -> EntryPointType { + let attrs = ctxt.tcx.hir().attrs(id.hir_id()); + if ctxt.tcx.sess.contains_name(attrs, sym::start) { + EntryPointType::Start + } else if ctxt.tcx.sess.contains_name(attrs, sym::rustc_main) { + EntryPointType::RustcMainAttr + } else { + if let Some(name) = ctxt.tcx.opt_item_name(id.def_id.to_def_id()) + && name == sym::main { + if at_root { + // This is a top-level function so can be `main`. + EntryPointType::MainNamed + } else { + EntryPointType::OtherMain + } + } else { + EntryPointType::None + } + } +} + +fn err_if_attr_found(ctxt: &EntryContext<'_>, attrs: &[Attribute], sym: Symbol) { + if let Some(attr) = ctxt.tcx.sess.find_by_name(attrs, sym) { + ctxt.tcx + .sess + .struct_span_err( + attr.span, + &format!("`{}` attribute can only be used on functions", sym), + ) + .emit(); + } +} + +fn find_item(id: ItemId, ctxt: &mut EntryContext<'_>) { + let at_root = ctxt.tcx.opt_local_parent(id.def_id) == Some(CRATE_DEF_ID); + + match entry_point_type(ctxt, id, at_root) { + EntryPointType::None => (), + _ if !matches!(ctxt.tcx.def_kind(id.def_id), DefKind::Fn) => { + let attrs = ctxt.tcx.hir().attrs(id.hir_id()); + err_if_attr_found(ctxt, attrs, sym::start); + err_if_attr_found(ctxt, attrs, sym::rustc_main); + } + EntryPointType::MainNamed => (), + EntryPointType::OtherMain => { + ctxt.non_main_fns.push(ctxt.tcx.def_span(id.def_id)); + } + EntryPointType::RustcMainAttr => { + if ctxt.attr_main_fn.is_none() { + ctxt.attr_main_fn = Some((id.def_id, ctxt.tcx.def_span(id.def_id.to_def_id()))); + } else { + struct_span_err!( + ctxt.tcx.sess, + ctxt.tcx.def_span(id.def_id.to_def_id()), + E0137, + "multiple functions with a `#[rustc_main]` attribute" + ) + .span_label( + ctxt.tcx.def_span(id.def_id.to_def_id()), + "additional `#[rustc_main]` function", + ) + .span_label(ctxt.attr_main_fn.unwrap().1, "first `#[rustc_main]` function") + .emit(); + } + } + EntryPointType::Start => { + if ctxt.start_fn.is_none() { + ctxt.start_fn = Some((id.def_id, ctxt.tcx.def_span(id.def_id.to_def_id()))); + } else { + struct_span_err!( + ctxt.tcx.sess, + ctxt.tcx.def_span(id.def_id.to_def_id()), + E0138, + "multiple `start` functions" + ) + .span_label(ctxt.start_fn.unwrap().1, "previous `#[start]` function here") + .span_label(ctxt.tcx.def_span(id.def_id.to_def_id()), "multiple `start` functions") + .emit(); + } + } + } +} + +fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) -> Option<(DefId, EntryFnType)> { + if let Some((def_id, _)) = visitor.start_fn { + Some((def_id.to_def_id(), EntryFnType::Start)) + } else if let Some((def_id, _)) = visitor.attr_main_fn { + Some((def_id.to_def_id(), EntryFnType::Main)) + } else { + if let Some(main_def) = tcx.resolutions(()).main_def && let Some(def_id) = main_def.opt_fn_def_id() { + // non-local main imports are handled below + if let Some(def_id) = def_id.as_local() && matches!(tcx.hir().find_by_def_id(def_id), Some(Node::ForeignItem(_))) { + tcx.sess + .struct_span_err( + tcx.def_span(def_id), + "the `main` function cannot be declared in an `extern` block", + ) + .emit(); + return None; + } + + if main_def.is_import && !tcx.features().imported_main { + let span = main_def.span; + feature_err( + &tcx.sess.parse_sess, + sym::imported_main, + span, + "using an imported function as entry point `main` is experimental", + ) + .emit(); + } + return Some((def_id, EntryFnType::Main)); + } + no_main_err(tcx, visitor); + None + } +} + +fn no_main_err(tcx: TyCtxt<'_>, visitor: &EntryContext<'_>) { + let sp = tcx.def_span(CRATE_DEF_ID); + if *tcx.sess.parse_sess.reached_eof.borrow() { + // There's an unclosed brace that made the parser reach `Eof`, we shouldn't complain about + // the missing `fn main()` then as it might have been hidden inside an unclosed block. + tcx.sess.delay_span_bug(sp, "`main` not found, but expected unclosed brace error"); + return; + } + + // There is no main function. + let mut err = struct_span_err!( + tcx.sess, + DUMMY_SP, + E0601, + "`main` function not found in crate `{}`", + tcx.crate_name(LOCAL_CRATE) + ); + let filename = &tcx.sess.local_crate_source_file; + let note = if !visitor.non_main_fns.is_empty() { + for &span in &visitor.non_main_fns { + err.span_note(span, "here is a function named `main`"); + } + err.note("you have one or more functions named `main` not defined at the crate level"); + err.help("consider moving the `main` function definitions"); + // There were some functions named `main` though. Try to give the user a hint. + format!( + "the main function must be defined at the crate level{}", + filename.as_ref().map(|f| format!(" (in `{}`)", f.display())).unwrap_or_default() + ) + } else if let Some(filename) = filename { + format!("consider adding a `main` function to `{}`", filename.display()) + } else { + String::from("consider adding a `main` function at the crate level") + }; + // The file may be empty, which leads to the diagnostic machinery not emitting this + // note. This is a relatively simple way to detect that case and emit a span-less + // note instead. + if tcx.sess.source_map().lookup_line(sp.hi()).is_ok() { + err.set_span(sp.shrink_to_hi()); + err.span_label(sp.shrink_to_hi(), ¬e); + } else { + err.note(¬e); + } + + if let Some(main_def) = tcx.resolutions(()).main_def && main_def.opt_fn_def_id().is_none(){ + // There is something at `crate::main`, but it is not a function definition. + err.span_label(main_def.span, "non-function item at `crate::main` is found"); + } + + if tcx.sess.teach(&err.get_code().unwrap()) { + err.note( + "If you don't know the basics of Rust, you can go look to the Rust Book \ + to get started: https://doc.rust-lang.org/book/", + ); + } + err.emit(); +} + +pub fn provide(providers: &mut Providers) { + *providers = Providers { entry_fn, ..*providers }; +} diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs new file mode 100644 index 000000000..5feb0e295 --- /dev/null +++ b/compiler/rustc_passes/src/errors.rs @@ -0,0 +1,645 @@ +use rustc_errors::{Applicability, MultiSpan}; +use rustc_macros::{LintDiagnostic, SessionDiagnostic, SessionSubdiagnostic}; +use rustc_span::{Span, Symbol}; + +#[derive(LintDiagnostic)] +#[lint(passes::outer_crate_level_attr)] +pub struct OuterCrateLevelAttr; + +#[derive(LintDiagnostic)] +#[lint(passes::inner_crate_level_attr)] +pub struct InnerCrateLevelAttr; + +#[derive(LintDiagnostic)] +#[lint(passes::ignored_attr_with_macro)] +pub struct IgnoredAttrWithMacro<'a> { + pub sym: &'a str, +} + +#[derive(LintDiagnostic)] +#[lint(passes::ignored_attr)] +pub struct IgnoredAttr<'a> { + pub sym: &'a str, +} + +#[derive(LintDiagnostic)] +#[lint(passes::inline_ignored_function_prototype)] +pub struct IgnoredInlineAttrFnProto; + +#[derive(LintDiagnostic)] +#[lint(passes::inline_ignored_constants)] +#[warn_] +#[note] +pub struct IgnoredInlineAttrConstants; + +#[derive(SessionDiagnostic)] +#[error(passes::inline_not_fn_or_closure, code = "E0518")] +pub struct InlineNotFnOrClosure { + #[primary_span] + pub attr_span: Span, + #[label] + pub defn_span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::no_coverage_ignored_function_prototype)] +pub struct IgnoredNoCoverageFnProto; + +#[derive(LintDiagnostic)] +#[lint(passes::no_coverage_propagate)] +pub struct IgnoredNoCoveragePropagate; + +#[derive(LintDiagnostic)] +#[lint(passes::no_coverage_fn_defn)] +pub struct IgnoredNoCoverageFnDefn; + +#[derive(SessionDiagnostic)] +#[error(passes::no_coverage_not_coverable, code = "E0788")] +pub struct IgnoredNoCoverageNotCoverable { + #[primary_span] + pub attr_span: Span, + #[label] + pub defn_span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::should_be_applied_to_fn)] +pub struct AttrShouldBeAppliedToFn { + #[primary_span] + pub attr_span: Span, + #[label] + pub defn_span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::naked_tracked_caller, code = "E0736")] +pub struct NakedTrackedCaller { + #[primary_span] + pub attr_span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::should_be_applied_to_fn, code = "E0739")] +pub struct TrackedCallerWrongLocation { + #[primary_span] + pub attr_span: Span, + #[label] + pub defn_span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::should_be_applied_to_struct_enum, code = "E0701")] +pub struct NonExhaustiveWrongLocation { + #[primary_span] + pub attr_span: Span, + #[label] + pub defn_span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::should_be_applied_to_trait)] +pub struct AttrShouldBeAppliedToTrait { + #[primary_span] + pub attr_span: Span, + #[label] + pub defn_span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::target_feature_on_statement)] +pub struct TargetFeatureOnStatement; + +#[derive(SessionDiagnostic)] +#[error(passes::should_be_applied_to_static)] +pub struct AttrShouldBeAppliedToStatic { + #[primary_span] + pub attr_span: Span, + #[label] + pub defn_span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_expect_str)] +pub struct DocExpectStr<'a> { + #[primary_span] + pub attr_span: Span, + pub attr_name: &'a str, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_alias_empty)] +pub struct DocAliasEmpty<'a> { + #[primary_span] + pub span: Span, + pub attr_str: &'a str, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_alias_bad_char)] +pub struct DocAliasBadChar<'a> { + #[primary_span] + pub span: Span, + pub attr_str: &'a str, + pub char_: char, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_alias_start_end)] +pub struct DocAliasStartEnd<'a> { + #[primary_span] + pub span: Span, + pub attr_str: &'a str, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_alias_bad_location)] +pub struct DocAliasBadLocation<'a> { + #[primary_span] + pub span: Span, + pub attr_str: &'a str, + pub location: &'a str, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_alias_not_an_alias)] +pub struct DocAliasNotAnAlias<'a> { + #[primary_span] + pub span: Span, + pub attr_str: &'a str, +} + +#[derive(LintDiagnostic)] +#[lint(passes::doc_alias_duplicated)] +pub struct DocAliasDuplicated { + #[label] + pub first_defn: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_alias_not_string_literal)] +pub struct DocAliasNotStringLiteral { + #[primary_span] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_alias_malformed)] +pub struct DocAliasMalformed { + #[primary_span] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_keyword_empty_mod)] +pub struct DocKeywordEmptyMod { + #[primary_span] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_keyword_not_mod)] +pub struct DocKeywordNotMod { + #[primary_span] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_keyword_invalid_ident)] +pub struct DocKeywordInvalidIdent { + #[primary_span] + pub span: Span, + pub doc_keyword: Symbol, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_fake_variadic_not_valid)] +pub struct DocFakeVariadicNotValid { + #[primary_span] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_keyword_only_impl)] +pub struct DocKeywordOnlyImpl { + #[primary_span] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_inline_conflict)] +#[help] +pub struct DocKeywordConflict { + #[primary_span] + pub spans: MultiSpan, +} + +#[derive(LintDiagnostic)] +#[lint(passes::doc_inline_only_use)] +#[note] +pub struct DocInlineOnlyUse { + #[label] + pub attr_span: Span, + #[label(passes::not_a_use_item_label)] + pub item_span: Option<Span>, +} + +#[derive(SessionDiagnostic)] +#[error(passes::doc_attr_not_crate_level)] +pub struct DocAttrNotCrateLevel<'a> { + #[primary_span] + pub span: Span, + pub attr_name: &'a str, +} + +#[derive(LintDiagnostic)] +#[lint(passes::doc_test_unknown)] +pub struct DocTestUnknown { + pub path: String, +} + +#[derive(LintDiagnostic)] +#[lint(passes::doc_test_takes_list)] +pub struct DocTestTakesList; + +#[derive(LintDiagnostic)] +#[lint(passes::doc_primitive)] +pub struct DocPrimitive; + +#[derive(LintDiagnostic)] +#[lint(passes::doc_test_unknown_any)] +pub struct DocTestUnknownAny { + pub path: String, +} + +#[derive(LintDiagnostic)] +#[lint(passes::doc_test_unknown_spotlight)] +#[note] +#[note(passes::no_op_note)] +pub struct DocTestUnknownSpotlight { + pub path: String, + #[suggestion_short(applicability = "machine-applicable", code = "notable_trait")] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::doc_test_unknown_include)] +pub struct DocTestUnknownInclude { + pub path: String, + pub value: String, + pub inner: &'static str, + #[suggestion(code = "#{inner}[doc = include_str!(\"{value}\")]")] + pub sugg: (Span, Applicability), +} + +#[derive(LintDiagnostic)] +#[lint(passes::doc_invalid)] +pub struct DocInvalid; + +#[derive(SessionDiagnostic)] +#[error(passes::pass_by_value)] +pub struct PassByValue { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::allow_incoherent_impl)] +pub struct AllowIncoherentImpl { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::has_incoherent_inherent_impl)] +pub struct HasIncoherentInherentImpl { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::must_use_async)] +pub struct MustUseAsync { + #[label] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::must_use_no_effect)] +pub struct MustUseNoEffect { + pub article: &'static str, + pub target: rustc_hir::Target, +} + +#[derive(SessionDiagnostic)] +#[error(passes::must_not_suspend)] +pub struct MustNotSuspend { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::cold)] +#[warn_] +pub struct Cold { + #[label] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::link)] +#[warn_] +pub struct Link { + #[label] + pub span: Option<Span>, +} + +#[derive(LintDiagnostic)] +#[lint(passes::link_name)] +#[warn_] +pub struct LinkName<'a> { + #[help] + pub attr_span: Option<Span>, + #[label] + pub span: Span, + pub value: &'a str, +} + +#[derive(SessionDiagnostic)] +#[error(passes::no_link)] +pub struct NoLink { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::export_name)] +pub struct ExportName { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_layout_scalar_valid_range_not_struct)] +pub struct RustcLayoutScalarValidRangeNotStruct { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_layout_scalar_valid_range_arg)] +pub struct RustcLayoutScalarValidRangeArg { + #[primary_span] + pub attr_span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_legacy_const_generics_only)] +pub struct RustcLegacyConstGenericsOnly { + #[primary_span] + pub attr_span: Span, + #[label] + pub param_span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_legacy_const_generics_index)] +pub struct RustcLegacyConstGenericsIndex { + #[primary_span] + pub attr_span: Span, + #[label] + pub generics_span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_legacy_const_generics_index_exceed)] +pub struct RustcLegacyConstGenericsIndexExceed { + #[primary_span] + #[label] + pub span: Span, + pub arg_count: usize, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_legacy_const_generics_index_negative)] +pub struct RustcLegacyConstGenericsIndexNegative { + #[primary_span] + pub invalid_args: Vec<Span>, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_dirty_clean)] +pub struct RustcDirtyClean { + #[primary_span] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::link_section)] +#[warn_] +pub struct LinkSection { + #[label] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::no_mangle_foreign)] +#[warn_] +#[note] +pub struct NoMangleForeign { + #[label] + pub span: Span, + #[suggestion(applicability = "machine-applicable")] + pub attr_span: Span, + pub foreign_item_kind: &'static str, +} + +#[derive(LintDiagnostic)] +#[lint(passes::no_mangle)] +#[warn_] +pub struct NoMangle { + #[label] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::repr_ident, code = "E0565")] +pub struct ReprIdent { + #[primary_span] + pub span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::repr_conflicting, code = "E0566")] +pub struct ReprConflicting; + +#[derive(SessionDiagnostic)] +#[error(passes::used_static)] +pub struct UsedStatic { + #[primary_span] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::used_compiler_linker)] +pub struct UsedCompilerLinker { + #[primary_span] + pub spans: Vec<Span>, +} + +#[derive(SessionDiagnostic)] +#[error(passes::allow_internal_unstable)] +pub struct AllowInternalUnstable { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::debug_visualizer_placement)] +pub struct DebugVisualizerPlacement { + #[primary_span] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::debug_visualizer_invalid)] +#[note(passes::note_1)] +#[note(passes::note_2)] +#[note(passes::note_3)] +pub struct DebugVisualizerInvalid { + #[primary_span] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_allow_const_fn_unstable)] +pub struct RustcAllowConstFnUnstable { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_std_internal_symbol)] +pub struct RustcStdInternalSymbol { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::const_trait)] +pub struct ConstTrait { + #[primary_span] + pub attr_span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::stability_promotable)] +pub struct StabilityPromotable { + #[primary_span] + pub attr_span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::deprecated)] +pub struct Deprecated; + +#[derive(LintDiagnostic)] +#[lint(passes::macro_use)] +pub struct MacroUse { + pub name: Symbol, +} + +#[derive(LintDiagnostic)] +#[lint(passes::macro_export)] +pub struct MacroExport; + +#[derive(LintDiagnostic)] +#[lint(passes::plugin_registrar)] +pub struct PluginRegistrar; + +#[derive(SessionSubdiagnostic)] +pub enum UnusedNote { + #[note(passes::unused_empty_lints_note)] + EmptyList { name: Symbol }, + #[note(passes::unused_no_lints_note)] + NoLints { name: Symbol }, + #[note(passes::unused_default_method_body_const_note)] + DefaultMethodBodyConst, +} + +#[derive(LintDiagnostic)] +#[lint(passes::unused)] +pub struct Unused { + #[suggestion(applicability = "machine-applicable")] + pub attr_span: Span, + #[subdiagnostic] + pub note: UnusedNote, +} + +#[derive(SessionDiagnostic)] +#[error(passes::non_exported_macro_invalid_attrs, code = "E0518")] +pub struct NonExportedMacroInvalidAttrs { + #[primary_span] + #[label] + pub attr_span: Span, +} + +#[derive(LintDiagnostic)] +#[lint(passes::unused_duplicate)] +pub struct UnusedDuplicate { + #[primary_span] + #[suggestion(code = "", applicability = "machine-applicable")] + pub this: Span, + #[note] + pub other: Span, + #[warn_] + pub warning: Option<()>, +} + +#[derive(SessionDiagnostic)] +#[error(passes::unused_multiple)] +pub struct UnusedMultiple { + #[primary_span] + #[suggestion(code = "", applicability = "machine-applicable")] + pub this: Span, + #[note] + pub other: Span, + pub name: Symbol, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_lint_opt_ty)] +pub struct RustcLintOptTy { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + +#[derive(SessionDiagnostic)] +#[error(passes::rustc_lint_opt_deny_field_access)] +pub struct RustcLintOptDenyFieldAccess { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} diff --git a/compiler/rustc_passes/src/hir_id_validator.rs b/compiler/rustc_passes/src/hir_id_validator.rs new file mode 100644 index 000000000..212ea9e57 --- /dev/null +++ b/compiler/rustc_passes/src/hir_id_validator.rs @@ -0,0 +1,164 @@ +use rustc_data_structures::sync::Lock; +use rustc_hir as hir; +use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID}; +use rustc_hir::intravisit; +use rustc_hir::{HirId, ItemLocalId}; +use rustc_index::bit_set::GrowableBitSet; +use rustc_middle::hir::map::Map; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::TyCtxt; + +pub fn check_crate(tcx: TyCtxt<'_>) { + tcx.dep_graph.assert_ignored(); + + if tcx.sess.opts.unstable_opts.hir_stats { + crate::hir_stats::print_hir_stats(tcx); + } + + #[cfg(debug_assertions)] + { + let errors = Lock::new(Vec::new()); + let hir_map = tcx.hir(); + + hir_map.par_for_each_module(|module_id| { + let mut v = HirIdValidator { + hir_map, + owner: None, + hir_ids_seen: Default::default(), + errors: &errors, + }; + + tcx.hir().visit_item_likes_in_module(module_id, &mut v); + }); + + let errors = errors.into_inner(); + + if !errors.is_empty() { + let message = errors.iter().fold(String::new(), |s1, s2| s1 + "\n" + s2); + tcx.sess.delay_span_bug(rustc_span::DUMMY_SP, &message); + } + } +} + +struct HirIdValidator<'a, 'hir> { + hir_map: Map<'hir>, + owner: Option<LocalDefId>, + hir_ids_seen: GrowableBitSet<ItemLocalId>, + errors: &'a Lock<Vec<String>>, +} + +impl<'a, 'hir> HirIdValidator<'a, 'hir> { + fn new_visitor(&self, hir_map: Map<'hir>) -> HirIdValidator<'a, 'hir> { + HirIdValidator { + hir_map, + owner: None, + hir_ids_seen: Default::default(), + errors: self.errors, + } + } + + #[cold] + #[inline(never)] + fn error(&self, f: impl FnOnce() -> String) { + self.errors.lock().push(f()); + } + + fn check<F: FnOnce(&mut HirIdValidator<'a, 'hir>)>(&mut self, owner: LocalDefId, walk: F) { + assert!(self.owner.is_none()); + self.owner = Some(owner); + walk(self); + + if owner == CRATE_DEF_ID { + return; + } + + // There's always at least one entry for the owning item itself + let max = self + .hir_ids_seen + .iter() + .map(|local_id| local_id.as_usize()) + .max() + .expect("owning item has no entry"); + + if max != self.hir_ids_seen.len() - 1 { + // Collect the missing ItemLocalIds + let missing: Vec<_> = (0..=max as u32) + .filter(|&i| !self.hir_ids_seen.contains(ItemLocalId::from_u32(i))) + .collect(); + + // Try to map those to something more useful + let mut missing_items = Vec::with_capacity(missing.len()); + + for local_id in missing { + let hir_id = HirId { owner, local_id: ItemLocalId::from_u32(local_id) }; + + trace!("missing hir id {:#?}", hir_id); + + missing_items.push(format!( + "[local_id: {}, owner: {}]", + local_id, + self.hir_map.def_path(owner).to_string_no_crate_verbose() + )); + } + self.error(|| { + format!( + "ItemLocalIds not assigned densely in {}. \ + Max ItemLocalId = {}, missing IDs = {:?}; seens IDs = {:?}", + self.hir_map.def_path(owner).to_string_no_crate_verbose(), + max, + missing_items, + self.hir_ids_seen + .iter() + .map(|local_id| HirId { owner, local_id }) + .map(|h| format!("({:?} {})", h, self.hir_map.node_to_string(h))) + .collect::<Vec<_>>() + ) + }); + } + } +} + +impl<'a, 'hir> intravisit::Visitor<'hir> for HirIdValidator<'a, 'hir> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.hir_map + } + + fn visit_item(&mut self, i: &'hir hir::Item<'hir>) { + let mut inner_visitor = self.new_visitor(self.hir_map); + inner_visitor.check(i.def_id, |this| intravisit::walk_item(this, i)); + } + + fn visit_id(&mut self, hir_id: HirId) { + let owner = self.owner.expect("no owner"); + + if owner != hir_id.owner { + self.error(|| { + format!( + "HirIdValidator: The recorded owner of {} is {} instead of {}", + self.hir_map.node_to_string(hir_id), + self.hir_map.def_path(hir_id.owner).to_string_no_crate_verbose(), + self.hir_map.def_path(owner).to_string_no_crate_verbose() + ) + }); + } + + self.hir_ids_seen.insert(hir_id.local_id); + } + + fn visit_foreign_item(&mut self, i: &'hir hir::ForeignItem<'hir>) { + let mut inner_visitor = self.new_visitor(self.hir_map); + inner_visitor.check(i.def_id, |this| intravisit::walk_foreign_item(this, i)); + } + + fn visit_trait_item(&mut self, i: &'hir hir::TraitItem<'hir>) { + let mut inner_visitor = self.new_visitor(self.hir_map); + inner_visitor.check(i.def_id, |this| intravisit::walk_trait_item(this, i)); + } + + fn visit_impl_item(&mut self, i: &'hir hir::ImplItem<'hir>) { + let mut inner_visitor = self.new_visitor(self.hir_map); + inner_visitor.check(i.def_id, |this| intravisit::walk_impl_item(this, i)); + } +} diff --git a/compiler/rustc_passes/src/hir_stats.rs b/compiler/rustc_passes/src/hir_stats.rs new file mode 100644 index 000000000..a3be827a7 --- /dev/null +++ b/compiler/rustc_passes/src/hir_stats.rs @@ -0,0 +1,344 @@ +// The visitors in this module collect sizes and counts of the most important +// pieces of AST and HIR. The resulting numbers are good approximations but not +// completely accurate (some things might be counted twice, others missed). + +use rustc_ast::visit as ast_visit; +use rustc_ast::visit::BoundKind; +use rustc_ast::{self as ast, AttrId, NodeId}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir as hir; +use rustc_hir::intravisit as hir_visit; +use rustc_hir::HirId; +use rustc_middle::hir::map::Map; +use rustc_middle::ty::TyCtxt; +use rustc_middle::util::common::to_readable_str; +use rustc_span::Span; + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +enum Id { + Node(HirId), + Attr(AttrId), + None, +} + +struct NodeData { + count: usize, + size: usize, +} + +struct StatCollector<'k> { + krate: Option<Map<'k>>, + data: FxHashMap<&'static str, NodeData>, + seen: FxHashSet<Id>, +} + +pub fn print_hir_stats(tcx: TyCtxt<'_>) { + let mut collector = StatCollector { + krate: Some(tcx.hir()), + data: FxHashMap::default(), + seen: FxHashSet::default(), + }; + tcx.hir().walk_toplevel_module(&mut collector); + tcx.hir().walk_attributes(&mut collector); + collector.print("HIR STATS"); +} + +pub fn print_ast_stats(krate: &ast::Crate, title: &str) { + let mut collector = + StatCollector { krate: None, data: FxHashMap::default(), seen: FxHashSet::default() }; + ast_visit::walk_crate(&mut collector, krate); + collector.print(title); +} + +impl<'k> StatCollector<'k> { + fn record<T>(&mut self, label: &'static str, id: Id, node: &T) { + if id != Id::None && !self.seen.insert(id) { + return; + } + + let entry = self.data.entry(label).or_insert(NodeData { count: 0, size: 0 }); + + entry.count += 1; + entry.size = std::mem::size_of_val(node); + } + + fn print(&self, title: &str) { + let mut stats: Vec<_> = self.data.iter().collect(); + + stats.sort_by_key(|&(_, ref d)| d.count * d.size); + + let mut total_size = 0; + + eprintln!("\n{}\n", title); + + eprintln!("{:<18}{:>18}{:>14}{:>14}", "Name", "Accumulated Size", "Count", "Item Size"); + eprintln!("----------------------------------------------------------------"); + + for (label, data) in stats { + eprintln!( + "{:<18}{:>18}{:>14}{:>14}", + label, + to_readable_str(data.count * data.size), + to_readable_str(data.count), + to_readable_str(data.size) + ); + + total_size += data.count * data.size; + } + eprintln!("----------------------------------------------------------------"); + eprintln!("{:<18}{:>18}\n", "Total", to_readable_str(total_size)); + } +} + +impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { + fn visit_param(&mut self, param: &'v hir::Param<'v>) { + self.record("Param", Id::Node(param.hir_id), param); + hir_visit::walk_param(self, param) + } + + fn visit_nested_item(&mut self, id: hir::ItemId) { + let nested_item = self.krate.unwrap().item(id); + self.visit_item(nested_item) + } + + fn visit_nested_trait_item(&mut self, trait_item_id: hir::TraitItemId) { + let nested_trait_item = self.krate.unwrap().trait_item(trait_item_id); + self.visit_trait_item(nested_trait_item) + } + + fn visit_nested_impl_item(&mut self, impl_item_id: hir::ImplItemId) { + let nested_impl_item = self.krate.unwrap().impl_item(impl_item_id); + self.visit_impl_item(nested_impl_item) + } + + fn visit_nested_foreign_item(&mut self, id: hir::ForeignItemId) { + let nested_foreign_item = self.krate.unwrap().foreign_item(id); + self.visit_foreign_item(nested_foreign_item); + } + + fn visit_nested_body(&mut self, body_id: hir::BodyId) { + let nested_body = self.krate.unwrap().body(body_id); + self.visit_body(nested_body) + } + + fn visit_item(&mut self, i: &'v hir::Item<'v>) { + self.record("Item", Id::Node(i.hir_id()), i); + hir_visit::walk_item(self, i) + } + + fn visit_foreign_item(&mut self, i: &'v hir::ForeignItem<'v>) { + self.record("ForeignItem", Id::Node(i.hir_id()), i); + hir_visit::walk_foreign_item(self, i) + } + + fn visit_local(&mut self, l: &'v hir::Local<'v>) { + self.record("Local", Id::Node(l.hir_id), l); + hir_visit::walk_local(self, l) + } + + fn visit_block(&mut self, b: &'v hir::Block<'v>) { + self.record("Block", Id::Node(b.hir_id), b); + hir_visit::walk_block(self, b) + } + + fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) { + self.record("Stmt", Id::Node(s.hir_id), s); + hir_visit::walk_stmt(self, s) + } + + fn visit_arm(&mut self, a: &'v hir::Arm<'v>) { + self.record("Arm", Id::Node(a.hir_id), a); + hir_visit::walk_arm(self, a) + } + + fn visit_pat(&mut self, p: &'v hir::Pat<'v>) { + self.record("Pat", Id::Node(p.hir_id), p); + hir_visit::walk_pat(self, p) + } + + fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) { + self.record("Expr", Id::Node(ex.hir_id), ex); + hir_visit::walk_expr(self, ex) + } + + fn visit_ty(&mut self, t: &'v hir::Ty<'v>) { + self.record("Ty", Id::Node(t.hir_id), t); + hir_visit::walk_ty(self, t) + } + + fn visit_fn( + &mut self, + fk: hir_visit::FnKind<'v>, + fd: &'v hir::FnDecl<'v>, + b: hir::BodyId, + s: Span, + id: hir::HirId, + ) { + self.record("FnDecl", Id::None, fd); + hir_visit::walk_fn(self, fk, fd, b, s, id) + } + + fn visit_where_predicate(&mut self, predicate: &'v hir::WherePredicate<'v>) { + self.record("WherePredicate", Id::None, predicate); + hir_visit::walk_where_predicate(self, predicate) + } + + fn visit_trait_item(&mut self, ti: &'v hir::TraitItem<'v>) { + self.record("TraitItem", Id::Node(ti.hir_id()), ti); + hir_visit::walk_trait_item(self, ti) + } + + fn visit_impl_item(&mut self, ii: &'v hir::ImplItem<'v>) { + self.record("ImplItem", Id::Node(ii.hir_id()), ii); + hir_visit::walk_impl_item(self, ii) + } + + fn visit_param_bound(&mut self, bounds: &'v hir::GenericBound<'v>) { + self.record("GenericBound", Id::None, bounds); + hir_visit::walk_param_bound(self, bounds) + } + + fn visit_field_def(&mut self, s: &'v hir::FieldDef<'v>) { + self.record("FieldDef", Id::Node(s.hir_id), s); + hir_visit::walk_field_def(self, s) + } + + fn visit_variant( + &mut self, + v: &'v hir::Variant<'v>, + g: &'v hir::Generics<'v>, + item_id: hir::HirId, + ) { + self.record("Variant", Id::None, v); + hir_visit::walk_variant(self, v, g, item_id) + } + + fn visit_lifetime(&mut self, lifetime: &'v hir::Lifetime) { + self.record("Lifetime", Id::Node(lifetime.hir_id), lifetime); + hir_visit::walk_lifetime(self, lifetime) + } + + fn visit_qpath(&mut self, qpath: &'v hir::QPath<'v>, id: hir::HirId, span: Span) { + self.record("QPath", Id::None, qpath); + hir_visit::walk_qpath(self, qpath, id, span) + } + + fn visit_path(&mut self, path: &'v hir::Path<'v>, _id: hir::HirId) { + self.record("Path", Id::None, path); + hir_visit::walk_path(self, path) + } + + fn visit_path_segment(&mut self, path_span: Span, path_segment: &'v hir::PathSegment<'v>) { + self.record("PathSegment", Id::None, path_segment); + hir_visit::walk_path_segment(self, path_span, path_segment) + } + + fn visit_assoc_type_binding(&mut self, type_binding: &'v hir::TypeBinding<'v>) { + self.record("TypeBinding", Id::Node(type_binding.hir_id), type_binding); + hir_visit::walk_assoc_type_binding(self, type_binding) + } + + fn visit_attribute(&mut self, attr: &'v ast::Attribute) { + self.record("Attribute", Id::Attr(attr.id), attr); + } +} + +impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { + fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) { + self.record("ForeignItem", Id::None, i); + ast_visit::walk_foreign_item(self, i) + } + + fn visit_item(&mut self, i: &'v ast::Item) { + self.record("Item", Id::None, i); + ast_visit::walk_item(self, i) + } + + fn visit_local(&mut self, l: &'v ast::Local) { + self.record("Local", Id::None, l); + ast_visit::walk_local(self, l) + } + + fn visit_block(&mut self, b: &'v ast::Block) { + self.record("Block", Id::None, b); + ast_visit::walk_block(self, b) + } + + fn visit_stmt(&mut self, s: &'v ast::Stmt) { + self.record("Stmt", Id::None, s); + ast_visit::walk_stmt(self, s) + } + + fn visit_arm(&mut self, a: &'v ast::Arm) { + self.record("Arm", Id::None, a); + ast_visit::walk_arm(self, a) + } + + fn visit_pat(&mut self, p: &'v ast::Pat) { + self.record("Pat", Id::None, p); + ast_visit::walk_pat(self, p) + } + + fn visit_expr(&mut self, ex: &'v ast::Expr) { + self.record("Expr", Id::None, ex); + ast_visit::walk_expr(self, ex) + } + + fn visit_ty(&mut self, t: &'v ast::Ty) { + self.record("Ty", Id::None, t); + ast_visit::walk_ty(self, t) + } + + fn visit_fn(&mut self, fk: ast_visit::FnKind<'v>, s: Span, _: NodeId) { + self.record("FnDecl", Id::None, fk.decl()); + ast_visit::walk_fn(self, fk, s) + } + + fn visit_assoc_item(&mut self, item: &'v ast::AssocItem, ctxt: ast_visit::AssocCtxt) { + let label = match ctxt { + ast_visit::AssocCtxt::Trait => "TraitItem", + ast_visit::AssocCtxt::Impl => "ImplItem", + }; + self.record(label, Id::None, item); + ast_visit::walk_assoc_item(self, item, ctxt); + } + + fn visit_param_bound(&mut self, bounds: &'v ast::GenericBound, _ctxt: BoundKind) { + self.record("GenericBound", Id::None, bounds); + ast_visit::walk_param_bound(self, bounds) + } + + fn visit_field_def(&mut self, s: &'v ast::FieldDef) { + self.record("FieldDef", Id::None, s); + ast_visit::walk_field_def(self, s) + } + + fn visit_variant(&mut self, v: &'v ast::Variant) { + self.record("Variant", Id::None, v); + ast_visit::walk_variant(self, v) + } + + fn visit_lifetime(&mut self, lifetime: &'v ast::Lifetime, _: ast_visit::LifetimeCtxt) { + self.record("Lifetime", Id::None, lifetime); + ast_visit::walk_lifetime(self, lifetime) + } + + fn visit_mac_call(&mut self, mac: &'v ast::MacCall) { + self.record("MacCall", Id::None, mac); + ast_visit::walk_mac(self, mac) + } + + fn visit_path_segment(&mut self, path_span: Span, path_segment: &'v ast::PathSegment) { + self.record("PathSegment", Id::None, path_segment); + ast_visit::walk_path_segment(self, path_span, path_segment) + } + + fn visit_assoc_constraint(&mut self, constraint: &'v ast::AssocConstraint) { + self.record("AssocConstraint", Id::None, constraint); + ast_visit::walk_assoc_constraint(self, constraint) + } + + fn visit_attribute(&mut self, attr: &'v ast::Attribute) { + self.record("Attribute", Id::None, attr); + } +} diff --git a/compiler/rustc_passes/src/lang_items.rs b/compiler/rustc_passes/src/lang_items.rs new file mode 100644 index 000000000..79900a90a --- /dev/null +++ b/compiler/rustc_passes/src/lang_items.rs @@ -0,0 +1,278 @@ +//! Detecting language items. +//! +//! Language items are items that represent concepts intrinsic to the language +//! itself. Examples are: +//! +//! * Traits that specify "kinds"; e.g., `Sync`, `Send`. +//! * Traits that represent operators; e.g., `Add`, `Sub`, `Index`. +//! * Functions called by the compiler itself. + +use crate::check_attr::target_from_impl_item; +use crate::weak_lang_items; + +use rustc_errors::{pluralize, struct_span_err}; +use rustc_hir as hir; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::DefId; +use rustc_hir::lang_items::{extract, GenericRequirement, ITEM_REFS}; +use rustc_hir::{HirId, LangItem, LanguageItems, Target}; +use rustc_middle::ty::TyCtxt; +use rustc_session::cstore::ExternCrate; +use rustc_span::Span; + +use rustc_middle::ty::query::Providers; + +struct LanguageItemCollector<'tcx> { + items: LanguageItems, + tcx: TyCtxt<'tcx>, +} + +impl<'tcx> LanguageItemCollector<'tcx> { + fn new(tcx: TyCtxt<'tcx>) -> LanguageItemCollector<'tcx> { + LanguageItemCollector { tcx, items: LanguageItems::new() } + } + + fn check_for_lang(&mut self, actual_target: Target, hir_id: HirId) { + let attrs = self.tcx.hir().attrs(hir_id); + if let Some((value, span)) = extract(&attrs) { + match ITEM_REFS.get(&value).cloned() { + // Known lang item with attribute on correct target. + Some((item_index, expected_target)) if actual_target == expected_target => { + self.collect_item_extended(item_index, hir_id, span); + } + // Known lang item with attribute on incorrect target. + Some((_, expected_target)) => { + struct_span_err!( + self.tcx.sess, + span, + E0718, + "`{}` language item must be applied to a {}", + value, + expected_target, + ) + .span_label( + span, + format!( + "attribute should be applied to a {}, not a {}", + expected_target, actual_target, + ), + ) + .emit(); + } + // Unknown lang item. + _ => { + struct_span_err!( + self.tcx.sess, + span, + E0522, + "definition of an unknown language item: `{}`", + value + ) + .span_label(span, format!("definition of unknown language item `{}`", value)) + .emit(); + } + } + } + } + + fn collect_item(&mut self, item_index: usize, item_def_id: DefId) { + // Check for duplicates. + if let Some(original_def_id) = self.items.items[item_index] { + if original_def_id != item_def_id { + let lang_item = LangItem::from_u32(item_index as u32).unwrap(); + let name = lang_item.name(); + let mut err = match self.tcx.hir().span_if_local(item_def_id) { + Some(span) => struct_span_err!( + self.tcx.sess, + span, + E0152, + "found duplicate lang item `{}`", + name + ), + None => match self.tcx.extern_crate(item_def_id) { + Some(ExternCrate { dependency_of, .. }) => { + self.tcx.sess.struct_err(&format!( + "duplicate lang item in crate `{}` (which `{}` depends on): `{}`.", + self.tcx.crate_name(item_def_id.krate), + self.tcx.crate_name(*dependency_of), + name + )) + } + _ => self.tcx.sess.struct_err(&format!( + "duplicate lang item in crate `{}`: `{}`.", + self.tcx.crate_name(item_def_id.krate), + name + )), + }, + }; + if let Some(span) = self.tcx.hir().span_if_local(original_def_id) { + err.span_note(span, "the lang item is first defined here"); + } else { + match self.tcx.extern_crate(original_def_id) { + Some(ExternCrate { dependency_of, .. }) => { + err.note(&format!( + "the lang item is first defined in crate `{}` (which `{}` depends on)", + self.tcx.crate_name(original_def_id.krate), + self.tcx.crate_name(*dependency_of) + )); + } + _ => { + err.note(&format!( + "the lang item is first defined in crate `{}`.", + self.tcx.crate_name(original_def_id.krate) + )); + } + } + let mut note_def = |which, def_id: DefId| { + let crate_name = self.tcx.crate_name(def_id.krate); + let note = if def_id.is_local() { + format!("{} definition in the local crate (`{}`)", which, crate_name) + } else { + let paths: Vec<_> = self + .tcx + .crate_extern_paths(def_id.krate) + .iter() + .map(|p| p.display().to_string()) + .collect(); + format!( + "{} definition in `{}` loaded from {}", + which, + crate_name, + paths.join(", ") + ) + }; + err.note(¬e); + }; + note_def("first", original_def_id); + note_def("second", item_def_id); + } + err.emit(); + } + } + + // Matched. + self.items.items[item_index] = Some(item_def_id); + if let Some(group) = LangItem::from_u32(item_index as u32).unwrap().group() { + self.items.groups[group as usize].push(item_def_id); + } + } + + // Like collect_item() above, but also checks whether the lang item is declared + // with the right number of generic arguments. + fn collect_item_extended(&mut self, item_index: usize, hir_id: HirId, span: Span) { + let item_def_id = self.tcx.hir().local_def_id(hir_id).to_def_id(); + let lang_item = LangItem::from_u32(item_index as u32).unwrap(); + let name = lang_item.name(); + + // Now check whether the lang_item has the expected number of generic + // arguments. Generally speaking, binary and indexing operations have + // one (for the RHS/index), unary operations have none, the closure + // traits have one for the argument list, generators have one for the + // resume argument, and ordering/equality relations have one for the RHS + // Some other types like Box and various functions like drop_in_place + // have minimum requirements. + + if let hir::Node::Item(hir::Item { kind, span: item_span, .. }) = self.tcx.hir().get(hir_id) + { + let (actual_num, generics_span) = match kind.generics() { + Some(generics) => (generics.params.len(), generics.span), + None => (0, *item_span), + }; + + let required = match lang_item.required_generics() { + GenericRequirement::Exact(num) if num != actual_num => { + Some((format!("{}", num), pluralize!(num))) + } + GenericRequirement::Minimum(num) if actual_num < num => { + Some((format!("at least {}", num), pluralize!(num))) + } + // If the number matches, or there is no requirement, handle it normally + _ => None, + }; + + if let Some((range_str, pluralized)) = required { + // We are issuing E0718 "incorrect target" here, because while the + // item kind of the target is correct, the target is still wrong + // because of the wrong number of generic arguments. + struct_span_err!( + self.tcx.sess, + span, + E0718, + "`{}` language item must be applied to a {} with {} generic argument{}", + name, + kind.descr(), + range_str, + pluralized, + ) + .span_label( + generics_span, + format!( + "this {} has {} generic argument{}", + kind.descr(), + actual_num, + pluralize!(actual_num), + ), + ) + .emit(); + + // return early to not collect the lang item + return; + } + } + + self.collect_item(item_index, item_def_id); + } +} + +/// Traverses and collects all the lang items in all crates. +fn get_lang_items(tcx: TyCtxt<'_>, (): ()) -> LanguageItems { + // Initialize the collector. + let mut collector = LanguageItemCollector::new(tcx); + + // Collect lang items in other crates. + for &cnum in tcx.crates(()).iter() { + for &(def_id, item_index) in tcx.defined_lang_items(cnum).iter() { + collector.collect_item(item_index, def_id); + } + } + + // Collect lang items in this crate. + let crate_items = tcx.hir_crate_items(()); + + for id in crate_items.items() { + collector.check_for_lang(Target::from_def_kind(tcx.def_kind(id.def_id)), id.hir_id()); + + if matches!(tcx.def_kind(id.def_id), DefKind::Enum) { + let item = tcx.hir().item(id); + if let hir::ItemKind::Enum(def, ..) = &item.kind { + for variant in def.variants { + collector.check_for_lang(Target::Variant, variant.id); + } + } + } + } + + // FIXME: avoid calling trait_item() when possible + for id in crate_items.trait_items() { + let item = tcx.hir().trait_item(id); + collector.check_for_lang(Target::from_trait_item(item), item.hir_id()) + } + + // FIXME: avoid calling impl_item() when possible + for id in crate_items.impl_items() { + let item = tcx.hir().impl_item(id); + collector.check_for_lang(target_from_impl_item(tcx, item), item.hir_id()) + } + + // Extract out the found lang items. + let LanguageItemCollector { mut items, .. } = collector; + + // Find all required but not-yet-defined lang items. + weak_lang_items::check_crate(tcx, &mut items); + + items +} + +pub fn provide(providers: &mut Providers) { + providers.get_lang_items = get_lang_items; +} diff --git a/compiler/rustc_passes/src/layout_test.rs b/compiler/rustc_passes/src/layout_test.rs new file mode 100644 index 000000000..fd03f6571 --- /dev/null +++ b/compiler/rustc_passes/src/layout_test.rs @@ -0,0 +1,132 @@ +use rustc_ast::Attribute; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::LocalDefId; +use rustc_middle::ty::layout::{HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers, TyAndLayout}; +use rustc_middle::ty::{ParamEnv, Ty, TyCtxt}; +use rustc_span::symbol::sym; +use rustc_span::Span; +use rustc_target::abi::{HasDataLayout, TargetDataLayout}; + +pub fn test_layout(tcx: TyCtxt<'_>) { + if tcx.features().rustc_attrs { + // if the `rustc_attrs` feature is not enabled, don't bother testing layout + for id in tcx.hir().items() { + if matches!( + tcx.def_kind(id.def_id), + DefKind::TyAlias | DefKind::Enum | DefKind::Struct | DefKind::Union + ) { + for attr in tcx.get_attrs(id.def_id.to_def_id(), sym::rustc_layout) { + dump_layout_of(tcx, id.def_id, attr); + } + } + } + } +} + +fn dump_layout_of<'tcx>(tcx: TyCtxt<'tcx>, item_def_id: LocalDefId, attr: &Attribute) { + let tcx = tcx; + let param_env = tcx.param_env(item_def_id); + let ty = tcx.type_of(item_def_id); + match tcx.layout_of(param_env.and(ty)) { + Ok(ty_layout) => { + // Check out the `#[rustc_layout(..)]` attribute to tell what to dump. + // The `..` are the names of fields to dump. + let meta_items = attr.meta_item_list().unwrap_or_default(); + for meta_item in meta_items { + match meta_item.name_or_empty() { + sym::abi => { + tcx.sess.span_err( + tcx.def_span(item_def_id.to_def_id()), + &format!("abi: {:?}", ty_layout.abi), + ); + } + + sym::align => { + tcx.sess.span_err( + tcx.def_span(item_def_id.to_def_id()), + &format!("align: {:?}", ty_layout.align), + ); + } + + sym::size => { + tcx.sess.span_err( + tcx.def_span(item_def_id.to_def_id()), + &format!("size: {:?}", ty_layout.size), + ); + } + + sym::homogeneous_aggregate => { + tcx.sess.span_err( + tcx.def_span(item_def_id.to_def_id()), + &format!( + "homogeneous_aggregate: {:?}", + ty_layout.homogeneous_aggregate(&UnwrapLayoutCx { tcx, param_env }), + ), + ); + } + + sym::debug => { + let normalized_ty = tcx.normalize_erasing_regions( + param_env.with_reveal_all_normalized(tcx), + ty, + ); + tcx.sess.span_err( + tcx.def_span(item_def_id.to_def_id()), + &format!("layout_of({:?}) = {:#?}", normalized_ty, *ty_layout), + ); + } + + name => { + tcx.sess.span_err( + meta_item.span(), + &format!("unrecognized field name `{}`", name), + ); + } + } + } + } + + Err(layout_error) => { + tcx.sess.span_err( + tcx.def_span(item_def_id.to_def_id()), + &format!("layout error: {:?}", layout_error), + ); + } + } +} + +struct UnwrapLayoutCx<'tcx> { + tcx: TyCtxt<'tcx>, + param_env: ParamEnv<'tcx>, +} + +impl<'tcx> LayoutOfHelpers<'tcx> for UnwrapLayoutCx<'tcx> { + type LayoutOfResult = TyAndLayout<'tcx>; + + fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! { + span_bug!( + span, + "`#[rustc_layout(..)]` test resulted in `layout_of({}) = Err({})`", + ty, + err + ); + } +} + +impl<'tcx> HasTyCtxt<'tcx> for UnwrapLayoutCx<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } +} + +impl<'tcx> HasParamEnv<'tcx> for UnwrapLayoutCx<'tcx> { + fn param_env(&self) -> ParamEnv<'tcx> { + self.param_env + } +} + +impl<'tcx> HasDataLayout for UnwrapLayoutCx<'tcx> { + fn data_layout(&self) -> &TargetDataLayout { + self.tcx.data_layout() + } +} diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs new file mode 100644 index 000000000..7b2f83958 --- /dev/null +++ b/compiler/rustc_passes/src/lib.rs @@ -0,0 +1,59 @@ +//! Various checks +//! +//! # Note +//! +//! This API is completely unstable and subject to change. + +#![allow(rustc::potential_query_instability)] +#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] +#![feature(iter_intersperse)] +#![feature(let_chains)] +#![feature(let_else)] +#![feature(map_try_insert)] +#![feature(min_specialization)] +#![feature(try_blocks)] +#![recursion_limit = "256"] + +#[macro_use] +extern crate rustc_middle; +#[macro_use] +extern crate tracing; + +use rustc_middle::ty::query::Providers; + +mod check_attr; +mod check_const; +pub mod dead; +mod debugger_visualizer; +mod diagnostic_items; +pub mod entry; +mod errors; +pub mod hir_id_validator; +pub mod hir_stats; +mod lang_items; +pub mod layout_test; +mod lib_features; +mod liveness; +pub mod loops; +mod naked_functions; +mod reachable; +pub mod stability; +mod upvars; +mod weak_lang_items; + +pub fn provide(providers: &mut Providers) { + check_attr::provide(providers); + check_const::provide(providers); + dead::provide(providers); + debugger_visualizer::provide(providers); + diagnostic_items::provide(providers); + entry::provide(providers); + lang_items::provide(providers); + lib_features::provide(providers); + loops::provide(providers); + naked_functions::provide(providers); + liveness::provide(providers); + reachable::provide(providers); + stability::provide(providers); + upvars::provide(providers); +} diff --git a/compiler/rustc_passes/src/lib_features.rs b/compiler/rustc_passes/src/lib_features.rs new file mode 100644 index 000000000..e05994f13 --- /dev/null +++ b/compiler/rustc_passes/src/lib_features.rs @@ -0,0 +1,138 @@ +//! Detecting lib features (i.e., features that are not lang features). +//! +//! These are declared using stability attributes (e.g., `#[stable (..)]` and `#[unstable (..)]`), +//! but are not declared in one single location (unlike lang features), which means we need to +//! collect them instead. + +use rustc_ast::{Attribute, MetaItemKind}; +use rustc_errors::struct_span_err; +use rustc_hir::intravisit::Visitor; +use rustc_middle::hir::nested_filter; +use rustc_middle::middle::lib_features::LibFeatures; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_span::symbol::Symbol; +use rustc_span::{sym, Span}; + +fn new_lib_features() -> LibFeatures { + LibFeatures { stable: Default::default(), unstable: Default::default() } +} + +pub struct LibFeatureCollector<'tcx> { + tcx: TyCtxt<'tcx>, + lib_features: LibFeatures, +} + +impl<'tcx> LibFeatureCollector<'tcx> { + fn new(tcx: TyCtxt<'tcx>) -> LibFeatureCollector<'tcx> { + LibFeatureCollector { tcx, lib_features: new_lib_features() } + } + + fn extract(&self, attr: &Attribute) -> Option<(Symbol, Option<Symbol>, Span)> { + let stab_attrs = + [sym::stable, sym::unstable, sym::rustc_const_stable, sym::rustc_const_unstable]; + + // Find a stability attribute: one of #[stable(…)], #[unstable(…)], + // #[rustc_const_stable(…)], or #[rustc_const_unstable(…)]. + if let Some(stab_attr) = stab_attrs.iter().find(|stab_attr| attr.has_name(**stab_attr)) { + let meta_kind = attr.meta_kind(); + if let Some(MetaItemKind::List(ref metas)) = meta_kind { + let mut feature = None; + let mut since = None; + for meta in metas { + if let Some(mi) = meta.meta_item() { + // Find the `feature = ".."` meta-item. + match (mi.name_or_empty(), mi.value_str()) { + (sym::feature, val) => feature = val, + (sym::since, val) => since = val, + _ => {} + } + } + } + if let Some(feature) = feature { + // This additional check for stability is to make sure we + // don't emit additional, irrelevant errors for malformed + // attributes. + let is_unstable = + matches!(*stab_attr, sym::unstable | sym::rustc_const_unstable); + if since.is_some() || is_unstable { + return Some((feature, since, attr.span)); + } + } + // We need to iterate over the other attributes, because + // `rustc_const_unstable` is not mutually exclusive with + // the other stability attributes, so we can't just `break` + // here. + } + } + + None + } + + fn collect_feature(&mut self, feature: Symbol, since: Option<Symbol>, span: Span) { + let already_in_stable = self.lib_features.stable.contains_key(&feature); + let already_in_unstable = self.lib_features.unstable.contains_key(&feature); + + match (since, already_in_stable, already_in_unstable) { + (Some(since), _, false) => { + if let Some((prev_since, _)) = self.lib_features.stable.get(&feature) { + if *prev_since != since { + self.span_feature_error( + span, + &format!( + "feature `{}` is declared stable since {}, \ + but was previously declared stable since {}", + feature, since, prev_since, + ), + ); + return; + } + } + + self.lib_features.stable.insert(feature, (since, span)); + } + (None, false, _) => { + self.lib_features.unstable.insert(feature, span); + } + (Some(_), _, true) | (None, true, _) => { + self.span_feature_error( + span, + &format!( + "feature `{}` is declared {}, but was previously declared {}", + feature, + if since.is_some() { "stable" } else { "unstable" }, + if since.is_none() { "stable" } else { "unstable" }, + ), + ); + } + } + } + + fn span_feature_error(&self, span: Span, msg: &str) { + struct_span_err!(self.tcx.sess, span, E0711, "{}", &msg).emit(); + } +} + +impl<'tcx> Visitor<'tcx> for LibFeatureCollector<'tcx> { + type NestedFilter = nested_filter::All; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + + fn visit_attribute(&mut self, attr: &'tcx Attribute) { + if let Some((feature, stable, span)) = self.extract(attr) { + self.collect_feature(feature, stable, span); + } + } +} + +fn lib_features(tcx: TyCtxt<'_>, (): ()) -> LibFeatures { + let mut collector = LibFeatureCollector::new(tcx); + tcx.hir().walk_attributes(&mut collector); + collector.lib_features +} + +pub fn provide(providers: &mut Providers) { + providers.lib_features = lib_features; +} diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs new file mode 100644 index 000000000..461dd52b9 --- /dev/null +++ b/compiler/rustc_passes/src/liveness.rs @@ -0,0 +1,1687 @@ +//! A classic liveness analysis based on dataflow over the AST. Computes, +//! for each local variable in a function, whether that variable is live +//! at a given point. Program execution points are identified by their +//! IDs. +//! +//! # Basic idea +//! +//! The basic model is that each local variable is assigned an index. We +//! represent sets of local variables using a vector indexed by this +//! index. The value in the vector is either 0, indicating the variable +//! is dead, or the ID of an expression that uses the variable. +//! +//! We conceptually walk over the AST in reverse execution order. If we +//! find a use of a variable, we add it to the set of live variables. If +//! we find an assignment to a variable, we remove it from the set of live +//! variables. When we have to merge two flows, we take the union of +//! those two flows -- if the variable is live on both paths, we simply +//! pick one ID. In the event of loops, we continue doing this until a +//! fixed point is reached. +//! +//! ## Checking initialization +//! +//! At the function entry point, all variables must be dead. If this is +//! not the case, we can report an error using the ID found in the set of +//! live variables, which identifies a use of the variable which is not +//! dominated by an assignment. +//! +//! ## Checking moves +//! +//! After each explicit move, the variable must be dead. +//! +//! ## Computing last uses +//! +//! Any use of the variable where the variable is dead afterwards is a +//! last use. +//! +//! # Implementation details +//! +//! The actual implementation contains two (nested) walks over the AST. +//! The outer walk has the job of building up the ir_maps instance for the +//! enclosing function. On the way down the tree, it identifies those AST +//! nodes and variable IDs that will be needed for the liveness analysis +//! and assigns them contiguous IDs. The liveness ID for an AST node is +//! called a `live_node` (it's a newtype'd `u32`) and the ID for a variable +//! is called a `variable` (another newtype'd `u32`). +//! +//! On the way back up the tree, as we are about to exit from a function +//! declaration we allocate a `liveness` instance. Now that we know +//! precisely how many nodes and variables we need, we can allocate all +//! the various arrays that we will need to precisely the right size. We then +//! perform the actual propagation on the `liveness` instance. +//! +//! This propagation is encoded in the various `propagate_through_*()` +//! methods. It effectively does a reverse walk of the AST; whenever we +//! reach a loop node, we iterate until a fixed point is reached. +//! +//! ## The `RWU` struct +//! +//! At each live node `N`, we track three pieces of information for each +//! variable `V` (these are encapsulated in the `RWU` struct): +//! +//! - `reader`: the `LiveNode` ID of some node which will read the value +//! that `V` holds on entry to `N`. Formally: a node `M` such +//! that there exists a path `P` from `N` to `M` where `P` does not +//! write `V`. If the `reader` is `None`, then the current +//! value will never be read (the variable is dead, essentially). +//! +//! - `writer`: the `LiveNode` ID of some node which will write the +//! variable `V` and which is reachable from `N`. Formally: a node `M` +//! such that there exists a path `P` from `N` to `M` and `M` writes +//! `V`. If the `writer` is `None`, then there is no writer +//! of `V` that follows `N`. +//! +//! - `used`: a boolean value indicating whether `V` is *used*. We +//! distinguish a *read* from a *use* in that a *use* is some read that +//! is not just used to generate a new value. For example, `x += 1` is +//! a read but not a use. This is used to generate better warnings. +//! +//! ## Special nodes and variables +//! +//! We generate various special nodes for various, well, special purposes. +//! These are described in the `Liveness` struct. + +use self::LiveNodeKind::*; +use self::VarKind::*; + +use rustc_ast::InlineAsmOptions; +use rustc_data_structures::fx::FxIndexMap; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::def::*; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{Expr, HirId, HirIdMap, HirIdSet}; +use rustc_index::vec::IndexVec; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::{self, DefIdTree, RootVariableMinCaptureList, Ty, TyCtxt}; +use rustc_session::lint; +use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::Span; + +use std::collections::VecDeque; +use std::io; +use std::io::prelude::*; +use std::rc::Rc; + +mod rwu_table; + +rustc_index::newtype_index! { + pub struct Variable { + DEBUG_FORMAT = "v({})", + } +} + +rustc_index::newtype_index! { + pub struct LiveNode { + DEBUG_FORMAT = "ln({})", + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +enum LiveNodeKind { + UpvarNode(Span), + ExprNode(Span, HirId), + VarDefNode(Span, HirId), + ClosureNode, + ExitNode, +} + +fn live_node_kind_to_string(lnk: LiveNodeKind, tcx: TyCtxt<'_>) -> String { + let sm = tcx.sess.source_map(); + match lnk { + UpvarNode(s) => format!("Upvar node [{}]", sm.span_to_diagnostic_string(s)), + ExprNode(s, _) => format!("Expr node [{}]", sm.span_to_diagnostic_string(s)), + VarDefNode(s, _) => format!("Var def node [{}]", sm.span_to_diagnostic_string(s)), + ClosureNode => "Closure node".to_owned(), + ExitNode => "Exit node".to_owned(), + } +} + +fn check_mod_liveness(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { + tcx.hir().visit_item_likes_in_module(module_def_id, &mut IrMaps::new(tcx)); +} + +pub fn provide(providers: &mut Providers) { + *providers = Providers { check_mod_liveness, ..*providers }; +} + +// ______________________________________________________________________ +// Creating ir_maps +// +// This is the first pass and the one that drives the main +// computation. It walks up and down the IR once. On the way down, +// we count for each function the number of variables as well as +// liveness nodes. A liveness node is basically an expression or +// capture clause that does something of interest: either it has +// interesting control flow or it uses/defines a local variable. +// +// On the way back up, at each function node we create liveness sets +// (we now know precisely how big to make our various vectors and so +// forth) and then do the data-flow propagation to compute the set +// of live variables at each program point. +// +// Finally, we run back over the IR one last time and, using the +// computed liveness, check various safety conditions. For example, +// there must be no live nodes at the definition site for a variable +// unless it has an initializer. Similarly, each non-mutable local +// variable must not be assigned if there is some successor +// assignment. And so forth. + +struct CaptureInfo { + ln: LiveNode, + var_hid: HirId, +} + +#[derive(Copy, Clone, Debug)] +struct LocalInfo { + id: HirId, + name: Symbol, + is_shorthand: bool, +} + +#[derive(Copy, Clone, Debug)] +enum VarKind { + Param(HirId, Symbol), + Local(LocalInfo), + Upvar(HirId, Symbol), +} + +struct IrMaps<'tcx> { + tcx: TyCtxt<'tcx>, + live_node_map: HirIdMap<LiveNode>, + variable_map: HirIdMap<Variable>, + capture_info_map: HirIdMap<Rc<Vec<CaptureInfo>>>, + var_kinds: IndexVec<Variable, VarKind>, + lnks: IndexVec<LiveNode, LiveNodeKind>, +} + +impl<'tcx> IrMaps<'tcx> { + fn new(tcx: TyCtxt<'tcx>) -> IrMaps<'tcx> { + IrMaps { + tcx, + live_node_map: HirIdMap::default(), + variable_map: HirIdMap::default(), + capture_info_map: Default::default(), + var_kinds: IndexVec::new(), + lnks: IndexVec::new(), + } + } + + fn add_live_node(&mut self, lnk: LiveNodeKind) -> LiveNode { + let ln = self.lnks.push(lnk); + + debug!("{:?} is of kind {}", ln, live_node_kind_to_string(lnk, self.tcx)); + + ln + } + + fn add_live_node_for_node(&mut self, hir_id: HirId, lnk: LiveNodeKind) { + let ln = self.add_live_node(lnk); + self.live_node_map.insert(hir_id, ln); + + debug!("{:?} is node {:?}", ln, hir_id); + } + + fn add_variable(&mut self, vk: VarKind) -> Variable { + let v = self.var_kinds.push(vk); + + match vk { + Local(LocalInfo { id: node_id, .. }) | Param(node_id, _) | Upvar(node_id, _) => { + self.variable_map.insert(node_id, v); + } + } + + debug!("{:?} is {:?}", v, vk); + + v + } + + fn variable(&self, hir_id: HirId, span: Span) -> Variable { + match self.variable_map.get(&hir_id) { + Some(&var) => var, + None => { + span_bug!(span, "no variable registered for id {:?}", hir_id); + } + } + } + + fn variable_name(&self, var: Variable) -> Symbol { + match self.var_kinds[var] { + Local(LocalInfo { name, .. }) | Param(_, name) | Upvar(_, name) => name, + } + } + + fn variable_is_shorthand(&self, var: Variable) -> bool { + match self.var_kinds[var] { + Local(LocalInfo { is_shorthand, .. }) => is_shorthand, + Param(..) | Upvar(..) => false, + } + } + + fn set_captures(&mut self, hir_id: HirId, cs: Vec<CaptureInfo>) { + self.capture_info_map.insert(hir_id, Rc::new(cs)); + } + + fn collect_shorthand_field_ids(&self, pat: &hir::Pat<'tcx>) -> HirIdSet { + // For struct patterns, take note of which fields used shorthand + // (`x` rather than `x: x`). + let mut shorthand_field_ids = HirIdSet::default(); + let mut pats = VecDeque::new(); + pats.push_back(pat); + + while let Some(pat) = pats.pop_front() { + use rustc_hir::PatKind::*; + match &pat.kind { + Binding(.., inner_pat) => { + pats.extend(inner_pat.iter()); + } + Struct(_, fields, _) => { + let (short, not_short): (Vec<_>, _) = + fields.iter().partition(|f| f.is_shorthand); + shorthand_field_ids.extend(short.iter().map(|f| f.pat.hir_id)); + pats.extend(not_short.iter().map(|f| f.pat)); + } + Ref(inner_pat, _) | Box(inner_pat) => { + pats.push_back(inner_pat); + } + TupleStruct(_, inner_pats, _) | Tuple(inner_pats, _) | Or(inner_pats) => { + pats.extend(inner_pats.iter()); + } + Slice(pre_pats, inner_pat, post_pats) => { + pats.extend(pre_pats.iter()); + pats.extend(inner_pat.iter()); + pats.extend(post_pats.iter()); + } + _ => {} + } + } + + shorthand_field_ids + } + + fn add_from_pat(&mut self, pat: &hir::Pat<'tcx>) { + let shorthand_field_ids = self.collect_shorthand_field_ids(pat); + + pat.each_binding(|_, hir_id, _, ident| { + self.add_live_node_for_node(hir_id, VarDefNode(ident.span, hir_id)); + self.add_variable(Local(LocalInfo { + id: hir_id, + name: ident.name, + is_shorthand: shorthand_field_ids.contains(&hir_id), + })); + }); + } +} + +impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + + fn visit_body(&mut self, body: &'tcx hir::Body<'tcx>) { + debug!("visit_body {:?}", body.id()); + + // swap in a new set of IR maps for this body + let mut maps = IrMaps::new(self.tcx); + let hir_id = maps.tcx.hir().body_owner(body.id()); + let local_def_id = maps.tcx.hir().local_def_id(hir_id); + let def_id = local_def_id.to_def_id(); + + // Don't run unused pass for #[derive()] + let parent = self.tcx.local_parent(local_def_id); + if let DefKind::Impl = self.tcx.def_kind(parent) + && self.tcx.has_attr(parent.to_def_id(), sym::automatically_derived) + { + return; + } + + // Don't run unused pass for #[naked] + if self.tcx.has_attr(def_id, sym::naked) { + return; + } + + if let Some(upvars) = maps.tcx.upvars_mentioned(def_id) { + for &var_hir_id in upvars.keys() { + let var_name = maps.tcx.hir().name(var_hir_id); + maps.add_variable(Upvar(var_hir_id, var_name)); + } + } + + // gather up the various local variables, significant expressions, + // and so forth: + intravisit::walk_body(&mut maps, body); + + // compute liveness + let mut lsets = Liveness::new(&mut maps, local_def_id); + let entry_ln = lsets.compute(&body, hir_id); + lsets.log_liveness(entry_ln, body.id().hir_id); + + // check for various error conditions + lsets.visit_body(body); + lsets.warn_about_unused_upvars(entry_ln); + lsets.warn_about_unused_args(body, entry_ln); + } + + fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) { + self.add_from_pat(&local.pat); + if local.els.is_some() { + self.add_live_node_for_node(local.hir_id, ExprNode(local.span, local.hir_id)); + } + intravisit::walk_local(self, local); + } + + fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { + self.add_from_pat(&arm.pat); + if let Some(hir::Guard::IfLet(ref let_expr)) = arm.guard { + self.add_from_pat(let_expr.pat); + } + intravisit::walk_arm(self, arm); + } + + fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { + let shorthand_field_ids = self.collect_shorthand_field_ids(param.pat); + param.pat.each_binding(|_bm, hir_id, _x, ident| { + let var = match param.pat.kind { + rustc_hir::PatKind::Struct(..) => Local(LocalInfo { + id: hir_id, + name: ident.name, + is_shorthand: shorthand_field_ids.contains(&hir_id), + }), + _ => Param(hir_id, ident.name), + }; + self.add_variable(var); + }); + intravisit::walk_param(self, param); + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + match expr.kind { + // live nodes required for uses or definitions of variables: + hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => { + debug!("expr {}: path that leads to {:?}", expr.hir_id, path.res); + if let Res::Local(_var_hir_id) = path.res { + self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); + } + intravisit::walk_expr(self, expr); + } + hir::ExprKind::Closure { .. } => { + // Interesting control flow (for loops can contain labeled + // breaks or continues) + self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); + + // Make a live_node for each mentioned variable, with the span + // being the location that the variable is used. This results + // in better error messages than just pointing at the closure + // construction site. + let mut call_caps = Vec::new(); + let closure_def_id = self.tcx.hir().local_def_id(expr.hir_id); + if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) { + call_caps.extend(upvars.keys().map(|var_id| { + let upvar = upvars[var_id]; + let upvar_ln = self.add_live_node(UpvarNode(upvar.span)); + CaptureInfo { ln: upvar_ln, var_hid: *var_id } + })); + } + self.set_captures(expr.hir_id, call_caps); + intravisit::walk_expr(self, expr); + } + + hir::ExprKind::Let(let_expr) => { + self.add_from_pat(let_expr.pat); + intravisit::walk_expr(self, expr); + } + + // live nodes required for interesting control flow: + hir::ExprKind::If(..) + | hir::ExprKind::Match(..) + | hir::ExprKind::Loop(..) + | hir::ExprKind::Yield(..) => { + self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); + intravisit::walk_expr(self, expr); + } + hir::ExprKind::Binary(op, ..) if op.node.is_lazy() => { + self.add_live_node_for_node(expr.hir_id, ExprNode(expr.span, expr.hir_id)); + intravisit::walk_expr(self, expr); + } + + // otherwise, live nodes are not required: + hir::ExprKind::Index(..) + | hir::ExprKind::Field(..) + | hir::ExprKind::Array(..) + | hir::ExprKind::Call(..) + | hir::ExprKind::MethodCall(..) + | hir::ExprKind::Tup(..) + | hir::ExprKind::Binary(..) + | hir::ExprKind::AddrOf(..) + | hir::ExprKind::Cast(..) + | hir::ExprKind::DropTemps(..) + | hir::ExprKind::Unary(..) + | hir::ExprKind::Break(..) + | hir::ExprKind::Continue(_) + | hir::ExprKind::Lit(_) + | hir::ExprKind::ConstBlock(..) + | hir::ExprKind::Ret(..) + | hir::ExprKind::Block(..) + | hir::ExprKind::Assign(..) + | hir::ExprKind::AssignOp(..) + | hir::ExprKind::Struct(..) + | hir::ExprKind::Repeat(..) + | hir::ExprKind::InlineAsm(..) + | hir::ExprKind::Box(..) + | hir::ExprKind::Type(..) + | hir::ExprKind::Err + | hir::ExprKind::Path(hir::QPath::TypeRelative(..)) + | hir::ExprKind::Path(hir::QPath::LangItem(..)) => { + intravisit::walk_expr(self, expr); + } + } + } +} + +// ______________________________________________________________________ +// Computing liveness sets +// +// Actually we compute just a bit more than just liveness, but we use +// the same basic propagation framework in all cases. + +const ACC_READ: u32 = 1; +const ACC_WRITE: u32 = 2; +const ACC_USE: u32 = 4; + +struct Liveness<'a, 'tcx> { + ir: &'a mut IrMaps<'tcx>, + typeck_results: &'a ty::TypeckResults<'tcx>, + param_env: ty::ParamEnv<'tcx>, + closure_min_captures: Option<&'tcx RootVariableMinCaptureList<'tcx>>, + successors: IndexVec<LiveNode, Option<LiveNode>>, + rwu_table: rwu_table::RWUTable, + + /// A live node representing a point of execution before closure entry & + /// after closure exit. Used to calculate liveness of captured variables + /// through calls to the same closure. Used for Fn & FnMut closures only. + closure_ln: LiveNode, + /// A live node representing every 'exit' from the function, whether it be + /// by explicit return, panic, or other means. + exit_ln: LiveNode, + + // mappings from loop node ID to LiveNode + // ("break" label should map to loop node ID, + // it probably doesn't now) + break_ln: HirIdMap<LiveNode>, + cont_ln: HirIdMap<LiveNode>, +} + +impl<'a, 'tcx> Liveness<'a, 'tcx> { + fn new(ir: &'a mut IrMaps<'tcx>, body_owner: LocalDefId) -> Liveness<'a, 'tcx> { + let typeck_results = ir.tcx.typeck(body_owner); + let param_env = ir.tcx.param_env(body_owner); + let closure_min_captures = typeck_results.closure_min_captures.get(&body_owner); + let closure_ln = ir.add_live_node(ClosureNode); + let exit_ln = ir.add_live_node(ExitNode); + + let num_live_nodes = ir.lnks.len(); + let num_vars = ir.var_kinds.len(); + + Liveness { + ir, + typeck_results, + param_env, + closure_min_captures, + successors: IndexVec::from_elem_n(None, num_live_nodes), + rwu_table: rwu_table::RWUTable::new(num_live_nodes, num_vars), + closure_ln, + exit_ln, + break_ln: Default::default(), + cont_ln: Default::default(), + } + } + + fn live_node(&self, hir_id: HirId, span: Span) -> LiveNode { + match self.ir.live_node_map.get(&hir_id) { + Some(&ln) => ln, + None => { + // This must be a mismatch between the ir_map construction + // above and the propagation code below; the two sets of + // code have to agree about which AST nodes are worth + // creating liveness nodes for. + span_bug!(span, "no live node registered for node {:?}", hir_id); + } + } + } + + fn variable(&self, hir_id: HirId, span: Span) -> Variable { + self.ir.variable(hir_id, span) + } + + fn define_bindings_in_pat(&mut self, pat: &hir::Pat<'_>, mut succ: LiveNode) -> LiveNode { + // In an or-pattern, only consider the first pattern; any later patterns + // must have the same bindings, and we also consider the first pattern + // to be the "authoritative" set of ids. + pat.each_binding_or_first(&mut |_, hir_id, pat_sp, ident| { + let ln = self.live_node(hir_id, pat_sp); + let var = self.variable(hir_id, ident.span); + self.init_from_succ(ln, succ); + self.define(ln, var); + succ = ln; + }); + succ + } + + fn live_on_entry(&self, ln: LiveNode, var: Variable) -> bool { + self.rwu_table.get_reader(ln, var) + } + + // Is this variable live on entry to any of its successor nodes? + fn live_on_exit(&self, ln: LiveNode, var: Variable) -> bool { + let successor = self.successors[ln].unwrap(); + self.live_on_entry(successor, var) + } + + fn used_on_entry(&self, ln: LiveNode, var: Variable) -> bool { + self.rwu_table.get_used(ln, var) + } + + fn assigned_on_entry(&self, ln: LiveNode, var: Variable) -> bool { + self.rwu_table.get_writer(ln, var) + } + + fn assigned_on_exit(&self, ln: LiveNode, var: Variable) -> bool { + let successor = self.successors[ln].unwrap(); + self.assigned_on_entry(successor, var) + } + + fn write_vars<F>(&self, wr: &mut dyn Write, mut test: F) -> io::Result<()> + where + F: FnMut(Variable) -> bool, + { + for var_idx in 0..self.ir.var_kinds.len() { + let var = Variable::from(var_idx); + if test(var) { + write!(wr, " {:?}", var)?; + } + } + Ok(()) + } + + #[allow(unused_must_use)] + fn ln_str(&self, ln: LiveNode) -> String { + let mut wr = Vec::new(); + { + let wr = &mut wr as &mut dyn Write; + write!(wr, "[{:?} of kind {:?} reads", ln, self.ir.lnks[ln]); + self.write_vars(wr, |var| self.rwu_table.get_reader(ln, var)); + write!(wr, " writes"); + self.write_vars(wr, |var| self.rwu_table.get_writer(ln, var)); + write!(wr, " uses"); + self.write_vars(wr, |var| self.rwu_table.get_used(ln, var)); + + write!(wr, " precedes {:?}]", self.successors[ln]); + } + String::from_utf8(wr).unwrap() + } + + fn log_liveness(&self, entry_ln: LiveNode, hir_id: hir::HirId) { + // hack to skip the loop unless debug! is enabled: + debug!( + "^^ liveness computation results for body {} (entry={:?})", + { + for ln_idx in 0..self.ir.lnks.len() { + debug!("{:?}", self.ln_str(LiveNode::from(ln_idx))); + } + hir_id + }, + entry_ln + ); + } + + fn init_empty(&mut self, ln: LiveNode, succ_ln: LiveNode) { + self.successors[ln] = Some(succ_ln); + + // It is not necessary to initialize the RWUs here because they are all + // empty when created, and the sets only grow during iterations. + } + + fn init_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) { + // more efficient version of init_empty() / merge_from_succ() + self.successors[ln] = Some(succ_ln); + self.rwu_table.copy(ln, succ_ln); + debug!("init_from_succ(ln={}, succ={})", self.ln_str(ln), self.ln_str(succ_ln)); + } + + fn merge_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) -> bool { + if ln == succ_ln { + return false; + } + + let changed = self.rwu_table.union(ln, succ_ln); + debug!("merge_from_succ(ln={:?}, succ={}, changed={})", ln, self.ln_str(succ_ln), changed); + changed + } + + // Indicates that a local variable was *defined*; we know that no + // uses of the variable can precede the definition (resolve checks + // this) so we just clear out all the data. + fn define(&mut self, writer: LiveNode, var: Variable) { + let used = self.rwu_table.get_used(writer, var); + self.rwu_table.set(writer, var, rwu_table::RWU { reader: false, writer: false, used }); + debug!("{:?} defines {:?}: {}", writer, var, self.ln_str(writer)); + } + + // Either read, write, or both depending on the acc bitset + fn acc(&mut self, ln: LiveNode, var: Variable, acc: u32) { + debug!("{:?} accesses[{:x}] {:?}: {}", ln, acc, var, self.ln_str(ln)); + + let mut rwu = self.rwu_table.get(ln, var); + + if (acc & ACC_WRITE) != 0 { + rwu.reader = false; + rwu.writer = true; + } + + // Important: if we both read/write, must do read second + // or else the write will override. + if (acc & ACC_READ) != 0 { + rwu.reader = true; + } + + if (acc & ACC_USE) != 0 { + rwu.used = true; + } + + self.rwu_table.set(ln, var, rwu); + } + + fn compute(&mut self, body: &hir::Body<'_>, hir_id: HirId) -> LiveNode { + debug!("compute: for body {:?}", body.id().hir_id); + + // # Liveness of captured variables + // + // When computing the liveness for captured variables we take into + // account how variable is captured (ByRef vs ByValue) and what is the + // closure kind (Generator / FnOnce vs Fn / FnMut). + // + // Variables captured by reference are assumed to be used on the exit + // from the closure. + // + // In FnOnce closures, variables captured by value are known to be dead + // on exit since it is impossible to call the closure again. + // + // In Fn / FnMut closures, variables captured by value are live on exit + // if they are live on the entry to the closure, since only the closure + // itself can access them on subsequent calls. + + if let Some(closure_min_captures) = self.closure_min_captures { + // Mark upvars captured by reference as used after closure exits. + for (&var_hir_id, min_capture_list) in closure_min_captures { + for captured_place in min_capture_list { + match captured_place.info.capture_kind { + ty::UpvarCapture::ByRef(_) => { + let var = self.variable( + var_hir_id, + captured_place.get_capture_kind_span(self.ir.tcx), + ); + self.acc(self.exit_ln, var, ACC_READ | ACC_USE); + } + ty::UpvarCapture::ByValue => {} + } + } + } + } + + let succ = self.propagate_through_expr(&body.value, self.exit_ln); + + if self.closure_min_captures.is_none() { + // Either not a closure, or closure without any captured variables. + // No need to determine liveness of captured variables, since there + // are none. + return succ; + } + + let ty = self.typeck_results.node_type(hir_id); + match ty.kind() { + ty::Closure(_def_id, substs) => match substs.as_closure().kind() { + ty::ClosureKind::Fn => {} + ty::ClosureKind::FnMut => {} + ty::ClosureKind::FnOnce => return succ, + }, + ty::Generator(..) => return succ, + _ => { + span_bug!( + body.value.span, + "{} has upvars so it should have a closure type: {:?}", + hir_id, + ty + ); + } + }; + + // Propagate through calls to the closure. + loop { + self.init_from_succ(self.closure_ln, succ); + for param in body.params { + param.pat.each_binding(|_bm, hir_id, _x, ident| { + let var = self.variable(hir_id, ident.span); + self.define(self.closure_ln, var); + }) + } + + if !self.merge_from_succ(self.exit_ln, self.closure_ln) { + break; + } + assert_eq!(succ, self.propagate_through_expr(&body.value, self.exit_ln)); + } + + succ + } + + fn propagate_through_block(&mut self, blk: &hir::Block<'_>, succ: LiveNode) -> LiveNode { + if blk.targeted_by_break { + self.break_ln.insert(blk.hir_id, succ); + } + let succ = self.propagate_through_opt_expr(blk.expr, succ); + blk.stmts.iter().rev().fold(succ, |succ, stmt| self.propagate_through_stmt(stmt, succ)) + } + + fn propagate_through_stmt(&mut self, stmt: &hir::Stmt<'_>, succ: LiveNode) -> LiveNode { + match stmt.kind { + hir::StmtKind::Local(ref local) => { + // Note: we mark the variable as defined regardless of whether + // there is an initializer. Initially I had thought to only mark + // the live variable as defined if it was initialized, and then we + // could check for uninit variables just by scanning what is live + // at the start of the function. But that doesn't work so well for + // immutable variables defined in a loop: + // loop { let x; x = 5; } + // because the "assignment" loops back around and generates an error. + // + // So now we just check that variables defined w/o an + // initializer are not live at the point of their + // initialization, which is mildly more complex than checking + // once at the func header but otherwise equivalent. + + if let Some(els) = local.els { + // Eventually, `let pat: ty = init else { els };` is mostly equivalent to + // `let (bindings, ...) = match init { pat => (bindings, ...), _ => els };` + // except that extended lifetime applies at the `init` location. + // + // (e) + // | + // v + // (expr) + // / \ + // | | + // v v + // bindings els + // | + // v + // ( succ ) + // + if let Some(init) = local.init { + let else_ln = self.propagate_through_block(els, succ); + let ln = self.live_node(local.hir_id, local.span); + self.init_from_succ(ln, succ); + self.merge_from_succ(ln, else_ln); + let succ = self.propagate_through_expr(init, ln); + self.define_bindings_in_pat(&local.pat, succ) + } else { + span_bug!( + stmt.span, + "variable is uninitialized but an unexpected else branch is found" + ) + } + } else { + let succ = self.propagate_through_opt_expr(local.init, succ); + self.define_bindings_in_pat(&local.pat, succ) + } + } + hir::StmtKind::Item(..) => succ, + hir::StmtKind::Expr(ref expr) | hir::StmtKind::Semi(ref expr) => { + self.propagate_through_expr(&expr, succ) + } + } + } + + fn propagate_through_exprs(&mut self, exprs: &[Expr<'_>], succ: LiveNode) -> LiveNode { + exprs.iter().rev().fold(succ, |succ, expr| self.propagate_through_expr(&expr, succ)) + } + + fn propagate_through_opt_expr( + &mut self, + opt_expr: Option<&Expr<'_>>, + succ: LiveNode, + ) -> LiveNode { + opt_expr.map_or(succ, |expr| self.propagate_through_expr(expr, succ)) + } + + fn propagate_through_expr(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode { + debug!("propagate_through_expr: {:?}", expr); + + match expr.kind { + // Interesting cases with control flow or which gen/kill + hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => { + self.access_path(expr.hir_id, path, succ, ACC_READ | ACC_USE) + } + + hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(&e, succ), + + hir::ExprKind::Closure { .. } => { + debug!("{:?} is an ExprKind::Closure", expr); + + // the construction of a closure itself is not important, + // but we have to consider the closed over variables. + let caps = self + .ir + .capture_info_map + .get(&expr.hir_id) + .cloned() + .unwrap_or_else(|| span_bug!(expr.span, "no registered caps")); + + caps.iter().rev().fold(succ, |succ, cap| { + self.init_from_succ(cap.ln, succ); + let var = self.variable(cap.var_hid, expr.span); + self.acc(cap.ln, var, ACC_READ | ACC_USE); + cap.ln + }) + } + + hir::ExprKind::Let(let_expr) => { + let succ = self.propagate_through_expr(let_expr.init, succ); + self.define_bindings_in_pat(let_expr.pat, succ) + } + + // Note that labels have been resolved, so we don't need to look + // at the label ident + hir::ExprKind::Loop(ref blk, ..) => self.propagate_through_loop(expr, &blk, succ), + + hir::ExprKind::Yield(ref e, ..) => { + let yield_ln = self.live_node(expr.hir_id, expr.span); + self.init_from_succ(yield_ln, succ); + self.merge_from_succ(yield_ln, self.exit_ln); + self.propagate_through_expr(e, yield_ln) + } + + hir::ExprKind::If(ref cond, ref then, ref else_opt) => { + // + // (cond) + // | + // v + // (expr) + // / \ + // | | + // v v + // (then)(els) + // | | + // v v + // ( succ ) + // + let else_ln = + self.propagate_through_opt_expr(else_opt.as_ref().map(|e| &**e), succ); + let then_ln = self.propagate_through_expr(&then, succ); + let ln = self.live_node(expr.hir_id, expr.span); + self.init_from_succ(ln, else_ln); + self.merge_from_succ(ln, then_ln); + self.propagate_through_expr(&cond, ln) + } + + hir::ExprKind::Match(ref e, arms, _) => { + // + // (e) + // | + // v + // (expr) + // / | \ + // | | | + // v v v + // (..arms..) + // | | | + // v v v + // ( succ ) + // + // + let ln = self.live_node(expr.hir_id, expr.span); + self.init_empty(ln, succ); + for arm in arms { + let body_succ = self.propagate_through_expr(&arm.body, succ); + + let guard_succ = arm.guard.as_ref().map_or(body_succ, |g| match g { + hir::Guard::If(e) => self.propagate_through_expr(e, body_succ), + hir::Guard::IfLet(let_expr) => { + let let_bind = self.define_bindings_in_pat(let_expr.pat, body_succ); + self.propagate_through_expr(let_expr.init, let_bind) + } + }); + let arm_succ = self.define_bindings_in_pat(&arm.pat, guard_succ); + self.merge_from_succ(ln, arm_succ); + } + self.propagate_through_expr(&e, ln) + } + + hir::ExprKind::Ret(ref o_e) => { + // Ignore succ and subst exit_ln. + self.propagate_through_opt_expr(o_e.as_ref().map(|e| &**e), self.exit_ln) + } + + hir::ExprKind::Break(label, ref opt_expr) => { + // Find which label this break jumps to + let target = match label.target_id { + Ok(hir_id) => self.break_ln.get(&hir_id), + Err(err) => span_bug!(expr.span, "loop scope error: {}", err), + } + .cloned(); + + // Now that we know the label we're going to, + // look it up in the break loop nodes table + + match target { + Some(b) => self.propagate_through_opt_expr(opt_expr.as_ref().map(|e| &**e), b), + None => span_bug!(expr.span, "`break` to unknown label"), + } + } + + hir::ExprKind::Continue(label) => { + // Find which label this expr continues to + let sc = label + .target_id + .unwrap_or_else(|err| span_bug!(expr.span, "loop scope error: {}", err)); + + // Now that we know the label we're going to, + // look it up in the continue loop nodes table + self.cont_ln + .get(&sc) + .cloned() + .unwrap_or_else(|| span_bug!(expr.span, "continue to unknown label")) + } + + hir::ExprKind::Assign(ref l, ref r, _) => { + // see comment on places in + // propagate_through_place_components() + let succ = self.write_place(&l, succ, ACC_WRITE); + let succ = self.propagate_through_place_components(&l, succ); + self.propagate_through_expr(&r, succ) + } + + hir::ExprKind::AssignOp(_, ref l, ref r) => { + // an overloaded assign op is like a method call + if self.typeck_results.is_method_call(expr) { + let succ = self.propagate_through_expr(&l, succ); + self.propagate_through_expr(&r, succ) + } else { + // see comment on places in + // propagate_through_place_components() + let succ = self.write_place(&l, succ, ACC_WRITE | ACC_READ); + let succ = self.propagate_through_expr(&r, succ); + self.propagate_through_place_components(&l, succ) + } + } + + // Uninteresting cases: just propagate in rev exec order + hir::ExprKind::Array(ref exprs) => self.propagate_through_exprs(exprs, succ), + + hir::ExprKind::Struct(_, ref fields, ref with_expr) => { + let succ = self.propagate_through_opt_expr(with_expr.as_ref().map(|e| &**e), succ); + fields + .iter() + .rev() + .fold(succ, |succ, field| self.propagate_through_expr(&field.expr, succ)) + } + + hir::ExprKind::Call(ref f, ref args) => { + let succ = self.check_is_ty_uninhabited(expr, succ); + let succ = self.propagate_through_exprs(args, succ); + self.propagate_through_expr(&f, succ) + } + + hir::ExprKind::MethodCall(.., ref args, _) => { + let succ = self.check_is_ty_uninhabited(expr, succ); + self.propagate_through_exprs(args, succ) + } + + hir::ExprKind::Tup(ref exprs) => self.propagate_through_exprs(exprs, succ), + + hir::ExprKind::Binary(op, ref l, ref r) if op.node.is_lazy() => { + let r_succ = self.propagate_through_expr(&r, succ); + + let ln = self.live_node(expr.hir_id, expr.span); + self.init_from_succ(ln, succ); + self.merge_from_succ(ln, r_succ); + + self.propagate_through_expr(&l, ln) + } + + hir::ExprKind::Index(ref l, ref r) | hir::ExprKind::Binary(_, ref l, ref r) => { + let r_succ = self.propagate_through_expr(&r, succ); + self.propagate_through_expr(&l, r_succ) + } + + hir::ExprKind::Box(ref e) + | hir::ExprKind::AddrOf(_, _, ref e) + | hir::ExprKind::Cast(ref e, _) + | hir::ExprKind::Type(ref e, _) + | hir::ExprKind::DropTemps(ref e) + | hir::ExprKind::Unary(_, ref e) + | hir::ExprKind::Repeat(ref e, _) => self.propagate_through_expr(&e, succ), + + hir::ExprKind::InlineAsm(ref asm) => { + // Handle non-returning asm + let mut succ = if asm.options.contains(InlineAsmOptions::NORETURN) { + self.exit_ln + } else { + succ + }; + + // Do a first pass for writing outputs only + for (op, _op_sp) in asm.operands.iter().rev() { + match op { + hir::InlineAsmOperand::In { .. } + | hir::InlineAsmOperand::Const { .. } + | hir::InlineAsmOperand::SymFn { .. } + | hir::InlineAsmOperand::SymStatic { .. } => {} + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + succ = self.write_place(expr, succ, ACC_WRITE); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + succ = self.write_place(expr, succ, ACC_READ | ACC_WRITE | ACC_USE); + } + hir::InlineAsmOperand::SplitInOut { out_expr, .. } => { + if let Some(expr) = out_expr { + succ = self.write_place(expr, succ, ACC_WRITE); + } + } + } + } + + // Then do a second pass for inputs + let mut succ = succ; + for (op, _op_sp) in asm.operands.iter().rev() { + match op { + hir::InlineAsmOperand::In { expr, .. } => { + succ = self.propagate_through_expr(expr, succ) + } + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + succ = self.propagate_through_place_components(expr, succ); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + succ = self.propagate_through_place_components(expr, succ); + } + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + if let Some(expr) = out_expr { + succ = self.propagate_through_place_components(expr, succ); + } + succ = self.propagate_through_expr(in_expr, succ); + } + hir::InlineAsmOperand::Const { .. } + | hir::InlineAsmOperand::SymFn { .. } + | hir::InlineAsmOperand::SymStatic { .. } => {} + } + } + succ + } + + hir::ExprKind::Lit(..) + | hir::ExprKind::ConstBlock(..) + | hir::ExprKind::Err + | hir::ExprKind::Path(hir::QPath::TypeRelative(..)) + | hir::ExprKind::Path(hir::QPath::LangItem(..)) => succ, + + // Note that labels have been resolved, so we don't need to look + // at the label ident + hir::ExprKind::Block(ref blk, _) => self.propagate_through_block(&blk, succ), + } + } + + fn propagate_through_place_components(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode { + // # Places + // + // In general, the full flow graph structure for an + // assignment/move/etc can be handled in one of two ways, + // depending on whether what is being assigned is a "tracked + // value" or not. A tracked value is basically a local + // variable or argument. + // + // The two kinds of graphs are: + // + // Tracked place Untracked place + // ----------------------++----------------------- + // || + // | || | + // v || v + // (rvalue) || (rvalue) + // | || | + // v || v + // (write of place) || (place components) + // | || | + // v || v + // (succ) || (succ) + // || + // ----------------------++----------------------- + // + // I will cover the two cases in turn: + // + // # Tracked places + // + // A tracked place is a local variable/argument `x`. In + // these cases, the link_node where the write occurs is linked + // to node id of `x`. The `write_place()` routine generates + // the contents of this node. There are no subcomponents to + // consider. + // + // # Non-tracked places + // + // These are places like `x[5]` or `x.f`. In that case, we + // basically ignore the value which is written to but generate + // reads for the components---`x` in these two examples. The + // components reads are generated by + // `propagate_through_place_components()` (this fn). + // + // # Illegal places + // + // It is still possible to observe assignments to non-places; + // these errors are detected in the later pass borrowck. We + // just ignore such cases and treat them as reads. + + match expr.kind { + hir::ExprKind::Path(_) => succ, + hir::ExprKind::Field(ref e, _) => self.propagate_through_expr(&e, succ), + _ => self.propagate_through_expr(expr, succ), + } + } + + // see comment on propagate_through_place() + fn write_place(&mut self, expr: &Expr<'_>, succ: LiveNode, acc: u32) -> LiveNode { + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => { + self.access_path(expr.hir_id, path, succ, acc) + } + + // We do not track other places, so just propagate through + // to their subcomponents. Also, it may happen that + // non-places occur here, because those are detected in the + // later pass borrowck. + _ => succ, + } + } + + fn access_var( + &mut self, + hir_id: HirId, + var_hid: HirId, + succ: LiveNode, + acc: u32, + span: Span, + ) -> LiveNode { + let ln = self.live_node(hir_id, span); + if acc != 0 { + self.init_from_succ(ln, succ); + let var = self.variable(var_hid, span); + self.acc(ln, var, acc); + } + ln + } + + fn access_path( + &mut self, + hir_id: HirId, + path: &hir::Path<'_>, + succ: LiveNode, + acc: u32, + ) -> LiveNode { + match path.res { + Res::Local(hid) => self.access_var(hir_id, hid, succ, acc, path.span), + _ => succ, + } + } + + fn propagate_through_loop( + &mut self, + expr: &Expr<'_>, + body: &hir::Block<'_>, + succ: LiveNode, + ) -> LiveNode { + /* + We model control flow like this: + + (expr) <-+ + | | + v | + (body) --+ + + Note that a `continue` expression targeting the `loop` will have a successor of `expr`. + Meanwhile, a `break` expression will have a successor of `succ`. + */ + + // first iteration: + let ln = self.live_node(expr.hir_id, expr.span); + self.init_empty(ln, succ); + debug!("propagate_through_loop: using id for loop body {} {:?}", expr.hir_id, body); + + self.break_ln.insert(expr.hir_id, succ); + + self.cont_ln.insert(expr.hir_id, ln); + + let body_ln = self.propagate_through_block(body, ln); + + // repeat until fixed point is reached: + while self.merge_from_succ(ln, body_ln) { + assert_eq!(body_ln, self.propagate_through_block(body, ln)); + } + + ln + } + + fn check_is_ty_uninhabited(&mut self, expr: &Expr<'_>, succ: LiveNode) -> LiveNode { + let ty = self.typeck_results.expr_ty(expr); + let m = self.ir.tcx.parent_module(expr.hir_id).to_def_id(); + if self.ir.tcx.is_ty_uninhabited_from(m, ty, self.param_env) { + match self.ir.lnks[succ] { + LiveNodeKind::ExprNode(succ_span, succ_id) => { + self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "expression"); + } + LiveNodeKind::VarDefNode(succ_span, succ_id) => { + self.warn_about_unreachable(expr.span, ty, succ_span, succ_id, "definition"); + } + _ => {} + }; + self.exit_ln + } else { + succ + } + } + + fn warn_about_unreachable( + &mut self, + orig_span: Span, + orig_ty: Ty<'tcx>, + expr_span: Span, + expr_id: HirId, + descr: &str, + ) { + if !orig_ty.is_never() { + // Unreachable code warnings are already emitted during type checking. + // However, during type checking, full type information is being + // calculated but not yet available, so the check for diverging + // expressions due to uninhabited result types is pretty crude and + // only checks whether ty.is_never(). Here, we have full type + // information available and can issue warnings for less obviously + // uninhabited types (e.g. empty enums). The check above is used so + // that we do not emit the same warning twice if the uninhabited type + // is indeed `!`. + + self.ir.tcx.struct_span_lint_hir( + lint::builtin::UNREACHABLE_CODE, + expr_id, + expr_span, + |lint| { + let msg = format!("unreachable {}", descr); + lint.build(&msg) + .span_label(expr_span, &msg) + .span_label(orig_span, "any code following this expression is unreachable") + .span_note( + orig_span, + &format!( + "this expression has type `{}`, which is uninhabited", + orig_ty + ), + ) + .emit(); + }, + ); + } + } +} + +// _______________________________________________________________________ +// Checking for error conditions + +impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> { + fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) { + self.check_unused_vars_in_pat(&local.pat, None, |spans, hir_id, ln, var| { + if local.init.is_some() { + self.warn_about_dead_assign(spans, hir_id, ln, var); + } + }); + + intravisit::walk_local(self, local); + } + + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { + check_expr(self, ex); + intravisit::walk_expr(self, ex); + } + + fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) { + self.check_unused_vars_in_pat(&arm.pat, None, |_, _, _, _| {}); + intravisit::walk_arm(self, arm); + } +} + +fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) { + match expr.kind { + hir::ExprKind::Assign(ref l, ..) => { + this.check_place(&l); + } + + hir::ExprKind::AssignOp(_, ref l, _) => { + if !this.typeck_results.is_method_call(expr) { + this.check_place(&l); + } + } + + hir::ExprKind::InlineAsm(ref asm) => { + for (op, _op_sp) in asm.operands { + match op { + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + this.check_place(expr); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + this.check_place(expr); + } + hir::InlineAsmOperand::SplitInOut { out_expr, .. } => { + if let Some(out_expr) = out_expr { + this.check_place(out_expr); + } + } + _ => {} + } + } + } + + hir::ExprKind::Let(let_expr) => { + this.check_unused_vars_in_pat(let_expr.pat, None, |_, _, _, _| {}); + } + + // no correctness conditions related to liveness + hir::ExprKind::Call(..) + | hir::ExprKind::MethodCall(..) + | hir::ExprKind::Match(..) + | hir::ExprKind::Loop(..) + | hir::ExprKind::Index(..) + | hir::ExprKind::Field(..) + | hir::ExprKind::Array(..) + | hir::ExprKind::Tup(..) + | hir::ExprKind::Binary(..) + | hir::ExprKind::Cast(..) + | hir::ExprKind::If(..) + | hir::ExprKind::DropTemps(..) + | hir::ExprKind::Unary(..) + | hir::ExprKind::Ret(..) + | hir::ExprKind::Break(..) + | hir::ExprKind::Continue(..) + | hir::ExprKind::Lit(_) + | hir::ExprKind::ConstBlock(..) + | hir::ExprKind::Block(..) + | hir::ExprKind::AddrOf(..) + | hir::ExprKind::Struct(..) + | hir::ExprKind::Repeat(..) + | hir::ExprKind::Closure { .. } + | hir::ExprKind::Path(_) + | hir::ExprKind::Yield(..) + | hir::ExprKind::Box(..) + | hir::ExprKind::Type(..) + | hir::ExprKind::Err => {} + } +} + +impl<'tcx> Liveness<'_, 'tcx> { + fn check_place(&mut self, expr: &'tcx Expr<'tcx>) { + match expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) => { + if let Res::Local(var_hid) = path.res { + // Assignment to an immutable variable or argument: only legal + // if there is no later assignment. If this local is actually + // mutable, then check for a reassignment to flag the mutability + // as being used. + let ln = self.live_node(expr.hir_id, expr.span); + let var = self.variable(var_hid, expr.span); + self.warn_about_dead_assign(vec![expr.span], expr.hir_id, ln, var); + } + } + _ => { + // For other kinds of places, no checks are required, + // and any embedded expressions are actually rvalues + intravisit::walk_expr(self, expr); + } + } + } + + fn should_warn(&self, var: Variable) -> Option<String> { + let name = self.ir.variable_name(var); + if name == kw::Empty { + return None; + } + let name = name.as_str(); + if name.as_bytes()[0] == b'_' { + return None; + } + Some(name.to_owned()) + } + + fn warn_about_unused_upvars(&self, entry_ln: LiveNode) { + let Some(closure_min_captures) = self.closure_min_captures else { + return; + }; + + // If closure_min_captures is Some(), upvars must be Some() too. + for (&var_hir_id, min_capture_list) in closure_min_captures { + for captured_place in min_capture_list { + match captured_place.info.capture_kind { + ty::UpvarCapture::ByValue => {} + ty::UpvarCapture::ByRef(..) => continue, + }; + let span = captured_place.get_capture_kind_span(self.ir.tcx); + let var = self.variable(var_hir_id, span); + if self.used_on_entry(entry_ln, var) { + if !self.live_on_entry(entry_ln, var) { + if let Some(name) = self.should_warn(var) { + self.ir.tcx.struct_span_lint_hir( + lint::builtin::UNUSED_ASSIGNMENTS, + var_hir_id, + vec![span], + |lint| { + lint.build(&format!( + "value captured by `{}` is never read", + name + )) + .help("did you mean to capture by reference instead?") + .emit(); + }, + ); + } + } + } else { + if let Some(name) = self.should_warn(var) { + self.ir.tcx.struct_span_lint_hir( + lint::builtin::UNUSED_VARIABLES, + var_hir_id, + vec![span], + |lint| { + lint.build(&format!("unused variable: `{}`", name)) + .help("did you mean to capture by reference instead?") + .emit(); + }, + ); + } + } + } + } + } + + fn warn_about_unused_args(&self, body: &hir::Body<'_>, entry_ln: LiveNode) { + for p in body.params { + self.check_unused_vars_in_pat(&p.pat, Some(entry_ln), |spans, hir_id, ln, var| { + if !self.live_on_entry(ln, var) { + self.report_unused_assign(hir_id, spans, var, |name| { + format!("value passed to `{}` is never read", name) + }); + } + }); + } + } + + fn check_unused_vars_in_pat( + &self, + pat: &hir::Pat<'_>, + entry_ln: Option<LiveNode>, + on_used_on_entry: impl Fn(Vec<Span>, HirId, LiveNode, Variable), + ) { + // In an or-pattern, only consider the variable; any later patterns must have the same + // bindings, and we also consider the first pattern to be the "authoritative" set of ids. + // However, we should take the ids and spans of variables with the same name from the later + // patterns so the suggestions to prefix with underscores will apply to those too. + let mut vars: FxIndexMap<Symbol, (LiveNode, Variable, Vec<(HirId, Span, Span)>)> = + <_>::default(); + + pat.each_binding(|_, hir_id, pat_sp, ident| { + let ln = entry_ln.unwrap_or_else(|| self.live_node(hir_id, pat_sp)); + let var = self.variable(hir_id, ident.span); + let id_and_sp = (hir_id, pat_sp, ident.span); + vars.entry(self.ir.variable_name(var)) + .and_modify(|(.., hir_ids_and_spans)| hir_ids_and_spans.push(id_and_sp)) + .or_insert_with(|| (ln, var, vec![id_and_sp])); + }); + + for (_, (ln, var, hir_ids_and_spans)) in vars { + if self.used_on_entry(ln, var) { + let id = hir_ids_and_spans[0].0; + let spans = + hir_ids_and_spans.into_iter().map(|(_, _, ident_span)| ident_span).collect(); + on_used_on_entry(spans, id, ln, var); + } else { + self.report_unused(hir_ids_and_spans, ln, var); + } + } + } + + fn report_unused( + &self, + hir_ids_and_spans: Vec<(HirId, Span, Span)>, + ln: LiveNode, + var: Variable, + ) { + let first_hir_id = hir_ids_and_spans[0].0; + + if let Some(name) = self.should_warn(var).filter(|name| name != "self") { + // annoying: for parameters in funcs like `fn(x: i32) + // {ret}`, there is only one node, so asking about + // assigned_on_exit() is not meaningful. + let is_assigned = + if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var) }; + + if is_assigned { + self.ir.tcx.struct_span_lint_hir( + lint::builtin::UNUSED_VARIABLES, + first_hir_id, + hir_ids_and_spans + .into_iter() + .map(|(_, _, ident_span)| ident_span) + .collect::<Vec<_>>(), + |lint| { + lint.build(&format!("variable `{}` is assigned to, but never used", name)) + .note(&format!("consider using `_{}` instead", name)) + .emit(); + }, + ) + } else { + let (shorthands, non_shorthands): (Vec<_>, Vec<_>) = + hir_ids_and_spans.iter().copied().partition(|(hir_id, _, ident_span)| { + let var = self.variable(*hir_id, *ident_span); + self.ir.variable_is_shorthand(var) + }); + + // If we have both shorthand and non-shorthand, prefer the "try ignoring + // the field" message, and suggest `_` for the non-shorthands. If we only + // have non-shorthand, then prefix with an underscore instead. + if !shorthands.is_empty() { + let shorthands = shorthands + .into_iter() + .map(|(_, pat_span, _)| (pat_span, format!("{}: _", name))) + .chain( + non_shorthands + .into_iter() + .map(|(_, pat_span, _)| (pat_span, "_".to_string())), + ) + .collect::<Vec<_>>(); + + self.ir.tcx.struct_span_lint_hir( + lint::builtin::UNUSED_VARIABLES, + first_hir_id, + hir_ids_and_spans + .iter() + .map(|(_, pat_span, _)| *pat_span) + .collect::<Vec<_>>(), + |lint| { + let mut err = lint.build(&format!("unused variable: `{}`", name)); + err.multipart_suggestion( + "try ignoring the field", + shorthands, + Applicability::MachineApplicable, + ); + err.emit(); + }, + ); + } else { + let non_shorthands = non_shorthands + .into_iter() + .map(|(_, _, ident_span)| (ident_span, format!("_{}", name))) + .collect::<Vec<_>>(); + + self.ir.tcx.struct_span_lint_hir( + lint::builtin::UNUSED_VARIABLES, + first_hir_id, + hir_ids_and_spans + .iter() + .map(|(_, _, ident_span)| *ident_span) + .collect::<Vec<_>>(), + |lint| { + let mut err = lint.build(&format!("unused variable: `{}`", name)); + err.multipart_suggestion( + "if this is intentional, prefix it with an underscore", + non_shorthands, + Applicability::MachineApplicable, + ); + err.emit(); + }, + ); + } + } + } + } + + fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) { + if !self.live_on_exit(ln, var) { + self.report_unused_assign(hir_id, spans, var, |name| { + format!("value assigned to `{}` is never read", name) + }); + } + } + + fn report_unused_assign( + &self, + hir_id: HirId, + spans: Vec<Span>, + var: Variable, + message: impl Fn(&str) -> String, + ) { + if let Some(name) = self.should_warn(var) { + self.ir.tcx.struct_span_lint_hir( + lint::builtin::UNUSED_ASSIGNMENTS, + hir_id, + spans, + |lint| { + lint.build(&message(&name)) + .help("maybe it is overwritten before being read?") + .emit(); + }, + ) + } + } +} diff --git a/compiler/rustc_passes/src/liveness/rwu_table.rs b/compiler/rustc_passes/src/liveness/rwu_table.rs new file mode 100644 index 000000000..6d5983f53 --- /dev/null +++ b/compiler/rustc_passes/src/liveness/rwu_table.rs @@ -0,0 +1,145 @@ +use crate::liveness::{LiveNode, Variable}; +use std::iter; + +#[derive(Clone, Copy)] +pub(super) struct RWU { + pub(super) reader: bool, + pub(super) writer: bool, + pub(super) used: bool, +} + +/// Conceptually, this is like a `Vec<Vec<RWU>>`. But the number of +/// RWU`s can get very large, so it uses a more compact representation. +pub(super) struct RWUTable { + /// Total number of live nodes. + live_nodes: usize, + /// Total number of variables. + vars: usize, + + /// A compressed representation of `RWU`s. + /// + /// Each word represents 2 different `RWU`s packed together. Each packed RWU + /// is stored in 4 bits: a reader bit, a writer bit, a used bit and a + /// padding bit. + /// + /// The data for each live node is contiguous and starts at a word boundary, + /// so there might be an unused space left. + words: Vec<u8>, + /// Number of words per each live node. + live_node_words: usize, +} + +impl RWUTable { + const RWU_READER: u8 = 0b0001; + const RWU_WRITER: u8 = 0b0010; + const RWU_USED: u8 = 0b0100; + const RWU_MASK: u8 = 0b1111; + + /// Size of packed RWU in bits. + const RWU_BITS: usize = 4; + /// Size of a word in bits. + const WORD_BITS: usize = std::mem::size_of::<u8>() * 8; + /// Number of packed RWUs that fit into a single word. + const WORD_RWU_COUNT: usize = Self::WORD_BITS / Self::RWU_BITS; + + pub(super) fn new(live_nodes: usize, vars: usize) -> RWUTable { + let live_node_words = (vars + Self::WORD_RWU_COUNT - 1) / Self::WORD_RWU_COUNT; + Self { live_nodes, vars, live_node_words, words: vec![0u8; live_node_words * live_nodes] } + } + + fn word_and_shift(&self, ln: LiveNode, var: Variable) -> (usize, u32) { + assert!(ln.index() < self.live_nodes); + assert!(var.index() < self.vars); + + let var = var.index(); + let word = var / Self::WORD_RWU_COUNT; + let shift = Self::RWU_BITS * (var % Self::WORD_RWU_COUNT); + (ln.index() * self.live_node_words + word, shift as u32) + } + + fn pick2_rows_mut(&mut self, a: LiveNode, b: LiveNode) -> (&mut [u8], &mut [u8]) { + assert!(a.index() < self.live_nodes); + assert!(b.index() < self.live_nodes); + assert!(a != b); + + let a_start = a.index() * self.live_node_words; + let b_start = b.index() * self.live_node_words; + + unsafe { + let ptr = self.words.as_mut_ptr(); + ( + std::slice::from_raw_parts_mut(ptr.add(a_start), self.live_node_words), + std::slice::from_raw_parts_mut(ptr.add(b_start), self.live_node_words), + ) + } + } + + pub(super) fn copy(&mut self, dst: LiveNode, src: LiveNode) { + if dst == src { + return; + } + + let (dst_row, src_row) = self.pick2_rows_mut(dst, src); + dst_row.copy_from_slice(src_row); + } + + /// Sets `dst` to the union of `dst` and `src`, returns true if `dst` was + /// changed. + pub(super) fn union(&mut self, dst: LiveNode, src: LiveNode) -> bool { + if dst == src { + return false; + } + + let mut changed = false; + let (dst_row, src_row) = self.pick2_rows_mut(dst, src); + for (dst_word, src_word) in iter::zip(dst_row, &*src_row) { + let old = *dst_word; + let new = *dst_word | src_word; + *dst_word = new; + changed |= old != new; + } + changed + } + + pub(super) fn get_reader(&self, ln: LiveNode, var: Variable) -> bool { + let (word, shift) = self.word_and_shift(ln, var); + (self.words[word] >> shift) & Self::RWU_READER != 0 + } + + pub(super) fn get_writer(&self, ln: LiveNode, var: Variable) -> bool { + let (word, shift) = self.word_and_shift(ln, var); + (self.words[word] >> shift) & Self::RWU_WRITER != 0 + } + + pub(super) fn get_used(&self, ln: LiveNode, var: Variable) -> bool { + let (word, shift) = self.word_and_shift(ln, var); + (self.words[word] >> shift) & Self::RWU_USED != 0 + } + + pub(super) fn get(&self, ln: LiveNode, var: Variable) -> RWU { + let (word, shift) = self.word_and_shift(ln, var); + let rwu_packed = self.words[word] >> shift; + RWU { + reader: rwu_packed & Self::RWU_READER != 0, + writer: rwu_packed & Self::RWU_WRITER != 0, + used: rwu_packed & Self::RWU_USED != 0, + } + } + + pub(super) fn set(&mut self, ln: LiveNode, var: Variable, rwu: RWU) { + let mut packed = 0; + if rwu.reader { + packed |= Self::RWU_READER; + } + if rwu.writer { + packed |= Self::RWU_WRITER; + } + if rwu.used { + packed |= Self::RWU_USED; + } + + let (word, shift) = self.word_and_shift(ln, var); + let word = &mut self.words[word]; + *word = (*word & !(Self::RWU_MASK << shift)) | (packed << shift) + } +} diff --git a/compiler/rustc_passes/src/loops.rs b/compiler/rustc_passes/src/loops.rs new file mode 100644 index 000000000..cdda0e388 --- /dev/null +++ b/compiler/rustc_passes/src/loops.rs @@ -0,0 +1,287 @@ +use Context::*; + +use rustc_errors::{struct_span_err, Applicability}; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{Destination, Movability, Node}; +use rustc_middle::hir::map::Map; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_session::Session; +use rustc_span::hygiene::DesugaringKind; +use rustc_span::Span; + +#[derive(Clone, Copy, Debug, PartialEq)] +enum Context { + Normal, + Loop(hir::LoopSource), + Closure(Span), + AsyncClosure(Span), + LabeledBlock, + AnonConst, +} + +#[derive(Copy, Clone)] +struct CheckLoopVisitor<'a, 'hir> { + sess: &'a Session, + hir_map: Map<'hir>, + cx: Context, +} + +fn check_mod_loops(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { + tcx.hir().visit_item_likes_in_module( + module_def_id, + &mut CheckLoopVisitor { sess: &tcx.sess, hir_map: tcx.hir(), cx: Normal }, + ); +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { check_mod_loops, ..*providers }; +} + +impl<'a, 'hir> Visitor<'hir> for CheckLoopVisitor<'a, 'hir> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.hir_map + } + + fn visit_anon_const(&mut self, c: &'hir hir::AnonConst) { + self.with_context(AnonConst, |v| intravisit::walk_anon_const(v, c)); + } + + fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) { + match e.kind { + hir::ExprKind::Loop(ref b, _, source, _) => { + self.with_context(Loop(source), |v| v.visit_block(&b)); + } + hir::ExprKind::Closure(&hir::Closure { + ref fn_decl, + body, + fn_decl_span, + movability, + .. + }) => { + let cx = if let Some(Movability::Static) = movability { + AsyncClosure(fn_decl_span) + } else { + Closure(fn_decl_span) + }; + self.visit_fn_decl(&fn_decl); + self.with_context(cx, |v| v.visit_nested_body(body)); + } + hir::ExprKind::Block(ref b, Some(_label)) => { + self.with_context(LabeledBlock, |v| v.visit_block(&b)); + } + hir::ExprKind::Break(break_label, ref opt_expr) => { + if let Some(e) = opt_expr { + self.visit_expr(e); + } + + if self.require_label_in_labeled_block(e.span, &break_label, "break") { + // If we emitted an error about an unlabeled break in a labeled + // block, we don't need any further checking for this break any more + return; + } + + let loop_id = match break_label.target_id { + Ok(loop_id) => Some(loop_id), + Err(hir::LoopIdError::OutsideLoopScope) => None, + Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => { + self.emit_unlabled_cf_in_while_condition(e.span, "break"); + None + } + Err(hir::LoopIdError::UnresolvedLabel) => None, + }; + + if let Some(Node::Block(_)) = loop_id.and_then(|id| self.hir_map.find(id)) { + return; + } + + if let Some(break_expr) = opt_expr { + let (head, loop_label, loop_kind) = if let Some(loop_id) = loop_id { + match self.hir_map.expect_expr(loop_id).kind { + hir::ExprKind::Loop(_, label, source, sp) => { + (Some(sp), label, Some(source)) + } + ref r => { + span_bug!(e.span, "break label resolved to a non-loop: {:?}", r) + } + } + } else { + (None, None, None) + }; + match loop_kind { + None | Some(hir::LoopSource::Loop) => (), + Some(kind) => { + let mut err = struct_span_err!( + self.sess, + e.span, + E0571, + "`break` with value from a `{}` loop", + kind.name() + ); + err.span_label( + e.span, + "can only break with a value inside `loop` or breakable block", + ); + if let Some(head) = head { + err.span_label( + head, + &format!( + "you can't `break` with a value in a `{}` loop", + kind.name() + ), + ); + } + err.span_suggestion( + e.span, + &format!( + "use `break` on its own without a value inside this `{}` loop", + kind.name(), + ), + format!( + "break{}", + break_label + .label + .map_or_else(String::new, |l| format!(" {}", l.ident)) + ), + Applicability::MaybeIncorrect, + ); + if let (Some(label), None) = (loop_label, break_label.label) { + match break_expr.kind { + hir::ExprKind::Path(hir::QPath::Resolved( + None, + hir::Path { + segments: [segment], + res: hir::def::Res::Err, + .. + }, + )) if label.ident.to_string() + == format!("'{}", segment.ident) => + { + // This error is redundant, we will have already emitted a + // suggestion to use the label when `segment` wasn't found + // (hence the `Res::Err` check). + err.delay_as_bug(); + } + _ => { + err.span_suggestion( + break_expr.span, + "alternatively, you might have meant to use the \ + available loop label", + label.ident, + Applicability::MaybeIncorrect, + ); + } + } + } + err.emit(); + } + } + } + + self.require_break_cx("break", e.span); + } + hir::ExprKind::Continue(destination) => { + self.require_label_in_labeled_block(e.span, &destination, "continue"); + + match destination.target_id { + Ok(loop_id) => { + if let Node::Block(block) = self.hir_map.find(loop_id).unwrap() { + struct_span_err!( + self.sess, + e.span, + E0696, + "`continue` pointing to a labeled block" + ) + .span_label(e.span, "labeled blocks cannot be `continue`'d") + .span_label(block.span, "labeled block the `continue` points to") + .emit(); + } + } + Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => { + self.emit_unlabled_cf_in_while_condition(e.span, "continue"); + } + Err(_) => {} + } + self.require_break_cx("continue", e.span) + } + _ => intravisit::walk_expr(self, e), + } + } +} + +impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> { + fn with_context<F>(&mut self, cx: Context, f: F) + where + F: FnOnce(&mut CheckLoopVisitor<'a, 'hir>), + { + let old_cx = self.cx; + self.cx = cx; + f(self); + self.cx = old_cx; + } + + fn require_break_cx(&self, name: &str, span: Span) { + let err_inside_of = |article, ty, closure_span| { + struct_span_err!(self.sess, span, E0267, "`{}` inside of {} {}", name, article, ty) + .span_label(span, format!("cannot `{}` inside of {} {}", name, article, ty)) + .span_label(closure_span, &format!("enclosing {}", ty)) + .emit(); + }; + + match self.cx { + LabeledBlock | Loop(_) => {} + Closure(closure_span) => err_inside_of("a", "closure", closure_span), + AsyncClosure(closure_span) => err_inside_of("an", "`async` block", closure_span), + Normal | AnonConst => { + struct_span_err!(self.sess, span, E0268, "`{}` outside of a loop", name) + .span_label(span, format!("cannot `{}` outside of a loop", name)) + .emit(); + } + } + } + + fn require_label_in_labeled_block( + &mut self, + span: Span, + label: &Destination, + cf_type: &str, + ) -> bool { + if !span.is_desugaring(DesugaringKind::QuestionMark) && self.cx == LabeledBlock { + if label.label.is_none() { + struct_span_err!( + self.sess, + span, + E0695, + "unlabeled `{}` inside of a labeled block", + cf_type + ) + .span_label( + span, + format!( + "`{}` statements that would diverge to or through \ + a labeled block need to bear a label", + cf_type + ), + ) + .emit(); + return true; + } + } + false + } + fn emit_unlabled_cf_in_while_condition(&mut self, span: Span, cf_type: &str) { + struct_span_err!( + self.sess, + span, + E0590, + "`break` or `continue` with no label in the condition of a `while` loop" + ) + .span_label(span, format!("unlabeled `{}` in the condition of a `while` loop", cf_type)) + .emit(); + } +} diff --git a/compiler/rustc_passes/src/naked_functions.rs b/compiler/rustc_passes/src/naked_functions.rs new file mode 100644 index 000000000..20765abf3 --- /dev/null +++ b/compiler/rustc_passes/src/naked_functions.rs @@ -0,0 +1,335 @@ +//! Checks validity of naked functions. + +use rustc_ast::{Attribute, InlineAsmOptions}; +use rustc_errors::{struct_span_err, Applicability}; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::{FnKind, Visitor}; +use rustc_hir::{ExprKind, HirId, InlineAsmOperand, StmtKind}; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_session::lint::builtin::UNDEFINED_NAKED_FUNCTION_ABI; +use rustc_span::symbol::sym; +use rustc_span::Span; +use rustc_target::spec::abi::Abi; + +fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { + tcx.hir().visit_item_likes_in_module(module_def_id, &mut CheckNakedFunctions { tcx }); +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { check_mod_naked_functions, ..*providers }; +} + +struct CheckNakedFunctions<'tcx> { + tcx: TyCtxt<'tcx>, +} + +impl<'tcx> Visitor<'tcx> for CheckNakedFunctions<'tcx> { + fn visit_fn( + &mut self, + fk: FnKind<'_>, + _fd: &'tcx hir::FnDecl<'tcx>, + body_id: hir::BodyId, + span: Span, + hir_id: HirId, + ) { + let ident_span; + let fn_header; + + match fk { + FnKind::Closure => { + // Closures with a naked attribute are rejected during attribute + // check. Don't validate them any further. + return; + } + FnKind::ItemFn(ident, _, ref header, ..) => { + ident_span = ident.span; + fn_header = header; + } + + FnKind::Method(ident, ref sig, ..) => { + ident_span = ident.span; + fn_header = &sig.header; + } + } + + let attrs = self.tcx.hir().attrs(hir_id); + let naked = attrs.iter().any(|attr| attr.has_name(sym::naked)); + if naked { + let body = self.tcx.hir().body(body_id); + check_abi(self.tcx, hir_id, fn_header.abi, ident_span); + check_no_patterns(self.tcx, body.params); + check_no_parameters_use(self.tcx, body); + check_asm(self.tcx, body, span); + check_inline(self.tcx, attrs); + } + } +} + +/// Check that the function isn't inlined. +fn check_inline(tcx: TyCtxt<'_>, attrs: &[Attribute]) { + for attr in attrs.iter().filter(|attr| attr.has_name(sym::inline)) { + tcx.sess.struct_span_err(attr.span, "naked functions cannot be inlined").emit(); + } +} + +/// Checks that function uses non-Rust ABI. +fn check_abi(tcx: TyCtxt<'_>, hir_id: HirId, abi: Abi, fn_ident_span: Span) { + if abi == Abi::Rust { + tcx.struct_span_lint_hir(UNDEFINED_NAKED_FUNCTION_ABI, hir_id, fn_ident_span, |lint| { + lint.build("Rust ABI is unsupported in naked functions").emit(); + }); + } +} + +/// Checks that parameters don't use patterns. Mirrors the checks for function declarations. +fn check_no_patterns(tcx: TyCtxt<'_>, params: &[hir::Param<'_>]) { + for param in params { + match param.pat.kind { + hir::PatKind::Wild + | hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, _, None) => {} + _ => { + tcx.sess + .struct_span_err( + param.pat.span, + "patterns not allowed in naked function parameters", + ) + .emit(); + } + } + } +} + +/// Checks that function parameters aren't used in the function body. +fn check_no_parameters_use<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>) { + let mut params = hir::HirIdSet::default(); + for param in body.params { + param.pat.each_binding(|_binding_mode, hir_id, _span, _ident| { + params.insert(hir_id); + }); + } + CheckParameters { tcx, params }.visit_body(body); +} + +struct CheckParameters<'tcx> { + tcx: TyCtxt<'tcx>, + params: hir::HirIdSet, +} + +impl<'tcx> Visitor<'tcx> for CheckParameters<'tcx> { + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + if let hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(var_hir_id), .. }, + )) = expr.kind + { + if self.params.contains(var_hir_id) { + self.tcx + .sess + .struct_span_err( + expr.span, + "referencing function parameters is not allowed in naked functions", + ) + .help("follow the calling convention in asm block to use parameters") + .emit(); + return; + } + } + hir::intravisit::walk_expr(self, expr); + } +} + +/// Checks that function body contains a single inline assembly block. +fn check_asm<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>, fn_span: Span) { + let mut this = CheckInlineAssembly { tcx, items: Vec::new() }; + this.visit_body(body); + if let [(ItemKind::Asm | ItemKind::Err, _)] = this.items[..] { + // Ok. + } else { + let mut diag = struct_span_err!( + tcx.sess, + fn_span, + E0787, + "naked functions must contain a single asm block" + ); + + let mut must_show_error = false; + let mut has_asm = false; + let mut has_err = false; + for &(kind, span) in &this.items { + match kind { + ItemKind::Asm if has_asm => { + must_show_error = true; + diag.span_label(span, "multiple asm blocks are unsupported in naked functions"); + } + ItemKind::Asm => has_asm = true, + ItemKind::NonAsm => { + must_show_error = true; + diag.span_label(span, "non-asm is unsupported in naked functions"); + } + ItemKind::Err => has_err = true, + } + } + + // If the naked function only contains a single asm block and a non-zero number of + // errors, then don't show an additional error. This allows for appending/prepending + // `compile_error!("...")` statements and reduces error noise. + if must_show_error || !has_err { + diag.emit(); + } else { + diag.cancel(); + } + } +} + +struct CheckInlineAssembly<'tcx> { + tcx: TyCtxt<'tcx>, + items: Vec<(ItemKind, Span)>, +} + +#[derive(Copy, Clone)] +enum ItemKind { + Asm, + NonAsm, + Err, +} + +impl<'tcx> CheckInlineAssembly<'tcx> { + fn check_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, span: Span) { + match expr.kind { + ExprKind::Box(..) + | ExprKind::ConstBlock(..) + | ExprKind::Array(..) + | ExprKind::Call(..) + | ExprKind::MethodCall(..) + | ExprKind::Tup(..) + | ExprKind::Binary(..) + | ExprKind::Unary(..) + | ExprKind::Lit(..) + | ExprKind::Cast(..) + | ExprKind::Type(..) + | ExprKind::Loop(..) + | ExprKind::Match(..) + | ExprKind::If(..) + | ExprKind::Closure { .. } + | ExprKind::Assign(..) + | ExprKind::AssignOp(..) + | ExprKind::Field(..) + | ExprKind::Index(..) + | ExprKind::Path(..) + | ExprKind::AddrOf(..) + | ExprKind::Let(..) + | ExprKind::Break(..) + | ExprKind::Continue(..) + | ExprKind::Ret(..) + | ExprKind::Struct(..) + | ExprKind::Repeat(..) + | ExprKind::Yield(..) => { + self.items.push((ItemKind::NonAsm, span)); + } + + ExprKind::InlineAsm(ref asm) => { + self.items.push((ItemKind::Asm, span)); + self.check_inline_asm(asm, span); + } + + ExprKind::DropTemps(..) | ExprKind::Block(..) => { + hir::intravisit::walk_expr(self, expr); + } + + ExprKind::Err => { + self.items.push((ItemKind::Err, span)); + } + } + } + + fn check_inline_asm(&self, asm: &'tcx hir::InlineAsm<'tcx>, span: Span) { + let unsupported_operands: Vec<Span> = asm + .operands + .iter() + .filter_map(|&(ref op, op_sp)| match op { + InlineAsmOperand::Const { .. } + | InlineAsmOperand::SymFn { .. } + | InlineAsmOperand::SymStatic { .. } => None, + InlineAsmOperand::In { .. } + | InlineAsmOperand::Out { .. } + | InlineAsmOperand::InOut { .. } + | InlineAsmOperand::SplitInOut { .. } => Some(op_sp), + }) + .collect(); + if !unsupported_operands.is_empty() { + struct_span_err!( + self.tcx.sess, + unsupported_operands, + E0787, + "only `const` and `sym` operands are supported in naked functions", + ) + .emit(); + } + + let unsupported_options: Vec<&'static str> = [ + (InlineAsmOptions::MAY_UNWIND, "`may_unwind`"), + (InlineAsmOptions::NOMEM, "`nomem`"), + (InlineAsmOptions::NOSTACK, "`nostack`"), + (InlineAsmOptions::PRESERVES_FLAGS, "`preserves_flags`"), + (InlineAsmOptions::PURE, "`pure`"), + (InlineAsmOptions::READONLY, "`readonly`"), + ] + .iter() + .filter_map(|&(option, name)| if asm.options.contains(option) { Some(name) } else { None }) + .collect(); + + if !unsupported_options.is_empty() { + struct_span_err!( + self.tcx.sess, + span, + E0787, + "asm options unsupported in naked functions: {}", + unsupported_options.join(", ") + ) + .emit(); + } + + if !asm.options.contains(InlineAsmOptions::NORETURN) { + let last_span = asm + .operands + .last() + .map_or_else(|| asm.template_strs.last().unwrap().2, |op| op.1) + .shrink_to_hi(); + + struct_span_err!( + self.tcx.sess, + span, + E0787, + "asm in naked functions must use `noreturn` option" + ) + .span_suggestion( + last_span, + "consider specifying that the asm block is responsible \ + for returning from the function", + ", options(noreturn)", + Applicability::MachineApplicable, + ) + .emit(); + } + } +} + +impl<'tcx> Visitor<'tcx> for CheckInlineAssembly<'tcx> { + fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) { + match stmt.kind { + StmtKind::Item(..) => {} + StmtKind::Local(..) => { + self.items.push((ItemKind::NonAsm, stmt.span)); + } + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { + self.check_expr(expr, stmt.span); + } + } + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + self.check_expr(&expr, expr.span); + } +} diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs new file mode 100644 index 000000000..f7e3fac6b --- /dev/null +++ b/compiler/rustc_passes/src/reachable.rs @@ -0,0 +1,417 @@ +// Finds items that are externally reachable, to determine which items +// need to have their metadata (and possibly their AST) serialized. +// All items that can be referred to through an exported name are +// reachable, and when a reachable thing is inline or generic, it +// makes all other generics or inline functions that it references +// reachable as well. + +use rustc_data_structures::fx::FxHashSet; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::Node; +use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; +use rustc_middle::middle::privacy; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::{self, DefIdTree, TyCtxt}; +use rustc_session::config::CrateType; +use rustc_target::spec::abi::Abi; + +// Returns true if the given item must be inlined because it may be +// monomorphized or it was marked with `#[inline]`. This will only return +// true for functions. +fn item_might_be_inlined(tcx: TyCtxt<'_>, item: &hir::Item<'_>, attrs: &CodegenFnAttrs) -> bool { + if attrs.requests_inline() { + return true; + } + + match item.kind { + hir::ItemKind::Fn(ref sig, ..) if sig.header.is_const() => true, + hir::ItemKind::Impl { .. } | hir::ItemKind::Fn(..) => { + let generics = tcx.generics_of(item.def_id); + generics.requires_monomorphization(tcx) + } + _ => false, + } +} + +fn method_might_be_inlined( + tcx: TyCtxt<'_>, + impl_item: &hir::ImplItem<'_>, + impl_src: LocalDefId, +) -> bool { + let codegen_fn_attrs = tcx.codegen_fn_attrs(impl_item.hir_id().owner.to_def_id()); + let generics = tcx.generics_of(impl_item.def_id); + if codegen_fn_attrs.requests_inline() || generics.requires_monomorphization(tcx) { + return true; + } + if let hir::ImplItemKind::Fn(method_sig, _) = &impl_item.kind { + if method_sig.header.is_const() { + return true; + } + } + match tcx.hir().find_by_def_id(impl_src) { + Some(Node::Item(item)) => item_might_be_inlined(tcx, &item, codegen_fn_attrs), + Some(..) | None => span_bug!(impl_item.span, "impl did is not an item"), + } +} + +// Information needed while computing reachability. +struct ReachableContext<'tcx> { + // The type context. + tcx: TyCtxt<'tcx>, + maybe_typeck_results: Option<&'tcx ty::TypeckResults<'tcx>>, + // The set of items which must be exported in the linkage sense. + reachable_symbols: FxHashSet<LocalDefId>, + // A worklist of item IDs. Each item ID in this worklist will be inlined + // and will be scanned for further references. + // FIXME(eddyb) benchmark if this would be faster as a `VecDeque`. + worklist: Vec<LocalDefId>, + // Whether any output of this compilation is a library + any_library: bool, +} + +impl<'tcx> Visitor<'tcx> for ReachableContext<'tcx> { + fn visit_nested_body(&mut self, body: hir::BodyId) { + let old_maybe_typeck_results = + self.maybe_typeck_results.replace(self.tcx.typeck_body(body)); + let body = self.tcx.hir().body(body); + self.visit_body(body); + self.maybe_typeck_results = old_maybe_typeck_results; + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + let res = match expr.kind { + hir::ExprKind::Path(ref qpath) => { + Some(self.typeck_results().qpath_res(qpath, expr.hir_id)) + } + hir::ExprKind::MethodCall(..) => self + .typeck_results() + .type_dependent_def(expr.hir_id) + .map(|(kind, def_id)| Res::Def(kind, def_id)), + _ => None, + }; + + if let Some(res) = res && let Some(def_id) = res.opt_def_id().and_then(|el| el.as_local()) { + if self.def_id_represents_local_inlined_item(def_id.to_def_id()) { + self.worklist.push(def_id); + } else { + match res { + // If this path leads to a constant, then we need to + // recurse into the constant to continue finding + // items that are reachable. + Res::Def(DefKind::Const | DefKind::AssocConst, _) => { + self.worklist.push(def_id); + } + + // If this wasn't a static, then the destination is + // surely reachable. + _ => { + self.reachable_symbols.insert(def_id); + } + } + } + } + + intravisit::walk_expr(self, expr) + } +} + +impl<'tcx> ReachableContext<'tcx> { + /// Gets the type-checking results for the current body. + /// As this will ICE if called outside bodies, only call when working with + /// `Expr` or `Pat` nodes (they are guaranteed to be found only in bodies). + #[track_caller] + fn typeck_results(&self) -> &'tcx ty::TypeckResults<'tcx> { + self.maybe_typeck_results + .expect("`ReachableContext::typeck_results` called outside of body") + } + + // Returns true if the given def ID represents a local item that is + // eligible for inlining and false otherwise. + fn def_id_represents_local_inlined_item(&self, def_id: DefId) -> bool { + let Some(def_id) = def_id.as_local() else { + return false; + }; + + match self.tcx.hir().find_by_def_id(def_id) { + Some(Node::Item(item)) => match item.kind { + hir::ItemKind::Fn(..) => { + item_might_be_inlined(self.tcx, &item, self.tcx.codegen_fn_attrs(def_id)) + } + _ => false, + }, + Some(Node::TraitItem(trait_method)) => match trait_method.kind { + hir::TraitItemKind::Const(_, ref default) => default.is_some(), + hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)) => true, + hir::TraitItemKind::Fn(_, hir::TraitFn::Required(_)) + | hir::TraitItemKind::Type(..) => false, + }, + Some(Node::ImplItem(impl_item)) => match impl_item.kind { + hir::ImplItemKind::Const(..) => true, + hir::ImplItemKind::Fn(..) => { + let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id); + let impl_did = self.tcx.hir().get_parent_item(hir_id); + method_might_be_inlined(self.tcx, impl_item, impl_did) + } + hir::ImplItemKind::TyAlias(_) => false, + }, + Some(_) => false, + None => false, // This will happen for default methods. + } + } + + // Step 2: Mark all symbols that the symbols on the worklist touch. + fn propagate(&mut self) { + let mut scanned = FxHashSet::default(); + while let Some(search_item) = self.worklist.pop() { + if !scanned.insert(search_item) { + continue; + } + + if let Some(ref item) = self.tcx.hir().find_by_def_id(search_item) { + self.propagate_node(item, search_item); + } + } + } + + fn propagate_node(&mut self, node: &Node<'tcx>, search_item: LocalDefId) { + if !self.any_library { + // If we are building an executable, only explicitly extern + // types need to be exported. + let reachable = + if let Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, ..), .. }) + | Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Fn(sig, ..), .. + }) = *node + { + sig.header.abi != Abi::Rust + } else { + false + }; + let codegen_attrs = if self.tcx.def_kind(search_item).has_codegen_attrs() { + self.tcx.codegen_fn_attrs(search_item) + } else { + CodegenFnAttrs::EMPTY + }; + let is_extern = codegen_attrs.contains_extern_indicator(); + let std_internal = + codegen_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL); + if reachable || is_extern || std_internal { + self.reachable_symbols.insert(search_item); + } + } else { + // If we are building a library, then reachable symbols will + // continue to participate in linkage after this product is + // produced. In this case, we traverse the ast node, recursing on + // all reachable nodes from this one. + self.reachable_symbols.insert(search_item); + } + + match *node { + Node::Item(item) => { + match item.kind { + hir::ItemKind::Fn(.., body) => { + if item_might_be_inlined( + self.tcx, + &item, + self.tcx.codegen_fn_attrs(item.def_id), + ) { + self.visit_nested_body(body); + } + } + + // Reachable constants will be inlined into other crates + // unconditionally, so we need to make sure that their + // contents are also reachable. + hir::ItemKind::Const(_, init) | hir::ItemKind::Static(_, _, init) => { + self.visit_nested_body(init); + } + + // These are normal, nothing reachable about these + // inherently and their children are already in the + // worklist, as determined by the privacy pass + hir::ItemKind::ExternCrate(_) + | hir::ItemKind::Use(..) + | hir::ItemKind::OpaqueTy(..) + | hir::ItemKind::TyAlias(..) + | hir::ItemKind::Macro(..) + | hir::ItemKind::Mod(..) + | hir::ItemKind::ForeignMod { .. } + | hir::ItemKind::Impl { .. } + | hir::ItemKind::Trait(..) + | hir::ItemKind::TraitAlias(..) + | hir::ItemKind::Struct(..) + | hir::ItemKind::Enum(..) + | hir::ItemKind::Union(..) + | hir::ItemKind::GlobalAsm(..) => {} + } + } + Node::TraitItem(trait_method) => { + match trait_method.kind { + hir::TraitItemKind::Const(_, None) + | hir::TraitItemKind::Fn(_, hir::TraitFn::Required(_)) => { + // Keep going, nothing to get exported + } + hir::TraitItemKind::Const(_, Some(body_id)) + | hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(body_id)) => { + self.visit_nested_body(body_id); + } + hir::TraitItemKind::Type(..) => {} + } + } + Node::ImplItem(impl_item) => match impl_item.kind { + hir::ImplItemKind::Const(_, body) => { + self.visit_nested_body(body); + } + hir::ImplItemKind::Fn(_, body) => { + let impl_def_id = self.tcx.local_parent(search_item); + if method_might_be_inlined(self.tcx, impl_item, impl_def_id) { + self.visit_nested_body(body) + } + } + hir::ImplItemKind::TyAlias(_) => {} + }, + Node::Expr(&hir::Expr { + kind: hir::ExprKind::Closure(&hir::Closure { body, .. }), + .. + }) => { + self.visit_nested_body(body); + } + // Nothing to recurse on for these + Node::ForeignItem(_) + | Node::Variant(_) + | Node::Ctor(..) + | Node::Field(_) + | Node::Ty(_) + | Node::Crate(_) => {} + _ => { + bug!( + "found unexpected node kind in worklist: {} ({:?})", + self.tcx + .hir() + .node_to_string(self.tcx.hir().local_def_id_to_hir_id(search_item)), + node, + ); + } + } + } +} + +fn check_item<'tcx>( + tcx: TyCtxt<'tcx>, + id: hir::ItemId, + worklist: &mut Vec<LocalDefId>, + access_levels: &privacy::AccessLevels, +) { + if has_custom_linkage(tcx, id.def_id) { + worklist.push(id.def_id); + } + + if !matches!(tcx.def_kind(id.def_id), DefKind::Impl) { + return; + } + + // We need only trait impls here, not inherent impls, and only non-exported ones + let item = tcx.hir().item(id); + if let hir::ItemKind::Impl(hir::Impl { of_trait: Some(ref trait_ref), ref items, .. }) = + item.kind + { + if !access_levels.is_reachable(item.def_id) { + worklist.extend(items.iter().map(|ii_ref| ii_ref.id.def_id)); + + let Res::Def(DefKind::Trait, trait_def_id) = trait_ref.path.res else { + unreachable!(); + }; + + if !trait_def_id.is_local() { + return; + } + + worklist.extend( + tcx.provided_trait_methods(trait_def_id).map(|assoc| assoc.def_id.expect_local()), + ); + } + } +} + +fn has_custom_linkage<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> bool { + // Anything which has custom linkage gets thrown on the worklist no + // matter where it is in the crate, along with "special std symbols" + // which are currently akin to allocator symbols. + if !tcx.def_kind(def_id).has_codegen_attrs() { + return false; + } + let codegen_attrs = tcx.codegen_fn_attrs(def_id); + codegen_attrs.contains_extern_indicator() + || codegen_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL) + // FIXME(nbdd0121): `#[used]` are marked as reachable here so it's picked up by + // `linked_symbols` in cg_ssa. They won't be exported in binary or cdylib due to their + // `SymbolExportLevel::Rust` export level but may end up being exported in dylibs. + || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED) + || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) +} + +fn reachable_set<'tcx>(tcx: TyCtxt<'tcx>, (): ()) -> FxHashSet<LocalDefId> { + let access_levels = &tcx.privacy_access_levels(()); + + let any_library = + tcx.sess.crate_types().iter().any(|ty| { + *ty == CrateType::Rlib || *ty == CrateType::Dylib || *ty == CrateType::ProcMacro + }); + let mut reachable_context = ReachableContext { + tcx, + maybe_typeck_results: None, + reachable_symbols: Default::default(), + worklist: Vec::new(), + any_library, + }; + + // Step 1: Seed the worklist with all nodes which were found to be public as + // a result of the privacy pass along with all local lang items and impl items. + // If other crates link to us, they're going to expect to be able to + // use the lang items, so we need to be sure to mark them as + // exported. + reachable_context.worklist.extend(access_levels.map.keys()); + for item in tcx.lang_items().items().iter() { + if let Some(def_id) = *item { + if let Some(def_id) = def_id.as_local() { + reachable_context.worklist.push(def_id); + } + } + } + { + // Some methods from non-exported (completely private) trait impls still have to be + // reachable if they are called from inlinable code. Generally, it's not known until + // monomorphization if a specific trait impl item can be reachable or not. So, we + // conservatively mark all of them as reachable. + // FIXME: One possible strategy for pruning the reachable set is to avoid marking impl + // items of non-exported traits (or maybe all local traits?) unless their respective + // trait items are used from inlinable code through method call syntax or UFCS, or their + // trait is a lang item. + let crate_items = tcx.hir_crate_items(()); + + for id in crate_items.items() { + check_item(tcx, id, &mut reachable_context.worklist, access_levels); + } + + for id in crate_items.impl_items() { + if has_custom_linkage(tcx, id.def_id) { + reachable_context.worklist.push(id.def_id); + } + } + } + + // Step 2: Mark all symbols that the symbols on the worklist touch. + reachable_context.propagate(); + + debug!("Inline reachability shows: {:?}", reachable_context.reachable_symbols); + + // Return the set of reachable symbols. + reachable_context.reachable_symbols +} + +pub fn provide(providers: &mut Providers) { + *providers = Providers { reachable_set, ..*providers }; +} diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs new file mode 100644 index 000000000..ca6a2ac3d --- /dev/null +++ b/compiler/rustc_passes/src/stability.rs @@ -0,0 +1,1063 @@ +//! A pass that annotates every item and method with its stability level, +//! propagating default levels lexically from parent to children ast nodes. + +use attr::StabilityLevel; +use rustc_attr::{self as attr, ConstStability, Stability, Unstable, UnstableReason}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; +use rustc_errors::{struct_span_err, Applicability}; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID}; +use rustc_hir::hir_id::CRATE_HIR_ID; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{FieldDef, Generics, HirId, Item, ItemKind, TraitRef, Ty, TyKind, Variant}; +use rustc_middle::hir::nested_filter; +use rustc_middle::middle::privacy::AccessLevels; +use rustc_middle::middle::stability::{AllowUnstable, DeprecationEntry, Index}; +use rustc_middle::ty::{query::Providers, TyCtxt}; +use rustc_session::lint; +use rustc_session::lint::builtin::{INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED}; +use rustc_session::Session; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::Span; +use rustc_target::spec::abi::Abi; + +use std::cmp::Ordering; +use std::iter; +use std::mem::replace; +use std::num::NonZeroU32; + +#[derive(PartialEq)] +enum AnnotationKind { + /// Annotation is required if not inherited from unstable parents. + Required, + /// Annotation is useless, reject it. + Prohibited, + /// Deprecation annotation is useless, reject it. (Stability attribute is still required.) + DeprecationProhibited, + /// Annotation itself is useless, but it can be propagated to children. + Container, +} + +/// Whether to inherit deprecation flags for nested items. In most cases, we do want to inherit +/// deprecation, because nested items rarely have individual deprecation attributes, and so +/// should be treated as deprecated if their parent is. However, default generic parameters +/// have separate deprecation attributes from their parents, so we do not wish to inherit +/// deprecation in this case. For example, inheriting deprecation for `T` in `Foo<T>` +/// would cause a duplicate warning arising from both `Foo` and `T` being deprecated. +#[derive(Clone)] +enum InheritDeprecation { + Yes, + No, +} + +impl InheritDeprecation { + fn yes(&self) -> bool { + matches!(self, InheritDeprecation::Yes) + } +} + +/// Whether to inherit const stability flags for nested items. In most cases, we do not want to +/// inherit const stability: just because an enclosing `fn` is const-stable does not mean +/// all `extern` imports declared in it should be const-stable! However, trait methods +/// inherit const stability attributes from their parent and do not have their own. +enum InheritConstStability { + Yes, + No, +} + +impl InheritConstStability { + fn yes(&self) -> bool { + matches!(self, InheritConstStability::Yes) + } +} + +enum InheritStability { + Yes, + No, +} + +impl InheritStability { + fn yes(&self) -> bool { + matches!(self, InheritStability::Yes) + } +} + +/// A private tree-walker for producing an `Index`. +struct Annotator<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + index: &'a mut Index, + parent_stab: Option<Stability>, + parent_const_stab: Option<ConstStability>, + parent_depr: Option<DeprecationEntry>, + in_trait_impl: bool, +} + +impl<'a, 'tcx> Annotator<'a, 'tcx> { + /// Determine the stability for a node based on its attributes and inherited stability. The + /// stability is recorded in the index and used as the parent. If the node is a function, + /// `fn_sig` is its signature. + fn annotate<F>( + &mut self, + def_id: LocalDefId, + item_sp: Span, + fn_sig: Option<&'tcx hir::FnSig<'tcx>>, + kind: AnnotationKind, + inherit_deprecation: InheritDeprecation, + inherit_const_stability: InheritConstStability, + inherit_from_parent: InheritStability, + visit_children: F, + ) where + F: FnOnce(&mut Self), + { + let attrs = self.tcx.hir().attrs(self.tcx.hir().local_def_id_to_hir_id(def_id)); + debug!("annotate(id = {:?}, attrs = {:?})", def_id, attrs); + + let depr = attr::find_deprecation(&self.tcx.sess, attrs); + let mut is_deprecated = false; + if let Some((depr, span)) = &depr { + is_deprecated = true; + + if kind == AnnotationKind::Prohibited || kind == AnnotationKind::DeprecationProhibited { + let hir_id = self.tcx.hir().local_def_id_to_hir_id(def_id); + self.tcx.struct_span_lint_hir(USELESS_DEPRECATED, hir_id, *span, |lint| { + lint.build("this `#[deprecated]` annotation has no effect") + .span_suggestion_short( + *span, + "remove the unnecessary deprecation attribute", + "", + rustc_errors::Applicability::MachineApplicable, + ) + .emit(); + }); + } + + // `Deprecation` is just two pointers, no need to intern it + let depr_entry = DeprecationEntry::local(*depr, def_id); + self.index.depr_map.insert(def_id, depr_entry); + } else if let Some(parent_depr) = self.parent_depr { + if inherit_deprecation.yes() { + is_deprecated = true; + info!("tagging child {:?} as deprecated from parent", def_id); + self.index.depr_map.insert(def_id, parent_depr); + } + } + + if !self.tcx.features().staged_api { + // Propagate unstability. This can happen even for non-staged-api crates in case + // -Zforce-unstable-if-unmarked is set. + if let Some(stab) = self.parent_stab { + if inherit_deprecation.yes() && stab.is_unstable() { + self.index.stab_map.insert(def_id, stab); + } + } + + self.recurse_with_stability_attrs( + depr.map(|(d, _)| DeprecationEntry::local(d, def_id)), + None, + None, + visit_children, + ); + return; + } + + let (stab, const_stab) = attr::find_stability(&self.tcx.sess, attrs, item_sp); + let mut const_span = None; + + let const_stab = const_stab.map(|(const_stab, const_span_node)| { + self.index.const_stab_map.insert(def_id, const_stab); + const_span = Some(const_span_node); + const_stab + }); + + // If the current node is a function, has const stability attributes and if it doesn not have an intrinsic ABI, + // check if the function/method is const or the parent impl block is const + if let (Some(const_span), Some(fn_sig)) = (const_span, fn_sig) { + if fn_sig.header.abi != Abi::RustIntrinsic + && fn_sig.header.abi != Abi::PlatformIntrinsic + && !fn_sig.header.is_const() + { + if !self.in_trait_impl + || (self.in_trait_impl && !self.tcx.is_const_fn_raw(def_id.to_def_id())) + { + missing_const_err(&self.tcx.sess, fn_sig.span, const_span); + } + } + } + + // `impl const Trait for Type` items forward their const stability to their + // immediate children. + if const_stab.is_none() { + debug!("annotate: const_stab not found, parent = {:?}", self.parent_const_stab); + if let Some(parent) = self.parent_const_stab { + if parent.is_const_unstable() { + self.index.const_stab_map.insert(def_id, parent); + } + } + } + + if let Some((rustc_attr::Deprecation { is_since_rustc_version: true, .. }, span)) = &depr { + if stab.is_none() { + struct_span_err!( + self.tcx.sess, + *span, + E0549, + "deprecated attribute must be paired with \ + either stable or unstable attribute" + ) + .emit(); + } + } + + let stab = stab.map(|(stab, span)| { + // Error if prohibited, or can't inherit anything from a container. + if kind == AnnotationKind::Prohibited + || (kind == AnnotationKind::Container && stab.level.is_stable() && is_deprecated) + { + self.tcx.sess.struct_span_err(span,"this stability annotation is useless") + .span_label(span, "useless stability annotation") + .span_label(item_sp, "the stability attribute annotates this item") + .emit(); + } + + debug!("annotate: found {:?}", stab); + + // Check if deprecated_since < stable_since. If it is, + // this is *almost surely* an accident. + if let (&Some(dep_since), &attr::Stable { since: stab_since, .. }) = + (&depr.as_ref().and_then(|(d, _)| d.since), &stab.level) + { + // Explicit version of iter::order::lt to handle parse errors properly + for (dep_v, stab_v) in + iter::zip(dep_since.as_str().split('.'), stab_since.as_str().split('.')) + { + match stab_v.parse::<u64>() { + Err(_) => { + self.tcx.sess.struct_span_err(span, "invalid stability version found") + .span_label(span, "invalid stability version") + .span_label(item_sp, "the stability attribute annotates this item") + .emit(); + break; + } + Ok(stab_vp) => match dep_v.parse::<u64>() { + Ok(dep_vp) => match dep_vp.cmp(&stab_vp) { + Ordering::Less => { + self.tcx.sess.struct_span_err(span, "an API can't be stabilized after it is deprecated") + .span_label(span, "invalid version") + .span_label(item_sp, "the stability attribute annotates this item") + .emit(); + break; + } + Ordering::Equal => continue, + Ordering::Greater => break, + }, + Err(_) => { + if dep_v != "TBD" { + self.tcx.sess.struct_span_err(span, "invalid deprecation version found") + .span_label(span, "invalid deprecation version") + .span_label(item_sp, "the stability attribute annotates this item") + .emit(); + } + break; + } + }, + } + } + } + + if let Stability { level: Unstable { implied_by: Some(implied_by), .. }, feature } = stab { + self.index.implications.insert(implied_by, feature); + } + + self.index.stab_map.insert(def_id, stab); + stab + }); + + if stab.is_none() { + debug!("annotate: stab not found, parent = {:?}", self.parent_stab); + if let Some(stab) = self.parent_stab { + if inherit_deprecation.yes() && stab.is_unstable() || inherit_from_parent.yes() { + self.index.stab_map.insert(def_id, stab); + } + } + } + + self.recurse_with_stability_attrs( + depr.map(|(d, _)| DeprecationEntry::local(d, def_id)), + stab, + if inherit_const_stability.yes() { const_stab } else { None }, + visit_children, + ); + } + + fn recurse_with_stability_attrs( + &mut self, + depr: Option<DeprecationEntry>, + stab: Option<Stability>, + const_stab: Option<ConstStability>, + f: impl FnOnce(&mut Self), + ) { + // These will be `Some` if this item changes the corresponding stability attribute. + let mut replaced_parent_depr = None; + let mut replaced_parent_stab = None; + let mut replaced_parent_const_stab = None; + + if let Some(depr) = depr { + replaced_parent_depr = Some(replace(&mut self.parent_depr, Some(depr))); + } + if let Some(stab) = stab { + replaced_parent_stab = Some(replace(&mut self.parent_stab, Some(stab))); + } + if let Some(const_stab) = const_stab { + replaced_parent_const_stab = + Some(replace(&mut self.parent_const_stab, Some(const_stab))); + } + + f(self); + + if let Some(orig_parent_depr) = replaced_parent_depr { + self.parent_depr = orig_parent_depr; + } + if let Some(orig_parent_stab) = replaced_parent_stab { + self.parent_stab = orig_parent_stab; + } + if let Some(orig_parent_const_stab) = replaced_parent_const_stab { + self.parent_const_stab = orig_parent_const_stab; + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { + /// Because stability levels are scoped lexically, we want to walk + /// nested items in the context of the outer item, so enable + /// deep-walking. + type NestedFilter = nested_filter::All; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + + fn visit_item(&mut self, i: &'tcx Item<'tcx>) { + let orig_in_trait_impl = self.in_trait_impl; + let mut kind = AnnotationKind::Required; + let mut const_stab_inherit = InheritConstStability::No; + let mut fn_sig = None; + + match i.kind { + // Inherent impls and foreign modules serve only as containers for other items, + // they don't have their own stability. They still can be annotated as unstable + // and propagate this instability to children, but this annotation is completely + // optional. They inherit stability from their parents when unannotated. + hir::ItemKind::Impl(hir::Impl { of_trait: None, .. }) + | hir::ItemKind::ForeignMod { .. } => { + self.in_trait_impl = false; + kind = AnnotationKind::Container; + } + hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) => { + self.in_trait_impl = true; + kind = AnnotationKind::DeprecationProhibited; + const_stab_inherit = InheritConstStability::Yes; + } + hir::ItemKind::Struct(ref sd, _) => { + if let Some(ctor_hir_id) = sd.ctor_hir_id() { + self.annotate( + self.tcx.hir().local_def_id(ctor_hir_id), + i.span, + None, + AnnotationKind::Required, + InheritDeprecation::Yes, + InheritConstStability::No, + InheritStability::Yes, + |_| {}, + ) + } + } + hir::ItemKind::Fn(ref item_fn_sig, _, _) => { + fn_sig = Some(item_fn_sig); + } + _ => {} + } + + self.annotate( + i.def_id, + i.span, + fn_sig, + kind, + InheritDeprecation::Yes, + const_stab_inherit, + InheritStability::No, + |v| intravisit::walk_item(v, i), + ); + self.in_trait_impl = orig_in_trait_impl; + } + + fn visit_trait_item(&mut self, ti: &'tcx hir::TraitItem<'tcx>) { + let fn_sig = match ti.kind { + hir::TraitItemKind::Fn(ref fn_sig, _) => Some(fn_sig), + _ => None, + }; + + self.annotate( + ti.def_id, + ti.span, + fn_sig, + AnnotationKind::Required, + InheritDeprecation::Yes, + InheritConstStability::No, + InheritStability::No, + |v| { + intravisit::walk_trait_item(v, ti); + }, + ); + } + + fn visit_impl_item(&mut self, ii: &'tcx hir::ImplItem<'tcx>) { + let kind = + if self.in_trait_impl { AnnotationKind::Prohibited } else { AnnotationKind::Required }; + + let fn_sig = match ii.kind { + hir::ImplItemKind::Fn(ref fn_sig, _) => Some(fn_sig), + _ => None, + }; + + self.annotate( + ii.def_id, + ii.span, + fn_sig, + kind, + InheritDeprecation::Yes, + InheritConstStability::No, + InheritStability::No, + |v| { + intravisit::walk_impl_item(v, ii); + }, + ); + } + + fn visit_variant(&mut self, var: &'tcx Variant<'tcx>, g: &'tcx Generics<'tcx>, item_id: HirId) { + self.annotate( + self.tcx.hir().local_def_id(var.id), + var.span, + None, + AnnotationKind::Required, + InheritDeprecation::Yes, + InheritConstStability::No, + InheritStability::Yes, + |v| { + if let Some(ctor_hir_id) = var.data.ctor_hir_id() { + v.annotate( + v.tcx.hir().local_def_id(ctor_hir_id), + var.span, + None, + AnnotationKind::Required, + InheritDeprecation::Yes, + InheritConstStability::No, + InheritStability::No, + |_| {}, + ); + } + + intravisit::walk_variant(v, var, g, item_id) + }, + ) + } + + fn visit_field_def(&mut self, s: &'tcx FieldDef<'tcx>) { + self.annotate( + self.tcx.hir().local_def_id(s.hir_id), + s.span, + None, + AnnotationKind::Required, + InheritDeprecation::Yes, + InheritConstStability::No, + InheritStability::Yes, + |v| { + intravisit::walk_field_def(v, s); + }, + ); + } + + fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem<'tcx>) { + self.annotate( + i.def_id, + i.span, + None, + AnnotationKind::Required, + InheritDeprecation::Yes, + InheritConstStability::No, + InheritStability::No, + |v| { + intravisit::walk_foreign_item(v, i); + }, + ); + } + + fn visit_generic_param(&mut self, p: &'tcx hir::GenericParam<'tcx>) { + let kind = match &p.kind { + // Allow stability attributes on default generic arguments. + hir::GenericParamKind::Type { default: Some(_), .. } + | hir::GenericParamKind::Const { default: Some(_), .. } => AnnotationKind::Container, + _ => AnnotationKind::Prohibited, + }; + + self.annotate( + self.tcx.hir().local_def_id(p.hir_id), + p.span, + None, + kind, + InheritDeprecation::No, + InheritConstStability::No, + InheritStability::No, + |v| { + intravisit::walk_generic_param(v, p); + }, + ); + } +} + +struct MissingStabilityAnnotations<'tcx> { + tcx: TyCtxt<'tcx>, + access_levels: &'tcx AccessLevels, +} + +impl<'tcx> MissingStabilityAnnotations<'tcx> { + fn check_missing_stability(&self, def_id: LocalDefId, span: Span) { + let stab = self.tcx.stability().local_stability(def_id); + if !self.tcx.sess.opts.test && stab.is_none() && self.access_levels.is_reachable(def_id) { + let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id()); + self.tcx.sess.span_err(span, &format!("{} has missing stability attribute", descr)); + } + } + + fn check_missing_const_stability(&self, def_id: LocalDefId, span: Span) { + if !self.tcx.features().staged_api { + return; + } + + let is_const = self.tcx.is_const_fn(def_id.to_def_id()) + || self.tcx.is_const_trait_impl_raw(def_id.to_def_id()); + let is_stable = self + .tcx + .lookup_stability(def_id) + .map_or(false, |stability| stability.level.is_stable()); + let missing_const_stability_attribute = self.tcx.lookup_const_stability(def_id).is_none(); + let is_reachable = self.access_levels.is_reachable(def_id); + + if is_const && is_stable && missing_const_stability_attribute && is_reachable { + let descr = self.tcx.def_kind(def_id).descr(def_id.to_def_id()); + self.tcx.sess.span_err(span, &format!("{descr} has missing const stability attribute")); + } + } +} + +impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + + fn visit_item(&mut self, i: &'tcx Item<'tcx>) { + // Inherent impls and foreign modules serve only as containers for other items, + // they don't have their own stability. They still can be annotated as unstable + // and propagate this instability to children, but this annotation is completely + // optional. They inherit stability from their parents when unannotated. + if !matches!( + i.kind, + hir::ItemKind::Impl(hir::Impl { of_trait: None, .. }) + | hir::ItemKind::ForeignMod { .. } + ) { + self.check_missing_stability(i.def_id, i.span); + } + + // Ensure stable `const fn` have a const stability attribute. + self.check_missing_const_stability(i.def_id, i.span); + + intravisit::walk_item(self, i) + } + + fn visit_trait_item(&mut self, ti: &'tcx hir::TraitItem<'tcx>) { + self.check_missing_stability(ti.def_id, ti.span); + intravisit::walk_trait_item(self, ti); + } + + fn visit_impl_item(&mut self, ii: &'tcx hir::ImplItem<'tcx>) { + let impl_def_id = self.tcx.hir().get_parent_item(ii.hir_id()); + if self.tcx.impl_trait_ref(impl_def_id).is_none() { + self.check_missing_stability(ii.def_id, ii.span); + self.check_missing_const_stability(ii.def_id, ii.span); + } + intravisit::walk_impl_item(self, ii); + } + + fn visit_variant(&mut self, var: &'tcx Variant<'tcx>, g: &'tcx Generics<'tcx>, item_id: HirId) { + self.check_missing_stability(self.tcx.hir().local_def_id(var.id), var.span); + intravisit::walk_variant(self, var, g, item_id); + } + + fn visit_field_def(&mut self, s: &'tcx FieldDef<'tcx>) { + self.check_missing_stability(self.tcx.hir().local_def_id(s.hir_id), s.span); + intravisit::walk_field_def(self, s); + } + + fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem<'tcx>) { + self.check_missing_stability(i.def_id, i.span); + intravisit::walk_foreign_item(self, i); + } + // Note that we don't need to `check_missing_stability` for default generic parameters, + // as we assume that any default generic parameters without attributes are automatically + // stable (assuming they have not inherited instability from their parent). +} + +fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index { + let mut index = Index { + stab_map: Default::default(), + const_stab_map: Default::default(), + depr_map: Default::default(), + implications: Default::default(), + }; + + { + let mut annotator = Annotator { + tcx, + index: &mut index, + parent_stab: None, + parent_const_stab: None, + parent_depr: None, + in_trait_impl: false, + }; + + // If the `-Z force-unstable-if-unmarked` flag is passed then we provide + // a parent stability annotation which indicates that this is private + // with the `rustc_private` feature. This is intended for use when + // compiling `librustc_*` crates themselves so we can leverage crates.io + // while maintaining the invariant that all sysroot crates are unstable + // by default and are unable to be used. + if tcx.sess.opts.unstable_opts.force_unstable_if_unmarked { + let stability = Stability { + level: attr::StabilityLevel::Unstable { + reason: UnstableReason::Default, + issue: NonZeroU32::new(27812), + is_soft: false, + implied_by: None, + }, + feature: sym::rustc_private, + }; + annotator.parent_stab = Some(stability); + } + + annotator.annotate( + CRATE_DEF_ID, + tcx.hir().span(CRATE_HIR_ID), + None, + AnnotationKind::Required, + InheritDeprecation::Yes, + InheritConstStability::No, + InheritStability::No, + |v| tcx.hir().walk_toplevel_module(v), + ); + } + index +} + +/// Cross-references the feature names of unstable APIs with enabled +/// features and possibly prints errors. +fn check_mod_unstable_api_usage(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { + tcx.hir().visit_item_likes_in_module(module_def_id, &mut Checker { tcx }); +} + +pub(crate) fn provide(providers: &mut Providers) { + *providers = Providers { + check_mod_unstable_api_usage, + stability_index, + stability_implications: |tcx, _| tcx.stability().implications.clone(), + lookup_stability: |tcx, id| tcx.stability().local_stability(id.expect_local()), + lookup_const_stability: |tcx, id| tcx.stability().local_const_stability(id.expect_local()), + lookup_deprecation_entry: |tcx, id| { + tcx.stability().local_deprecation_entry(id.expect_local()) + }, + ..*providers + }; +} + +struct Checker<'tcx> { + tcx: TyCtxt<'tcx>, +} + +impl<'tcx> Visitor<'tcx> for Checker<'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + + /// Because stability levels are scoped lexically, we want to walk + /// nested items in the context of the outer item, so enable + /// deep-walking. + fn nested_visit_map(&mut self) -> Self::Map { + self.tcx.hir() + } + + fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) { + match item.kind { + hir::ItemKind::ExternCrate(_) => { + // compiler-generated `extern crate` items have a dummy span. + // `std` is still checked for the `restricted-std` feature. + if item.span.is_dummy() && item.ident.name != sym::std { + return; + } + + let Some(cnum) = self.tcx.extern_mod_stmt_cnum(item.def_id) else { + return; + }; + let def_id = cnum.as_def_id(); + self.tcx.check_stability(def_id, Some(item.hir_id()), item.span, None); + } + + // For implementations of traits, check the stability of each item + // individually as it's possible to have a stable trait with unstable + // items. + hir::ItemKind::Impl(hir::Impl { + of_trait: Some(ref t), + self_ty, + items, + constness, + .. + }) => { + let features = self.tcx.features(); + if features.staged_api { + let attrs = self.tcx.hir().attrs(item.hir_id()); + let (stab, const_stab) = attr::find_stability(&self.tcx.sess, attrs, item.span); + + // If this impl block has an #[unstable] attribute, give an + // error if all involved types and traits are stable, because + // it will have no effect. + // See: https://github.com/rust-lang/rust/issues/55436 + if let Some((Stability { level: attr::Unstable { .. }, .. }, span)) = stab { + let mut c = CheckTraitImplStable { tcx: self.tcx, fully_stable: true }; + c.visit_ty(self_ty); + c.visit_trait_ref(t); + if c.fully_stable { + self.tcx.struct_span_lint_hir( + INEFFECTIVE_UNSTABLE_TRAIT_IMPL, + item.hir_id(), + span, + |lint| {lint + .build("an `#[unstable]` annotation here has no effect") + .note("see issue #55436 <https://github.com/rust-lang/rust/issues/55436> for more information") + .emit();} + ); + } + } + + // `#![feature(const_trait_impl)]` is unstable, so any impl declared stable + // needs to have an error emitted. + if features.const_trait_impl + && *constness == hir::Constness::Const + && const_stab.map_or(false, |(stab, _)| stab.is_const_stable()) + { + self.tcx + .sess + .struct_span_err(item.span, "trait implementations cannot be const stable yet") + .note("see issue #67792 <https://github.com/rust-lang/rust/issues/67792> for more information") + .emit(); + } + } + + for impl_item_ref in *items { + let impl_item = self.tcx.associated_item(impl_item_ref.id.def_id); + + if let Some(def_id) = impl_item.trait_item_def_id { + // Pass `None` to skip deprecation warnings. + self.tcx.check_stability(def_id, None, impl_item_ref.span, None); + } + } + } + + _ => (/* pass */), + } + intravisit::walk_item(self, item); + } + + fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, id: hir::HirId) { + if let Some(def_id) = path.res.opt_def_id() { + let method_span = path.segments.last().map(|s| s.ident.span); + let item_is_allowed = self.tcx.check_stability_allow_unstable( + def_id, + Some(id), + path.span, + method_span, + if is_unstable_reexport(self.tcx, id) { + AllowUnstable::Yes + } else { + AllowUnstable::No + }, + ); + + let is_allowed_through_unstable_modules = |def_id| { + self.tcx + .lookup_stability(def_id) + .map(|stab| match stab.level { + StabilityLevel::Stable { allowed_through_unstable_modules, .. } => { + allowed_through_unstable_modules + } + _ => false, + }) + .unwrap_or(false) + }; + + if item_is_allowed && !is_allowed_through_unstable_modules(def_id) { + // Check parent modules stability as well if the item the path refers to is itself + // stable. We only emit warnings for unstable path segments if the item is stable + // or allowed because stability is often inherited, so the most common case is that + // both the segments and the item are unstable behind the same feature flag. + // + // We check here rather than in `visit_path_segment` to prevent visiting the last + // path segment twice + // + // We include special cases via #[rustc_allowed_through_unstable_modules] for items + // that were accidentally stabilized through unstable paths before this check was + // added, such as `core::intrinsics::transmute` + let parents = path.segments.iter().rev().skip(1); + for path_segment in parents { + if let Some(def_id) = path_segment.res.as_ref().and_then(Res::opt_def_id) { + // use `None` for id to prevent deprecation check + self.tcx.check_stability_allow_unstable( + def_id, + None, + path.span, + None, + if is_unstable_reexport(self.tcx, id) { + AllowUnstable::Yes + } else { + AllowUnstable::No + }, + ); + } + } + } + } + + intravisit::walk_path(self, path) + } +} + +/// Check whether a path is a `use` item that has been marked as unstable. +/// +/// See issue #94972 for details on why this is a special case +fn is_unstable_reexport<'tcx>(tcx: TyCtxt<'tcx>, id: hir::HirId) -> bool { + // Get the LocalDefId so we can lookup the item to check the kind. + let Some(def_id) = tcx.hir().opt_local_def_id(id) else { return false; }; + + let Some(stab) = tcx.stability().local_stability(def_id) else { + return false; + }; + + if stab.level.is_stable() { + // The re-export is not marked as unstable, don't override + return false; + } + + // If this is a path that isn't a use, we don't need to do anything special + if !matches!(tcx.hir().item(hir::ItemId { def_id }).kind, ItemKind::Use(..)) { + return false; + } + + true +} + +struct CheckTraitImplStable<'tcx> { + tcx: TyCtxt<'tcx>, + fully_stable: bool, +} + +impl<'tcx> Visitor<'tcx> for CheckTraitImplStable<'tcx> { + fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _id: hir::HirId) { + if let Some(def_id) = path.res.opt_def_id() { + if let Some(stab) = self.tcx.lookup_stability(def_id) { + self.fully_stable &= stab.level.is_stable(); + } + } + intravisit::walk_path(self, path) + } + + fn visit_trait_ref(&mut self, t: &'tcx TraitRef<'tcx>) { + if let Res::Def(DefKind::Trait, trait_did) = t.path.res { + if let Some(stab) = self.tcx.lookup_stability(trait_did) { + self.fully_stable &= stab.level.is_stable(); + } + } + intravisit::walk_trait_ref(self, t) + } + + fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) { + if let TyKind::Never = t.kind { + self.fully_stable = false; + } + intravisit::walk_ty(self, t) + } +} + +/// Given the list of enabled features that were not language features (i.e., that +/// were expected to be library features), and the list of features used from +/// libraries, identify activated features that don't exist and error about them. +pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) { + let is_staged_api = + tcx.sess.opts.unstable_opts.force_unstable_if_unmarked || tcx.features().staged_api; + if is_staged_api { + let access_levels = &tcx.privacy_access_levels(()); + let mut missing = MissingStabilityAnnotations { tcx, access_levels }; + missing.check_missing_stability(CRATE_DEF_ID, tcx.hir().span(CRATE_HIR_ID)); + tcx.hir().walk_toplevel_module(&mut missing); + tcx.hir().visit_all_item_likes_in_crate(&mut missing); + } + + let declared_lang_features = &tcx.features().declared_lang_features; + let mut lang_features = FxHashSet::default(); + for &(feature, span, since) in declared_lang_features { + if let Some(since) = since { + // Warn if the user has enabled an already-stable lang feature. + unnecessary_stable_feature_lint(tcx, span, feature, since); + } + if !lang_features.insert(feature) { + // Warn if the user enables a lang feature multiple times. + duplicate_feature_err(tcx.sess, span, feature); + } + } + + let declared_lib_features = &tcx.features().declared_lib_features; + let mut remaining_lib_features = FxIndexMap::default(); + for (feature, span) in declared_lib_features { + if !tcx.sess.opts.unstable_features.is_nightly_build() { + struct_span_err!( + tcx.sess, + *span, + E0554, + "`#![feature]` may not be used on the {} release channel", + env!("CFG_RELEASE_CHANNEL") + ) + .emit(); + } + if remaining_lib_features.contains_key(&feature) { + // Warn if the user enables a lib feature multiple times. + duplicate_feature_err(tcx.sess, *span, *feature); + } + remaining_lib_features.insert(feature, *span); + } + // `stdbuild` has special handling for `libc`, so we need to + // recognise the feature when building std. + // Likewise, libtest is handled specially, so `test` isn't + // available as we'd like it to be. + // FIXME: only remove `libc` when `stdbuild` is active. + // FIXME: remove special casing for `test`. + remaining_lib_features.remove(&sym::libc); + remaining_lib_features.remove(&sym::test); + + // We always collect the lib features declared in the current crate, even if there are + // no unknown features, because the collection also does feature attribute validation. + let local_defined_features = tcx.lib_features(()); + let mut all_lib_features: FxHashMap<_, _> = + local_defined_features.to_vec().iter().map(|el| *el).collect(); + let mut implications = tcx.stability_implications(rustc_hir::def_id::LOCAL_CRATE).clone(); + for &cnum in tcx.crates(()) { + implications.extend(tcx.stability_implications(cnum)); + all_lib_features.extend(tcx.defined_lib_features(cnum).iter().map(|el| *el)); + } + + // Check that every feature referenced by an `implied_by` exists (for features defined in the + // local crate). + for (implied_by, feature) in tcx.stability_implications(rustc_hir::def_id::LOCAL_CRATE) { + // Only `implied_by` needs to be checked, `feature` is guaranteed to exist. + if !all_lib_features.contains_key(implied_by) { + let span = local_defined_features + .stable + .get(feature) + .map(|(_, span)| span) + .or_else(|| local_defined_features.unstable.get(feature)) + .expect("feature that implied another does not exist"); + tcx.sess + .struct_span_err( + *span, + format!("feature `{implied_by}` implying `{feature}` does not exist"), + ) + .emit(); + } + } + + if !remaining_lib_features.is_empty() { + for (feature, since) in all_lib_features.iter() { + if let Some(since) = since && let Some(span) = remaining_lib_features.get(&feature) { + // Warn if the user has enabled an already-stable lib feature. + if let Some(implies) = implications.get(&feature) { + unnecessary_partially_stable_feature_lint(tcx, *span, *feature, *implies, *since); + } else { + unnecessary_stable_feature_lint(tcx, *span, *feature, *since); + } + } + remaining_lib_features.remove(&feature); + if remaining_lib_features.is_empty() { + break; + } + } + } + + for (feature, span) in remaining_lib_features { + struct_span_err!(tcx.sess, span, E0635, "unknown feature `{}`", feature).emit(); + } + + // FIXME(#44232): the `used_features` table no longer exists, so we + // don't lint about unused features. We should re-enable this one day! +} + +fn unnecessary_partially_stable_feature_lint( + tcx: TyCtxt<'_>, + span: Span, + feature: Symbol, + implies: Symbol, + since: Symbol, +) { + tcx.struct_span_lint_hir(lint::builtin::STABLE_FEATURES, hir::CRATE_HIR_ID, span, |lint| { + lint.build(&format!( + "the feature `{feature}` has been partially stabilized since {since} and is succeeded \ + by the feature `{implies}`" + )) + .span_suggestion( + span, + &format!( + "if you are using features which are still unstable, change to using `{implies}`" + ), + implies, + Applicability::MaybeIncorrect, + ) + .span_suggestion( + tcx.sess.source_map().span_extend_to_line(span), + "if you are using features which are now stable, remove this line", + "", + Applicability::MaybeIncorrect, + ) + .emit(); + }); +} + +fn unnecessary_stable_feature_lint(tcx: TyCtxt<'_>, span: Span, feature: Symbol, since: Symbol) { + tcx.struct_span_lint_hir(lint::builtin::STABLE_FEATURES, hir::CRATE_HIR_ID, span, |lint| { + lint.build(&format!( + "the feature `{feature}` has been stable since {since} and no longer requires an \ + attribute to enable", + )) + .emit(); + }); +} + +fn duplicate_feature_err(sess: &Session, span: Span, feature: Symbol) { + struct_span_err!(sess, span, E0636, "the feature `{}` has already been declared", feature) + .emit(); +} + +fn missing_const_err(session: &Session, fn_sig_span: Span, const_span: Span) { + const ERROR_MSG: &'static str = "attributes `#[rustc_const_unstable]` \ + and `#[rustc_const_stable]` require \ + the function or method to be `const`"; + + session + .struct_span_err(fn_sig_span, ERROR_MSG) + .span_help(fn_sig_span, "make the function or method const") + .span_label(const_span, "attribute specified here") + .emit(); +} diff --git a/compiler/rustc_passes/src/upvars.rs b/compiler/rustc_passes/src/upvars.rs new file mode 100644 index 000000000..68d9bf22b --- /dev/null +++ b/compiler/rustc_passes/src/upvars.rs @@ -0,0 +1,97 @@ +//! Upvar (closure capture) collection from cross-body HIR uses of `Res::Local`s. + +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::intravisit::{self, Visitor}; +use rustc_hir::{self, HirId}; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_span::Span; + +pub fn provide(providers: &mut Providers) { + providers.upvars_mentioned = |tcx, def_id| { + if !tcx.is_closure(def_id) { + return None; + } + + let local_def_id = def_id.expect_local(); + let body = tcx.hir().body(tcx.hir().maybe_body_owned_by(local_def_id)?); + + let mut local_collector = LocalCollector::default(); + local_collector.visit_body(body); + + let mut capture_collector = CaptureCollector { + tcx, + locals: &local_collector.locals, + upvars: FxIndexMap::default(), + }; + capture_collector.visit_body(body); + + if !capture_collector.upvars.is_empty() { + Some(tcx.arena.alloc(capture_collector.upvars)) + } else { + None + } + }; +} + +#[derive(Default)] +struct LocalCollector { + // FIXME(eddyb) perhaps use `ItemLocalId` instead? + locals: FxHashSet<HirId>, +} + +impl<'tcx> Visitor<'tcx> for LocalCollector { + fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) { + if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind { + self.locals.insert(hir_id); + } + intravisit::walk_pat(self, pat); + } +} + +struct CaptureCollector<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + locals: &'a FxHashSet<HirId>, + upvars: FxIndexMap<HirId, hir::Upvar>, +} + +impl CaptureCollector<'_, '_> { + fn visit_local_use(&mut self, var_id: HirId, span: Span) { + if !self.locals.contains(&var_id) { + self.upvars.entry(var_id).or_insert(hir::Upvar { span }); + } + } +} + +impl<'tcx> Visitor<'tcx> for CaptureCollector<'_, 'tcx> { + fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) { + if let Res::Local(var_id) = path.res { + self.visit_local_use(var_id, path.span); + } + + intravisit::walk_path(self, path); + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + if let hir::ExprKind::Closure { .. } = expr.kind { + let closure_def_id = self.tcx.hir().local_def_id(expr.hir_id); + if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) { + // Every capture of a closure expression is a local in scope, + // that is moved/copied/borrowed into the closure value, and + // for this analysis they are like any other access to a local. + // + // E.g. in `|b| |c| (a, b, c)`, the upvars of the inner closure + // are `a` and `b`, and while `a` is not directly used in the + // outer closure, it needs to be an upvar there too, so that + // the inner closure can take it (from the outer closure's env). + for (&var_id, upvar) in upvars { + self.visit_local_use(var_id, upvar.span); + } + } + } + + intravisit::walk_expr(self, expr); + } +} diff --git a/compiler/rustc_passes/src/weak_lang_items.rs b/compiler/rustc_passes/src/weak_lang_items.rs new file mode 100644 index 000000000..c48b4ecf8 --- /dev/null +++ b/compiler/rustc_passes/src/weak_lang_items.rs @@ -0,0 +1,91 @@ +//! Validity checking for weak lang items + +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::struct_span_err; +use rustc_hir::lang_items::{self, LangItem}; +use rustc_hir::weak_lang_items::WEAK_ITEMS_REFS; +use rustc_middle::middle::lang_items::required; +use rustc_middle::ty::TyCtxt; +use rustc_session::config::CrateType; + +/// Checks the crate for usage of weak lang items, returning a vector of all the +/// language items required by this crate, but not defined yet. +pub fn check_crate<'tcx>(tcx: TyCtxt<'tcx>, items: &mut lang_items::LanguageItems) { + // These are never called by user code, they're generated by the compiler. + // They will never implicitly be added to the `missing` array unless we do + // so here. + if items.eh_personality().is_none() { + items.missing.push(LangItem::EhPersonality); + } + if tcx.sess.target.os == "emscripten" && items.eh_catch_typeinfo().is_none() { + items.missing.push(LangItem::EhCatchTypeinfo); + } + + let crate_items = tcx.hir_crate_items(()); + for id in crate_items.foreign_items() { + let attrs = tcx.hir().attrs(id.hir_id()); + if let Some((lang_item, _)) = lang_items::extract(attrs) { + if let Some(&item) = WEAK_ITEMS_REFS.get(&lang_item) { + if items.require(item).is_err() { + items.missing.push(item); + } + } else { + let span = tcx.def_span(id.def_id); + struct_span_err!( + tcx.sess, + span, + E0264, + "unknown external lang item: `{}`", + lang_item + ) + .emit(); + } + } + } + + verify(tcx, items); +} + +fn verify<'tcx>(tcx: TyCtxt<'tcx>, items: &lang_items::LanguageItems) { + // We only need to check for the presence of weak lang items if we're + // emitting something that's not an rlib. + let needs_check = tcx.sess.crate_types().iter().any(|kind| match *kind { + CrateType::Dylib + | CrateType::ProcMacro + | CrateType::Cdylib + | CrateType::Executable + | CrateType::Staticlib => true, + CrateType::Rlib => false, + }); + if !needs_check { + return; + } + + let mut missing = FxHashSet::default(); + for &cnum in tcx.crates(()).iter() { + for &item in tcx.missing_lang_items(cnum).iter() { + missing.insert(item); + } + } + + for (name, &item) in WEAK_ITEMS_REFS.iter() { + if missing.contains(&item) && required(tcx, item) && items.require(item).is_err() { + if item == LangItem::PanicImpl { + tcx.sess.err("`#[panic_handler]` function required, but not found"); + } else if item == LangItem::Oom { + if !tcx.features().default_alloc_error_handler { + tcx.sess.err("`#[alloc_error_handler]` function required, but not found"); + tcx.sess.note_without_error("use `#![feature(default_alloc_error_handler)]` for a default error handler"); + } + } else { + tcx + .sess + .diagnostic() + .struct_err(&format!("language item required, but not found: `{}`", name)) + .note(&format!("this can occur when a binary crate with `#![no_std]` is compiled for a target where `{}` is defined in the standard library", name)) + .help(&format!("you may be able to compile for a target that doesn't need `{}`, specify a target with `--target` or in `.cargo/config`", name)) + .emit(); + } + } + } +} |