1
Fork 0

Make ptr_cast_add_auto_to_object lint into hard error

In Rust 1.81, we added a FCW lint (including linting in dependencies)
against pointer casts that add an auto trait to dyn bounds.  This was
part of work making casts of pointers involving trait objects stricter
which was needed to restabilize trait upcasting.

We considered just making this a hard error at the time, but opted
against it due to breakage found by crater.  This breakage was mostly
due to the `anymap` crate which has been a persistent problem for us.

It's now a year later, and the fact that this is not yet a hard error
is giving us pause about stabilizing arbitrary self types and
`derive(CoercePointee)`.  So let's now make a hard error of this.
This commit is contained in:
Travis Cross 2025-02-09 06:08:59 +00:00
parent ed49386d3a
commit ef337a6599
12 changed files with 109 additions and 112 deletions

View file

@ -0,0 +1,41 @@
An auto trait cannot be added to the bounds of a `dyn Trait` type via
a pointer cast.
Erroneous code example:
```rust,edition2021,compile_fail,E0804
let ptr: *const dyn core::any::Any = &();
_ = ptr as *const (dyn core::any::Any + Send);
```
Adding an auto trait can make the vtable invalid, potentially causing
UB in safe code afterwards. For example:
```rust,edition2021,no_run
use core::{mem::transmute, ptr::NonNull};
trait Trait {
fn f(&self)
where
Self: Send;
}
impl Trait for NonNull<()> {
fn f(&self) {
unreachable!()
}
}
fn main() {
let unsend: &dyn Trait = &NonNull::dangling();
let bad: &(dyn Trait + Send) = unsafe { transmute(unsend) };
// This crashes, since the vtable for `NonNull as dyn Trait` does
// not have an entry for `Trait::f`.
bad.f();
}
```
To fix this error, you can use `transmute` rather than pointer casts,
but you must ensure that the vtable is valid for the pointer's type
before calling a method on the trait object or allowing other code to
do so.

View file

@ -547,6 +547,7 @@ E0800: 0800,
E0801: 0801, E0801: 0801,
E0802: 0802, E0802: 0802,
E0803: 0803, E0803: 0803,
E0804: 0804,
); );
) )
} }

View file

@ -154,10 +154,13 @@ hir_typeck_pass_to_variadic_function = can't pass `{$ty}` to variadic function
.suggestion = cast the value to `{$cast_ty}` .suggestion = cast the value to `{$cast_ty}`
.teach_help = certain types, like `{$ty}`, must be casted before passing them to a variadic function, because of arcane ABI rules dictated by the C standard .teach_help = certain types, like `{$ty}`, must be casted before passing them to a variadic function, because of arcane ABI rules dictated by the C standard
hir_typeck_ptr_cast_add_auto_to_object = adding {$traits_len -> hir_typeck_ptr_cast_add_auto_to_object = cannot add {$traits_len ->
[1] an auto trait {$traits} [1] auto trait {$traits}
*[other] auto traits {$traits} *[other] auto traits {$traits}
} to a trait object in a pointer cast may cause UB later on } to dyn bound via pointer cast
.note = this could allow UB elsewhere
.help = use `transmute` if you're sure this is sound
.label = unsupported cast
hir_typeck_remove_semi_for_coerce = you might have meant to return the `match` expression hir_typeck_remove_semi_for_coerce = you might have meant to return the `match` expression
hir_typeck_remove_semi_for_coerce_expr = this could be implicitly returned but it is a statement, not a tail expression hir_typeck_remove_semi_for_coerce_expr = this could be implicitly returned but it is a statement, not a tail expression

View file

@ -936,23 +936,19 @@ impl<'a, 'tcx> CastCheck<'tcx> {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !added.is_empty() { if !added.is_empty() {
tcx.emit_node_span_lint( tcx.dcx().emit_err(errors::PtrCastAddAutoToObject {
lint::builtin::PTR_CAST_ADD_AUTO_TO_OBJECT, span: self.span,
self.expr.hir_id, traits_len: added.len(),
self.span, traits: {
errors::PtrCastAddAutoToObject { let mut traits: Vec<_> = added
traits_len: added.len(), .into_iter()
traits: { .map(|trait_did| tcx.def_path_str(trait_did))
let mut traits: Vec<_> = added .collect();
.into_iter()
.map(|trait_did| tcx.def_path_str(trait_did))
.collect();
traits.sort(); traits.sort();
traits.into() traits.into()
},
}, },
) });
} }
Ok(CastKind::PtrPtrCast) Ok(CastKind::PtrPtrCast)

