Skip to content

Commit

Permalink
Allow calling const unsafe fn in const fn behind a feature gate
Browse files Browse the repository at this point in the history
  • Loading branch information
oli-obk committed Dec 4, 2018
1 parent 91d5d56 commit f2ae7b7
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 37 deletions.
3 changes: 2 additions & 1 deletion src/librustc/mir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions src/librustc/ty/constness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion src/librustc_mir/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(..) => {
Expand All @@ -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 =
Expand Down
81 changes: 55 additions & 26 deletions src/librustc_mir/transform/check_unsafety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ast::NodeId>,
inherited_blocks: Vec<(ast::NodeId, bool)>,
}
Expand Down Expand Up @@ -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)
}
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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() {
Expand All @@ -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)
}
}
}
Expand All @@ -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 =
Expand All @@ -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
}
};
Expand Down
3 changes: 3 additions & 0 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! (
Expand Down
2 changes: 2 additions & 0 deletions src/test/ui/consts/min_const_fn/min_const_fn_unsafe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>() -> *const T { 0 as *const T }
Expand Down
14 changes: 7 additions & 7 deletions src/test/ui/consts/min_const_fn/min_const_fn_unsafe.stderr
Original file line number Diff line number Diff line change
@@ -1,53 +1,53 @@
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
| ^^
|
= 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
| ^^^^^^^^^^^^^^^
|
= 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
|
= 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::<String>() } //~ ERROR unsafe operations are 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.rs:25:14
--> $DIR/min_const_fn_unsafe.rs:27:14
|
LL | unsafe { foo6::<Vec<std::cell::Cell<u32>>>() } //~ 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.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
|
= 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <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(min_const_unsafe_fn)]

// ok
const unsafe fn foo4() -> i32 { 42 }
const unsafe fn foo5<T>() -> *const T { 0 as *const T }
const unsafe fn foo6<T>() -> *mut T { 0 as *mut T }
const fn no_unsafe() { unsafe {} }

const fn foo8() -> i32 {
unsafe { foo4() }
}
const fn foo9() -> *const String {
unsafe { foo5::<String>() }
}
const fn foo10() -> *const Vec<std::cell::Cell<u32>> {
unsafe { foo6::<Vec<std::cell::Cell<u32>>>() }
}
const unsafe fn foo8_3() -> i32 {
unsafe { foo4() }
}
const unsafe fn foo9_3() -> *const String {
unsafe { foo5::<String>() }
}
const unsafe fn foo10_3() -> *const Vec<std::cell::Cell<u32>> {
unsafe { foo6::<Vec<std::cell::Cell<u32>>>() }
}
// not ok
const unsafe fn foo8_2() -> i32 {
foo4() //~ ERROR not allowed in const fn
}
const unsafe fn foo9_2() -> *const String {
foo5::<String>() //~ ERROR not allowed in const fn
}
const unsafe fn foo10_2() -> *const Vec<std::cell::Cell<u32>> {
foo6::<Vec<std::cell::Cell<u32>>>() //~ 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
}
Loading

0 comments on commit f2ae7b7

Please sign in to comment.