Rollup merge of #129392 - compiler-errors:raw-ref-op-doesnt-diverge-but-more, r=lcnr
Do not consider match/let/ref of place that evaluates to `!` to diverge, disallow coercions from them too Fixes #117288. This PR implements a heuristic which disables two things that are currently being performed on the HIR when we have **expressions that involve place-like expressions that point to `!`**. Specifically, it will (in certain cases explained below): ### (1.) Disable the `NeverToAny` coercion we implicitly insert for `!`. Which fixes this inadvertent, sneaky unsoundness: ``` unsafe { let x: *const ! = &0 as *const u8 as *const !; let _: () = *x; } ``` which is UB because currently rust emits an *implicit* NeverToAny coercion even though we really shouldn't be, since there's no read of the value pointed by `x`. ### (2.) Disable the logic which considers expression which evaluate to `!` to diverge, which affects the type returned by the containing block. Which fixes this unsoundness: ``` fn make_up_a_value<T>() -> T { unsafe { let x: *const ! = &0 as *const u8 as *const !; let _ = *x; } } ``` We disable these two operations **if** the expression is a place-like expression (locals, statics, field projections, index operations, and deref operations), and if the parent expression is either: (1.) the LHS of an assignment (2.) AddrOf (3.) A match or let **unless** all of the *patterns consitute a read*, which is explained below: And finally, a pattern currently is considered to constitute a read **unless** it is a wildcard, or an OR pattern. An OR pattern is considered to constitute a read if all of its subpatterns constitute a read, to remain as conservative as possible in cases like `_ | subpat` or `subpat | _`. All other patterns are considered currently to constitute a read. Specifically, because `NeverToAny` is a coercion performed on a *value* and not a *place*, `Struct { .. }` on a `!` type must be a coercion currently, and we currently rely on this behavior to allow us to perform coercions like `let _: i32 = x;` where `x: !`. This is already considered UB by [miri](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=daf3a2246433fe43fdc07d1389c276c9), but also means it does not affect the preexisting UB in this case: ``` let Struct { .. } = *never_ptr; ``` Even though it's likely up for debate since we're not actually reading any data out of the struct, it almost certainly causes inference changes which I do *NOT* want to fix in this PR.
This commit is contained in:
commit
9aaebd481a
@ -82,6 +82,11 @@ struct Coerce<'a, 'tcx> {
|
||||
/// See #47489 and #48598
|
||||
/// See docs on the "AllowTwoPhase" type for a more detailed discussion
|
||||
allow_two_phase: AllowTwoPhase,
|
||||
/// Whether we allow `NeverToAny` coercions. This is unsound if we're
|
||||
/// coercing a place expression without it counting as a read in the MIR.
|
||||
/// This is a side-effect of HIR not really having a great distinction
|
||||
/// between places and values.
|
||||
coerce_never: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Deref for Coerce<'a, 'tcx> {
|
||||
@ -125,8 +130,9 @@ fn new(
|
||||
fcx: &'f FnCtxt<'f, 'tcx>,
|
||||
cause: ObligationCause<'tcx>,
|
||||
allow_two_phase: AllowTwoPhase,
|
||||
coerce_never: bool,
|
||||
) -> Self {
|
||||
Coerce { fcx, cause, allow_two_phase, use_lub: false }
|
||||
Coerce { fcx, cause, allow_two_phase, use_lub: false, coerce_never }
|
||||
}
|
||||
|
||||
fn unify(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> InferResult<'tcx, Ty<'tcx>> {
|
||||
@ -177,7 +183,12 @@ fn coerce(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> CoerceResult<'tcx> {
|
||||
|
||||
// Coercing from `!` to any type is allowed:
|
||||
if a.is_never() {
|
||||
return success(simple(Adjust::NeverToAny)(b), b, vec![]);
|
||||
if self.coerce_never {
|
||||
return success(simple(Adjust::NeverToAny)(b), b, vec![]);
|
||||
} else {
|
||||
// Otherwise the only coercion we can do is unification.
|
||||
return self.unify_and(a, b, identity);
|
||||
}
|
||||
}
|
||||
|
||||
// Coercing *from* an unresolved inference variable means that
|
||||
@ -1038,7 +1049,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||
/// The expressions *must not* have any preexisting adjustments.
|
||||
pub(crate) fn coerce(
|
||||
&self,
|
||||
expr: &hir::Expr<'_>,
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
expr_ty: Ty<'tcx>,
|
||||
mut target: Ty<'tcx>,
|
||||
allow_two_phase: AllowTwoPhase,
|
||||
@ -1055,7 +1066,12 @@ pub(crate) fn coerce(
|
||||
|
||||
let cause =
|
||||
cause.unwrap_or_else(|| self.cause(expr.span, ObligationCauseCode::ExprAssignable));
|
||||
let coerce = Coerce::new(self, cause, allow_two_phase);
|
||||
let coerce = Coerce::new(
|
||||
self,
|
||||
cause,
|
||||
allow_two_phase,
|
||||
self.expr_guaranteed_to_constitute_read_for_never(expr),
|
||||
);
|
||||
let ok = self.commit_if_ok(|_| coerce.coerce(source, target))?;
|
||||
|
||||
let (adjustments, _) = self.register_infer_ok_obligations(ok);
|
||||
@ -1077,8 +1093,9 @@ pub(crate) fn can_coerce(&self, expr_ty: Ty<'tcx>, target: Ty<'tcx>) -> bool {
|
||||
debug!("coercion::can_with_predicates({:?} -> {:?})", source, target);
|
||||
|
||||
let cause = self.cause(DUMMY_SP, ObligationCauseCode::ExprAssignable);
|
||||
// We don't ever need two-phase here since we throw out the result of the coercion
|
||||
let coerce = Coerce::new(self, cause, AllowTwoPhase::No);
|
||||
// We don't ever need two-phase here since we throw out the result of the coercion.
|
||||
// We also just always set `coerce_never` to true, since this is a heuristic.
|
||||
let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true);
|
||||
self.probe(|_| {
|
||||
let Ok(ok) = coerce.coerce(source, target) else {
|
||||
return false;
|
||||
@ -1090,12 +1107,16 @@ pub(crate) fn can_coerce(&self, expr_ty: Ty<'tcx>, target: Ty<'tcx>) -> bool {
|
||||
}
|
||||
|
||||
/// Given a type and a target type, this function will calculate and return
|
||||
/// how many dereference steps needed to achieve `expr_ty <: target`. If
|
||||
/// how many dereference steps needed to coerce `expr_ty` to `target`. If
|
||||
/// it's not possible, return `None`.
|
||||
pub(crate) fn deref_steps(&self, expr_ty: Ty<'tcx>, target: Ty<'tcx>) -> Option<usize> {
|
||||
pub(crate) fn deref_steps_for_suggestion(
|
||||
&self,
|
||||
expr_ty: Ty<'tcx>,
|
||||
target: Ty<'tcx>,
|
||||
) -> Option<usize> {
|
||||
let cause = self.cause(DUMMY_SP, ObligationCauseCode::ExprAssignable);
|
||||
// We don't ever need two-phase here since we throw out the result of the coercion
|
||||
let coerce = Coerce::new(self, cause, AllowTwoPhase::No);
|
||||
// We don't ever need two-phase here since we throw out the result of the coercion.
|
||||
let coerce = Coerce::new(self, cause, AllowTwoPhase::No, true);
|
||||
coerce
|
||||
.autoderef(DUMMY_SP, expr_ty)
|
||||
.find_map(|(ty, steps)| self.probe(|_| coerce.unify(ty, target)).ok().map(|_| steps))
|
||||
@ -1252,7 +1273,10 @@ fn try_find_coercion_lub<E>(
|
||||
// probably aren't processing function arguments here and even if we were,
|
||||
// they're going to get autorefed again anyway and we can apply 2-phase borrows
|
||||
// at that time.
|
||||
let mut coerce = Coerce::new(self, cause.clone(), AllowTwoPhase::No);
|
||||
//
|
||||
// NOTE: we set `coerce_never` to `true` here because coercion LUBs only
|
||||
// operate on values and not places, so a never coercion is valid.
|
||||
let mut coerce = Coerce::new(self, cause.clone(), AllowTwoPhase::No, true);
|
||||
coerce.use_lub = true;
|
||||
|
||||
// First try to coerce the new expression to the type of the previous ones,
|
||||
|
@ -1,3 +1,6 @@
|
||||
// ignore-tidy-filelength
|
||||
// FIXME: we should move the field error reporting code somewhere else.
|
||||
|
||||
//! Type checking expressions.
|
||||
//!
|
||||
//! See [`rustc_hir_analysis::check`] for more context on type checking in general.
|
||||
@ -62,7 +65,7 @@ pub(crate) fn check_expr_has_type_or_error(
|
||||
|
||||
// While we don't allow *arbitrary* coercions here, we *do* allow
|
||||
// coercions from ! to `expected`.
|
||||
if ty.is_never() {
|
||||
if ty.is_never() && self.expr_guaranteed_to_constitute_read_for_never(expr) {
|
||||
if let Some(_) = self.typeck_results.borrow().adjustments().get(expr.hir_id) {
|
||||
let reported = self.dcx().span_delayed_bug(
|
||||
expr.span,
|
||||
@ -238,8 +241,11 @@ pub(super) fn check_expr_with_expectation_and_args(
|
||||
_ => self.warn_if_unreachable(expr.hir_id, expr.span, "expression"),
|
||||
}
|
||||
|
||||
// Any expression that produces a value of type `!` must have diverged
|
||||
if ty.is_never() {
|
||||
// Any expression that produces a value of type `!` must have diverged,
|
||||
// unless it's a place expression that isn't being read from, in which case
|
||||
// diverging would be unsound since we may never actually read the `!`.
|
||||
// e.g. `let _ = *never_ptr;` with `never_ptr: *const !`.
|
||||
if ty.is_never() && self.expr_guaranteed_to_constitute_read_for_never(expr) {
|
||||
self.diverges.set(self.diverges.get() | Diverges::always(expr.span));
|
||||
}
|
||||
|
||||
@ -257,6 +263,185 @@ pub(super) fn check_expr_with_expectation_and_args(
|
||||
ty
|
||||
}
|
||||
|
||||
/// Whether this expression constitutes a read of value of the type that
|
||||
/// it evaluates to.
|
||||
///
|
||||
/// This is used to determine if we should consider the block to diverge
|
||||
/// if the expression evaluates to `!`, and if we should insert a `NeverToAny`
|
||||
/// coercion for values of type `!`.
|
||||
///
|
||||
/// This function generally returns `false` if the expression is a place
|
||||
/// expression and the *parent* expression is the scrutinee of a match or
|
||||
/// the pointee of an `&` addr-of expression, since both of those parent
|
||||
/// expressions take a *place* and not a value.
|
||||
pub(super) fn expr_guaranteed_to_constitute_read_for_never(
|
||||
&self,
|
||||
expr: &'tcx hir::Expr<'tcx>,
|
||||
) -> bool {
|
||||
// We only care about place exprs. Anything else returns an immediate
|
||||
// which would constitute a read. We don't care about distinguishing
|
||||
// "syntactic" place exprs since if the base of a field projection is
|
||||
// not a place then it would've been UB to read from it anyways since
|
||||
// that constitutes a read.
|
||||
if !expr.is_syntactic_place_expr() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let parent_node = self.tcx.parent_hir_node(expr.hir_id);
|
||||
match parent_node {
|
||||
hir::Node::Expr(parent_expr) => {
|
||||
match parent_expr.kind {
|
||||
// Addr-of, field projections, and LHS of assignment don't constitute reads.
|
||||
// Assignment does call `drop_in_place`, though, but its safety
|
||||
// requirements are not the same.
|
||||
ExprKind::AddrOf(..) | hir::ExprKind::Field(..) => false,
|
||||
ExprKind::Assign(lhs, _, _) => {
|
||||
// Only the LHS does not constitute a read
|
||||
expr.hir_id != lhs.hir_id
|
||||
}
|
||||
|
||||
// See note on `PatKind::Or` below for why this is `all`.
|
||||
ExprKind::Match(scrutinee, arms, _) => {
|
||||
assert_eq!(scrutinee.hir_id, expr.hir_id);
|
||||
arms.iter()
|
||||
.all(|arm| self.pat_guaranteed_to_constitute_read_for_never(arm.pat))
|
||||
}
|
||||
ExprKind::Let(hir::LetExpr { init, pat, .. }) => {
|
||||
assert_eq!(init.hir_id, expr.hir_id);
|
||||
self.pat_guaranteed_to_constitute_read_for_never(*pat)
|
||||
}
|
||||
|
||||
// Any expression child of these expressions constitute reads.
|
||||
ExprKind::Array(_)
|
||||
| ExprKind::Call(_, _)
|
||||
| ExprKind::MethodCall(_, _, _, _)
|
||||
| ExprKind::Tup(_)
|
||||
| ExprKind::Binary(_, _, _)
|
||||
| ExprKind::Unary(_, _)
|
||||
| ExprKind::Cast(_, _)
|
||||
| ExprKind::Type(_, _)
|
||||
| ExprKind::DropTemps(_)
|
||||
| ExprKind::If(_, _, _)
|
||||
| ExprKind::Closure(_)
|
||||
| ExprKind::Block(_, _)
|
||||
| ExprKind::AssignOp(_, _, _)
|
||||
| ExprKind::Index(_, _, _)
|
||||
| ExprKind::Break(_, _)
|
||||
| ExprKind::Ret(_)
|
||||
| ExprKind::Become(_)
|
||||
| ExprKind::InlineAsm(_)
|
||||
| ExprKind::Struct(_, _, _)
|
||||
| ExprKind::Repeat(_, _)
|
||||
| ExprKind::Yield(_, _) => true,
|
||||
|
||||
// These expressions have no (direct) sub-exprs.
|
||||
ExprKind::ConstBlock(_)
|
||||
| ExprKind::Loop(_, _, _, _)
|
||||
| ExprKind::Lit(_)
|
||||
| ExprKind::Path(_)
|
||||
| ExprKind::Continue(_)
|
||||
| ExprKind::OffsetOf(_, _)
|
||||
| ExprKind::Err(_) => unreachable!("no sub-expr expected for {:?}", expr.kind),
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a subpattern that performs a read, we want to consider this
|
||||
// to diverge for compatibility to support something like `let x: () = *never_ptr;`.
|
||||
hir::Node::LetStmt(hir::LetStmt { init: Some(target), pat, .. }) => {
|
||||
assert_eq!(target.hir_id, expr.hir_id);
|
||||
self.pat_guaranteed_to_constitute_read_for_never(*pat)
|
||||
}
|
||||
|
||||
// These nodes (if they have a sub-expr) do constitute a read.
|
||||
hir::Node::Block(_)
|
||||
| hir::Node::Arm(_)
|
||||
| hir::Node::ExprField(_)
|
||||
| hir::Node::AnonConst(_)
|
||||
| hir::Node::ConstBlock(_)
|
||||
| hir::Node::ConstArg(_)
|
||||
| hir::Node::Stmt(_)
|
||||
| hir::Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Const(..) | hir::ItemKind::Static(..),
|
||||
..
|
||||
})
|
||||
| hir::Node::TraitItem(hir::TraitItem {
|
||||
kind: hir::TraitItemKind::Const(..), ..
|
||||
})
|
||||
| hir::Node::ImplItem(hir::ImplItem { kind: hir::ImplItemKind::Const(..), .. }) => true,
|
||||
|
||||
// These nodes do not have direct sub-exprs.
|
||||
hir::Node::Param(_)
|
||||
| hir::Node::Item(_)
|
||||
| hir::Node::ForeignItem(_)
|
||||
| hir::Node::TraitItem(_)
|
||||
| hir::Node::ImplItem(_)
|
||||
| hir::Node::Variant(_)
|
||||
| hir::Node::Field(_)
|
||||
| hir::Node::PathSegment(_)
|
||||
| hir::Node::Ty(_)
|
||||
| hir::Node::AssocItemConstraint(_)
|
||||
| hir::Node::TraitRef(_)
|
||||
| hir::Node::Pat(_)
|
||||
| hir::Node::PatField(_)
|
||||
| hir::Node::LetStmt(_)
|
||||
| hir::Node::Synthetic
|
||||
| hir::Node::Err(_)
|
||||
| hir::Node::Ctor(_)
|
||||
| hir::Node::Lifetime(_)
|
||||
| hir::Node::GenericParam(_)
|
||||
| hir::Node::Crate(_)
|
||||
| hir::Node::Infer(_)
|
||||
| hir::Node::WhereBoundPredicate(_)
|
||||
| hir::Node::ArrayLenInfer(_)
|
||||
| hir::Node::PreciseCapturingNonLifetimeArg(_)
|
||||
| hir::Node::OpaqueTy(_) => {
|
||||
unreachable!("no sub-expr expected for {parent_node:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this pattern constitutes a read of value of the scrutinee that
|
||||
/// it is matching against. This is used to determine whether we should
|
||||
/// perform `NeverToAny` coercions.
|
||||
///
|
||||
/// See above for the nuances of what happens when this returns true.
|
||||
pub(super) fn pat_guaranteed_to_constitute_read_for_never(&self, pat: &hir::Pat<'_>) -> bool {
|
||||
match pat.kind {
|
||||
// Does not constitute a read.
|
||||
hir::PatKind::Wild => false,
|
||||
|
||||
// This is unnecessarily restrictive when the pattern that doesn't
|
||||
// constitute a read is unreachable.
|
||||
//
|
||||
// For example `match *never_ptr { value => {}, _ => {} }` or
|
||||
// `match *never_ptr { _ if false => {}, value => {} }`.
|
||||
//
|
||||
// It is however fine to be restrictive here; only returning `true`
|
||||
// can lead to unsoundness.
|
||||
hir::PatKind::Or(subpats) => {
|
||||
subpats.iter().all(|pat| self.pat_guaranteed_to_constitute_read_for_never(pat))
|
||||
}
|
||||
|
||||
// Does constitute a read, since it is equivalent to a discriminant read.
|
||||
hir::PatKind::Never => true,
|
||||
|
||||
// All of these constitute a read, or match on something that isn't `!`,
|
||||
// which would require a `NeverToAny` coercion.
|
||||
hir::PatKind::Binding(_, _, _, _)
|
||||
| hir::PatKind::Struct(_, _, _)
|
||||
| hir::PatKind::TupleStruct(_, _, _)
|
||||
| hir::PatKind::Path(_)
|
||||
| hir::PatKind::Tuple(_, _)
|
||||
| hir::PatKind::Box(_)
|
||||
| hir::PatKind::Ref(_, _)
|
||||
| hir::PatKind::Deref(_)
|
||||
| hir::PatKind::Lit(_)
|
||||
| hir::PatKind::Range(_, _, _)
|
||||
| hir::PatKind::Slice(_, _, _)
|
||||
| hir::PatKind::Err(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, expr), level = "debug")]
|
||||
fn check_expr_kind(
|
||||
&self,
|
||||
|
@ -2608,7 +2608,7 @@ pub(crate) fn suggest_deref_or_ref(
|
||||
}
|
||||
|
||||
if let hir::ExprKind::Unary(hir::UnOp::Deref, inner) = expr.kind
|
||||
&& let Some(1) = self.deref_steps(expected, checked_ty)
|
||||
&& let Some(1) = self.deref_steps_for_suggestion(expected, checked_ty)
|
||||
{
|
||||
// We have `*&T`, check if what was expected was `&T`.
|
||||
// If so, we may want to suggest removing a `*`.
|
||||
@ -2738,7 +2738,7 @@ pub(crate) fn suggest_deref_or_ref(
|
||||
}
|
||||
}
|
||||
(_, &ty::RawPtr(ty_b, mutbl_b), &ty::Ref(_, ty_a, mutbl_a)) => {
|
||||
if let Some(steps) = self.deref_steps(ty_a, ty_b)
|
||||
if let Some(steps) = self.deref_steps_for_suggestion(ty_a, ty_b)
|
||||
// Only suggest valid if dereferencing needed.
|
||||
&& steps > 0
|
||||
// The pointer type implements `Copy` trait so the suggestion is always valid.
|
||||
@ -2782,7 +2782,7 @@ pub(crate) fn suggest_deref_or_ref(
|
||||
}
|
||||
}
|
||||
_ if sp == expr.span => {
|
||||
if let Some(mut steps) = self.deref_steps(checked_ty, expected) {
|
||||
if let Some(mut steps) = self.deref_steps_for_suggestion(checked_ty, expected) {
|
||||
let mut expr = expr.peel_blocks();
|
||||
let mut prefix_span = expr.span.shrink_to_lo();
|
||||
let mut remove = String::new();
|
||||
|
@ -1,5 +1,7 @@
|
||||
// Various tests ensuring that underscore patterns really just construct the place, but don't check its contents.
|
||||
#![feature(strict_provenance)]
|
||||
#![feature(never_type)]
|
||||
|
||||
use std::ptr;
|
||||
|
||||
fn main() {
|
||||
@ -9,6 +11,7 @@ fn main() {
|
||||
invalid_let();
|
||||
dangling_let_type_annotation();
|
||||
invalid_let_type_annotation();
|
||||
never();
|
||||
}
|
||||
|
||||
fn dangling_match() {
|
||||
@ -34,6 +37,13 @@ union Uninit<T: Copy> {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let x: Uninit<!> = Uninit { uninit: () };
|
||||
match x.value {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dangling_let() {
|
||||
@ -41,6 +51,11 @@ fn dangling_let() {
|
||||
let ptr = ptr::without_provenance::<bool>(0x40);
|
||||
let _ = *ptr;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ptr = ptr::without_provenance::<!>(0x40);
|
||||
let _ = *ptr;
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_let() {
|
||||
@ -49,6 +64,12 @@ fn invalid_let() {
|
||||
let ptr = ptr::addr_of!(val).cast::<bool>();
|
||||
let _ = *ptr;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let val = 3u8;
|
||||
let ptr = ptr::addr_of!(val).cast::<!>();
|
||||
let _ = *ptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Adding a type annotation used to change how MIR is generated, make sure we cover both cases.
|
||||
@ -57,6 +78,11 @@ fn dangling_let_type_annotation() {
|
||||
let ptr = ptr::without_provenance::<bool>(0x40);
|
||||
let _: bool = *ptr;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ptr = ptr::without_provenance::<!>(0x40);
|
||||
let _: ! = *ptr;
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_let_type_annotation() {
|
||||
@ -65,7 +91,28 @@ fn invalid_let_type_annotation() {
|
||||
let ptr = ptr::addr_of!(val).cast::<bool>();
|
||||
let _: bool = *ptr;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let val = 3u8;
|
||||
let ptr = ptr::addr_of!(val).cast::<!>();
|
||||
let _: ! = *ptr;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: we should also test `!`, not just `bool` -- but that s currently buggy:
|
||||
// https://github.com/rust-lang/rust/issues/117288
|
||||
// Regression test from <https://github.com/rust-lang/rust/issues/117288>.
|
||||
fn never() {
|
||||
unsafe {
|
||||
let x = 3u8;
|
||||
let x: *const ! = &x as *const u8 as *const _;
|
||||
let _: ! = *x;
|
||||
}
|
||||
|
||||
// Without a type annotation, make sure we don't implicitly coerce `!` to `()`
|
||||
// when we do the noop `*x` (as that would require a `!` *value*, creating
|
||||
// which is UB).
|
||||
unsafe {
|
||||
let x = 3u8;
|
||||
let x: *const ! = &x as *const u8 as *const _;
|
||||
let _ = *x;
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,8 @@
|
||||
fn process_never(_1: *const !) -> () {
|
||||
debug input => _1;
|
||||
let mut _0: ();
|
||||
let _2: &!;
|
||||
scope 1 {
|
||||
debug _input => _2;
|
||||
debug _input => const ();
|
||||
}
|
||||
|
||||
bb0: {
|
||||
|
@ -4,7 +4,7 @@ fn process_void(_1: *const Void) -> () {
|
||||
debug input => _1;
|
||||
let mut _0: ();
|
||||
scope 1 {
|
||||
debug _input => _1;
|
||||
debug _input => const ZeroSized: Void;
|
||||
}
|
||||
|
||||
bb0: {
|
||||
|
@ -1,18 +1,19 @@
|
||||
// skip-filecheck
|
||||
#![feature(never_type)]
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Void {}
|
||||
|
||||
// EMIT_MIR uninhabited_enum.process_never.SimplifyLocals-final.after.mir
|
||||
#[no_mangle]
|
||||
pub fn process_never(input: *const !) {
|
||||
let _input = unsafe { &*input };
|
||||
let _input = unsafe { *input };
|
||||
}
|
||||
|
||||
// EMIT_MIR uninhabited_enum.process_void.SimplifyLocals-final.after.mir
|
||||
#[no_mangle]
|
||||
pub fn process_void(input: *const Void) {
|
||||
let _input = unsafe { &*input };
|
||||
let _input = unsafe { *input };
|
||||
// In the future, this should end with `unreachable`, but we currently only do
|
||||
// unreachability analysis for `!`.
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
// MIR for `main` after SimplifyLocals-final
|
||||
|
||||
fn main() -> () {
|
||||
let mut _0: ();
|
||||
let _1: u8;
|
||||
let mut _2: *const !;
|
||||
let mut _3: *const u8;
|
||||
let _4: u8;
|
||||
let mut _5: *const !;
|
||||
let mut _6: *const u8;
|
||||
scope 1 {
|
||||
debug x => _1;
|
||||
scope 2 {
|
||||
debug x => _2;
|
||||
scope 3 {
|
||||
}
|
||||
}
|
||||
}
|
||||
scope 4 {
|
||||
debug x => _4;
|
||||
scope 5 {
|
||||
debug x => _5;
|
||||
scope 6 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bb0: {
|
||||
StorageLive(_1);
|
||||
_1 = const 3_u8;
|
||||
StorageLive(_2);
|
||||
StorageLive(_3);
|
||||
_3 = &raw const _1;
|
||||
_2 = move _3 as *const ! (PtrToPtr);
|
||||
StorageDead(_3);
|
||||
StorageDead(_2);
|
||||
StorageDead(_1);
|
||||
StorageLive(_4);
|
||||
_4 = const 3_u8;
|
||||
StorageLive(_5);
|
||||
StorageLive(_6);
|
||||
_6 = &raw const _4;
|
||||
_5 = move _6 as *const ! (PtrToPtr);
|
||||
StorageDead(_6);
|
||||
StorageDead(_5);
|
||||
StorageDead(_4);
|
||||
return;
|
||||
}
|
||||
}
|
26
tests/mir-opt/uninhabited_not_read.rs
Normal file
26
tests/mir-opt/uninhabited_not_read.rs
Normal file
@ -0,0 +1,26 @@
|
||||
// skip-filecheck
|
||||
|
||||
//@ edition: 2021
|
||||
// In ed 2021 and below, we don't fallback `!` to `()`.
|
||||
// This would introduce a `! -> ()` coercion which would
|
||||
// be UB if we didn't disallow this explicitly.
|
||||
|
||||
#![feature(never_type)]
|
||||
|
||||
// EMIT_MIR uninhabited_not_read.main.SimplifyLocals-final.after.mir
|
||||
fn main() {
|
||||
// With a type annotation
|
||||
unsafe {
|
||||
let x = 3u8;
|
||||
let x: *const ! = &x as *const u8 as *const _;
|
||||
let _: ! = *x;
|
||||
}
|
||||
|
||||
// Without a type annotation, make sure we don't implicitly coerce `!` to `()`
|
||||
// when we do the noop `*x`.
|
||||
unsafe {
|
||||
let x = 3u8;
|
||||
let x: *const ! = &x as *const u8 as *const _;
|
||||
let _ = *x;
|
||||
}
|
||||
}
|
@ -7,14 +7,12 @@ LL | let c1 = || match x { };
|
||||
| ^ `x` used here but it isn't initialized
|
||||
|
||||
error[E0381]: used binding `x` isn't initialized
|
||||
--> $DIR/pattern-matching-should-fail.rs:15:14
|
||||
--> $DIR/pattern-matching-should-fail.rs:15:23
|
||||
|
|
||||
LL | let x: !;
|
||||
| - binding declared here but left uninitialized
|
||||
LL | let c2 = || match x { _ => () };
|
||||
| ^^ - borrow occurs due to use in closure
|
||||
| |
|
||||
| `x` used here but it isn't initialized
|
||||
| ^ `x` used here but it isn't initialized
|
||||
|
||||
error[E0381]: used binding `variant` isn't initialized
|
||||
--> $DIR/pattern-matching-should-fail.rs:27:13
|
||||
|
74
tests/ui/never_type/diverging-place-match.rs
Normal file
74
tests/ui/never_type/diverging-place-match.rs
Normal file
@ -0,0 +1,74 @@
|
||||
#![feature(never_type)]
|
||||
|
||||
fn not_a_read() -> ! {
|
||||
unsafe {
|
||||
//~^ ERROR mismatched types
|
||||
let x: *const ! = 0 as _;
|
||||
let _: ! = *x;
|
||||
// Since `*x` "diverges" in HIR, but doesn't count as a read in MIR, this
|
||||
// is unsound since we act as if it diverges but it doesn't.
|
||||
}
|
||||
}
|
||||
|
||||
fn not_a_read_implicit() -> ! {
|
||||
unsafe {
|
||||
//~^ ERROR mismatched types
|
||||
let x: *const ! = 0 as _;
|
||||
let _ = *x;
|
||||
}
|
||||
}
|
||||
|
||||
fn not_a_read_guide_coercion() -> ! {
|
||||
unsafe {
|
||||
//~^ ERROR mismatched types
|
||||
let x: *const ! = 0 as _;
|
||||
let _: () = *x;
|
||||
//~^ ERROR mismatched types
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_match() -> ! {
|
||||
unsafe {
|
||||
//~^ ERROR mismatched types
|
||||
let x: *const ! = 0 as _;
|
||||
match *x { _ => {} };
|
||||
}
|
||||
}
|
||||
|
||||
fn field_projection() -> ! {
|
||||
unsafe {
|
||||
//~^ ERROR mismatched types
|
||||
let x: *const (!, ()) = 0 as _;
|
||||
let _ = (*x).0;
|
||||
// ^ I think this is still UB, but because of the inbounds projection.
|
||||
}
|
||||
}
|
||||
|
||||
fn covered_arm() -> ! {
|
||||
unsafe {
|
||||
//~^ ERROR mismatched types
|
||||
let x: *const ! = 0 as _;
|
||||
let (_ | 1i32) = *x;
|
||||
//~^ ERROR mismatched types
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: This *could* be considered a read of `!`, but we're not that sophisticated..
|
||||
fn uncovered_arm() -> ! {
|
||||
unsafe {
|
||||
//~^ ERROR mismatched types
|
||||
let x: *const ! = 0 as _;
|
||||
let (1i32 | _) = *x;
|
||||
//~^ ERROR mismatched types
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_ref_binding() -> ! {
|
||||
unsafe {
|
||||
let x: *const ! = 0 as _;
|
||||
let ref _x: () = *x;
|
||||
//~^ ERROR mismatched types
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
142
tests/ui/never_type/diverging-place-match.stderr
Normal file
142
tests/ui/never_type/diverging-place-match.stderr
Normal file
@ -0,0 +1,142 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:4:5
|
||||
|
|
||||
LL | / unsafe {
|
||||
LL | |
|
||||
LL | | let x: *const ! = 0 as _;
|
||||
LL | | let _: ! = *x;
|
||||
LL | | // Since `*x` "diverges" in HIR, but doesn't count as a read in MIR, this
|
||||
LL | | // is unsound since we act as if it diverges but it doesn't.
|
||||
LL | | }
|
||||
| |_____^ expected `!`, found `()`
|
||||
|
|
||||
= note: expected type `!`
|
||||
found unit type `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:14:5
|
||||
|
|
||||
LL | / unsafe {
|
||||
LL | |
|
||||
LL | | let x: *const ! = 0 as _;
|
||||
LL | | let _ = *x;
|
||||
LL | | }
|
||||
| |_____^ expected `!`, found `()`
|
||||
|
|
||||
= note: expected type `!`
|
||||
found unit type `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:25:21
|
||||
|
|
||||
LL | let _: () = *x;
|
||||
| -- ^^ expected `()`, found `!`
|
||||
| |
|
||||
| expected due to this
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found type `!`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:22:5
|
||||
|
|
||||
LL | / unsafe {
|
||||
LL | |
|
||||
LL | | let x: *const ! = 0 as _;
|
||||
LL | | let _: () = *x;
|
||||
LL | |
|
||||
LL | | }
|
||||
| |_____^ expected `!`, found `()`
|
||||
|
|
||||
= note: expected type `!`
|
||||
found unit type `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:31:5
|
||||
|
|
||||
LL | / unsafe {
|
||||
LL | |
|
||||
LL | | let x: *const ! = 0 as _;
|
||||
LL | | match *x { _ => {} };
|
||||
LL | | }
|
||||
| |_____^ expected `!`, found `()`
|
||||
|
|
||||
= note: expected type `!`
|
||||
found unit type `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:39:5
|
||||
|
|
||||
LL | / unsafe {
|
||||
LL | |
|
||||
LL | | let x: *const (!, ()) = 0 as _;
|
||||
LL | | let _ = (*x).0;
|
||||
LL | | // ^ I think this is still UB, but because of the inbounds projection.
|
||||
LL | | }
|
||||
| |_____^ expected `!`, found `()`
|
||||
|
|
||||
= note: expected type `!`
|
||||
found unit type `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:51:18
|
||||
|
|
||||
LL | let (_ | 1i32) = *x;
|
||||
| ^^^^ -- this expression has type `!`
|
||||
| |
|
||||
| expected `!`, found `i32`
|
||||
|
|
||||
= note: expected type `!`
|
||||
found type `i32`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:48:5
|
||||
|
|
||||
LL | / unsafe {
|
||||
LL | |
|
||||
LL | | let x: *const ! = 0 as _;
|
||||
LL | | let (_ | 1i32) = *x;
|
||||
LL | |
|
||||
LL | | }
|
||||
| |_____^ expected `!`, found `()`
|
||||
|
|
||||
= note: expected type `!`
|
||||
found unit type `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:61:14
|
||||
|
|
||||
LL | let (1i32 | _) = *x;
|
||||
| ^^^^ -- this expression has type `!`
|
||||
| |
|
||||
| expected `!`, found `i32`
|
||||
|
|
||||
= note: expected type `!`
|
||||
found type `i32`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:58:5
|
||||
|
|
||||
LL | / unsafe {
|
||||
LL | |
|
||||
LL | | let x: *const ! = 0 as _;
|
||||
LL | | let (1i32 | _) = *x;
|
||||
LL | |
|
||||
LL | | }
|
||||
| |_____^ expected `!`, found `()`
|
||||
|
|
||||
= note: expected type `!`
|
||||
found unit type `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/diverging-place-match.rs:69:26
|
||||
|
|
||||
LL | let ref _x: () = *x;
|
||||
| ^^ expected `()`, found `!`
|
||||
|
|
||||
= note: expected unit type `()`
|
||||
found type `!`
|
||||
|
||||
error: aborting due to 11 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
22
tests/ui/raw-ref-op/never-place-isnt-diverging.rs
Normal file
22
tests/ui/raw-ref-op/never-place-isnt-diverging.rs
Normal file
@ -0,0 +1,22 @@
|
||||
#![feature(never_type)]
|
||||
|
||||
fn make_up_a_value<T>() -> T {
|
||||
unsafe {
|
||||
//~^ ERROR mismatched types
|
||||
let x: *const ! = 0 as _;
|
||||
&raw const *x;
|
||||
// Since `*x` is `!`, HIR typeck used to think that it diverges
|
||||
// and allowed the block to coerce to any value, leading to UB.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn make_up_a_pointer<T>() -> *const T {
|
||||
unsafe {
|
||||
let x: *const ! = 0 as _;
|
||||
&raw const *x
|
||||
//~^ ERROR mismatched types
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
34
tests/ui/raw-ref-op/never-place-isnt-diverging.stderr
Normal file
34
tests/ui/raw-ref-op/never-place-isnt-diverging.stderr
Normal file
@ -0,0 +1,34 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/never-place-isnt-diverging.rs:4:5
|
||||
|
|
||||
LL | fn make_up_a_value<T>() -> T {
|
||||
| - expected this type parameter
|
||||
LL | / unsafe {
|
||||
LL | |
|
||||
LL | | let x: *const ! = 0 as _;
|
||||
LL | | &raw const *x;
|
||||
LL | | // Since `*x` is `!`, HIR typeck used to think that it diverges
|
||||
LL | | // and allowed the block to coerce to any value, leading to UB.
|
||||
LL | | }
|
||||
| |_____^ expected type parameter `T`, found `()`
|
||||
|
|
||||
= note: expected type parameter `T`
|
||||
found unit type `()`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/never-place-isnt-diverging.rs:17:9
|
||||
|
|
||||
LL | fn make_up_a_pointer<T>() -> *const T {
|
||||
| - -------- expected `*const T` because of return type
|
||||
| |
|
||||
| expected this type parameter
|
||||
...
|
||||
LL | &raw const *x
|
||||
| ^^^^^^^^^^^^^ expected `*const T`, found `*const !`
|
||||
|
|
||||
= note: expected raw pointer `*const T`
|
||||
found raw pointer `*const !`
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
@ -14,12 +14,13 @@ LL | #![deny(unreachable_code)]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unreachable expression
|
||||
--> $DIR/expr_assign.rs:20:14
|
||||
--> $DIR/expr_assign.rs:20:9
|
||||
|
|
||||
LL | *p = return;
|
||||
| -- ^^^^^^ unreachable expression
|
||||
| |
|
||||
| any code following this expression is unreachable
|
||||
| ^^^^^------
|
||||
| | |
|
||||
| | any code following this expression is unreachable
|
||||
| unreachable expression
|
||||
|
||||
error: unreachable expression
|
||||
--> $DIR/expr_assign.rs:26:15
|
||||
|
Loading…
Reference in New Issue
Block a user