View file

@ -373,9 +373,14 @@ pub(crate) struct LossyProvenanceInt2Ptr<'tcx> {
pub sugg: LossyProvenanceInt2PtrSuggestion, pub sugg: LossyProvenanceInt2PtrSuggestion,
} }
#[derive(LintDiagnostic)] #[derive(Diagnostic)]
#[diag(hir_typeck_ptr_cast_add_auto_to_object)] #[diag(hir_typeck_ptr_cast_add_auto_to_object, code = E0804)]
#[note]
#[help]
pub(crate) struct PtrCastAddAutoToObject { pub(crate) struct PtrCastAddAutoToObject {
#[primary_span]
#[label]
pub span: Span,
pub traits_len: usize, pub traits_len: usize,
pub traits: DiagSymbolList<String>, pub traits: DiagSymbolList<String>,
} }

View file

@ -601,6 +601,11 @@ fn register_builtins(store: &mut LintStore) {
"converted into hard error, \ "converted into hard error, \
see <https://github.com/rust-lang/rust/issues/73333> for more information", see <https://github.com/rust-lang/rust/issues/73333> for more information",
); );
store.register_removed(
"ptr_cast_add_auto_to_object",
"converted into hard error, see issue #127323 \
<https://github.com/rust-lang/rust/issues/127323> for more information",
);
} }
fn register_internals(store: &mut LintStore) { fn register_internals(store: &mut LintStore) {

View file

@ -80,7 +80,6 @@ declare_lint_pass! {
PRIVATE_BOUNDS, PRIVATE_BOUNDS,
PRIVATE_INTERFACES, PRIVATE_INTERFACES,
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK, PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
PTR_CAST_ADD_AUTO_TO_OBJECT,
PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS,
PUB_USE_OF_PRIVATE_EXTERN_CRATE, PUB_USE_OF_PRIVATE_EXTERN_CRATE,
REDUNDANT_IMPORTS, REDUNDANT_IMPORTS,
@ -4827,58 +4826,6 @@ declare_lint! {
}; };
} }
declare_lint! {
/// The `ptr_cast_add_auto_to_object` lint detects casts of raw pointers to trait
/// objects, which add auto traits.
///
/// ### Example
///
/// ```rust,edition2021,compile_fail
/// let ptr: *const dyn core::any::Any = &();
/// _ = ptr as *const dyn core::any::Any + Send;
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Adding an auto trait can make the vtable invalid, potentially causing
/// UB in safe code afterwards. For example:
///
/// ```ignore (causes a warning)
/// #![feature(arbitrary_self_types)]
///
/// trait Trait {
/// fn f(self: *const Self)
/// where
/// Self: Send;
/// }
///
/// impl Trait for *const () {
/// fn f(self: *const Self) {
/// unreachable!()
/// }
/// }
///
/// fn main() {
/// let unsend: *const () = &();
/// let unsend: *const dyn Trait = &unsend;
/// let send_bad: *const (dyn Trait + Send) = unsend as _;
/// send_bad.f(); // this crashes, since vtable for `*const ()` does not have an entry for `f`
/// }
/// ```
///
/// Generally you must ensure that vtable is right for the pointer's type,
/// before passing the pointer to safe code.
pub PTR_CAST_ADD_AUTO_TO_OBJECT,
Warn,
"detects `as` casts from pointers to `dyn Trait` to pointers to `dyn Trait + Auto`",
@future_incompatible = FutureIncompatibleInfo {
reason: FutureIncompatibilityReason::FutureReleaseErrorReportInDeps,
reference: "issue #127323 <https://github.com/rust-lang/rust/issues/127323>",
};
}
declare_lint! { declare_lint! {
/// The `out_of_scope_macro_calls` lint detects `macro_rules` called when they are not in scope, /// The `out_of_scope_macro_calls` lint detects `macro_rules` called when they are not in scope,
/// above their definition, which may happen in key-value attributes. /// above their definition, which may happen in key-value attributes.

View file

@ -1,18 +1,20 @@
//@ check-pass
trait Trait<'a> {} trait Trait<'a> {}
fn add_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send) { fn add_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send) {
x as _ x as _
//~^ warning: adding an auto trait `Send` to a trait object in a pointer cast may cause UB later on //~^ ERROR cannot add auto trait `Send` to dyn bound via pointer cast
//~| warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! //~| NOTE unsupported cast
//~| NOTE this could allow UB elsewhere
//~| HELP use `transmute` if you're sure this is sound
} }
// (to test diagnostic list formatting) // (to test diagnostic list formatting)
fn add_multiple_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send + Sync + Unpin) { fn add_multiple_auto<'a>(x: *mut dyn Trait<'a>) -> *mut (dyn Trait<'a> + Send + Sync + Unpin) {
x as _ x as _
//~^ warning: adding auto traits `Send`, `Sync`, and `Unpin` to a trait object in a pointer cast may cause UB later on //~^ ERROR cannot add auto traits `Send`, `Sync`, and `Unpin` to dyn bound via pointer cast
//~| warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! //~| NOTE unsupported cast
//~| NOTE this could allow UB elsewhere
//~| HELP use `transmute` if you're sure this is sound
} }
fn main() {} fn main() {}

