Skip to content

Commit

Permalink
Treat safe target_feature functions as unsafe by default
Browse files Browse the repository at this point in the history
  • Loading branch information
oli-obk committed Jan 15, 2025
1 parent a907c56 commit 56178dd
Show file tree
Hide file tree
Showing 20 changed files with 159 additions and 56 deletions.
15 changes: 13 additions & 2 deletions compiler/rustc_ast_lowering/src/delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,14 @@ impl<'hir> LoweringContext<'_, 'hir> {
) -> hir::FnSig<'hir> {
let header = if let Some(local_sig_id) = sig_id.as_local() {
match self.resolver.delegation_fn_sigs.get(&local_sig_id) {
Some(sig) => self.lower_fn_header(sig.header, hir::Safety::Safe),
Some(sig) => self.lower_fn_header(
sig.header,
// HACK: we override the default safety instead of generating attributes from the ether.
// We are not forwarding the attributes, as the delegation fn sigs are collected on the ast,
// and here we need the hir attributes.
if sig.target_feature { hir::Safety::Unsafe } else { hir::Safety::Safe },
&[],
),
None => self.generate_header_error(),
}
} else {
Expand All @@ -198,7 +205,11 @@ impl<'hir> LoweringContext<'_, 'hir> {
Asyncness::No => hir::IsAsync::NotAsync,
};
hir::FnHeader {
safety: sig.safety.into(),
safety: if self.tcx.codegen_fn_attrs(sig_id).safe_target_features {
hir::HeaderSafety::SafeTargetFeatures
} else {
hir::HeaderSafety::Normal(sig.safety)
},
constness: self.tcx.constness(sig_id),
asyncness,
abi: sig.abi,
Expand Down
24 changes: 19 additions & 5 deletions compiler/rustc_ast_lowering/src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
});
let sig = hir::FnSig {
decl,
header: this.lower_fn_header(*header, hir::Safety::Safe),
header: this.lower_fn_header(*header, hir::Safety::Safe, attrs),
span: this.lower_span(*fn_sig_span),
};
hir::ItemKind::Fn { sig, generics, body: body_id, has_body: body.is_some() }
Expand Down Expand Up @@ -610,7 +610,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
fn lower_foreign_item(&mut self, i: &ForeignItem) -> &'hir hir::ForeignItem<'hir> {
let hir_id = hir::HirId::make_owner(self.current_hir_id_owner.def_id);
let owner_id = hir_id.expect_owner();
self.lower_attrs(hir_id, &i.attrs);
let attrs = self.lower_attrs(hir_id, &i.attrs);
let item = hir::ForeignItem {
owner_id,
ident: self.lower_ident(i.ident),
Expand All @@ -634,7 +634,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
});

// Unmarked safety in unsafe block defaults to unsafe.
let header = self.lower_fn_header(sig.header, hir::Safety::Unsafe);
let header = self.lower_fn_header(sig.header, hir::Safety::Unsafe, attrs);

hir::ForeignItemKind::Fn(
hir::FnSig { header, decl, span: self.lower_span(sig.span) },
Expand Down Expand Up @@ -776,6 +776,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
i.id,
FnDeclKind::Trait,
sig.header.coroutine_kind,
attrs,
);
(generics, hir::TraitItemKind::Fn(sig, hir::TraitFn::Required(names)), false)
}
Expand All @@ -795,6 +796,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
i.id,
FnDeclKind::Trait,
sig.header.coroutine_kind,
attrs,
);
(generics, hir::TraitItemKind::Fn(sig, hir::TraitFn::Provided(body_id)), true)
}
Expand Down Expand Up @@ -911,6 +913,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
i.id,
if self.is_in_trait_impl { FnDeclKind::Impl } else { FnDeclKind::Inherent },
sig.header.coroutine_kind,
attrs,
);

