From f2ae7b78d63695c1b9ff7bdf4079c7a02e77f73e Mon Sep 17 00:00:00 2001 From: Oliver Scherer Date: Sat, 3 Nov 2018 00:22:12 +0100 Subject: [PATCH] Allow calling `const unsafe fn` in `const fn` behind a feature gate --- src/librustc/mir/mod.rs | 3 +- src/librustc/ty/constness.rs | 4 +- src/librustc_mir/build/mod.rs | 9 ++- src/librustc_mir/transform/check_unsafety.rs | 81 +++++++++++++------ src/libsyntax/feature_gate.rs | 3 + .../min_const_fn/min_const_fn_unsafe.rs | 2 + .../min_const_fn/min_const_fn_unsafe.stderr | 14 ++-- .../min_const_fn_unsafe_feature_gate.rs | 56 +++++++++++++ .../min_const_fn_unsafe_feature_gate.stderr | 59 ++++++++++++++ 9 files changed, 194 insertions(+), 37 deletions(-) create mode 100644 src/test/ui/consts/min_const_fn/min_const_fn_unsafe_feature_gate.rs create mode 100644 src/test/ui/consts/min_const_fn/min_const_fn_unsafe_feature_gate.stderr diff --git a/src/librustc/mir/mod.rs b/src/librustc/mir/mod.rs index 368f83eb61127..c0581121efa1c 100644 --- a/src/librustc/mir/mod.rs +++ b/src/librustc/mir/mod.rs @@ -2770,7 +2770,8 @@ impl Location { #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)] pub enum UnsafetyViolationKind { General, - /// unsafety is not allowed at all in min const fn + /// Right now function calls to `const unsafe fn` are the only permitted unsafe operation in + /// const fn. Also, even `const unsafe fn` need an `unsafe` block to do the allowed operations MinConstFn, ExternStatic(ast::NodeId), BorrowPacked(ast::NodeId), diff --git a/src/librustc/ty/constness.rs b/src/librustc/ty/constness.rs index e32913b8905b7..f067a125c5dc9 100644 --- a/src/librustc/ty/constness.rs +++ b/src/librustc/ty/constness.rs @@ -55,7 +55,7 @@ impl<'a, 'tcx> TyCtxt<'a, 'tcx, 'tcx> { } // in order for a libstd function to be considered min_const_fn // it needs to be stable and have no `rustc_const_unstable` attribute - match self.lookup_stability(def_id) { + self.is_const_fn_raw(def_id) && match self.lookup_stability(def_id) { // stable functions with unstable const fn aren't `min_const_fn` Some(&attr::Stability { const_stability: Some(_), .. }) => false, // unstable functions don't need to conform @@ -66,7 +66,7 @@ impl<'a, 'tcx> TyCtxt<'a, 'tcx, 'tcx> { } } else { // users enabling the `const_fn` feature gate can do what they want - !self.features().const_fn + self.is_const_fn_raw(def_id) && !self.features().const_fn } } } diff --git a/src/librustc_mir/build/mod.rs b/src/librustc_mir/build/mod.rs index d95a74be77696..cc927df6350bd 100644 --- a/src/librustc_mir/build/mod.rs +++ b/src/librustc_mir/build/mod.rs @@ -91,8 +91,9 @@ pub fn mir_build<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> Mir<'t // types/lifetimes replaced) let fn_hir_id = tcx.hir.node_to_hir_id(id); let fn_sig = cx.tables().liberated_fn_sigs()[fn_hir_id].clone(); + let fn_def_id = tcx.hir.local_def_id(id); - let ty = tcx.type_of(tcx.hir.local_def_id(id)); + let ty = tcx.type_of(fn_def_id); let mut abi = fn_sig.abi; let implicit_argument = match ty.sty { ty::Closure(..) => { @@ -113,6 +114,12 @@ pub fn mir_build<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> Mir<'t hir::Unsafety::Normal => Safety::Safe, hir::Unsafety::Unsafe => Safety::FnUnsafe, }; + let safety = if implicit_argument.is_none() && tcx.is_min_const_fn(fn_def_id) { + // the body of `const unsafe fn`s is treated like the body of safe `const fn`s + Safety::Safe + } else { + safety + }; let body = tcx.hir.body(body_id); let explicit_arguments = diff --git a/src/librustc_mir/transform/check_unsafety.rs b/src/librustc_mir/transform/check_unsafety.rs index 3404772f8255f..d096bb32d9586 100644 --- a/src/librustc_mir/transform/check_unsafety.rs +++ b/src/librustc_mir/transform/check_unsafety.rs @@ -34,6 +34,7 @@ pub struct UnsafetyChecker<'a, 'tcx: 'a> { source_info: SourceInfo, tcx: TyCtxt<'a, 'tcx, 'tcx>, param_env: ty::ParamEnv<'tcx>, + /// mark an `unsafe` block as used, so we don't lint it used_unsafe: FxHashSet, inherited_blocks: Vec<(ast::NodeId, bool)>, } @@ -93,7 +94,7 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> { if let hir::Unsafety::Unsafe = sig.unsafety() { self.require_unsafe("call to unsafe function", "consult the function's documentation for information on how to avoid \ - undefined behavior") + undefined behavior", UnsafetyViolationKind::MinConstFn) } } } @@ -121,7 +122,8 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> { StatementKind::InlineAsm { .. } => { self.require_unsafe("use of inline assembly", - "inline assembly is entirely unchecked and can cause undefined behavior") + "inline assembly is entirely unchecked and can cause undefined behavior", + UnsafetyViolationKind::General) }, } self.super_statement(block, statement, location); @@ -189,7 +191,7 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> { self.require_unsafe("dereference of raw pointer", "raw pointers may be NULL, dangling or unaligned; they can violate \ aliasing rules and cause data races: all of these are undefined \ - behavior") + behavior", UnsafetyViolationKind::General) } ty::Adt(adt, _) => { if adt.is_union() { @@ -212,14 +214,15 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> { "assignment to non-`Copy` union field", "the previous content of the field will be dropped, which \ causes undefined behavior if the field was not properly \ - initialized") + initialized", UnsafetyViolationKind::General) } else { // write to non-move union, safe } } else { self.require_unsafe("access to union field", "the field may not be properly initialized: using \ - uninitialized data will cause undefined behavior") + uninitialized data will cause undefined behavior", + UnsafetyViolationKind::General) } } } @@ -237,7 +240,8 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> { if self.tcx.is_static(def_id) == Some(hir::Mutability::MutMutable) { self.require_unsafe("use of mutable static", "mutable statics can be mutated by multiple threads: aliasing violations \ - or data races will cause undefined behavior"); + or data races will cause undefined behavior", + UnsafetyViolationKind::General); } else if self.tcx.is_foreign_item(def_id) { let source_info = self.source_info; let lint_root = @@ -260,45 +264,70 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> { } impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> { - fn require_unsafe(&mut self, - description: &'static str, - details: &'static str) - { + fn require_unsafe( + &mut self, + description: &'static str, + details: &'static str, + kind: UnsafetyViolationKind, + ) { let source_info = self.source_info; self.register_violations(&[UnsafetyViolation { source_info, description: Symbol::intern(description).as_interned_str(), details: Symbol::intern(details).as_interned_str(), - kind: UnsafetyViolationKind::General, + kind, }], &[]); } fn register_violations(&mut self, violations: &[UnsafetyViolation], unsafe_blocks: &[(ast::NodeId, bool)]) { - if self.min_const_fn { - for violation in violations { - let mut violation = violation.clone(); - violation.kind = UnsafetyViolationKind::MinConstFn; - if !self.violations.contains(&violation) { - self.violations.push(violation) - } - } - } - let within_unsafe = match self.source_scope_local_data[self.source_info.scope].safety { - Safety::Safe => { + let safety = self.source_scope_local_data[self.source_info.scope].safety; + let within_unsafe = match (safety, self.min_const_fn) { + // FIXME: erring on the safe side here and disallowing builtin unsafety in const fn + (Safety::BuiltinUnsafe, true) | + // `unsafe` blocks are required even in `const unsafe fn` + (Safety::FnUnsafe, true) | + // `unsafe` blocks are required in safe code + (Safety::Safe, _) => { for violation in violations { - if !self.violations.contains(violation) { - self.violations.push(violation.clone()) + let mut violation = violation.clone(); + if self.min_const_fn { + // overwrite unsafety violation in const fn with a single hard error kind + violation.kind = UnsafetyViolationKind::MinConstFn; + } else if let UnsafetyViolationKind::MinConstFn = violation.kind { + // outside of const fns we treat `MinConstFn` and `General` the same + violation.kind = UnsafetyViolationKind::General; + } + if !self.violations.contains(&violation) { + self.violations.push(violation) } } false } - Safety::BuiltinUnsafe | Safety::FnUnsafe => true, - Safety::ExplicitUnsafe(node_id) => { + (Safety::BuiltinUnsafe, false) | (Safety::FnUnsafe, false) => true, + (Safety::ExplicitUnsafe(node_id), _) => { if !violations.is_empty() { self.used_unsafe.insert(node_id); } + // only some unsafety is allowed in const fn + if self.min_const_fn { + for violation in violations { + match violation.kind { + // these are allowed + UnsafetyViolationKind::MinConstFn + if self.tcx.sess.features_untracked().min_const_unsafe_fn => {}, + _ => { + let mut violation = violation.clone(); + // overwrite unsafety violation in const fn with a hard error + violation.kind = UnsafetyViolationKind::MinConstFn; + if !self.violations.contains(&violation) { + self.violations.push(violation) + } + }, + } + } + } true } }; diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index fac7ff2bf342d..026b159f80f9a 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -492,6 +492,9 @@ declare_features! ( // `extern crate self as foo;` puts local crate root into extern prelude under name `foo`. (active, extern_crate_self, "1.31.0", Some(56409), None), + + // Allows calling `const unsafe fn` inside `unsafe` blocks in `const fn` functions. + (active, min_const_unsafe_fn, "1.31.0", Some(55607), None), ); declare_features! ( diff --git a/src/test/ui/consts/min_const_fn/min_const_fn_unsafe.rs b/src/test/ui/consts/min_const_fn/min_const_fn_unsafe.rs index 67332c6d2cfb2..7a84992e14b36 100644 --- a/src/test/ui/consts/min_const_fn/min_const_fn_unsafe.rs +++ b/src/test/ui/consts/min_const_fn/min_const_fn_unsafe.rs @@ -8,6 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +// gate-test-min_const_unsafe_fn + // ok const unsafe fn foo4() -> i32 { 42 } const unsafe fn foo5() -> *const T { 0 as *const T } diff --git a/src/test/ui/consts/min_const_fn/min_const_fn_unsafe.stderr b/src/test/ui/consts/min_const_fn/min_const_fn_unsafe.stderr index 8cff0d491d8a4..17cba8569c148 100644 --- a/src/test/ui/consts/min_const_fn/min_const_fn_unsafe.stderr +++ b/src/test/ui/consts/min_const_fn/min_const_fn_unsafe.stderr @@ -1,5 +1,5 @@ error[E0658]: dereferencing raw pointers in constant functions is unstable (see issue #51911) - --> $DIR/min_const_fn_unsafe.rs:27:51 + --> $DIR/min_const_fn_unsafe.rs:29:51 | LL | const unsafe fn foo30_3(x: *mut usize) -> usize { *x } //~ ERROR not allowed in const fn | ^^ @@ -7,7 +7,7 @@ LL | const unsafe fn foo30_3(x: *mut usize) -> usize { *x } //~ ERROR not allowe = help: add #![feature(const_raw_ptr_deref)] to the crate attributes to enable error[E0658]: unions in const fn are unstable (see issue #51909) - --> $DIR/min_const_fn_unsafe.rs:34:5 + --> $DIR/min_const_fn_unsafe.rs:36:5 | LL | Foo { x: () }.y //~ ERROR not allowed in const fn | ^^^^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | Foo { x: () }.y //~ ERROR not allowed in const fn = help: add #![feature(const_fn_union)] to the crate attributes to enable error: call to unsafe function is unsafe and unsafe operations are not allowed in const fn - --> $DIR/min_const_fn_unsafe.rs:19:14 + --> $DIR/min_const_fn_unsafe.rs:21:14 | LL | unsafe { foo4() } //~ ERROR unsafe operations are not allowed in const fn | ^^^^^^ call to unsafe function @@ -23,7 +23,7 @@ LL | unsafe { foo4() } //~ ERROR unsafe operations are not allowed in const = note: consult the function's documentation for information on how to avoid undefined behavior error: call to unsafe function is unsafe and unsafe operations are not allowed in const fn - --> $DIR/min_const_fn_unsafe.rs:22:14 + --> $DIR/min_const_fn_unsafe.rs:24:14 | LL | unsafe { foo5::() } //~ ERROR unsafe operations are not allowed in const fn | ^^^^^^^^^^^^^^^^ call to unsafe function @@ -31,7 +31,7 @@ LL | unsafe { foo5::() } //~ ERROR unsafe operations are not allowed = note: consult the function's documentation for information on how to avoid undefined behavior error: call to unsafe function is unsafe and unsafe operations are not allowed in const fn - --> $DIR/min_const_fn_unsafe.rs:25:14 + --> $DIR/min_const_fn_unsafe.rs:27:14 | LL | unsafe { foo6::>>() } //~ ERROR not allowed in const fn | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function @@ -39,7 +39,7 @@ LL | unsafe { foo6::>>() } //~ ERROR not allowed in = note: consult the function's documentation for information on how to avoid undefined behavior error: dereference of raw pointer is unsafe and unsafe operations are not allowed in const fn - --> $DIR/min_const_fn_unsafe.rs:27:51 + --> $DIR/min_const_fn_unsafe.rs:29:51 | LL | const unsafe fn foo30_3(x: *mut usize) -> usize { *x } //~ ERROR not allowed in const fn | ^^ dereference of raw pointer @@ -47,7 +47,7 @@ LL | const unsafe fn foo30_3(x: *mut usize) -> usize { *x } //~ ERROR not allowe = note: raw pointers may be NULL, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior error: access to union field is unsafe and unsafe operations are not allowed in const fn - --> $DIR/min_const_fn_unsafe.rs:34:5 + --> $DIR/min_const_fn_unsafe.rs:36:5 | LL | Foo { x: () }.y //~ ERROR not allowed in const fn | ^^^^^^^^^^^^^^^ access to union field diff --git a/src/test/ui/consts/min_const_fn/min_const_fn_unsafe_feature_gate.rs b/src/test/ui/consts/min_const_fn/min_const_fn_unsafe_feature_gate.rs new file mode 100644 index 0000000000000..f3e85ebe5f60b --- /dev/null +++ b/src/test/ui/consts/min_const_fn/min_const_fn_unsafe_feature_gate.rs @@ -0,0 +1,56 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(min_const_unsafe_fn)] + +// ok +const unsafe fn foo4() -> i32 { 42 } +const unsafe fn foo5() -> *const T { 0 as *const T } +const unsafe fn foo6() -> *mut T { 0 as *mut T } +const fn no_unsafe() { unsafe {} } + +const fn foo8() -> i32 { + unsafe { foo4() } +} +const fn foo9() -> *const String { + unsafe { foo5::() } +} +const fn foo10() -> *const Vec> { + unsafe { foo6::>>() } +} +const unsafe fn foo8_3() -> i32 { + unsafe { foo4() } +} +const unsafe fn foo9_3() -> *const String { + unsafe { foo5::() } +} +const unsafe fn foo10_3() -> *const Vec> { + unsafe { foo6::>>() } +} +// not ok +const unsafe fn foo8_2() -> i32 { + foo4() //~ ERROR not allowed in const fn +} +const unsafe fn foo9_2() -> *const String { + foo5::() //~ ERROR not allowed in const fn +} +const unsafe fn foo10_2() -> *const Vec> { + foo6::>>() //~ ERROR not allowed in const fn +} +const unsafe fn foo30_3(x: *mut usize) -> usize { *x } //~ ERROR not allowed in const fn +//~^ dereferencing raw pointers in constant functions + +fn main() {} + +const unsafe fn no_union() { + union Foo { x: (), y: () } + Foo { x: () }.y //~ ERROR not allowed in const fn + //~^ unions in const fn +} diff --git a/src/test/ui/consts/min_const_fn/min_const_fn_unsafe_feature_gate.stderr b/src/test/ui/consts/min_const_fn/min_const_fn_unsafe_feature_gate.stderr new file mode 100644 index 0000000000000..78bf99b0a4d99 --- /dev/null +++ b/src/test/ui/consts/min_const_fn/min_const_fn_unsafe_feature_gate.stderr @@ -0,0 +1,59 @@ +error[E0658]: dereferencing raw pointers in constant functions is unstable (see issue #51911) + --> $DIR/min_const_fn_unsafe_feature_gate.rs:47:51 + | +LL | const unsafe fn foo30_3(x: *mut usize) -> usize { *x } //~ ERROR not allowed in const fn + | ^^ + | + = help: add #![feature(const_raw_ptr_deref)] to the crate attributes to enable + +error[E0658]: unions in const fn are unstable (see issue #51909) + --> $DIR/min_const_fn_unsafe_feature_gate.rs:54:5 + | +LL | Foo { x: () }.y //~ ERROR not allowed in const fn + | ^^^^^^^^^^^^^^^ + | + = help: add #![feature(const_fn_union)] to the crate attributes to enable + +error: call to unsafe function is unsafe and unsafe operations are not allowed in const fn + --> $DIR/min_const_fn_unsafe_feature_gate.rs:39:5 + | +LL | foo4() //~ ERROR not allowed in const fn + | ^^^^^^ call to unsafe function + | + = note: consult the function's documentation for information on how to avoid undefined behavior + +error: call to unsafe function is unsafe and unsafe operations are not allowed in const fn + --> $DIR/min_const_fn_unsafe_feature_gate.rs:42:5 + | +LL | foo5::() //~ ERROR not allowed in const fn + | ^^^^^^^^^^^^^^^^ call to unsafe function + | + = note: consult the function's documentation for information on how to avoid undefined behavior + +error: call to unsafe function is unsafe and unsafe operations are not allowed in const fn + --> $DIR/min_const_fn_unsafe_feature_gate.rs:45:5 + | +LL | foo6::>>() //~ ERROR not allowed in const fn + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function + | + = note: consult the function's documentation for information on how to avoid undefined behavior + +error: dereference of raw pointer is unsafe and unsafe operations are not allowed in const fn + --> $DIR/min_const_fn_unsafe_feature_gate.rs:47:51 + | +LL | const unsafe fn foo30_3(x: *mut usize) -> usize { *x } //~ ERROR not allowed in const fn + | ^^ dereference of raw pointer + | + = note: raw pointers may be NULL, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior + +error: access to union field is unsafe and unsafe operations are not allowed in const fn + --> $DIR/min_const_fn_unsafe_feature_gate.rs:54:5 + | +LL | Foo { x: () }.y //~ ERROR not allowed in const fn + | ^^^^^^^^^^^^^^^ access to union field + | + = note: the field may not be properly initialized: using uninitialized data will cause undefined behavior + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0658`.