Skip to content
Closed
2 changes: 2 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use rustc_session::parse::{ParseSess, feature_err};
use rustc_span::{ErrorGuaranteed, Span, Symbol, sym};
use thin_vec::ThinVec;

use crate::attributes::AttributeSafety;
use crate::context::{AcceptContext, ShouldEmit, Stage};
use crate::parser::{
AllowExprMetavar, ArgParser, MetaItemListParser, MetaItemOrLitParser, NameValueParser,
Expand Down Expand Up @@ -408,6 +409,7 @@ fn parse_cfg_attr_internal<'a>(
attribute.style,
AttrPath { segments: attribute.path().into_boxed_slice(), span: attribute.span },
Some(attribute.get_normal_item().unsafety),
AttributeSafety::Normal,
ParsedDescription::Attribute,
pred_span,
CRATE_NODE_ID,
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/cfg_select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use rustc_session::Session;
use rustc_session::lint::builtin::UNREACHABLE_CFG_SELECT_PREDICATES;
use rustc_span::{ErrorGuaranteed, Span, Symbol, sym};

use crate::attributes::AttributeSafety;
use crate::parser::{AllowExprMetavar, MetaItemOrLitParser};
use crate::{AttributeParser, ParsedDescription, ShouldEmit, errors, parse_cfg_entry};

Expand Down Expand Up @@ -105,6 +106,7 @@ pub fn parse_cfg_select(
AttrStyle::Inner,
AttrPath { segments: vec![sym::cfg_select].into_boxed_slice(), span: cfg_span },
None,
AttributeSafety::Normal,
ParsedDescription::Macro,
cfg_span,
lint_node_id,
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, RtsanSetting, SanitizerSet, UsedBy};
use rustc_session::parse::feature_err;
use rustc_span::edition::Edition::Edition2024;

use super::prelude::*;
use crate::attributes::AttributeSafety;
use crate::session_diagnostics::{
NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, NullOnObjcSelector,
ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral,
Expand Down Expand Up @@ -121,6 +123,7 @@ pub(crate) struct ExportNameParser;
impl<S: Stage> SingleAttributeParser<S> for ExportNameParser {
const PATH: &[rustc_span::Symbol] = &[sym::export_name];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: Some(Edition2024) };
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
Allow(Target::Static),
Allow(Target::Fn),
Expand Down Expand Up @@ -238,6 +241,7 @@ impl<S: Stage> AttributeParser<S> for NakedParser {
this.span = Some(cx.attr_span);
}
})];
const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None };
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
Allow(Target::Fn),
Allow(Target::Method(MethodKind::Inherent)),
Expand Down Expand Up @@ -363,6 +367,7 @@ pub(crate) struct NoMangleParser;
impl<S: Stage> NoArgsAttributeParser<S> for NoMangleParser {
const PATH: &[Symbol] = &[sym::no_mangle];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: Some(Edition2024) };
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
Allow(Target::Fn),
Allow(Target::Static),
Expand Down Expand Up @@ -565,6 +570,7 @@ pub(crate) struct ForceTargetFeatureParser;
impl<S: Stage> CombineAttributeParser<S> for ForceTargetFeatureParser {
type Item = (Symbol, Span);
const PATH: &[Symbol] = &[sym::force_target_feature];
const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None };
const CONVERT: ConvertFn<Self::Item> = |items, span| AttributeKind::TargetFeature {
features: items,
attr_span: span,
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ use rustc_hir::attrs::*;
use rustc_session::Session;
use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
use rustc_session::parse::feature_err;
use rustc_span::edition::Edition::Edition2024;
use rustc_span::kw;
use rustc_target::spec::{Arch, BinaryFormat};

use super::prelude::*;
use super::util::parse_single_integer;
use crate::attributes::AttributeSafety;
use crate::attributes::cfg::parse_cfg_entry;
use crate::session_diagnostics::{
AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ExportSymbolsNeedsStatic,
Expand Down Expand Up @@ -468,6 +470,7 @@ pub(crate) struct LinkSectionParser;
impl<S: Stage> SingleAttributeParser<S> for LinkSectionParser {
const PATH: &[Symbol] = &[sym::link_section];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: Some(Edition2024) };
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
Allow(Target::Static),
Allow(Target::Fn),
Expand Down Expand Up @@ -513,6 +516,7 @@ pub(crate) struct FfiConstParser;
impl<S: Stage> NoArgsAttributeParser<S> for FfiConstParser {
const PATH: &[Symbol] = &[sym::ffi_const];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None };
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]);
const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiConst;
}
Expand All @@ -521,6 +525,7 @@ pub(crate) struct FfiPureParser;
impl<S: Stage> NoArgsAttributeParser<S> for FfiPureParser {
const PATH: &[Symbol] = &[sym::ffi_pure];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
const SAFETY: AttributeSafety = AttributeSafety::Unsafe { unsafe_since: None };
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]);
const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure;
}
Expand Down
20 changes: 20 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::marker::PhantomData;