View file

@ -1,43 +1,21 @@
warning: adding an auto trait `Send` to a trait object in a pointer cast may cause UB later on error[E0804]: cannot add auto trait `Send` to dyn bound via pointer cast
--> $DIR/ptr-to-trait-obj-add-auto.rs:6:5 --> $DIR/ptr-to-trait-obj-add-auto.rs:4:5
| |
LL | x as _ LL | x as _
| ^^^^^^ | ^^^^^^ unsupported cast
| |
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: this could allow UB elsewhere
= note: for more information, see issue #127323 <https://github.com/rust-lang/rust/issues/127323> = help: use `transmute` if you're sure this is sound
= note: `#[warn(ptr_cast_add_auto_to_object)]` on by default
warning: adding auto traits `Send`, `Sync`, and `Unpin` to a trait object in a pointer cast may cause UB later on error[E0804]: cannot add auto traits `Send`, `Sync`, and `Unpin` to dyn bound via pointer cast
--> $DIR/ptr-to-trait-obj-add-auto.rs:13:5 --> $DIR/ptr-to-trait-obj-add-auto.rs:13:5
| |
LL | x as _ LL | x as _
| ^^^^^^ | ^^^^^^ unsupported cast
| |
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: this could allow UB elsewhere
= note: for more information, see issue #127323 <https://github.com/rust-lang/rust/issues/127323> = help: use `transmute` if you're sure this is sound
warning: 2 warnings emitted error: aborting due to 2 previous errors
Future incompatibility report: Future breakage diagnostic:
warning: adding an auto trait `Send` to a trait object in a pointer cast may cause UB later on
--> $DIR/ptr-to-trait-obj-add-auto.rs:6:5
|
LL | x as _
| ^^^^^^
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #127323 <https://github.com/rust-lang/rust/issues/127323>
= note: `#[warn(ptr_cast_add_auto_to_object)]` on by default
Future breakage diagnostic:
warning: adding auto traits `Send`, `Sync`, and `Unpin` to a trait object in a pointer cast may cause UB later on
--> $DIR/ptr-to-trait-obj-add-auto.rs:13:5
|
LL | x as _
| ^^^^^^
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #127323 <https://github.com/rust-lang/rust/issues/127323>
= note: `#[warn(ptr_cast_add_auto_to_object)]` on by default
For more information about this error, try `rustc --explain E0804`.

View file

@ -0,0 +1,4 @@
# Removed lints
This directory contains tests to confirm that lints that have been
removed do not cause errors and produce the appropriate warnings.

View file

@ -0,0 +1,5 @@
//@ check-pass
#![deny(ptr_cast_add_auto_to_object)]
//~^ WARN lint `ptr_cast_add_auto_to_object` has been removed
fn main() {}

View file

@ -0,0 +1,10 @@
warning: lint `ptr_cast_add_auto_to_object` has been removed: converted into hard error, see issue #127323 <https://github.com/rust-lang/rust/issues/127323> for more information
--> $DIR/ptr_cast_add_auto_to_object.rs:3:9
|
LL | #![deny(ptr_cast_add_auto_to_object)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(renamed_and_removed_lints)]` on by default
warning: 1 warning emitted