Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow panicking with string literal messages inside constants #52011

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/librustc/ich/impls_ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,6 @@ for ::mir::interpret::EvalErrorKind<'gcx, O> {
DeallocateNonBasePtr |
HeapAllocZeroBytes |
Unreachable |
Panic |
ReadFromReturnPointer |
UnimplementedTraitSelection |
TypeckError |
Expand All @@ -550,6 +549,12 @@ for ::mir::interpret::EvalErrorKind<'gcx, O> {
GeneratorResumedAfterReturn |
GeneratorResumedAfterPanic |
InfiniteLoop => {}
Panic { ref msg, ref file, line, col } => {
msg.hash_stable(hcx, hasher);
file.hash_stable(hcx, hasher);
line.hash_stable(hcx, hasher);
col.hash_stable(hcx, hasher);
},
ReferencedConstant(ref err) => err.hash_stable(hcx, hasher),
MachineError(ref err) => err.hash_stable(hcx, hasher),
FunctionPointerTyMismatch(a, b) => {
Expand Down
2 changes: 2 additions & 0 deletions src/librustc/middle/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ language_item_table! {
PanicBoundsCheckFnLangItem, "panic_bounds_check", panic_bounds_check_fn;
PanicInfoLangItem, "panic_info", panic_info;
PanicImplLangItem, "panic_impl", panic_impl;
// Libstd panic entry point. Necessary for const eval to be able to catch it
BeginPanicFnLangItem, "begin_panic", begin_panic_fn;

ExchangeMallocFnLangItem, "exchange_malloc", exchange_malloc_fn;
BoxFreeFnLangItem, "box_free", box_free_fn;
Expand Down
12 changes: 10 additions & 2 deletions src/librustc/mir/interpret/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use errors::DiagnosticBuilder;

use syntax_pos::Span;
use syntax::ast;
use syntax::symbol::Symbol;

pub type ConstEvalResult<'tcx> = Result<&'tcx ty::Const<'tcx>, Lrc<ConstEvalErr<'tcx>>>;

Expand Down Expand Up @@ -250,7 +251,12 @@ pub enum EvalErrorKind<'tcx, O> {
HeapAllocZeroBytes,
HeapAllocNonPowerOfTwoAlignment(u64),
Unreachable,
Panic,
Panic {
msg: Symbol,
line: u32,
col: u32,
file: Symbol,
},
ReadFromReturnPointer,
PathNotFound(Vec<String>),
UnimplementedTraitSelection,
Expand Down Expand Up @@ -370,7 +376,7 @@ impl<'tcx, O> EvalErrorKind<'tcx, O> {
"tried to re-, de-, or allocate heap memory with alignment that is not a power of two",
Unreachable =>
"entered unreachable code",
Panic =>
Panic { .. } =>
"the evaluated program panicked",
ReadFromReturnPointer =>
"tried to read from the return pointer",
Expand Down Expand Up @@ -465,6 +471,8 @@ impl<'tcx, O: fmt::Debug> fmt::Debug for EvalErrorKind<'tcx, O> {
write!(f, "{}", inner),
IncorrectAllocationInformation(size, size2, align, align2) =>
write!(f, "incorrect alloc info: expected size {} and align {}, got size {} and align {}", size.bytes(), align.abi(), size2.bytes(), align2.abi()),
Panic { ref msg, line, col, ref file } =>
write!(f, "the evaluated program panicked at '{}', {}:{}:{}", msg, file, line, col),
_ => write!(f, "{}", self.description()),
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/librustc/ty/structural_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,11 @@ impl<'a, 'tcx, O: Lift<'tcx>> Lift<'tcx> for interpret::EvalErrorKind<'a, O> {
HeapAllocZeroBytes => HeapAllocZeroBytes,
HeapAllocNonPowerOfTwoAlignment(n) => HeapAllocNonPowerOfTwoAlignment(n),
Unreachable => Unreachable,
Panic => Panic,
Panic { ref msg, ref file, line, col } => Panic {
msg: msg.clone(),
file: file.clone(),
line, col,
},
ReadFromReturnPointer => ReadFromReturnPointer,
PathNotFound(ref v) => PathNotFound(v.clone()),
UnimplementedTraitSelection => UnimplementedTraitSelection,
Expand Down
95 changes: 81 additions & 14 deletions src/librustc_mir/interpret/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ use rustc::hir;
use rustc::mir::interpret::ConstEvalErr;
use rustc::mir;
use rustc::ty::{self, TyCtxt, Instance};
use rustc::ty::layout::{LayoutOf, Primitive, TyLayout};
use rustc::ty::layout::{LayoutOf, Primitive, TyLayout, Size};
use rustc::ty::subst::Subst;
use rustc_data_structures::indexed_vec::{IndexVec, Idx};

use syntax::ast::Mutability;
use syntax::source_map::Span;
use syntax::source_map::DUMMY_SP;
use syntax::symbol::Symbol;

use rustc::mir::interpret::{
EvalResult, EvalError, EvalErrorKind, GlobalId,
Scalar, AllocId, Allocation, ConstValue,
};
use super::{
Place, PlaceExtra, PlaceTy, MemPlace, OpTy, Operand, Value,
EvalContext, StackPopCleanup, Memory, MemoryKind
EvalContext, StackPopCleanup, Memory, MemoryKind, MPlaceTy,
};

pub fn mk_borrowck_eval_cx<'a, 'mir, 'tcx>(
Expand Down Expand Up @@ -237,23 +238,56 @@ impl<'mir, 'tcx> super::Machine<'mir, 'tcx> for CompileTimeEvaluator {
if !ecx.tcx.is_const_fn(instance.def_id()) {
let def_id = instance.def_id();
// Some fn calls are actually BinOp intrinsics
let (op, oflo) = if let Some(op) = ecx.tcx.is_binop_lang_item(def_id) {
op
let _: ! = if let Some((op, oflo)) = ecx.tcx.is_binop_lang_item(def_id) {
let (dest, bb) = destination.expect("128 lowerings can't diverge");
let l = ecx.read_value(args[0])?;
let r = ecx.read_value(args[1])?;
if oflo {
ecx.binop_with_overflow(op, l, r, dest)?;
} else {
ecx.binop_ignore_overflow(op, l, r, dest)?;
}
ecx.goto_block(bb);
return Ok(true);
} else if Some(def_id) == ecx.tcx.lang_items().panic_fn() {
assert!(args.len() == 1);
// &(&'static str, &'static str, u32, u32)
let ptr = ecx.read_value(args[0])?;
let place = ecx.ref_to_mplace(ptr)?;
let (msg, file, line, col) = (
place_field(ecx, 0, place)?,
place_field(ecx, 1, place)?,
place_field(ecx, 2, place)?,
place_field(ecx, 3, place)?,
);

let msg = to_str(ecx, msg)?;
let file = to_str(ecx, file)?;
let line = to_u32(line)?;
let col = to_u32(col)?;
return Err(EvalErrorKind::Panic { msg, file, line, col }.into());
} else if Some(def_id) == ecx.tcx.lang_items().begin_panic_fn() {
assert!(args.len() == 2);
// &'static str, &(&'static str, u32, u32)
let msg = ecx.read_value(args[0])?;
let ptr = ecx.read_value(args[1])?;
let place = ecx.ref_to_mplace(ptr)?;
let (file, line, col) = (
place_field(ecx, 0, place)?,
place_field(ecx, 1, place)?,
place_field(ecx, 2, place)?,
);

let msg = to_str(ecx, msg.value)?;
let file = to_str(ecx, file)?;
let line = to_u32(line)?;
let col = to_u32(col)?;
return Err(EvalErrorKind::Panic { msg, file, line, col }.into());
} else {
return Err(
ConstEvalError::NotConst(format!("calling non-const fn `{}`", instance)).into(),
);
};
let (dest, bb) = destination.expect("128 lowerings can't diverge");
let l = ecx.read_value(args[0])?;
let r = ecx.read_value(args[1])?;
if oflo {
ecx.binop_with_overflow(op, l, r, dest)?;
} else {
ecx.binop_ignore_overflow(op, l, r, dest)?;
}
ecx.goto_block(bb);
return Ok(true);
}
let mir = match ecx.load_mir(instance.def) {
Ok(mir) => mir,
Expand Down Expand Up @@ -412,6 +446,39 @@ impl<'mir, 'tcx> super::Machine<'mir, 'tcx> for CompileTimeEvaluator {
}
}

fn place_field<'a, 'tcx, 'mir>(
ecx: &mut EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>,
i: u64,
place: MPlaceTy<'tcx>,
) -> EvalResult<'tcx, Value> {
let place = ecx.mplace_field(place, i)?;
Ok(ecx.try_read_value_from_mplace(place)?.expect("bad panic arg layout"))
}

fn to_str<'a, 'tcx, 'mir>(
ecx: &mut EvalContext<'a, 'mir, 'tcx, CompileTimeEvaluator>,
val: Value,
) -> EvalResult<'tcx, Symbol> {
if let Value::ScalarPair(ptr, len) = val {
let len = len.not_undef()?.to_bits(ecx.memory.pointer_size())?;
let bytes = ecx.memory.read_bytes(ptr.not_undef()?, Size::from_bytes(len as u64))?;
let str = ::std::str::from_utf8(bytes).map_err(|err| EvalErrorKind::ValidationFailure(err.to_string()))?;
Ok(Symbol::intern(str))
} else {
bug!("panic arg is not a str")
}
}

fn to_u32<'a, 'tcx, 'mir>(
val: Value,
) -> EvalResult<'tcx, u32> {
if let Value::Scalar(n) = val {
Ok(n.not_undef()?.to_bits(Size::from_bits(32))? as u32)
} else {
bug!("panic arg is not a str")
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have that same code in miri...^^


/// Project to a field of a (variant of a) const
pub fn const_field<'a, 'tcx>(
tcx: TyCtxt<'a, 'tcx, 'tcx>,
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/interpret/operand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ fn from_known_layout<'tcx>(
impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> EvalContext<'a, 'mir, 'tcx, M> {
/// Try reading a value in memory; this is interesting particularily for ScalarPair.
/// Return None if the layout does not permit loading this as a value.
fn try_read_value_from_mplace(
pub(super) fn try_read_value_from_mplace(
&self,
mplace: MPlaceTy<'tcx>,
) -> EvalResult<'tcx, Option<Value>> {
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/transform/const_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ impl<'b, 'a, 'tcx:'b> ConstPropagator<'b, 'a, 'tcx> {
// FIXME: implement
=> {},

| Panic
| Panic { .. }
| BoundsCheck{..}
| Overflow(_)
| OverflowNeg
Expand Down
26 changes: 24 additions & 2 deletions src/librustc_mir/transform/qualify_consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,11 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {

(self.qualif, Lrc::new(promoted_temps))
}

fn is_const_panic_fn(&self, def_id: DefId) -> bool {
Some(def_id) == self.tcx.lang_items().panic_fn() ||
Some(def_id) == self.tcx.lang_items().begin_panic_fn()
}
}

/// Accumulates an Rvalue or Call's effects in self.qualif.
Expand Down Expand Up @@ -834,7 +839,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
}
}
_ => {
if self.tcx.is_const_fn(def_id) {
if self.tcx.is_const_fn(def_id) || self.is_const_panic_fn(def_id) {
is_const_fn = Some(def_id);
}
}
Expand Down Expand Up @@ -880,8 +885,25 @@ impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {

// Const fn calls.
if let Some(def_id) = is_const_fn {
// check the const_panic feature gate or
// find corresponding rustc_const_unstable feature
if let Some(&attr::Stability {
// FIXME: cannot allow this inside `allow_internal_unstable` because that would make
// `panic!` insta stable in constants, since the macro is marked with the attr
if self.is_const_panic_fn(def_id) {
if self.mode == Mode::Fn {
// never promote panics
self.qualif = Qualif::NOT_CONST;
} else if !self.tcx.sess.features_untracked().const_panic {
// don't allow panics in constants without the feature gate
emit_feature_err(
&self.tcx.sess.parse_sess,
"const_panic",
self.span,
GateIssue::Language,
&format!("panicking in {}s is unstable", self.mode),
);
}
} else if let Some(&attr::Stability {
rustc_const_unstable: Some(attr::RustcConstUnstable {
feature: ref feature_name
}),
Expand Down
1 change: 1 addition & 0 deletions src/libstd/panicking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ fn continue_panic_fmt(info: &PanicInfo) -> ! {
#[unstable(feature = "libstd_sys_internals",
reason = "used by the panic! macro",
issue = "0")]
#[cfg_attr(not(any(stage0, test)), lang = "begin_panic")]
#[inline(never)] #[cold] // avoid code bloat at the call sites as much as possible
pub fn begin_panic<M: Any + Send>(msg: M, file_line_col: &(&'static str, u32, u32)) -> ! {
// Note that this should be the only allocation performed in this code path.
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 @@ -224,6 +224,9 @@ declare_features! (
// Allows comparing raw pointers during const eval
(active, const_compare_raw_pointers, "1.27.0", Some(53020), None),

// Allows panicking during const eval (produces compile-time errors)
(active, const_panic, "1.30.0", Some(51999), None),

// Allows using #[prelude_import] on glob `use` items.
//
// rustc internal
Expand Down
22 changes: 22 additions & 0 deletions src/test/ui/consts/const-eval/const_panic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +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_panic)]

fn main() {}

const Z: () = panic!("cheese");
//~^ ERROR this constant cannot be used

const Y: () = unreachable!();
//~^ ERROR this constant cannot be used

const X: () = unimplemented!();
//~^ ERROR this constant cannot be used
33 changes: 33 additions & 0 deletions src/test/ui/consts/const-eval/const_panic.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
error: this constant cannot be used
--> $DIR/const_panic.rs:15:1
|
LL | const Z: () = panic!("cheese");
| ^^^^^^^^^^^^^^----------------^
| |
| the evaluated program panicked at 'cheese', $DIR/const_panic.rs:15:15
|
= note: #[deny(const_err)] on by default
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error: this constant cannot be used
--> $DIR/const_panic.rs:18:1
|
LL | const Y: () = unreachable!();
| ^^^^^^^^^^^^^^--------------^
| |
| the evaluated program panicked at 'internal error: entered unreachable code', $DIR/const_panic.rs:18:15
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error: this constant cannot be used
--> $DIR/const_panic.rs:21:1
|
LL | const X: () = unimplemented!();
| ^^^^^^^^^^^^^^----------------^
| |
| the evaluated program panicked at 'not yet implemented', $DIR/const_panic.rs:21:15
|
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

error: aborting due to 3 previous errors

22 changes: 22 additions & 0 deletions src/test/ui/consts/const-eval/const_panic_libcore.rs
Original file line number Diff line number Diff line change
@@ -0,0 +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.

#![no_std]
#![crate_type = "lib"]
#![feature(const_panic)]

const Z: () = panic!("cheese");
//~^ ERROR this constant cannot be used

const Y: () = unreachable!();
//~^ ERROR this constant cannot be used

const X: () = unimplemented!();
//~^ ERROR this constant cannot be used
Loading