use rustc_feature::{AttributeTemplate, template};
use rustc_hir::attrs::AttributeKind;
use rustc_span::edition::Edition;
use rustc_span::{Span, Symbol};
use thin_vec::ThinVec;

Expand Down Expand Up @@ -98,6 +99,7 @@ pub(crate) trait AttributeParser<S: Stage>: Default + 'static {
/// If an attribute has this symbol, the `accept` function will be called on it.
const ATTRIBUTES: AcceptMapping<Self, S>;
const ALLOWED_TARGETS: AllowedTargets;
const SAFETY: AttributeSafety = AttributeSafety::Normal;

/// The parser has gotten a chance to accept the attributes on an item,
/// here it can produce an attribute.
Expand Down Expand Up @@ -128,6 +130,7 @@ pub(crate) trait SingleAttributeParser<S: Stage>: 'static {
/// Configures what to do when when the same attribute is
/// applied more than once on the same syntax node.
const ON_DUPLICATE: OnDuplicate<S>;
const SAFETY: AttributeSafety = AttributeSafety::Normal;

const ALLOWED_TARGETS: AllowedTargets;

Expand Down Expand Up @@ -166,6 +169,7 @@ impl<T: SingleAttributeParser<S>, S: Stage> AttributeParser<S> for Single<T, S>
},
)];
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
const SAFETY: AttributeSafety = T::SAFETY;

fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
Some(self.1?.0)
Expand Down Expand Up @@ -218,6 +222,18 @@ impl<S: Stage> OnDuplicate<S> {
}
}

#[derive(Copy, Clone, PartialEq, Debug)]
pub enum AttributeSafety {
/// Normal attribute that does not need `#[unsafe(...)]`
Normal,
/// Unsafe attribute that requires safety obligations to be discharged.
///
/// An error is emitted when `#[unsafe(...)]` is omitted, except when the attribute's edition
/// is less than the one stored in `unsafe_since`. This handles attributes that were safe in
/// earlier editions, but become unsafe in later ones.
Unsafe { unsafe_since: Option<Edition> },
}

/// An even simpler version of [`SingleAttributeParser`]:
/// now automatically check that there are no arguments provided to the attribute.
///
Expand All @@ -227,6 +243,7 @@ pub(crate) trait NoArgsAttributeParser<S: Stage>: 'static {
const PATH: &[Symbol];
const ON_DUPLICATE: OnDuplicate<S>;
const ALLOWED_TARGETS: AllowedTargets;
const SAFETY: AttributeSafety = AttributeSafety::Normal;

/// Create the [`AttributeKind`] given attribute's [`Span`].
const CREATE: fn(Span) -> AttributeKind;
Expand All @@ -243,6 +260,7 @@ impl<T: NoArgsAttributeParser<S>, S: Stage> Default for WithoutArgs<T, S> {
impl<T: NoArgsAttributeParser<S>, S: Stage> SingleAttributeParser<S> for WithoutArgs<T, S> {
const PATH: &[Symbol] = T::PATH;
const ON_DUPLICATE: OnDuplicate<S> = T::ON_DUPLICATE;
const SAFETY: AttributeSafety = T::SAFETY;
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
const TEMPLATE: AttributeTemplate = template!(Word);

Expand Down Expand Up @@ -272,6 +290,7 @@ pub(crate) trait CombineAttributeParser<S: Stage>: 'static {
/// For example, individual representations from `#[repr(...)]` attributes into an `AttributeKind::Repr(x)`,
/// where `x` is a vec of these individual reprs.
const CONVERT: ConvertFn<Self::Item>;
const SAFETY: AttributeSafety = AttributeSafety::Normal;

const ALLOWED_TARGETS: AllowedTargets;

Expand Down Expand Up @@ -313,6 +332,7 @@ impl<T: CombineAttributeParser<S>, S: Stage> AttributeParser<S> for Combine<T, S
group.items.extend(T::extend(cx, args))
})];
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
const SAFETY: AttributeSafety = T::SAFETY;

fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
if let Some(first_span) = self.first_span {
Expand Down
4 changes: 3 additions & 1 deletion compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ use crate::attributes::stability::*;
use crate::attributes::test_attrs::*;
use crate::attributes::traits::*;
use crate::attributes::transparency::*;
use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs};
use crate::attributes::{AttributeParser as _, AttributeSafety, Combine, Single, WithoutArgs};
use crate::parser::{ArgParser, RefPathParser};
use crate::session_diagnostics::{
AttributeParseError, AttributeParseErrorReason, AttributeParseErrorSuggestions,
Expand All @@ -76,6 +76,7 @@ pub(super) struct GroupTypeInnerAccept<S: Stage> {
pub(super) template: AttributeTemplate,
pub(super) accept_fn: AcceptFn<S>,
pub(super) allowed_targets: AllowedTargets,
pub(super) safety: AttributeSafety,
pub(super) finalizer: FinalizeFn<S>,
}

Expand Down Expand Up @@ -126,6 +127,7 @@ macro_rules! attribute_parsers {
accept_fn(s, cx, args)
})
}),
safety: <$names as crate::attributes::AttributeParser<$stage>>::SAFETY,
allowed_targets: <$names as crate::attributes::AttributeParser<$stage>>::ALLOWED_TARGETS,
finalizer: Box::new(|cx| {
let state = STATE_OBJECT.take();
Expand Down
35 changes: 27 additions & 8 deletions compiler/rustc_attr_parsing/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use rustc_session::Session;
use rustc_session::lint::{BuiltinLintDiag, LintId};
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};

use crate::attributes::AttributeSafety;
use crate::context::{AcceptContext, FinalizeContext, FinalizeFn, SharedContext, Stage};
use crate::early_parsed::{EARLY_PARSED_ATTRIBUTES, EarlyParsedState};
use crate::parser::{AllowExprMetavar, ArgParser, PathParser, RefPathParser};
Expand Down Expand Up @@ -172,6 +173,7 @@ impl<'sess> AttributeParser<'sess, Early> {
parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser) -> Option<T>,
template: &AttributeTemplate,
allow_expr_metavar: AllowExprMetavar,
expected_safety: AttributeSafety,
) -> Option<T> {
let ast::AttrKind::Normal(normal_attr) = &attr.kind else {
panic!("parse_single called on a doc attr")
Expand All @@ -194,6 +196,7 @@ impl<'sess> AttributeParser<'sess, Early> {
attr.style,
path,
Some(normal_attr.item.unsafety),
expected_safety,
ParsedDescription::Attribute,
target_span,
target_node_id,
Expand All @@ -215,6 +218,7 @@ impl<'sess> AttributeParser<'sess, Early> {
attr_style: AttrStyle,
attr_path: AttrPath,
attr_safety: Option<Safety>,
expected_safety: AttributeSafety,
parsed_description: ParsedDescription,
target_span: Span,
target_node_id: NodeId,
Expand All @@ -236,7 +240,13 @@ impl<'sess> AttributeParser<'sess, Early> {
)
};
if let Some(safety) = attr_safety {
parser.check_attribute_safety(&attr_path, inner_span, safety, &mut emit_lint)
parser.check_attribute_safety(
&attr_path,
inner_span,
safety,
expected_safety,
&mut emit_lint,
)
}
let attr_id = sess.psess.attr_id_generator.mk_attr_id();
let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext {
Expand Down Expand Up @@ -353,17 +363,18 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
}
};

self.check_attribute_safety(
&attr_path,
lower_span(n.item.span()),
n.item.unsafety,
&mut emit_lint,
);

let parts =
n.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();

if let Some(accept) = S::parsers().accepters.get(parts.as_slice()) {
self.check_attribute_safety(
&attr_path,
lower_span(n.item.span()),
n.item.unsafety,
accept.safety,
&mut emit_lint,
);

let Some(args) = ArgParser::from_attr_args(
args,
&parts,
Expand Down Expand Up @@ -437,6 +448,14 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
span: attr_span,
};

self.check_attribute_safety(
&attr_path,
lower_span(n.item.span()),
n.item.unsafety,
AttributeSafety::Normal,
&mut emit_lint,
);

if !matches!(self.stage.should_emit(), ShouldEmit::Nothing)
&& target == Target::Crate
{
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ mod session_diagnostics;
mod target_checking;
pub mod validate_attr;

pub use attributes::AttributeSafety;
pub use attributes::cfg::{
CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg, parse_cfg_attr, parse_cfg_entry,
};
Expand Down
24 changes: 8 additions & 16 deletions compiler/rustc_attr_parsing/src/safety.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use rustc_ast::Safety;
use rustc_feature::{AttributeSafety, BUILTIN_ATTRIBUTE_MAP};
use rustc_hir::AttrPath;
use rustc_hir::lints::AttributeLintKind;
use rustc_session::lint::LintId;
use rustc_session::lint::builtin::UNSAFE_ATTR_OUTSIDE_UNSAFE;
use rustc_span::Span;

use crate::attributes::AttributeSafety;
use crate::context::Stage;
use crate::{AttributeParser, ShouldEmit};

Expand All @@ -15,28 +15,23 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
attr_path: &AttrPath,
attr_span: Span,
attr_safety: Safety,
expected_safety: AttributeSafety,
emit_lint: &mut impl FnMut(LintId, Span, AttributeLintKind),
) {
if matches!(self.stage.should_emit(), ShouldEmit::Nothing) {
return;
}

let name = (attr_path.segments.len() == 1).then_some(attr_path.segments[0]);

// FIXME: We should retrieve this information from the attribute parsers instead of from `BUILTIN_ATTRIBUTE_MAP`
let builtin_attr_info = name.and_then(|name| BUILTIN_ATTRIBUTE_MAP.get(&name));
let builtin_attr_safety = builtin_attr_info.map(|x| x.safety);

match (builtin_attr_safety, attr_safety) {
match (expected_safety, attr_safety) {
// - Unsafe builtin attribute
// - User wrote `#[unsafe(..)]`, which is permitted on any edition
(Some(AttributeSafety::Unsafe { .. }), Safety::Unsafe(..)) => {
(AttributeSafety::Unsafe { .. }, Safety::Unsafe(..)) => {
// OK
}

// - Unsafe builtin attribute
// - User did not write `#[unsafe(..)]`
(Some(AttributeSafety::Unsafe { unsafe_since }), Safety::Default) => {
(AttributeSafety::Unsafe { unsafe_since }, Safety::Default) => {
let path_span = attr_path.span;

// If the `attr_item`'s span is not from a macro, then just suggest
Expand Down Expand Up @@ -95,7 +90,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {

// - Normal builtin attribute
// - Writing `#[unsafe(..)]` is not permitted on normal builtin attributes
(None | Some(AttributeSafety::Normal), Safety::Unsafe(unsafe_span)) => {
(AttributeSafety::Normal, Safety::Unsafe(unsafe_span)) => {
self.stage.emit_err(
self.sess,
crate::session_diagnostics::InvalidAttrUnsafe {
Expand All @@ -107,14 +102,11 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {

// - Normal builtin attribute
// - No explicit `#[unsafe(..)]` written.
(None | Some(AttributeSafety::Normal), Safety::Default) => {
(AttributeSafety::Normal, Safety::Default) => {
// OK
}

(
Some(AttributeSafety::Unsafe { .. } | AttributeSafety::Normal) | None,
Safety::Safe(..),
) => {
(_, Safety::Safe(..)) => {
self.sess.dcx().span_delayed_bug(
attr_span,
"`check_attribute_safety` does not expect `Safety::Safe` on attributes",
Expand Down
Loading
Loading