Skip to content

Commit 45f830a

Browse files
Evaluating place expr that is never read from does not diverge
1 parent 59d4114 commit 45f830a

10 files changed

+146
-12
lines changed

compiler/rustc_hir_typeck/src/expr.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
238238
_ => self.warn_if_unreachable(expr.hir_id, expr.span, "expression"),
239239
}
240240

241-
// Any expression that produces a value of type `!` must have diverged
242-
if ty.is_never() {
241+
// Any expression that produces a value of type `!` must have diverged,
242+
// unless it's a place expression that isn't being read from, in which case
243+
// diverging would be unsound since we may never actually read the `!`.
244+
// e.g. `let _ = *never_ptr;` with `never_ptr: *const !`.
245+
if ty.is_never() && self.expr_constitutes_read(expr) {
243246
self.diverges.set(self.diverges.get() | Diverges::always(expr.span));
244247
}
245248

@@ -257,6 +260,66 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
257260
ty
258261
}
259262

263+
pub(super) fn expr_constitutes_read(&self, expr: &'tcx hir::Expr<'tcx>) -> bool {
264+
// We only care about place exprs. Anything else returns an immediate
265+
// which would constitute a read. We don't care about distinguishing
266+
// "syntactic" place exprs since if the base of a field projection is
267+
// not a place then it would've been UB to read from it anyways since
268+
// that constitutes a read.
269+
if !expr.is_syntactic_place_expr() {
270+
return true;
271+
}
272+
273+
// If this expression has any adjustments applied after the place expression,
274+
// they may constitute reads.
275+
if !self.typeck_results.borrow().expr_adjustments(expr).is_empty() {
276+
return true;
277+
}
278+
279+
fn pat_does_read(pat: &hir::Pat<'_>) -> bool {
280+
let mut does_read = false;
281+
pat.walk(|pat| {
282+
if matches!(
283+
pat.kind,
284+
hir::PatKind::Wild | hir::PatKind::Never | hir::PatKind::Or(_)
285+
) {
286+
true
287+
} else {
288+
does_read = true;
289+
// No need to continue.
290+
false
291+
}
292+
});
293+
does_read
294+
}
295+
296+
match self.tcx.parent_hir_node(expr.hir_id) {
297+
// Addr-of, field projections, and LHS of assignment don't constitute reads.
298+
// Assignment does call `drop_in_place`, though, but its safety
299+
// requirements are not the same.
300+
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::AddrOf(..), .. }) => false,
301+
hir::Node::Expr(hir::Expr {
302+
kind: hir::ExprKind::Assign(target, _, _) | hir::ExprKind::Field(target, _),
303+
..
304+
}) if expr.hir_id == target.hir_id => false,
305+
306+
// If we have a subpattern that performs a read, we want to consider this
307+
// to diverge for compatibility to support something like `let x: () = *never_ptr;`.
308+
hir::Node::LetStmt(hir::LetStmt { init: Some(target), pat, .. })
309+
| hir::Node::Expr(hir::Expr {
310+
kind: hir::ExprKind::Let(hir::LetExpr { init: target, pat, .. }),
311+
..
312+
}) if expr.hir_id == target.hir_id && !pat_does_read(*pat) => false,
313+
hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Match(target, arms, _), .. })
314+
if expr.hir_id == target.hir_id
315+
&& !arms.iter().any(|arm| pat_does_read(arm.pat)) =>
316+
{
317+
false
318+
}
319+
_ => true,
320+
}
321+
}
322+
260323
#[instrument(skip(self, expr), level = "debug")]
261324
fn check_expr_kind(
262325
&self,

compiler/rustc_hir_typeck/src/pat.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use tracing::{debug, instrument, trace};
2828
use ty::VariantDef;
2929

3030
use super::report_unexpected_variant_res;
31+
use crate::diverges::Diverges;
3132
use crate::gather_locals::DeclOrigin;
3233
use crate::{errors, FnCtxt, LoweredTy};
3334

@@ -277,6 +278,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
277278
}
278279
};
279280

281+
// All other patterns constitute a read, which causes us to diverge
282+
// if the type is never.
283+
if ty.is_never() && !matches!(pat.kind, PatKind::Wild | PatKind::Never | PatKind::Or(_)) {
284+
self.diverges.set(self.diverges.get() | Diverges::always(pat.span));
285+
}
286+
280287
self.write_ty(pat.hir_id, ty);
281288