(generics, hir::ImplItemKind::Fn(sig, body_id))
Expand Down Expand Up @@ -1339,8 +1342,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
id: NodeId,
kind: FnDeclKind,
coroutine_kind: Option<CoroutineKind>,
attrs: &[hir::Attribute],
) -> (&'hir hir::Generics<'hir>, hir::FnSig<'hir>) {
let header = self.lower_fn_header(sig.header, hir::Safety::Safe);
let header = self.lower_fn_header(sig.header, hir::Safety::Safe, attrs);
let itctx = ImplTraitContext::Universal;
let (generics, decl) = self.lower_generics(generics, id, itctx, |this| {
this.lower_fn_decl(&sig.decl, id, sig.span, kind, coroutine_kind)
Expand All @@ -1352,6 +1356,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
&mut self,
h: FnHeader,
default_safety: hir::Safety,
attrs: &[hir::Attribute],
) -> hir::FnHeader {
let asyncness = if let Some(CoroutineKind::Async { span, .. }) = h.coroutine_kind {
hir::IsAsync::Async(span)
Expand All @@ -1360,7 +1365,16 @@ impl<'hir> LoweringContext<'_, 'hir> {
};

let safety = self.lower_safety(h.safety, default_safety);
let safety = safety.into();

// Treat safe `#[target_feature]` functions as unsafe, but also remember that we did so.
let safety = if attrs.iter().any(|attr| attr.has_name(sym::target_feature))
&& safety.is_safe()
&& !self.tcx.sess.target.is_like_wasm
{
hir::HeaderSafety::SafeTargetFeatures
} else {
safety.into()
};

hir::FnHeader {
safety,
Expand Down
12 changes: 8 additions & 4 deletions compiler/rustc_codegen_ssa/src/codegen_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,14 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
}
}
sym::target_feature => {
if !tcx.is_closure_like(did.to_def_id())
&& let Some(fn_sig) = fn_sig()
&& fn_sig.skip_binder().safety().is_safe()
{
let Some(sig) = tcx.hir_node_by_def_id(did).fn_sig() else {
tcx.dcx().span_delayed_bug(attr.span, "target_feature applied to non-fn");
continue;
};
let safe_target_features =
matches!(sig.header.safety, hir::HeaderSafety::SafeTargetFeatures);
codegen_fn_attrs.safe_target_features = safe_target_features;
if safe_target_features {
if tcx.sess.target.is_like_wasm || tcx.sess.opts.actually_rustdoc {
// The `#[target_feature]` attribute is allowed on
// WebAssembly targets on all functions, including safe
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3762,8 +3762,18 @@ impl fmt::Display for Constness {
}
}

/// The actualy safety specified in syntax. We may treat
/// its safety different within the type system to create a
/// "sound by default" system that needs checking this enum
/// explicitly to allow unsafe operations.
#[derive(Copy, Clone, Debug, HashStable_Generic, PartialEq, Eq)]
pub enum HeaderSafety {
/// A safe function annotated with `#[target_features]`.
/// The type system treats this function as an unsafe function,
/// but safety checking will check this enum to treat it as safe
/// and allowing calling other safe target feature functions with
/// the same features without requiring an additional unsafe block.
SafeTargetFeatures,
Normal(Safety),
}

Expand Down Expand Up @@ -3800,6 +3810,7 @@ impl FnHeader {

pub fn safety(&self) -> Safety {
match self.safety {
HeaderSafety::SafeTargetFeatures => Safety::Unsafe,
HeaderSafety::Normal(safety) => safety,
}
}
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_hir_pretty/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2424,6 +2424,10 @@ impl<'a> State<'a> {
self.print_constness(header.constness);

let safety = match header.safety {
hir::HeaderSafety::SafeTargetFeatures => {
self.word_nbsp("#[target_feature]");
hir::Safety::Safe
}
hir::HeaderSafety::Normal(safety) => safety,
};

Expand Down
12 changes: 9 additions & 3 deletions compiler/rustc_hir_typeck/src/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -932,9 +932,15 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
return Err(TypeError::ForceInlineCast);
}

// Safe `#[target_feature]` functions are not assignable to safe fn pointers (RFC 2396),
// report a better error than a safety mismatch.
// FIXME(target_feature): do this inside `coerce_from_safe_fn`.
let fn_attrs = self.tcx.codegen_fn_attrs(def_id);
if matches!(fn_attrs.inline, InlineAttr::Force { .. }) {
return Err(TypeError::ForceInlineCast);
}

// FIXME(target_feature): Safe `#[target_feature]` functions could be cast to safe fn pointers (RFC 2396),
// as you can already write that "cast" in user code by wrapping a target_feature fn call in a closure,
// which is safe. This is sound because you already need to be executing code that is satisfying the target
// feature constraints..
if b_hdr.safety.is_safe()
&& self.tcx.codegen_fn_attrs(def_id).safe_target_features
{
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_mir_build/src/check_unsafety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,10 @@ pub(crate) fn check_unsafety(tcx: TyCtxt<'_>, def: LocalDefId) {
let hir_id = tcx.local_def_id_to_hir_id(def);
let safety_context = tcx.hir().fn_sig_by_hir_id(hir_id).map_or(SafetyContext::Safe, |fn_sig| {
match fn_sig.header.safety {
// We typeck the body as safe, but otherwise treat it as unsafe everywhere else.
// Call sites to other SafeTargetFeatures functions are checked explicitly and don't need
// to care about safety of the body.
hir::HeaderSafety::SafeTargetFeatures => SafetyContext::Safe,
hir::HeaderSafety::Normal(safety) => match safety {
hir::Safety::Unsafe => SafetyContext::UnsafeFn,
hir::Safety::Safe => SafetyContext::Safe,
Expand Down
15 changes: 13 additions & 2 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,14 +668,25 @@ impl Item {
ty::Asyncness::Yes => hir::IsAsync::Async(DUMMY_SP),
ty::Asyncness::No => hir::IsAsync::NotAsync,
};
hir::FnHeader { safety: sig.safety().into(), abi: sig.abi(), constness, asyncness }
hir::FnHeader {
safety: if tcx.codegen_fn_attrs(def_id).safe_target_features {
hir::HeaderSafety::SafeTargetFeatures
} else {
sig.safety().into()
},
abi: sig.abi(),
constness,
asyncness,
}
}
let header = match self.kind {
ItemKind::ForeignFunctionItem(_, safety) => {
let def_id = self.def_id().unwrap();
let abi = tcx.fn_sig(def_id).skip_binder().abi();
hir::FnHeader {
safety: if abi == ExternAbi::RustIntrinsic {
safety: if tcx.codegen_fn_attrs(def_id).safe_target_features {
hir::HeaderSafety::SafeTargetFeatures
} else if abi == ExternAbi::RustIntrinsic {
intrinsic_operation_unsafety(tcx, def_id.expect_local()).into()
} else {
safety.into()
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/html/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1640,6 +1640,7 @@ impl PrintWithSpace for hir::Safety {
impl PrintWithSpace for hir::HeaderSafety {
fn print_with_space(&self) -> &str {
match self {
hir::HeaderSafety::SafeTargetFeatures => "",
hir::HeaderSafety::Normal(safety) => safety.print_with_space(),
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error[E0277]: the trait bound `fn() -> Pin<Box<(dyn Future<Output = ()> + 'static)>> {target_feature}: AsyncFn()` is not satisfied
error[E0277]: the trait bound `unsafe fn() -> Pin<Box<(dyn Future<Output = ()> + 'static)>> {target_feature}: AsyncFn()` is not satisfied
--> $DIR/fn-exception-target-features.rs:16:10
|
LL | test(target_feature);
| ---- ^^^^^^^^^^^^^^ the trait `AsyncFn()` is not implemented for fn item `fn() -> Pin<Box<(dyn Future<Output = ()> + 'static)>> {target_feature}`
| ---- ^^^^^^^^^^^^^^ the trait `AsyncFn()` is not implemented for fn item `unsafe fn() -> Pin<Box<(dyn Future<Output = ()> + 'static)>> {target_feature}`
| |
| required by a bound introduced by this call
|
Expand Down
2 changes: 0 additions & 2 deletions tests/ui/macros/issue-68060.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ fn main() {
.map(
#[target_feature(enable = "")]
//~^ ERROR: attribute should be applied to a function
//~| ERROR: feature named `` is not valid
//~| NOTE: `` is not valid for this target
#[track_caller]
//~^ ERROR: `#[track_caller]` on closures is currently unstable
//~| NOTE: see issue #87417
Expand Down
10 changes: 2 additions & 8 deletions tests/ui/macros/issue-68060.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@ LL | #[target_feature(enable = "")]
LL | |_| (),
| ------ not a function definition

error: the feature named `` is not valid for this target
--> $DIR/issue-68060.rs:4:30
|
LL | #[target_feature(enable = "")]
| ^^^^^^^^^^^ `` is not valid for this target

error[E0658]: `#[track_caller]` on closures is currently unstable
--> $DIR/issue-68060.rs:8:13
--> $DIR/issue-68060.rs:6:13
|
LL | #[track_caller]
| ^^^^^^^^^^^^^^^
Expand All @@ -23,6 +17,6 @@ LL | #[track_caller]
= help: add `#![feature(closure_track_caller)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date

error: aborting due to 3 previous errors
error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0658`.
8 changes: 2 additions & 6 deletions tests/ui/rfcs/rfc-2396-target_feature-11/fn-ptr.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,9 @@ LL | let foo: fn() = foo;
| expected due to this
|
= note: expected fn pointer `fn()`
found fn item `fn() {foo}`
= note: fn items are distinct from fn pointers
found fn item `unsafe fn() {foo}`
= note: functions with `#[target_feature]` can only be coerced to `unsafe` function pointers
help: consider casting to a fn pointer
|
LL | let foo: fn() = foo as fn();
| ~~~~~~~~~~~
= note: unsafe functions cannot be coerced into safe function pointers

error: aborting due to 1 previous error

Expand Down
6 changes: 3 additions & 3 deletions tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ fn call_once(f: impl FnOnce()) {
}

fn main() {
call(foo); //~ ERROR expected a `Fn()` closure, found `fn() {foo}`
call_mut(foo); //~ ERROR expected a `FnMut()` closure, found `fn() {foo}`
call_once(foo); //~ ERROR expected a `FnOnce()` closure, found `fn() {foo}`
call(foo); //~ ERROR expected a `Fn()` closure, found `unsafe fn() {foo}`
call_mut(foo); //~ ERROR expected a `FnMut()` closure, found `unsafe fn() {foo}`
call_once(foo); //~ ERROR expected a `FnOnce()` closure, found `unsafe fn() {foo}`

call(foo_unsafe);
//~^ ERROR expected a `Fn()` closure, found `unsafe fn() {foo_unsafe}`
Expand Down
27 changes: 15 additions & 12 deletions tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.stderr
Original file line number Diff line number Diff line change
@@ -1,47 +1,50 @@
error[E0277]: expected a `Fn()` closure, found `fn() {foo}`
error[E0277]: expected a `Fn()` closure, found `unsafe fn() {foo}`
--> $DIR/fn-traits.rs:24:10
|
LL | call(foo);
| ---- ^^^ expected an `Fn()` closure, found `fn() {foo}`
| ---- ^^^ call the function in a closure: `|| unsafe { /* code */ }`
| |
| required by a bound introduced by this call
|
= help: the trait `Fn()` is not implemented for fn item `fn() {foo}`
= note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ }`
= help: the trait `Fn()` is not implemented for fn item `unsafe fn() {foo}`
= note: unsafe function cannot be called generically without an unsafe block
= note: wrap the `unsafe fn() {foo}` in a closure with no arguments: `|| { /* code */ }`
= note: `#[target_feature]` functions do not implement the `Fn` traits
note: required by a bound in `call`
--> $DIR/fn-traits.rs:11:17
|
LL | fn call(f: impl Fn()) {
| ^^^^ required by this bound in `call`

error[E0277]: expected a `FnMut()` closure, found `fn() {foo}`
error[E0277]: expected a `FnMut()` closure, found `unsafe fn() {foo}`
--> $DIR/fn-traits.rs:25:14
|
LL | call_mut(foo);
| -------- ^^^ expected an `FnMut()` closure, found `fn() {foo}`
| -------- ^^^ call the function in a closure: `|| unsafe { /* code */ }`
| |
| required by a bound introduced by this call
|
= help: the trait `FnMut()` is not implemented for fn item `fn() {foo}`
= note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ }`
= help: the trait `FnMut()` is not implemented for fn item `unsafe fn() {foo}`
= note: unsafe function cannot be called generically without an unsafe block
= note: wrap the `unsafe fn() {foo}` in a closure with no arguments: `|| { /* code */ }`
= note: `#[target_feature]` functions do not implement the `Fn` traits
note: required by a bound in `call_mut`
--> $DIR/fn-traits.rs:15:25
|
LL | fn call_mut(mut f: impl FnMut()) {
| ^^^^^^^ required by this bound in `call_mut`

error[E0277]: expected a `FnOnce()` closure, found `fn() {foo}`
error[E0277]: expected a `FnOnce()` closure, found `unsafe fn() {foo}`
--> $DIR/fn-traits.rs:26:15
|
LL | call_once(foo);
| --------- ^^^ expected an `FnOnce()` closure, found `fn() {foo}`
| --------- ^^^ call the function in a closure: `|| unsafe { /* code */ }`
| |
| required by a bound introduced by this call
|
= help: the trait `FnOnce()` is not implemented for fn item `fn() {foo}`
= note: wrap the `fn() {foo}` in a closure with no arguments: `|| { /* code */ }`
= help: the trait `FnOnce()` is not implemented for fn item `unsafe fn() {foo}`
= note: unsafe function cannot be called generically without an unsafe block
= note: wrap the `unsafe fn() {foo}` in a closure with no arguments: `|| { /* code */ }`
= note: `#[target_feature]` functions do not implement the `Fn` traits
note: required by a bound in `call_once`
--> $DIR/fn-traits.rs:19:22
Expand Down
Loading

0 comments on commit 56178dd

Please sign in to comment.