| use std::borrow::Cow; | |
| use rustc_ast::{AttrStyle, Safety}; | |
| use rustc_attr_ir::target::{MethodKind, Target}; | |
| use rustc_attr_ir::{AttrItem, Attribute, AttributeKind}; | |
| use rustc_errors::{DiagArgValue, MultiSpan, StashKey}; | |
| use rustc_feature::Features; | |
| use rustc_span::{BytePos, FileName, RemapPathScopeComponents, Span, Symbol, sym}; | |
| use crate::context::AcceptContext; | |
| use crate::diagnostics::{ | |
| InvalidAttrAtCrateLevel, InvalidTarget, InvalidTargetHelp, ItemFollowingInnerAttr, | |
| UnsupportedAttributesInWhere, | |
| }; | |
| use crate::target_checking::Policy::Allow; | |
| use crate::{AttributeParser, ShouldEmit}; | |
| #[derive(Debug)] | |
| pub(crate) enum AllowedTargets<'a> { | |
| AllowList(&'a [Policy]), | |
| AllowListWarnRest(&'a [Policy]), | |
| /// This is useful for argument-dependent target checking. | |
| /// If debug assertions are enabled, | |
| /// this emits a delayed bug if the `cx.check_target(...)` method is not called during attribute parsing. | |
| ManuallyChecked, | |
| } | |
| pub(crate) enum AllowedResult { | |
| Allowed, | |
| Warn, | |
| Error, | |
| } | |
| impl AllowedTargets<'_> { | |
| pub(crate) fn is_allowed(&self, target: Target) -> AllowedResult { | |
| match self { | |
| AllowedTargets::AllowList(list) => { | |
| if list.contains(&Policy::Allow(target)) | |
| || list.contains(&Policy::AllowSilent(target)) | |
| { | |
| AllowedResult::Allowed | |
| } else if list.contains(&Policy::Warn(target)) { | |
| AllowedResult::Warn | |
| } else { | |
| AllowedResult::Error | |
| } | |
| } | |
| AllowedTargets::AllowListWarnRest(list) => { | |
| if list.contains(&Policy::Allow(target)) | |
| || list.contains(&Policy::AllowSilent(target)) | |
| { | |
| AllowedResult::Allowed | |
| } else if list.contains(&Policy::Error(target)) { | |
| AllowedResult::Error | |
| } else { | |
| AllowedResult::Warn | |
| } | |
| } | |
| AllowedTargets::ManuallyChecked => unreachable!(), | |
| } | |
| } | |
| pub(crate) fn allowed_targets(&self) -> Vec<Target> { | |
| match self { | |
| AllowedTargets::AllowList(list) | AllowedTargets::AllowListWarnRest(list) => list, | |
| AllowedTargets::ManuallyChecked => unreachable!(), | |
| } | |
| .iter() | |
| .filter_map(|target| match target { | |
| Policy::Allow(target) => Some(*target), | |
| Policy::AllowSilent(_) | Policy::Warn(_) | Policy::Error(_) => None, | |
| }) | |
| .collect() | |
| } | |
| } | |
| /// This policy determines what diagnostics should be emitted based on the `Target` of the attribute. | |
| #[derive(Debug, Eq, PartialEq)] | |
| pub(crate) enum Policy { | |
| /// A target that is allowed. | |
| Allow(Target), | |
| /// A target that is allowed and not listed in the possible targets. | |
| /// This is useful if the target is checked elsewhere. | |
| AllowSilent(Target), | |
| /// Emits a FCW on this target. | |
| /// This is useful if the target was previously allowed but should not be. | |
| Warn(Target), | |
| /// Emits an error on this target. | |
| Error(Target), | |
| } | |
| impl<'sess> AttributeParser<'sess> { | |
| pub(crate) fn check_target( | |
| allowed_targets: &AllowedTargets<'_>, | |
| attribute_args: &str, | |
| cx: &mut AcceptContext<'_, 'sess>, | |
| ) { | |
| if matches!(cx.should_emit, ShouldEmit::Nothing) { | |
| return; | |
| } | |
| if let AllowedTargets::ManuallyChecked = allowed_targets { | |
| #[cfg(debug_assertions)] | |
| if !cx.has_target_been_checked { | |
| cx.dcx().delayed_bug("Attribute target has not been checked"); | |
| } | |
| return; | |
| } | |
| // For crate-level attributes we emit a specific set of lints to warn | |
| // people about accidentally not using them on the crate. | |
| if let &AllowedTargets::AllowList(&[Allow(Target::Crate)]) = allowed_targets { | |
| Self::check_crate_level(cx, false); | |
| return; | |
| } | |
| if let &AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate)]) = allowed_targets { | |
| Self::check_crate_level(cx, true); | |
| return; | |
| } | |
| let result = allowed_targets.is_allowed(cx.target); | |
| if matches!(result, AllowedResult::Allowed) { | |
| return; | |
| } | |
| let allowed_targets = allowed_targets.allowed_targets(); | |
| let (applied, only) = allowed_targets_applied(allowed_targets, cx.target, cx.features); | |
| let is_diagnostic_attr = cx.attr_path.segments[0] == sym::diagnostic; | |
| let diag = InvalidTarget { | |
| span: if attribute_args.is_empty() { | |
| // Example: for the attribute `#[inline]`, name+attribute_args gives "inline", | |
| // and the path span covers `inline` which is just what we want. | |
| cx.attr_path.span | |
| } else { | |
| // Example 1: for the attribute `#[repr(C)]`, name+attribute_args gives | |
| // "repr(C)", and the inner span covers `repr(C)` which is just what we want. | |
| // | |
| // Example 2: for the attribute `#[repr(C, packed)]`, name+attribute_args gives | |
| // "repr(C)", and the inner span covers `repr(C, packed)` which doesn't match | |
| // exactly but is as close as we can get. | |
| cx.inner_span | |
| }, | |
| attr_span: cx.attr_span, | |
| name: cx.attr_path.clone(), | |
| target: cx.target.plural_name(), | |
| only: if only { "only " } else { "" }, | |
| applied: DiagArgValue::StrListSepByAnd(applied.into_iter().map(Cow::Owned).collect()), | |
| attribute_args: attribute_args.to_string(), | |
| help: Self::target_checking_help(attribute_args, cx), | |
| previously_accepted: matches!(result, AllowedResult::Warn) && !is_diagnostic_attr, | |
| on_macro_call: matches!(cx.target, Target::MacroCall), | |
| }; | |
| match result { | |
| AllowedResult::Allowed => unreachable!("Should have early returned above"), | |
| AllowedResult::Warn => { | |
| let lint = if cx.attr_path.segments[0] == sym::deprecated | |
| && ![ | |
| Target::Closure, | |
| Target::Expression, | |
| Target::Statement, | |
| Target::Arm, | |
| Target::MacroCall, | |
| ] | |
| .contains(&cx.target) | |
| { | |
| rustc_session::lint::builtin::USELESS_DEPRECATED | |
| } else if is_diagnostic_attr { | |
| rustc_session::lint::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES | |
| } else { | |
| rustc_session::lint::builtin::UNUSED_ATTRIBUTES | |
| }; | |
| let attr_span = cx.attr_span; | |
| cx.emit_lint(lint, diag, attr_span); | |
| } | |
| AllowedResult::Error => { | |
| cx.dcx().emit_err(diag); | |
| } | |
| } | |
| } | |
| fn target_checking_help( | |
| attribute_args: &str, | |
| cx: &AcceptContext<'_, '_>, | |
| ) -> Option<InvalidTargetHelp> { | |
| match &*cx.attr_path.segments { | |
| [sym::link_name] if cx.target == Target::Static => { | |
| let needs_unsafe_wrapper = matches!(cx.attr_safety, Safety::Default); | |
| Some(InvalidTargetHelp::UseExportName { | |
| unsafe_open: needs_unsafe_wrapper.then(|| cx.inner_span.shrink_to_lo()), | |
| name: cx.attr_path.span, | |
| unsafe_close: needs_unsafe_wrapper.then(|| cx.inner_span.shrink_to_hi()), | |
| }) | |
| } | |
| [sym::repr] if attribute_args == "(align(...))" => match cx.target { | |
| Target::Fn | Target::Method(..) if cx.features().fn_align() => { | |
| Some(InvalidTargetHelp::UseRustcAlign) | |
| } | |
| Target::Static if cx.features().static_align() => { | |
| Some(InvalidTargetHelp::UseRustcAlignStatic) | |
| } | |
| _ => None, | |
| }, | |
| _ => None, | |
| } | |
| } | |
| pub(crate) fn check_crate_level(cx: &mut AcceptContext<'_, 'sess>, warn: bool) { | |
| if cx.target == Target::Crate { | |
| return; | |
| } | |
| let name = cx.attr_path.to_string(); | |
| let is_used_as_inner = cx.attr_style == AttrStyle::Inner; | |
| let target_span = cx.target_span; | |
| let attr_span = cx.attr_span; | |
| let (show_crate_root_help, crate_root_path) = is_used_as_inner | |
| .then(|| cx.cx.sess.local_crate_source_file()) | |
| .flatten() | |
| .filter(|src| { | |
| !matches!( | |
| cx.cx.sess.source_map().span_to_filename(attr_span), | |
| FileName::Real(ref name) if name == src | |
| ) | |
| }) | |
| .map(|src| { | |
| (true, src.path(RemapPathScopeComponents::DIAGNOSTICS).display().to_string()) | |
| }) | |
| .unwrap_or_default(); | |
| let diag = crate::diagnostics::InvalidAttrStyle { | |
| name, | |
| is_used_as_inner, | |
| target_span: (!is_used_as_inner).then_some(target_span), | |
| target: cx.target.name(), | |
| crate_root_path, | |
| show_crate_root_help, | |
| span: attr_span, | |
| }; | |
| if warn { | |
| cx.emit_lint(rustc_session::lint::builtin::UNUSED_ATTRIBUTES, diag, attr_span); | |
| } else { | |
| cx.emit_err(diag); | |
| } | |
| } | |
| // FIXME: Fix "Cannot determine resolution" error and remove built-in macros | |
| // from this check. | |
| pub(crate) fn check_invalid_crate_level_attr_item(&self, attr: &AttrItem, inner_span: Span) { | |
| // 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::derive, sym::test, sym::test_case, sym::global_allocator, sym::bench]; | |
| // FIXME(jdonszelmann): all attrs should be combined here cleaning this up some day. | |
| if let Some(name) = ATTRS_TO_CHECK.iter().find(|attr_to_check| matches!(attr.path.segments.as_ref(), [segment] if segment == *attr_to_check)) { | |
| let span = attr.span; | |
| let name = *name; | |
| let item = self.first_line_of_next_item(span).map(|span| ItemFollowingInnerAttr { span }); | |
| let err = self.dcx().create_err(InvalidAttrAtCrateLevel { | |
| span, | |
| pound_to_opening_bracket: span.until(inner_span), | |
| name, | |
| item, | |
| }); | |
| self.dcx().try_steal_replace_and_emit_err( | |
| attr.path.span, | |
| StashKey::UndeterminedMacroResolution, | |
| err, | |
| ); | |
| } | |
| } | |
| fn first_line_of_next_item(&self, span: Span) -> Option<Span> { | |
| // We can't exactly call `tcx.hir_free_items()` here because it's too early and querying | |
| // this would create a circular dependency. Instead, we resort to getting the original | |
| // source code that follows `span` and find the next item from here. | |
| self.sess() | |
| .source_map() | |
| .span_to_source(span, |content, _, span_end| { | |
| let mut source = &content[span_end..]; | |
| let initial_source_len = source.len(); | |
| let span = try { | |
| loop { | |
| let first = source.chars().next()?; | |
| if first.is_whitespace() { | |
| let split_idx = source.find(|c: char| !c.is_whitespace())?; | |
| source = &source[split_idx..]; | |
| } else if source.starts_with("//") { | |
| let line_idx = source.find('\n')?; | |
| source = &source[line_idx + '\n'.len_utf8()..]; | |
| } else if source.starts_with("/*") { | |
| // FIXME: support nested comments. | |
| let close_idx = source.find("*/")?; | |
| source = &source[close_idx + "*/".len()..]; | |
| } else if first == '#' { | |
| // FIXME: properly find the end of the attributes in order to accurately | |
| // skip them. This version just consumes the source code until the next | |
| // `]`. | |
| let close_idx = source.find(']')?; | |
| source = &source[close_idx + ']'.len_utf8()..]; | |
| } else { | |
| let lo = span_end + initial_source_len - source.len(); | |
| let last_line = source.split('\n').next().map(|s| s.trim_end())?; | |
| let hi = lo + last_line.len(); | |
| let lo = BytePos(lo as u32); | |
| let hi = BytePos(hi as u32); | |
| let next_item_span = Span::new(lo, hi, span.ctxt(), None); | |
| break next_item_span; | |
| } | |
| } | |
| }; | |
| Ok(span) | |
| }) | |
| .ok() | |
| .flatten() | |
| } | |
| pub(crate) fn check_invalid_where_predicate_attrs<'attr>( | |
| &self, | |
| attrs: impl IntoIterator<Item = &'attr Attribute>, | |
| ) { | |
| // FIXME(where_clause_attrs): Currently, as the following check shows, | |
| // only `#[cfg]` and `#[cfg_attr]` are allowed, but it should be removed | |
| // if we allow more attributes (e.g., tool attributes and `allow/deny/warn`) | |
| // in where clauses. After that, this function would become useless. | |
| let spans = attrs | |
| .into_iter() | |
| .filter_map(|attr| { | |
| match attr { | |
| Attribute::Parsed(AttributeKind::DocComment { span, .. }) => Some(*span), | |
| // FIXME: We shouldn't need to special-case `doc`! | |
| Attribute::Parsed(AttributeKind::Doc(attr)) => Some(attr.first_span), | |
| // Checked during attribute parsing target checking | |
| Attribute::Parsed(_) => None, | |
| Attribute::Unparsed(attr) => Some(attr.span), | |
| } | |
| }) | |
| .collect::<Vec<_>>(); | |
| if !spans.is_empty() { | |
| self.dcx() | |
| .emit_err(UnsupportedAttributesInWhere { span: MultiSpan::from_spans(spans) }); | |
| } | |
| } | |
| } | |
| /// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to. | |
| /// Does some heuristic-based filtering to remove uninteresting targets, and formats the targets into a string | |
| pub(crate) fn allowed_targets_applied( | |
| mut allowed_targets: Vec<Target>, | |
| target: Target, | |
| features: Option<&Features>, | |
| ) -> (Vec<String>, bool) { | |
| // Remove unstable targets from `allowed_targets` if their features are not enabled | |
| if let Some(features) = features { | |
| if !features.fn_delegation() { | |
| allowed_targets.retain(|t| !matches!(t, Target::Delegation { .. })); | |
| } | |
| if !features.stmt_expr_attributes() { | |
| allowed_targets.retain(|t| !matches!(t, Target::Expression | Target::Statement)); | |
| } | |
| if !features.extern_types() { | |
| allowed_targets.retain(|t| !matches!(t, Target::ForeignTy)); | |
| } | |
| } | |
| // We define groups of "similar" targets. | |
| // If at least two of the targets are allowed, and the `target` is not in the group, | |
| // we collapse the entire group to a single entry to simplify the target list | |
| const FUNCTION_LIKE: &[Target] = &[ | |
| Target::Fn, | |
| Target::Closure, | |
| Target::ForeignFn, | |
| Target::Method(MethodKind::Inherent), | |
| Target::Method(MethodKind::Trait { body: false }), | |
| Target::Method(MethodKind::Trait { body: true }), | |
| Target::Method(MethodKind::TraitImpl), | |
| ]; | |
| const FUNCTION_WITH_BODY_LIKE: &[Target] = &[ | |
| Target::Fn, | |
| Target::Closure, | |
| Target::Method(MethodKind::Inherent), | |
| Target::Method(MethodKind::Trait { body: true }), | |
| Target::Method(MethodKind::TraitImpl), | |
| ]; | |
| const METHOD_LIKE: &[Target] = &[ | |
| Target::Method(MethodKind::Inherent), | |
| Target::Method(MethodKind::Trait { body: false }), | |
| Target::Method(MethodKind::Trait { body: true }), | |
| Target::Method(MethodKind::TraitImpl), | |
| ]; | |
| const IMPL_LIKE: &[Target] = | |
| &[Target::Impl { of_trait: false }, Target::Impl { of_trait: true }]; | |
| const ADT_LIKE: &[Target] = &[Target::Struct, Target::Enum, Target::Union]; | |
| let mut added_fake_targets = Vec::new(); | |
| filter_targets( | |
| &mut allowed_targets, | |
| FUNCTION_LIKE, | |
| "functions", | |
| target, | |
| &mut added_fake_targets, | |
| ); | |
| filter_targets( | |
| &mut allowed_targets, | |
| FUNCTION_WITH_BODY_LIKE, | |
| "functions with a body", | |
| target, | |
| &mut added_fake_targets, | |
| ); | |
| filter_targets(&mut allowed_targets, METHOD_LIKE, "methods", target, &mut added_fake_targets); | |
| filter_targets(&mut allowed_targets, IMPL_LIKE, "impl blocks", target, &mut added_fake_targets); | |
| filter_targets(&mut allowed_targets, ADT_LIKE, "data types", target, &mut added_fake_targets); | |
| let mut target_strings: Vec<_> = added_fake_targets | |
| .iter() | |
| .copied() | |
| .chain(allowed_targets.iter().map(|t| t.plural_name())) | |
| .map(|i| i.to_string()) | |
| .collect(); | |
| // ensure a consistent order | |
| target_strings.sort(); | |
| target_strings.dedup(); | |
| // If there is now only 1 target left, show that as the only possible target | |
| let only_target = target_strings.len() == 1; | |
| (target_strings, only_target) | |
| } | |
| fn filter_targets( | |
| allowed_targets: &mut Vec<Target>, | |
| target_group: &'static [Target], | |
| target_group_name: &'static str, | |
| target: Target, | |
| added_fake_targets: &mut Vec<&'static str>, | |
| ) { | |
| if target_group.contains(&target) { | |
| return; | |
| } | |
| if allowed_targets.iter().filter(|at| target_group.contains(at)).count() < 2 { | |
| return; | |
| } | |
| allowed_targets.retain(|t| !target_group.contains(t)); | |
| added_fake_targets.push(target_group_name); | |
| } | |
| impl<'f, 'sess> AcceptContext<'f, 'sess> { | |
| pub(crate) fn check_target( | |
| &mut self, | |
| attribute_args: &str, | |
| allowed_targets: &AllowedTargets<'_>, | |
| ) { | |
| self.ignore_target_checks(); | |
| AttributeParser::check_target(allowed_targets, attribute_args, self); | |
| } | |
| pub(crate) fn ignore_target_checks(&mut self) { | |
| #[cfg(debug_assertions)] | |
| { | |
| self.has_target_been_checked = true; | |
| } | |
| } | |
| } | |
| /// This is the list of all targets to which a attribute can be applied | |
| /// This is used for: | |
| /// - `rustc_dummy`, which can be applied to all targets | |
| /// - Attributes that are not parted to the new target system yet can use this list as a placeholder | |
| pub(crate) const ALL_TARGETS: &[Policy] = { | |
| use Policy::Allow; | |
| &[ | |
| Allow(Target::ExternCrate), | |
| Allow(Target::Use), | |
| Allow(Target::Static), | |
| Allow(Target::Const), | |
| Allow(Target::Fn), | |
| Allow(Target::Closure), | |
| Allow(Target::Mod), | |
| Allow(Target::ForeignMod), | |
| Allow(Target::GlobalAsm), | |
| Allow(Target::TyAlias), | |
| Allow(Target::Enum), | |
| Allow(Target::Variant), | |
| Allow(Target::Struct), | |
| Allow(Target::Field), | |
| Allow(Target::Union), | |
| Allow(Target::Trait), | |
| Allow(Target::TraitAlias), | |
| Allow(Target::Impl { of_trait: false }), | |
| Allow(Target::Impl { of_trait: true }), | |
| Allow(Target::Expression), | |
| Allow(Target::Statement), | |
| Allow(Target::Arm), | |
| Allow(Target::AssocConst), | |
| Allow(Target::Method(MethodKind::Inherent)), | |
| Allow(Target::Method(MethodKind::Trait { body: false })), | |
| Allow(Target::Method(MethodKind::Trait { body: true })), | |
| Allow(Target::Method(MethodKind::TraitImpl)), | |
| Allow(Target::AssocTy), | |
| Allow(Target::ForeignFn), | |
| Allow(Target::ForeignStatic), | |
| Allow(Target::ForeignTy), | |
| Allow(Target::MacroDef), | |
| Allow(Target::Param), | |
| Allow(Target::PatField), | |
| Allow(Target::ExprField), | |
| Allow(Target::WherePredicate), | |
| Allow(Target::MacroCall), | |
| Allow(Target::Crate), | |
| Allow(Target::Delegation { mac: false }), | |
| Allow(Target::Delegation { mac: true }), | |
| Allow(Target::GenericParam { | |
| kind: rustc_attr_ir::target::GenericParamKind::Const, | |
| has_default: false, | |
| }), | |
| Allow(Target::GenericParam { | |
| kind: rustc_attr_ir::target::GenericParamKind::Const, | |
| has_default: true, | |
| }), | |
| Allow(Target::GenericParam { | |
| kind: rustc_attr_ir::target::GenericParamKind::Lifetime, | |
| has_default: false, | |
| }), | |
| Allow(Target::GenericParam { | |
| kind: rustc_attr_ir::target::GenericParamKind::Lifetime, | |
| has_default: true, | |
| }), | |
| Allow(Target::GenericParam { | |
| kind: rustc_attr_ir::target::GenericParamKind::Type, | |
| has_default: false, | |
| }), | |
| Allow(Target::GenericParam { | |
| kind: rustc_attr_ir::target::GenericParamKind::Type, | |
| has_default: true, | |
| }), | |
| Allow(Target::Loop), | |
| Allow(Target::ForLoop), | |
| Allow(Target::While), | |
| Allow(Target::Break), | |
| ] | |
| }; |