mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-06 12:04:36 +00:00
Auto merge of #54125 - varkor:less-conservative-uninhabitedness-check, r=nikomatsakis
Less conservative uninhabitedness check Extends the uninhabitedness check to structs, non-empty enums, tuples and arrays. Pulled out of #47291 and #50262. Fixes https://github.com/rust-lang/rust/issues/54586. r? @nikomatsakis
This commit is contained in:
commit
3f7c718926
@ -415,8 +415,8 @@ 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() {
|
||||
let m = self.tcx.hir().get_module_parent(call_expr.id);
|
||||
if self.tcx.is_ty_uninhabited_from(m, self.tables.expr_ty(call_expr)) {
|
||||
self.add_unreachable_node()
|
||||
} else {
|
||||
ret
|
||||
|
@ -1197,8 +1197,8 @@ 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 m = self.ir.tcx.hir().get_module_parent(expr.id);
|
||||
let succ = if self.ir.tcx.is_ty_uninhabited_from(m, self.tables.expr_ty(expr)) {
|
||||
self.s.exit_ln
|
||||
} else {
|
||||
succ
|
||||
@ -1208,8 +1208,8 @@ 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 m = self.ir.tcx.hir().get_module_parent(expr.id);
|
||||
let succ = if self.ir.tcx.is_ty_uninhabited_from(m, self.tables.expr_ty(expr)) {
|
||||
self.s.exit_ln
|
||||
} else {
|
||||
succ
|
||||
|
@ -191,7 +191,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_privately_uninhabited(tcx) {
|
||||
assert!(layout.abi.is_uninhabited());
|
||||
}
|
||||
}
|
||||
layout
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -205,12 +212,11 @@ pub fn provide(providers: &mut ty::query::Providers<'_>) {
|
||||
|
||||
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();
|
||||
@ -551,13 +557,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_privately_uninhabited(tcx) {
|
||||
Abi::Uninhabited
|
||||
} else {
|
||||
Abi::Aggregate { sized: true }
|
||||
};
|
||||
|
||||
tcx.intern_layout(LayoutDetails {
|
||||
variants: Variants::Single { index: VariantIdx::new(0) },
|
||||
fields: FieldPlacement::Array {
|
||||
stride: element.size,
|
||||
count
|
||||
},
|
||||
abi: Abi::Aggregate { sized: true },
|
||||
abi,
|
||||
align: element.align,
|
||||
size
|
||||
})
|
||||
|
@ -1543,6 +1543,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_privately_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_privately_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_privately_uninhabited(tcx)
|
||||
})
|
||||
})
|
||||
}
|
||||
ty::Tuple(tys) => tys.iter().any(|ty| ty.conservative_is_privately_uninhabited(tcx)),
|
||||
ty::Array(ty, len) => {
|
||||
match len.assert_usize(tcx) {
|
||||
// If the array is definitely non-empty, it's uninhabited if
|
||||
// the type of its elements is uninhabited.
|
||||
Some(n) if n != 0 => ty.conservative_is_privately_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,
|
||||
|
@ -1545,8 +1545,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_privately_uninhabited(self.tcx()) {
|
||||
span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
@ -332,7 +330,10 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||
func: fun,
|
||||
args,
|
||||
cleanup: Some(cleanup),
|
||||
destination: if diverges {
|
||||
// FIXME(varkor): replace this with an uninhabitedness-based check.
|
||||
// This requires getting access to the current module to call
|
||||
// `tcx.is_ty_uninhabited_from`, which is currently tricky to do.
|
||||
destination: if expr.ty.is_never() {
|
||||
None
|
||||
} else {
|
||||
Some((destination.clone(), success))
|
||||
@ -421,8 +422,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||
});
|
||||
|
||||
let rvalue = unpack!(block = this.as_local_rvalue(block, expr));
|
||||
this.cfg
|
||||
.push_assign(block, source_info, destination, rvalue);
|
||||
this.cfg.push_assign(block, source_info, destination, rvalue);
|
||||
block.unit()
|
||||
}
|
||||
};
|
||||
|
@ -230,7 +230,11 @@ 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)
|
||||
match pat_ty.sty {
|
||||
ty::Never => true,
|
||||
ty::Adt(def, _) => def.variants.is_empty(),
|
||||
_ => false
|
||||
}
|
||||
};
|
||||
if !scrutinee_is_uninhabited {
|
||||
// We know the type is inhabited, so this must be wrong
|
||||
@ -258,15 +262,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, self.param_env, module, |ref mut cx| {
|
||||
|
@ -729,6 +729,9 @@ impl TcpListener {
|
||||
/// ```
|
||||
#[stable(feature = "rust1", since = "1.0.0")]
|
||||
pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> {
|
||||
// On WASM, `TcpStream` is uninhabited (as it's unsupported) and so
|
||||
// the `a` variable here is technically unused.
|
||||
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))]
|
||||
self.0.accept().map(|(a, b)| (TcpStream(a), b))
|
||||
}
|
||||
|
||||
|
@ -1,55 +0,0 @@
|
||||
// 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
|
||||
// ignore-lldb
|
||||
|
||||
|
||||
// Require LLVM with DW_TAG_variant_part and a gdb that can read it.
|
||||
// gdb 8.2.0 crashes on this test case, see
|
||||
// https://sourceware.org/bugzilla/show_bug.cgi?id=23626
|
||||
// This will be fixed in the next release, which will be >= 8.2.1.
|
||||
// min-system-llvm-version: 7.0
|
||||
// min-gdb-version: 8.2.1
|
||||
|
||||
// compile-flags:-g
|
||||
// gdb-command:run
|
||||
|
||||
// gdb-command:print first
|
||||
// gdbr-check:$1 = nil_enum::ANilEnum {<No data fields>}
|
||||
|
||||
// gdb-command:print second
|
||||
// gdbr-check:$2 = nil_enum::AnotherNilEnum {<No data fields>}
|
||||
|
||||
#![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 {}
|
||||
|
||||
// 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();
|
||||
|
||||
zzz(); // #break
|
||||
}
|
||||
}
|
||||
|
||||
fn zzz() {()}
|
37
src/test/mir-opt/uninhabited-enum.rs
Normal file
37
src/test/mir-opt/uninhabited-enum.rs
Normal file
@ -0,0 +1,37 @@
|
||||
#![feature(never_type)]
|
||||
|
||||
pub enum Void {}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn process_never(input: *const !) {
|
||||
let _input = unsafe { &*input };
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn process_void(input: *const Void) {
|
||||
let _input = unsafe { &*input };
|
||||
// In the future, this should end with `unreachable`, but we currently only do
|
||||
// unreachability analysis for `!`.
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
||||
// END RUST SOURCE
|
||||
//
|
||||
// START rustc.process_never.SimplifyLocals.after.mir
|
||||
// bb0: {
|
||||
// StorageLive(_2);
|
||||
// _2 = &(*_1);
|
||||
// StorageDead(_2);
|
||||
// unreachable;
|
||||
// }
|
||||
// END rustc.process_never.SimplifyLocals.after.mir
|
||||
//
|
||||
// START rustc.process_void.SimplifyLocals.after.mir
|
||||
// bb0: {
|
||||
// StorageLive(_2);
|
||||
// _2 = &(*_1);
|
||||
// StorageDead(_2);
|
||||
// return;
|
||||
// }
|
||||
// END rustc.process_void.SimplifyLocals.after.mir
|
@ -14,6 +14,7 @@
|
||||
#![feature(slice_patterns)]
|
||||
#![allow(unreachable_patterns)]
|
||||
#![allow(unreachable_code)]
|
||||
#![allow(unused_variables)]
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn foo(z: !) {
|
||||
|
@ -11,6 +11,7 @@
|
||||
// run-pass
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_variables)]
|
||||
#![recursion_limit = "128"]
|
||||
// this used to cause exponential code-size blowup during LLVM passes.
|
||||
|
||||
#![feature(test)]
|
||||
|
@ -1,13 +1,3 @@
|
||||
// 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)]
|
||||
#![allow(const_err)] // make sure we cannot allow away the errors tested here
|
||||
|
||||
@ -16,14 +6,18 @@ 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 it is undefined behavior to use this value
|
||||
|
||||
const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
|
||||
//~^ ERROR it is undefined behavior to use this value
|
||||
|
||||
const BAD_BAD_ARRAY: [Bar; 1] = unsafe { mem::transmute(()) };
|
||||
const BAD_BAD_ARRAY: [Bar; 1] = unsafe { (TransmuteUnion::<(), [Bar; 1]> { a: () }).b };
|
||||
//~^ ERROR it is undefined behavior to use this value
|
||||
|
||||
fn main() {
|
||||
}
|
||||
fn main() {}
|
||||
|
@ -1,13 +1,13 @@
|
||||
error[E0080]: it is undefined behavior to use this value
|
||||
--> $DIR/ub-uninhabit.rs:19:1
|
||||
--> $DIR/ub-uninhabit.rs:14: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]: it is undefined behavior to use this value
|
||||
--> $DIR/ub-uninhabit.rs:22:1
|
||||
--> $DIR/ub-uninhabit.rs:17:1
|
||||
|
|
||||
LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type at .<deref>
|
||||
@ -15,10 +15,10 @@ LL | const BAD_BAD_REF: &Bar = unsafe { mem::transmute(1usize) };
|
||||
= 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]: it is undefined behavior to use this value
|
||||
--> $DIR/ub-uninhabit.rs:25:1
|
||||
--> $DIR/ub-uninhabit.rs:20: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
|
||||
|
||||
|
@ -2,7 +2,7 @@ error[E0080]: it is undefined behavior to use this value
|
||||
--> $DIR/validate_never_arrays.rs:3:1
|
||||
|
|
||||
LL | const FOO: &[!; 1] = unsafe { &*(1_usize as *const [!; 1]) }; //~ ERROR undefined behavior
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a value of an uninhabited type at .<deref>[0]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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
|
||||
|
||||
|
20
src/test/ui/uninhabited/privately-uninhabited-dead-code.rs
Normal file
20
src/test/ui/uninhabited/privately-uninhabited-dead-code.rs
Normal file
@ -0,0 +1,20 @@
|
||||
// compile-pass
|
||||
|
||||
#![deny(unused_variables)]
|
||||
|
||||
mod foo {
|
||||
enum Bar {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Foo {
|
||||
value: Bar, // "privately" uninhabited
|
||||
}
|
||||
|
||||
pub fn give_foo() -> Foo { panic!() }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a = 42;
|
||||
foo::give_foo();
|
||||
println!("Hello, {}", a); // ok: we can't tell that this code is dead
|
||||
}
|
Loading…
Reference in New Issue
Block a user