Skip to content

Commit

Permalink
Rollup merge of rust-lang#54125 - varkor:less-conservative-uninhabite…
Browse files Browse the repository at this point in the history
…dness-check, r=nikomatsakis

Less conservative uninhabitedness check

Extends the uninhabitedness check to structs, non-empty enums, tuples and arrays.

Pulled out of rust-lang#47291 and rust-lang#50262.

Fixes rust-lang#54586.

r? @nikomatsakis
  • Loading branch information
pietroalbini authored Oct 23, 2018
2 parents f99911a + f912fda commit 7ee68d7
Show file tree
Hide file tree
Showing 12 changed files with 92 additions and 106 deletions.
3 changes: 1 addition & 2 deletions src/librustc/cfg/construct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,8 +415,7 @@ impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
args: I) -> CFGIndex {
let func_or_rcvr_exit = self.expr(func_or_rcvr, pred);
let ret = self.straightline(call_expr, func_or_rcvr_exit, args);
// FIXME(canndrew): This is_never should probably be an is_uninhabited.
if self.tables.expr_ty(call_expr).is_never() {
if self.tables.expr_ty(call_expr).conservative_is_uninhabited(self.tcx) {
self.add_unreachable_node()
} else {
ret
Expand Down
6 changes: 2 additions & 4 deletions src/librustc/middle/liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1197,8 +1197,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
}

hir::ExprKind::Call(ref f, ref args) => {
// FIXME(canndrew): This is_never should really be an is_uninhabited
let succ = if self.tables.expr_ty(expr).is_never() {
let succ = if self.tables.expr_ty(expr).conservative_is_uninhabited(self.ir.tcx) {
self.s.exit_ln
} else {
succ
Expand All @@ -1208,8 +1207,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
}

hir::ExprKind::MethodCall(.., ref args) => {
// FIXME(canndrew): This is_never should really be an is_uninhabited
let succ = if self.tables.expr_ty(expr).is_never() {
let succ = if self.tables.expr_ty(expr).conservative_is_uninhabited(self.ir.tcx) {
self.s.exit_ln
} else {
succ
Expand Down
22 changes: 17 additions & 5 deletions src/librustc/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,14 @@ fn layout_raw<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,

ty::tls::enter_context(&icx, |_| {
let cx = LayoutCx { tcx, param_env };
cx.layout_raw_uncached(ty)
let layout = cx.layout_raw_uncached(ty);
// Type-level uninhabitedness should always imply ABI uninhabitedness.
if let Ok(layout) = layout {
if ty.conservative_is_uninhabited(tcx) {
assert!(layout.abi.is_uninhabited());
}
}
layout
})
})
}
Expand All @@ -205,12 +212,11 @@ pub fn provide(providers: &mut ty::query::Providers<'_>) {
#[derive(Copy, Clone)]
pub struct LayoutCx<'tcx, C> {
pub tcx: C,
pub param_env: ty::ParamEnv<'tcx>
pub param_env: ty::ParamEnv<'tcx>,
}

impl<'a, 'tcx> LayoutCx<'tcx, TyCtxt<'a, 'tcx, 'tcx>> {
fn layout_raw_uncached(self, ty: Ty<'tcx>)
-> Result<&'tcx LayoutDetails, LayoutError<'tcx>> {
fn layout_raw_uncached(self, ty: Ty<'tcx>) -> Result<&'tcx LayoutDetails, LayoutError<'tcx>> {
let tcx = self.tcx;
let param_env = self.param_env;
let dl = self.data_layout();
Expand Down Expand Up @@ -555,13 +561,19 @@ impl<'a, 'tcx> LayoutCx<'tcx, TyCtxt<'a, 'tcx, 'tcx>> {
let size = element.size.checked_mul(count, dl)
.ok_or(LayoutError::SizeOverflow(ty))?;

let abi = if count != 0 && ty.conservative_is_uninhabited(tcx) {
Abi::Uninhabited
} else {
Abi::Aggregate { sized: true }
};

tcx.intern_layout(LayoutDetails {
variants: Variants::Single { index: 0 },
fields: FieldPlacement::Array {
stride: element.size,
count
},
abi: Abi::Aggregate { sized: true },
abi,
align: element.align,
size
})
Expand Down
45 changes: 45 additions & 0 deletions src/librustc/ty/sty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,51 @@ impl<'a, 'gcx, 'tcx> TyS<'tcx> {
}
}

/// Checks whether a type is definitely uninhabited. This is
/// conservative: for some types that are uninhabited we return `false`,
/// but we only return `true` for types that are definitely uninhabited.
/// `ty.conservative_is_uninhabited` implies that any value of type `ty`
/// will be `Abi::Uninhabited`. (Note that uninhabited types may have nonzero
/// size, to account for partial initialisation. See #49298 for details.)
pub fn conservative_is_uninhabited(&self, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> bool {
// FIXME(varkor): we can make this less conversative by substituting concrete
// type arguments.
match self.sty {
ty::Never => true,
ty::Adt(def, _) if def.is_union() => {
// For now, `union`s are never considered uninhabited.
false
}
ty::Adt(def, _) => {
// Any ADT is uninhabited if either:
// (a) It has no variants (i.e. an empty `enum`);
// (b) Each of its variants (a single one in the case of a `struct`) has at least
// one uninhabited field.
def.variants.iter().all(|var| {
var.fields.iter().any(|field| {
tcx.type_of(field.did).conservative_is_uninhabited(tcx)
})
})
}
ty::Tuple(tys) => tys.iter().any(|ty| ty.conservative_is_uninhabited(tcx)),
ty::Array(ty, len) => {
match len.val.try_to_scalar() {
// If the array is definitely non-empty, it's uninhabited if
// the type of its elements is uninhabited.
Some(n) if !n.is_null() => ty.conservative_is_uninhabited(tcx),
_ => false
}
}
ty::Ref(..) => {
// References to uninitialised memory is valid for any type, including
// uninhabited types, in unsafe code, so we treat all references as
// inhabited.
false
}
_ => false,
}
}

pub fn is_primitive(&self) -> bool {
match self.sty {
Bool | Char | Int(_) | Uint(_) | Float(_) => true,
Expand Down
3 changes: 1 addition & 2 deletions src/librustc_mir/borrow_check/nll/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1562,8 +1562,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
}
}
None => {
// FIXME(canndrew): This is_never should probably be an is_uninhabited
if !sig.output().is_never() {
if !sig.output().conservative_is_uninhabited(self.tcx()) {
span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig);
}
}
Expand Down
4 changes: 1 addition & 3 deletions src/librustc_mir/build/expr/into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,6 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
exit_block.unit()
}
ExprKind::Call { ty, fun, args, from_hir_call } => {
// FIXME(canndrew): This is_never should probably be an is_uninhabited
let diverges = expr.ty.is_never();
let intrinsic = match ty.sty {
ty::FnDef(def_id, _) => {
let f = ty.fn_sig(this.hir.tcx());
Expand Down Expand Up @@ -332,7 +330,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
func: fun,
args,
cleanup: Some(cleanup),
destination: if diverges {
destination: if expr.ty.conservative_is_uninhabited(this.hir.tcx()) {
None
} else {
Some((destination.clone(), success))
Expand Down
11 changes: 1 addition & 10 deletions src/librustc_mir/hair/pattern/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ impl<'a, 'tcx> MatchVisitor<'a, 'tcx> {
let scrutinee_is_uninhabited = if self.tcx.features().exhaustive_patterns {
self.tcx.is_ty_uninhabited_from(module, pat_ty)
} else {
self.conservative_is_uninhabited(pat_ty)
pat_ty.conservative_is_uninhabited(self.tcx)
};
if !scrutinee_is_uninhabited {
// We know the type is inhabited, so this must be wrong
Expand Down Expand Up @@ -257,15 +257,6 @@ impl<'a, 'tcx> MatchVisitor<'a, 'tcx> {
})
}

fn conservative_is_uninhabited(&self, scrutinee_ty: Ty<'tcx>) -> bool {
// "rustc-1.0-style" uncontentious uninhabitableness check
match scrutinee_ty.sty {
ty::Never => true,
ty::Adt(def, _) => def.variants.is_empty(),
_ => false
}
}

fn check_irrefutable(&self, pat: &'tcx Pat, origin: &str) {
let module = self.tcx.hir.get_module_parent(pat.id);
MatchCheckCtxt::create_and_enter(self.tcx, module, |ref mut cx| {
Expand Down
38 changes: 6 additions & 32 deletions src/test/debuginfo/nil-enum.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,24 @@
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// NOTE Instantiating an empty enum is UB. This test may break in the future.

// LLDB can't handle zero-sized values
// LLDB can't handle zero-sized values.
// ignore-lldb


// compile-flags:-g
// gdb-command:run

// gdb-command:print first
// gdb-command:print *first
// gdbg-check:$1 = {<No data fields>}
// gdbr-check:$1 = <error reading variable>

// gdb-command:print second
// gdbg-check:$2 = {<No data fields>}
// gdbr-check:$2 = <error reading variable>

#![allow(unused_variables)]
#![feature(omit_gdb_pretty_printer_section)]
#![feature(maybe_uninit)]
#![omit_gdb_pretty_printer_section]

use std::mem::MaybeUninit;

enum ANilEnum {}
enum AnotherNilEnum {}
enum Void {}

// This test relies on gdbg printing the string "{<No data fields>}" for empty
// structs (which may change some time)
// The error from gdbr is expected since nil enums are not supposed to exist.
fn main() {
unsafe {
let first: ANilEnum = MaybeUninit::uninitialized().into_inner();
let second: AnotherNilEnum = MaybeUninit::uninitialized().into_inner();
let first: *const Void = 1 as *const _;

zzz(); // #break
}
zzz(); // #break
}

fn zzz() {()}
fn zzz() {}
22 changes: 8 additions & 14 deletions src/test/ui/consts/const-eval/ub-uninhabit.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![feature(const_transmute)]

use std::mem;

#[derive(Copy, Clone)]
enum Bar {}

const BAD_BAD_BAD: Bar = unsafe { mem::transmute(()) };
union TransmuteUnion<A: Clone + Copy, B: Clone + Copy> {
a: A,
b: B,
}

const BAD_BAD_BAD: Bar = unsafe { (TransmuteUnion::<(), Bar> { a: () }).b };
//~^ ERROR this constant likely exhibits undefined behavior

const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
//~^ ERROR this constant likely exhibits undefined behavior

const BAD_BAD_ARRAY: [Bar; 1] = unsafe { mem::transmute(()) };
const BAD_BAD_ARRAY: [Bar; 1] = unsafe { (TransmuteUnion::<(), [Bar; 1]> { a: () }).b };
//~^ ERROR this constant likely exhibits undefined behavior

fn main() {
}
fn main() {}
14 changes: 7 additions & 7 deletions src/test/ui/consts/const-eval/ub-uninhabit.stderr
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-uninhabit.rs:18:1
--> $DIR/ub-uninhabit.rs:13:1
|
LL | const BAD_BAD_BAD: Bar = unsafe { mem::transmute(()) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type
LL | const BAD_BAD_BAD: Bar = unsafe { (TransmuteUnion::<(), Bar> { a: () }).b };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior

error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-uninhabit.rs:21:1
--> $DIR/ub-uninhabit.rs:16:1
|
LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type at .<deref>
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior

error[E0080]: this constant likely exhibits undefined behavior
--> $DIR/ub-uninhabit.rs:24:1
--> $DIR/ub-uninhabit.rs:19:1
|
LL | const BAD_BAD_ARRAY: [Bar; 1] = unsafe { mem::transmute(()) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type at [0]
LL | const BAD_BAD_ARRAY: [Bar; 1] = unsafe { (TransmuteUnion::<(), [Bar; 1]> { a: () }).b };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior

Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/uninhabited/uninhabited-matches-feature-gated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ fn main() {
let _ = match x {}; //~ ERROR non-exhaustive

let x: (Void,) = unsafe { std::mem::uninitialized() };
let _ = match x {}; //~ ERROR non-exhaustive
let _ = match x {}; // okay

let x: [Void; 1] = unsafe { std::mem::uninitialized() };
let _ = match x {}; //~ ERROR non-exhaustive
let _ = match x {}; // okay

let x: &[Void] = unsafe { std::mem::uninitialized() };
let _ = match x { //~ ERROR non-exhaustive
Expand Down
26 changes: 1 addition & 25 deletions src/test/ui/uninhabited/uninhabited-matches-feature-gated.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,6 @@ help: Please ensure that all possible cases are being handled; possibly adding w
LL | let _ = match x {}; //~ ERROR non-exhaustive
| ^

error[E0004]: non-exhaustive patterns: type (Void,) is non-empty
--> $DIR/uninhabited-matches-feature-gated.rs:23:19
|
LL | let _ = match x {}; //~ ERROR non-exhaustive
| ^
|
help: Please ensure that all possible cases are being handled; possibly adding wildcards or more match arms.
--> $DIR/uninhabited-matches-feature-gated.rs:23:19
|
LL | let _ = match x {}; //~ ERROR non-exhaustive
| ^

error[E0004]: non-exhaustive patterns: type [Void; 1] is non-empty
--> $DIR/uninhabited-matches-feature-gated.rs:26:19
|
LL | let _ = match x {}; //~ ERROR non-exhaustive
| ^
|
help: Please ensure that all possible cases are being handled; possibly adding wildcards or more match arms.
--> $DIR/uninhabited-matches-feature-gated.rs:26:19
|
LL | let _ = match x {}; //~ ERROR non-exhaustive
| ^

error[E0004]: non-exhaustive patterns: `&[_]` not covered
--> $DIR/uninhabited-matches-feature-gated.rs:29:19
|
Expand All @@ -58,7 +34,7 @@ error[E0005]: refutable pattern in local binding: `Err(_)` not covered
LL | let Ok(x) = x;
| ^^^^^ pattern `Err(_)` not covered

error: aborting due to 7 previous errors
error: aborting due to 5 previous errors

Some errors occurred: E0004, E0005.
For more information about an error, try `rustc --explain E0004`.

0 comments on commit 7ee68d7

Please sign in to comment.