Skip to main content

rustc_attr_parsing/
target_checking.rs

1use std::borrow::Cow;
2
3use rustc_ast::AttrStyle;
4use rustc_errors::{DiagArgValue, MultiSpan, StashKey};
5use rustc_feature::Features;
6use rustc_hir::attrs::AttributeKind;
7use rustc_hir::{AttrItem, Attribute, MethodKind, Target};
8use rustc_span::{BytePos, FileName, RemapPathScopeComponents, Span, Symbol, sym};
9
10use crate::context::AcceptContext;
11use crate::diagnostics::{
12    InvalidAttrAtCrateLevel, ItemFollowingInnerAttr, UnsupportedAttributesInWhere,
13};
14use crate::session_diagnostics::{InvalidTarget, InvalidTargetHelp};
15use crate::target_checking::Policy::Allow;
16use crate::{AttributeParser, ShouldEmit};
17
18#[derive(#[automatically_derived]
impl<'a> ::core::fmt::Debug for AllowedTargets<'a> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            AllowedTargets::AllowList(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "AllowList", &__self_0),
            AllowedTargets::AllowListWarnRest(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "AllowListWarnRest", &__self_0),
            AllowedTargets::ManuallyChecked =>
                ::core::fmt::Formatter::write_str(f, "ManuallyChecked"),
        }
    }
}Debug)]
19pub(crate) enum AllowedTargets<'a> {
20    AllowList(&'a [Policy]),
21    AllowListWarnRest(&'a [Policy]),
22    /// This is useful for argument-dependent target checking.
23    /// If debug assertions are enabled,
24    /// this emits a delayed bug if the `cx.check_target(...)` method is not called during attribute parsing.
25    ManuallyChecked,
26}
27
28pub(crate) enum AllowedResult {
29    Allowed,
30    Warn,
31    Error,
32}
33
34impl AllowedTargets<'_> {
35    pub(crate) fn is_allowed(&self, target: Target) -> AllowedResult {
36        match self {
37            AllowedTargets::AllowList(list) => {
38                if list.contains(&Policy::Allow(target))
39                    || list.contains(&Policy::AllowSilent(target))
40                {
41                    AllowedResult::Allowed
42                } else if list.contains(&Policy::Warn(target)) {
43                    AllowedResult::Warn
44                } else {
45                    AllowedResult::Error
46                }
47            }
48            AllowedTargets::AllowListWarnRest(list) => {
49                if list.contains(&Policy::Allow(target))
50                    || list.contains(&Policy::AllowSilent(target))
51                {
52                    AllowedResult::Allowed
53                } else if list.contains(&Policy::Error(target)) {
54                    AllowedResult::Error
55                } else {
56                    AllowedResult::Warn
57                }
58            }
59            AllowedTargets::ManuallyChecked => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
60        }
61    }
62
63    pub(crate) fn allowed_targets(&self) -> Vec<Target> {
64        match self {
65            AllowedTargets::AllowList(list) | AllowedTargets::AllowListWarnRest(list) => list,
66            AllowedTargets::ManuallyChecked => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
67        }
68        .iter()
69        .filter_map(|target| match target {
70            Policy::Allow(target) => Some(*target),
71            Policy::AllowSilent(_) | Policy::Warn(_) | Policy::Error(_) => None,
72        })
73        .collect()
74    }
75}
76
77/// This policy determines what diagnostics should be emitted based on the `Target` of the attribute.
78#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Policy {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Policy::Allow(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Allow",
                    &__self_0),
            Policy::AllowSilent(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "AllowSilent", &__self_0),
            Policy::Warn(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Warn",
                    &__self_0),
            Policy::Error(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Error",
                    &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl ::core::cmp::Eq for Policy {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Target>;
    }
}Eq, #[automatically_derived]
impl ::core::cmp::PartialEq for Policy {
    #[inline]
    fn eq(&self, other: &Policy) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (Policy::Allow(__self_0), Policy::Allow(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (Policy::AllowSilent(__self_0), Policy::AllowSilent(__arg1_0))
                    => __self_0 == __arg1_0,
                (Policy::Warn(__self_0), Policy::Warn(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (Policy::Error(__self_0), Policy::Error(__arg1_0)) =>
                    __self_0 == __arg1_0,
                _ => unsafe { ::core::intrinsics::unreachable() }
            }
    }
}PartialEq)]
79pub(crate) enum Policy {
80    /// A target that is allowed.
81    Allow(Target),
82    /// A target that is allowed and not listed in the possible targets.
83    /// This is useful if the target is checked elsewhere.
84    AllowSilent(Target),
85    /// Emits a FCW on this target.
86    /// This is useful if the target was previously allowed but should not be.
87    Warn(Target),
88    /// Emits an error on this target.
89    Error(Target),
90}
91
92impl<'sess> AttributeParser<'sess> {
93    pub(crate) fn check_target(
94        allowed_targets: &AllowedTargets<'_>,
95        attribute_args: &str,
96        cx: &mut AcceptContext<'_, 'sess>,
97    ) {
98        if #[allow(non_exhaustive_omitted_patterns)] match cx.should_emit {
    ShouldEmit::Nothing => true,
    _ => false,
}matches!(cx.should_emit, ShouldEmit::Nothing) {
99            return;
100        }
101
102        if let AllowedTargets::ManuallyChecked = allowed_targets {
103            #[cfg(debug_assertions)]
104            if !cx.has_target_been_checked {
105                cx.dcx().delayed_bug("Attribute target has not been checked");
106            }
107
108            return;
109        }
110
111        // For crate-level attributes we emit a specific set of lints to warn
112        // people about accidentally not using them on the crate.
113        if let &AllowedTargets::AllowList(&[Allow(Target::Crate)]) = allowed_targets {
114            Self::check_crate_level(cx, false);
115            return;
116        }
117        if let &AllowedTargets::AllowListWarnRest(&[Allow(Target::Crate)]) = allowed_targets {
118            Self::check_crate_level(cx, true);
119            return;
120        }
121
122        let result = allowed_targets.is_allowed(cx.target);
123        if #[allow(non_exhaustive_omitted_patterns)] match result {
    AllowedResult::Allowed => true,
    _ => false,
}matches!(result, AllowedResult::Allowed) {
124            return;
125        }
126
127        let allowed_targets = allowed_targets.allowed_targets();
128        let (applied, only) = allowed_targets_applied(allowed_targets, cx.target, cx.features);
129        let is_diagnostic_attr = cx.attr_path.segments[0] == sym::diagnostic;
130
131        let diag = InvalidTarget {
132            span: cx.attr_span,
133            name: cx.attr_path.clone(),
134            target: cx.target.plural_name(),
135            only: if only { "only " } else { "" },
136            applied: DiagArgValue::StrListSepByAnd(applied.into_iter().map(Cow::Owned).collect()),
137            attribute_args: attribute_args.to_string(),
138            help: Self::target_checking_help(attribute_args, cx),
139            previously_accepted: #[allow(non_exhaustive_omitted_patterns)] match result {
    AllowedResult::Warn => true,
    _ => false,
}matches!(result, AllowedResult::Warn) && !is_diagnostic_attr,
140            on_macro_call: #[allow(non_exhaustive_omitted_patterns)] match cx.target {
    Target::MacroCall => true,
    _ => false,
}matches!(cx.target, Target::MacroCall),
141        };
142
143        match result {
144            AllowedResult::Allowed => {
    ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
            format_args!("Should have early returned above")));
}unreachable!("Should have early returned above"),
145            AllowedResult::Warn => {
146                let lint = if cx.attr_path.segments[0] == sym::deprecated
147                    && ![
148                        Target::Closure,
149                        Target::Expression,
150                        Target::Statement,
151                        Target::Arm,
152                        Target::MacroCall,
153                    ]
154                    .contains(&cx.target)
155                {
156                    rustc_session::lint::builtin::USELESS_DEPRECATED
157                } else if is_diagnostic_attr {
158                    rustc_session::lint::builtin::MISPLACED_DIAGNOSTIC_ATTRIBUTES
159                } else {
160                    rustc_session::lint::builtin::UNUSED_ATTRIBUTES
161                };
162
163                let attr_span = cx.attr_span;
164                cx.emit_lint(lint, diag, attr_span);
165            }
166            AllowedResult::Error => {
167                cx.dcx().emit_err(diag);
168            }
169        }
170    }
171
172    fn target_checking_help(
173        attribute_args: &str,
174        cx: &AcceptContext<'_, '_>,
175    ) -> Option<InvalidTargetHelp> {
176        match &*cx.attr_path.segments {
177            [sym::repr] if attribute_args == "(align(...))" => match cx.target {
178                Target::Fn | Target::Method(..) if cx.features().fn_align() => {
179                    Some(InvalidTargetHelp::UseRustcAlign)
180                }
181                Target::Static if cx.features().static_align() => {
182                    Some(InvalidTargetHelp::UseRustcAlignStatic)
183                }
184                _ => None,
185            },
186            _ => None,
187        }
188    }
189
190    pub(crate) fn check_crate_level(cx: &mut AcceptContext<'_, 'sess>, warn: bool) {
191        if cx.target == Target::Crate {
192            return;
193        }
194
195        let name = cx.attr_path.to_string();
196        let is_used_as_inner = cx.attr_style == AttrStyle::Inner;
197        let target_span = cx.target_span;
198        let attr_span = cx.attr_span;
199
200        let (show_crate_root_help, crate_root_path) = is_used_as_inner
201            .then(|| cx.cx.sess.local_crate_source_file())
202            .flatten()
203            .filter(|src| {
204                !#[allow(non_exhaustive_omitted_patterns)] match cx.cx.sess.source_map().span_to_filename(attr_span)
    {
    FileName::Real(ref name) if name == src => true,
    _ => false,
}matches!(
205                    cx.cx.sess.source_map().span_to_filename(attr_span),
206                    FileName::Real(ref name) if name == src
207                )
208            })
209            .map(|src| {
210                (true, src.path(RemapPathScopeComponents::DIAGNOSTICS).display().to_string())
211            })
212            .unwrap_or_default();
213
214        let diag = crate::diagnostics::InvalidAttrStyle {
215            name,
216            is_used_as_inner,
217            target_span: (!is_used_as_inner).then_some(target_span),
218            target: cx.target.name(),
219            crate_root_path,
220            show_crate_root_help,
221            span: attr_span,
222        };
223        if warn {
224            cx.emit_lint(rustc_session::lint::builtin::UNUSED_ATTRIBUTES, diag, attr_span);
225        } else {
226            cx.emit_err(diag);
227        }
228    }
229
230    // FIXME: Fix "Cannot determine resolution" error and remove built-in macros
231    // from this check.
232    pub(crate) fn check_invalid_crate_level_attr_item(&self, attr: &AttrItem, inner_span: Span) {
233        // Check for builtin attributes at the crate level
234        // which were unsuccessfully resolved due to cannot determine
235        // resolution for the attribute macro error.
236        const ATTRS_TO_CHECK: &[Symbol] =
237            &[sym::derive, sym::test, sym::test_case, sym::global_allocator, sym::bench];
238
239        // FIXME(jdonszelmann): all attrs should be combined here cleaning this up some day.
240        if let Some(name) = ATTRS_TO_CHECK.iter().find(|attr_to_check| #[allow(non_exhaustive_omitted_patterns)] match attr.path.segments.as_ref() {
    [segment] if segment == *attr_to_check => true,
    _ => false,
}matches!(attr.path.segments.as_ref(), [segment] if segment == *attr_to_check)) {
241            let span = attr.span;
242            let name = *name;
243
244            let item = self.first_line_of_next_item(span).map(|span| ItemFollowingInnerAttr { span });
245
246            let err = self.dcx().create_err(InvalidAttrAtCrateLevel {
247                span,
248                pound_to_opening_bracket: span.until(inner_span),
249                name,
250                item,
251            });
252
253            self.dcx().try_steal_replace_and_emit_err(
254                attr.path.span,
255                StashKey::UndeterminedMacroResolution,
256                err,
257            );
258        }
259    }
260
261    fn first_line_of_next_item(&self, span: Span) -> Option<Span> {
262        // We can't exactly call `tcx.hir_free_items()` here because it's too early and querying
263        // this would create a circular dependency. Instead, we resort to getting the original
264        // source code that follows `span` and find the next item from here.
265
266        self.sess()
267            .source_map()
268            .span_to_source(span, |content, _, span_end| {
269                let mut source = &content[span_end..];
270                let initial_source_len = source.len();
271                let span = try {
272                    loop {
273                        let first = source.chars().next()?;
274
275                        if first.is_whitespace() {
276                            let split_idx = source.find(|c: char| !c.is_whitespace())?;
277                            source = &source[split_idx..];
278                        } else if source.starts_with("//") {
279                            let line_idx = source.find('\n')?;
280                            source = &source[line_idx + '\n'.len_utf8()..];
281                        } else if source.starts_with("/*") {
282                            // FIXME: support nested comments.
283                            let close_idx = source.find("*/")?;
284                            source = &source[close_idx + "*/".len()..];
285                        } else if first == '#' {
286                            // FIXME: properly find the end of the attributes in order to accurately
287                            // skip them. This version just consumes the source code until the next
288                            // `]`.
289                            let close_idx = source.find(']')?;
290                            source = &source[close_idx + ']'.len_utf8()..];
291                        } else {
292                            let lo = span_end + initial_source_len - source.len();
293                            let last_line = source.split('\n').next().map(|s| s.trim_end())?;
294
295                            let hi = lo + last_line.len();
296                            let lo = BytePos(lo as u32);
297                            let hi = BytePos(hi as u32);
298                            let next_item_span = Span::new(lo, hi, span.ctxt(), None);
299
300                            break next_item_span;
301                        }
302                    }
303                };
304
305                Ok(span)
306            })
307            .ok()
308            .flatten()
309    }
310
311    pub(crate) fn check_invalid_where_predicate_attrs<'attr>(
312        &self,
313        attrs: impl IntoIterator<Item = &'attr Attribute>,
314    ) {
315        // FIXME(where_clause_attrs): Currently, as the following check shows,
316        // only `#[cfg]` and `#[cfg_attr]` are allowed, but it should be removed
317        // if we allow more attributes (e.g., tool attributes and `allow/deny/warn`)
318        // in where clauses. After that, this function would become useless.
319        let spans = attrs
320            .into_iter()
321            .filter_map(|attr| {
322                match attr {
323                    Attribute::Parsed(AttributeKind::DocComment { span, .. }) => Some(*span),
324                    // FIXME: We shouldn't need to special-case `doc`!
325                    Attribute::Parsed(AttributeKind::Doc(attr)) => Some(attr.first_span),
326                    // Checked during attribute parsing target checking
327                    Attribute::Parsed(_) => None,
328                    Attribute::Unparsed(attr) => Some(attr.span),
329                }
330            })
331            .collect::<Vec<_>>();
332        if !spans.is_empty() {
333            self.dcx()
334                .emit_err(UnsupportedAttributesInWhere { span: MultiSpan::from_spans(spans) });
335        }
336    }
337}
338
339/// Takes a list of `allowed_targets` for an attribute, and the `target` the attribute was applied to.
340/// Does some heuristic-based filtering to remove uninteresting targets, and formats the targets into a string
341pub(crate) fn allowed_targets_applied(
342    mut allowed_targets: Vec<Target>,
343    target: Target,
344    features: Option<&Features>,
345) -> (Vec<String>, bool) {
346    // Remove unstable targets from `allowed_targets` if their features are not enabled
347    if let Some(features) = features {
348        if !features.fn_delegation() {
349            allowed_targets.retain(|t| !#[allow(non_exhaustive_omitted_patterns)] match t {
    Target::Delegation { .. } => true,
    _ => false,
}matches!(t, Target::Delegation { .. }));
350        }
351        if !features.stmt_expr_attributes() {
352            allowed_targets.retain(|t| !#[allow(non_exhaustive_omitted_patterns)] match t {
    Target::Expression | Target::Statement => true,
    _ => false,
}matches!(t, Target::Expression | Target::Statement));
353        }
354        if !features.extern_types() {
355            allowed_targets.retain(|t| !#[allow(non_exhaustive_omitted_patterns)] match t {
    Target::ForeignTy => true,
    _ => false,
}matches!(t, Target::ForeignTy));
356        }
357    }
358
359    // We define groups of "similar" targets.
360    // If at least two of the targets are allowed, and the `target` is not in the group,
361    // we collapse the entire group to a single entry to simplify the target list
362    const FUNCTION_LIKE: &[Target] = &[
363        Target::Fn,
364        Target::Closure,
365        Target::ForeignFn,
366        Target::Method(MethodKind::Inherent),
367        Target::Method(MethodKind::Trait { body: false }),
368        Target::Method(MethodKind::Trait { body: true }),
369        Target::Method(MethodKind::TraitImpl),
370    ];
371    const FUNCTION_WITH_BODY_LIKE: &[Target] = &[
372        Target::Fn,
373        Target::Closure,
374        Target::Method(MethodKind::Inherent),
375        Target::Method(MethodKind::Trait { body: true }),
376        Target::Method(MethodKind::TraitImpl),
377    ];
378    const METHOD_LIKE: &[Target] = &[
379        Target::Method(MethodKind::Inherent),
380        Target::Method(MethodKind::Trait { body: false }),
381        Target::Method(MethodKind::Trait { body: true }),
382        Target::Method(MethodKind::TraitImpl),
383    ];
384    const IMPL_LIKE: &[Target] =
385        &[Target::Impl { of_trait: false }, Target::Impl { of_trait: true }];
386    const ADT_LIKE: &[Target] = &[Target::Struct, Target::Enum, Target::Union];
387
388    let mut added_fake_targets = Vec::new();
389    filter_targets(
390        &mut allowed_targets,
391        FUNCTION_LIKE,
392        "functions",
393        target,
394        &mut added_fake_targets,
395    );
396    filter_targets(
397        &mut allowed_targets,
398        FUNCTION_WITH_BODY_LIKE,
399        "functions with a body",
400        target,
401        &mut added_fake_targets,
402    );
403    filter_targets(&mut allowed_targets, METHOD_LIKE, "methods", target, &mut added_fake_targets);
404    filter_targets(&mut allowed_targets, IMPL_LIKE, "impl blocks", target, &mut added_fake_targets);
405    filter_targets(&mut allowed_targets, ADT_LIKE, "data types", target, &mut added_fake_targets);
406
407    let mut target_strings: Vec<_> = added_fake_targets
408        .iter()
409        .copied()
410        .chain(allowed_targets.iter().map(|t| t.plural_name()))
411        .map(|i| i.to_string())
412        .collect();
413
414    // ensure a consistent order
415    target_strings.sort();
416    target_strings.dedup();
417
418    // If there is now only 1 target left, show that as the only possible target
419    let only_target = target_strings.len() == 1;
420
421    (target_strings, only_target)
422}
423
424fn filter_targets(
425    allowed_targets: &mut Vec<Target>,
426    target_group: &'static [Target],
427    target_group_name: &'static str,
428    target: Target,
429    added_fake_targets: &mut Vec<&'static str>,
430) {
431    if target_group.contains(&target) {
432        return;
433    }
434    if allowed_targets.iter().filter(|at| target_group.contains(at)).count() < 2 {
435        return;
436    }
437    allowed_targets.retain(|t| !target_group.contains(t));
438    added_fake_targets.push(target_group_name);
439}
440
441impl<'f, 'sess> AcceptContext<'f, 'sess> {
442    pub(crate) fn check_target(
443        &mut self,
444        attribute_args: &str,
445        allowed_targets: &AllowedTargets<'_>,
446    ) {
447        self.ignore_target_checks();
448        AttributeParser::check_target(allowed_targets, attribute_args, self);
449    }
450
451    pub(crate) fn ignore_target_checks(&mut self) {
452        #[cfg(debug_assertions)]
453        {
454            self.has_target_been_checked = true;
455        }
456    }
457}
458
459/// This is the list of all targets to which a attribute can be applied
460/// This is used for:
461/// - `rustc_dummy`, which can be applied to all targets
462/// - Attributes that are not parted to the new target system yet can use this list as a placeholder
463pub(crate) const ALL_TARGETS: &[Policy] = {
464    use Policy::Allow;
465    &[
466        Allow(Target::ExternCrate),
467        Allow(Target::Use),
468        Allow(Target::Static),
469        Allow(Target::Const),
470        Allow(Target::Fn),
471        Allow(Target::Closure),
472        Allow(Target::Mod),
473        Allow(Target::ForeignMod),
474        Allow(Target::GlobalAsm),
475        Allow(Target::TyAlias),
476        Allow(Target::Enum),
477        Allow(Target::Variant),
478        Allow(Target::Struct),
479        Allow(Target::Field),
480        Allow(Target::Union),
481        Allow(Target::Trait),
482        Allow(Target::TraitAlias),
483        Allow(Target::Impl { of_trait: false }),
484        Allow(Target::Impl { of_trait: true }),
485        Allow(Target::Expression),
486        Allow(Target::Statement),
487        Allow(Target::Arm),
488        Allow(Target::AssocConst),
489        Allow(Target::Method(MethodKind::Inherent)),
490        Allow(Target::Method(MethodKind::Trait { body: false })),
491        Allow(Target::Method(MethodKind::Trait { body: true })),
492        Allow(Target::Method(MethodKind::TraitImpl)),
493        Allow(Target::AssocTy),
494        Allow(Target::ForeignFn),
495        Allow(Target::ForeignStatic),
496        Allow(Target::ForeignTy),
497        Allow(Target::MacroDef),
498        Allow(Target::Param),
499        Allow(Target::PatField),
500        Allow(Target::ExprField),
501        Allow(Target::WherePredicate),
502        Allow(Target::MacroCall),
503        Allow(Target::Crate),
504        Allow(Target::Delegation { mac: false }),
505        Allow(Target::Delegation { mac: true }),
506        Allow(Target::GenericParam {
507            kind: rustc_hir::target::GenericParamKind::Const,
508            has_default: false,
509        }),
510        Allow(Target::GenericParam {
511            kind: rustc_hir::target::GenericParamKind::Const,
512            has_default: true,
513        }),
514        Allow(Target::GenericParam {
515            kind: rustc_hir::target::GenericParamKind::Lifetime,
516            has_default: false,
517        }),
518        Allow(Target::GenericParam {
519            kind: rustc_hir::target::GenericParamKind::Lifetime,
520            has_default: true,
521        }),
522        Allow(Target::GenericParam {
523            kind: rustc_hir::target::GenericParamKind::Type,
524            has_default: false,
525        }),
526        Allow(Target::GenericParam {
527            kind: rustc_hir::target::GenericParamKind::Type,
528            has_default: true,
529        }),
530        Allow(Target::Loop),
531        Allow(Target::ForLoop),
532        Allow(Target::While),
533        Allow(Target::Break),
534    ]
535};