282289
// (note_1): In most of the cases where (note_1) is referenced

tests/mir-opt/uninhabited_enum.process_never.SimplifyLocals-final.after.mir

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
fn process_never(_1: *const !) -> () {
44
debug input => _1;
55
let mut _0: ();
6-
let _2: &!;
76
scope 1 {
8-
debug _input => _2;
7+
debug _input => _1;
98
}
109

1110
bb0: {
12-
unreachable;
11+
return;
1312
}
1413
}

tests/mir-opt/uninhabited_enum.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ pub fn process_never(input: *const !) {
1313
#[no_mangle]
1414
pub fn process_void(input: *const Void) {
1515
let _input = unsafe { &*input };
16-
// In the future, this should end with `unreachable`, but we currently only do
17-
// unreachability analysis for `!`.
1816
}
1917

2018
fn main() {}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![feature(never_type)]
2+
3+
fn make_up_a_value<T>() -> T {
4+
unsafe {
5+
//~^ ERROR mismatched types
6+
let x: *const ! = 0 as _;
7+
let _: ! = *x;
8+
// Since `*x` "diverges" in HIR, but doesn't count as a read in MIR, this
9+
// is unsound since we act as if it diverges but it doesn't.
10+
}
11+
}
12+
13+
fn main() {}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/diverging-place-match.rs:4:5
3+
|
4+
LL | fn make_up_a_value<T>() -> T {
5+
| - expected this type parameter
6+
LL | / unsafe {
7+
LL | |
8+
LL | | let x: *const ! = 0 as _;
9+
LL | | let _: ! = *x;
10+
LL | | // Since `*x` "diverges" in HIR, but doesn't count as a read in MIR, this
11+
LL | | // is unsound since we act as if it diverges but it doesn't.
12+
LL | | }
13+
| |_____^ expected type parameter `T`, found `()`
14+
|
15+
= note: expected type parameter `T`
16+
found unit type `()`
17+
18+
error: aborting due to 1 previous error
19+
20+
For more information about this error, try `rustc --explain E0308`.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#![feature(never_type)]
2+
3+
fn make_up_a_value<T>() -> T {
4+
unsafe {
5+
//~^ ERROR mismatched types
6+
let x: *const ! = 0 as _;
7+
&raw const *x;
8+
// Since `*x` is `!`, HIR typeck used to think that it diverges
9+
// and allowed the block to coerce to any value, leading to UB.
10+
}
11+
}
12+
13+
fn main() {}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/never-place-isnt-diverging.rs:4:5
3+
|
4+
LL | fn make_up_a_value<T>() -> T {
5+
| - expected this type parameter
6+
LL | / unsafe {
7+
LL | |
8+
LL | | let x: *const ! = 0 as _;
9+
LL | | &raw const *x;
10+
LL | | // Since `*x` is `!`, HIR typeck used to think that it diverges
11+
LL | | // and allowed the block to coerce to any value, leading to UB.
12+
LL | | }
13+
| |_____^ expected type parameter `T`, found `()`
14+
|
15+
= note: expected type parameter `T`
16+
found unit type `()`
17+
18+
error: aborting due to 1 previous error
19+
20+
For more information about this error, try `rustc --explain E0308`.

tests/ui/reachable/expr_assign.stderr

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ LL | #![deny(unreachable_code)]
1414
| ^^^^^^^^^^^^^^^^
1515

1616
error: unreachable expression
17-
--> $DIR/expr_assign.rs:20:14
17+
--> $DIR/expr_assign.rs:20:9
1818
|
1919
LL | *p = return;
20-
| -- ^^^^^^ unreachable expression
21-
| |
22-
| any code following this expression is unreachable
20+
| ^^^^^------
21+
| | |
22+
| | any code following this expression is unreachable
23+
| unreachable expression
2324

2425
error: unreachable expression
2526
--> $DIR/expr_assign.rs:26:15

tests/ui/reachable/unwarned-match-on-never.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ error: unreachable expression
22
--> $DIR/unwarned-match-on-never.rs:10:5
33
|
44
LL | match x {}
5-
| - any code following this expression is unreachable
5+
| ---------- any code following this expression is unreachable
66
LL | // But matches in unreachable code are warned.
77
LL | match x {}
88
| ^^^^^^^^^^ unreachable expression

0 commit comments

Comments
 (0)