提交 e919f82d 编写于 作者: F Felix S. Klock II

Address arith-overflow and error-handling in `const_eval.rs`.

 1. Detect and report arithmetic overflow during const-expr eval.

 2. Instead `eval_const_expr_partial` returning `Err(String)`, it now
    has a dedicated enum of different cases. The main benefit of this
    is the ability to pass along an interpretable payload, namely the
    two inputs that caused an overlfow.

I attempted to minimize fallout to error output in tests, but some was
unavoidable. Those changes are in a follow-on commit.
上级 f9bbef7f
......@@ -307,8 +307,9 @@ fn visit_expr(&mut self, ex: &ast::Expr) {
match const_eval::eval_const_expr_partial(self.tcx, ex, None) {
Ok(_) => {}
Err(msg) => {
span_err!(self.tcx.sess, ex.span, E0020,
"{} in a constant expression", msg)
span_err!(self.tcx.sess, msg.span, E0020,
"{} in a constant expression",
msg.description())
}
}
}
......
......@@ -25,6 +25,7 @@
use syntax::ptr::P;
use syntax::{ast_map, ast_util, codemap};
use std::borrow::{Cow, IntoCow};
use std::num::wrapping::OverflowingOps;
use std::cmp::Ordering;
use std::collections::hash_map::Entry::Vacant;
......@@ -203,42 +204,193 @@ pub fn const_expr_to_pat(tcx: &ty::ctxt, expr: &Expr, span: Span) -> P<ast::Pat>
pub fn eval_const_expr(tcx: &ty::ctxt, e: &Expr) -> const_val {
match eval_const_expr_partial(tcx, e, None) {
Ok(r) => r,
Err(s) => tcx.sess.span_fatal(e.span, &s[..])
Err(s) => tcx.sess.span_fatal(s.span, s.description().as_slice())
}
}
fn checked_add_int(a: i64, b: i64) -> Result<const_val, String> {
#[derive(Clone)]
pub struct ConstEvalErr {
pub span: Span,
pub kind: ErrKind,
}
#[derive(Clone)]
pub enum ErrKind {
CannotCast,
CannotCastTo(&'static str),
InvalidOpForBools(ast::BinOp_),
InvalidOpForFloats(ast::BinOp_),
InvalidOpForIntUint(ast::BinOp_),
InvalidOpForUintInt(ast::BinOp_),
NegateOnString,
NegateOnBoolean,
NotOnFloat,
NotOnString,
AddiWithOverflow(i64, i64),
SubiWithOverflow(i64, i64),
MuliWithOverflow(i64, i64),
AdduWithOverflow(u64, u64),
SubuWithOverflow(u64, u64),
MuluWithOverflow(u64, u64),
DivideByZero,
DivideWithOverflow,
ModuloByZero,
ModuloWithOverflow,
MissingStructField,
NonConstPath,
NonConstStruct,
TupleIndexOutOfBounds,
MiscBinaryOp,
MiscCatchAll,
}
impl ConstEvalErr {
pub fn description(&self) -> Cow<str> {
use self::ErrKind::*;
match self.kind {
CannotCast => "can't cast this type".into_cow(),
CannotCastTo(s) => format!("can't cast this type to {}", s).into_cow(),
InvalidOpForBools(_) => "can't do this op on bools".into_cow(),
InvalidOpForFloats(_) => "can't do this op on floats".into_cow(),
InvalidOpForIntUint(..) => "can't do this op on an int and uint".into_cow(),
InvalidOpForUintInt(..) => "can't do this op on a uint and int".into_cow(),
NegateOnString => "negate on string".into_cow(),
NegateOnBoolean => "negate on boolean".into_cow(),
NotOnFloat => "not on float or string".into_cow(),
NotOnString => "not on float or string".into_cow(),
AddiWithOverflow(..) => "attempted to add with overflow".into_cow(),
SubiWithOverflow(..) => "attempted to sub with overflow".into_cow(),
MuliWithOverflow(..) => "attempted to mul with overflow".into_cow(),
AdduWithOverflow(..) => "attempted to add with overflow".into_cow(),
SubuWithOverflow(..) => "attempted to sub with overflow".into_cow(),
MuluWithOverflow(..) => "attempted to mul with overflow".into_cow(),
DivideByZero => "attempted to divide by zero".into_cow(),
DivideWithOverflow => "attempted to divide with overflow".into_cow(),
ModuloByZero => "attempted remainder with a divisor of zero".into_cow(),
ModuloWithOverflow => "attempted remainder with overflow".into_cow(),
MissingStructField => "nonexistent struct field".into_cow(),
NonConstPath => "non-constant path in constant expr".into_cow(),
NonConstStruct => "non-constant struct in constant expr".into_cow(),
TupleIndexOutOfBounds => "tuple index out of bounds".into_cow(),
MiscBinaryOp => "bad operands for binary".into_cow(),
MiscCatchAll => "unsupported constant expr".into_cow(),
}
}
}
fn invalid_op_for_bools(e: &Expr, op: ast::BinOp_) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::InvalidOpForBools(op) }
}
fn invalid_op_for_floats(e: &Expr, op: ast::BinOp_) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::InvalidOpForFloats(op) }
}
fn invalid_op_for_int_uint(e: &Expr, op: ast::BinOp_) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::InvalidOpForIntUint(op) }
}
fn invalid_op_for_uint_int(e: &Expr, op: ast::BinOp_) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::InvalidOpForUintInt(op) }
}
fn negate_on_string(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::NegateOnString }
}
fn negate_on_boolean(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::NegateOnBoolean }
}
fn not_on_float(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::NotOnFloat }
}
fn not_on_string(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::NotOnString }
}
fn addi_with_overflow(e: &Expr, a: i64, b: i64) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::AddiWithOverflow(a, b) }
}
fn subi_with_overflow(e: &Expr, a: i64, b: i64) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::SubiWithOverflow(a, b) }
}
fn muli_with_overflow(e: &Expr, a: i64, b: i64) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::MuliWithOverflow(a, b) }
}
fn addu_with_overflow(e: &Expr, a: u64, b: u64) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::AdduWithOverflow(a, b) }
}
fn subu_with_overflow(e: &Expr, a: u64, b: u64) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::SubuWithOverflow(a, b) }
}
fn mulu_with_overflow(e: &Expr, a: u64, b: u64) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::MuluWithOverflow(a, b) }
}
fn divide_by_zero(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::DivideByZero }
}
fn divide_with_overflow(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::DivideWithOverflow }
}
fn modulo_by_zero(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::ModuloByZero }
}
fn modulo_with_overflow(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::ModuloWithOverflow }
}
fn missing_struct_field(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::MissingStructField }
}
fn non_const_path(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::NonConstPath }
}
fn non_const_struct(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::NonConstStruct }
}
fn tuple_index_out_of_bounds(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::TupleIndexOutOfBounds }
}
fn misc_binary_op(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::MiscBinaryOp }
}
fn misc_catch_all(e: &Expr) -> ConstEvalErr {
ConstEvalErr { span: e.span, kind: ErrKind::MiscCatchAll }
}
fn checked_add_int(e: &Expr, a: i64, b: i64) -> Result<const_val, ConstEvalErr> {
let (ret, oflo) = a.overflowing_add(b);
if !oflo { Ok(const_int(ret)) } else { Err(format!("constant arithmetic overflow")) }
if !oflo { Ok(const_int(ret)) } else { Err(addi_with_overflow(e, a, b)) }
}
fn checked_sub_int(a: i64, b: i64) -> Result<const_val, String> {
fn checked_sub_int(e: &Expr, a: i64, b: i64) -> Result<const_val, ConstEvalErr> {
let (ret, oflo) = a.overflowing_sub(b);
if !oflo { Ok(const_int(ret)) } else { Err(format!("constant arithmetic overflow")) }
if !oflo { Ok(const_int(ret)) } else { Err(subi_with_overflow(e, a, b)) }
}
fn checked_mul_int(a: i64, b: i64) -> Result<const_val, String> {
fn checked_mul_int(e: &Expr, a: i64, b: i64) -> Result<const_val, ConstEvalErr> {
let (ret, oflo) = a.overflowing_mul(b);
if !oflo { Ok(const_int(ret)) } else { Err(format!("constant arithmetic overflow")) }
if !oflo { Ok(const_int(ret)) } else { Err(muli_with_overflow(e, a, b)) }
}
fn checked_add_uint(a: u64, b: u64) -> Result<const_val, String> {
fn checked_add_uint(e: &Expr, a: u64, b: u64) -> Result<const_val, ConstEvalErr> {
let (ret, oflo) = a.overflowing_add(b);
if !oflo { Ok(const_uint(ret)) } else { Err(format!("constant arithmetic overflow")) }
if !oflo { Ok(const_uint(ret)) } else { Err(addu_with_overflow(e, a, b)) }
}
fn checked_sub_uint(a: u64, b: u64) -> Result<const_val, String> {
fn checked_sub_uint(e: &Expr, a: u64, b: u64) -> Result<const_val, ConstEvalErr> {
let (ret, oflo) = a.overflowing_sub(b);
if !oflo { Ok(const_uint(ret)) } else { Err(format!("constant arithmetic overflow")) }
if !oflo { Ok(const_uint(ret)) } else { Err(subu_with_overflow(e, a, b)) }
}
fn checked_mul_uint(a: u64, b: u64) -> Result<const_val, String> {
fn checked_mul_uint(e: &Expr, a: u64, b: u64) -> Result<const_val, ConstEvalErr> {
let (ret, oflo) = a.overflowing_mul(b);
if !oflo { Ok(const_uint(ret)) } else { Err(format!("constant arithmetic overflow")) }
if !oflo { Ok(const_uint(ret)) } else { Err(mulu_with_overflow(e, a, b)) }
}
pub fn eval_const_expr_partial<'tcx>(tcx: &ty::ctxt<'tcx>,
e: &Expr,
ty_hint: Option<Ty<'tcx>>)
-> Result<const_val, String> {
fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
-> Result<const_val, ConstEvalErr> {
fn fromb<T>(b: bool) -> Result<const_val, T> { Ok(const_int(b as i64)) }
let ety = ty_hint.or_else(|| ty::expr_ty_opt(tcx, e));
......@@ -248,9 +400,9 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
Ok(const_float(f)) => Ok(const_float(-f)),
Ok(const_int(i)) => Ok(const_int(-i)),
Ok(const_uint(i)) => Ok(const_uint(-i)),
Ok(const_str(_)) => Err("negate on string".to_string()),
Ok(const_bool(_)) => Err("negate on boolean".to_string()),
ref err => ((*err).clone())
Ok(const_str(_)) => Err(negate_on_string(e)),
Ok(const_bool(_)) => Err(negate_on_boolean(e)),
err => err
}
}
ast::ExprUnary(ast::UnNot, ref inner) => {
......@@ -258,7 +410,9 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
Ok(const_int(i)) => Ok(const_int(!i)),
Ok(const_uint(i)) => Ok(const_uint(!i)),
Ok(const_bool(b)) => Ok(const_bool(!b)),
_ => Err("not on float or string".to_string())
Ok(const_str(_)) => Err(not_on_string(e)),
Ok(const_float(_)) => Err(not_on_float(e)),
err => err
}
}
ast::ExprBinary(op, ref a, ref b) => {
......@@ -281,7 +435,7 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
ast::BiNe => fromb(a != b),
ast::BiGe => fromb(a >= b),
ast::BiGt => fromb(a > b),
_ => Err("can't do this op on floats".to_string())
_ => Err(invalid_op_for_floats(e, op.node)),
}
}
(Ok(const_int(a)), Ok(const_int(b))) => {
......@@ -304,23 +458,23 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
}
};
match op.node {
ast::BiAdd => checked_add_int(a, b),
ast::BiSub => checked_sub_int(a, b),
ast::BiMul => checked_mul_int(a, b),
ast::BiAdd => checked_add_int(e, a, b),
ast::BiSub => checked_sub_int(e, a, b),
ast::BiMul => checked_mul_int(e, a, b),
ast::BiDiv => {
if b == 0 {
Err("attempted to divide by zero".to_string())
Err(divide_by_zero(e))
} else if b == -1 && is_a_min_value() {
Err("attempted to divide with overflow".to_string())
Err(divide_with_overflow(e))
} else {
Ok(const_int(a / b))
}
}
ast::BiRem => {
if b == 0 {
Err("attempted remainder with a divisor of zero".to_string())
Err(modulo_by_zero(e))
} else if b == -1 && is_a_min_value() {
Err("attempted remainder with overflow".to_string())
Err(modulo_with_overflow(e))
} else {
Ok(const_int(a % b))
}
......@@ -340,17 +494,12 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
}
(Ok(const_uint(a)), Ok(const_uint(b))) => {
match op.node {
ast::BiAdd => checked_add_uint(a, b),
ast::BiSub => checked_sub_uint(a, b),
ast::BiMul => checked_mul_uint(a, b),
ast::BiDiv if b == 0 => {
Err("attempted to divide by zero".to_string())
}
ast::BiAdd => checked_add_uint(e, a, b),
ast::BiSub => checked_sub_uint(e, a, b),
ast::BiMul => checked_mul_uint(e, a, b),
ast::BiDiv if b == 0 => Err(divide_by_zero(e)),
ast::BiDiv => Ok(const_uint(a / b)),
ast::BiRem if b == 0 => {
Err("attempted remainder with a divisor of \
zero".to_string())
}
ast::BiRem if b == 0 => Err(modulo_by_zero(e)),
ast::BiRem => Ok(const_uint(a % b)),
ast::BiAnd | ast::BiBitAnd => Ok(const_uint(a & b)),
ast::BiOr | ast::BiBitOr => Ok(const_uint(a | b)),
......@@ -370,14 +519,14 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
match op.node {
ast::BiShl => Ok(const_int(a << b as uint)),
ast::BiShr => Ok(const_int(a >> b as uint)),
_ => Err("can't do this op on an int and uint".to_string())
_ => Err(invalid_op_for_int_uint(e, op.node)),
}
}
(Ok(const_uint(a)), Ok(const_int(b))) => {
match op.node {
ast::BiShl => Ok(const_uint(a << b as uint)),
ast::BiShr => Ok(const_uint(a >> b as uint)),
_ => Err("can't do this op on a uint and int".to_string())
_ => Err(invalid_op_for_uint_int(e, op.node)),
}
}
(Ok(const_bool(a)), Ok(const_bool(b))) => {
......@@ -389,10 +538,13 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
ast::BiBitOr => a | b,
ast::BiEq => a == b,
ast::BiNe => a != b,
_ => return Err("can't do this op on bools".to_string())
_ => return Err(invalid_op_for_bools(e, op.node)),
}))
}
_ => Err("bad operands for binary".to_string())
(err @ Err(..), _) |
(_, err @ Err(..)) => err,
_ => Err(misc_binary_op(e)),
}
}
ast::ExprCast(ref base, ref target_ty) => {
......@@ -407,7 +559,10 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
// Prefer known type to noop, but always have a type hint.
let base_hint = ty::expr_ty_opt(tcx, &**base).unwrap_or(ety);
let val = try!(eval_const_expr_partial(tcx, &**base, Some(base_hint)));
cast_const(val, ety)
match cast_const(val, ety) {
Ok(val) => Ok(val),
Err(kind) => Err(ConstEvalErr { span: e.span, kind: kind }),
}
}
ast::ExprPath(..) => {
let opt_def = tcx.def_map.borrow().get(&e.id).map(|d| d.full_def());
......@@ -434,7 +589,7 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
};
let const_expr = match const_expr {
Some(actual_e) => actual_e,
None => return Err("non-constant path in constant expr".to_string())
None => return Err(non_const_path(e)),
};
let ety = ety.or_else(|| const_ty.and_then(|ty| ast_ty_to_prim_ty(tcx, ty)));
eval_const_expr_partial(tcx, const_expr, ety)
......@@ -456,11 +611,11 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
if fields.len() > index.node {
return eval_const_expr_partial(tcx, &*fields[index.node], None)
} else {
return Err("tuple index out of bounds".to_string())
return Err(tuple_index_out_of_bounds(e))
}
}
Err("non-constant struct in constant expr".to_string())
Err(non_const_struct(e))
}
ast::ExprField(ref base, field_name) => {
// Get the base expression if it is a struct and it is constant
......@@ -471,17 +626,17 @@ fn fromb(b: bool) -> Result<const_val, String> { Ok(const_int(b as i64)) }
f.ident.node.as_str() == field_name.node.as_str()) {
return eval_const_expr_partial(tcx, &*f.expr, None)
} else {
return Err("nonexistent struct field".to_string())
return Err(missing_struct_field(e));
}
}
Err("non-constant struct in constant expr".to_string())
Err(non_const_struct(e))
}
_ => Err("unsupported constant expr".to_string())
_ => Err(misc_catch_all(e))
}
}
fn cast_const(val: const_val, ty: Ty) -> Result<const_val, String> {
fn cast_const(val: const_val, ty: Ty) -> Result<const_val, ErrKind> {
macro_rules! define_casts {
($($ty_pat:pat => (
$intermediate_ty:ty,
......@@ -494,11 +649,10 @@ fn cast_const(val: const_val, ty: Ty) -> Result<const_val, String> {
const_uint(u) => Ok($const_type(u as $intermediate_ty as $target_ty)),
const_int(i) => Ok($const_type(i as $intermediate_ty as $target_ty)),
const_float(f) => Ok($const_type(f as $intermediate_ty as $target_ty)),
_ => Err(concat!("can't cast this type to ",
stringify!($const_type)).to_string())
_ => Err(ErrKind::CannotCastTo(stringify!($const_type))),
}
},)*
_ => Err("can't cast this type".to_string())
_ => Err(ErrKind::CannotCast),
})
}
......@@ -572,15 +726,15 @@ pub fn compare_lit_exprs<'tcx>(tcx: &ty::ctxt<'tcx>,
-> Option<Ordering> {
let a = match eval_const_expr_partial(tcx, a, ty_hint) {
Ok(a) => a,
Err(s) => {
tcx.sess.span_err(a.span, &s[..]);
Err(e) => {
tcx.sess.span_err(a.span, e.description().as_slice());
return None;
}
};
let b = match eval_const_expr_partial(tcx, b, ty_hint) {
Ok(b) => b,
Err(s) => {
tcx.sess.span_err(b.span, &s[..]);
Err(e) => {
tcx.sess.span_err(b.span, e.description().as_slice());
return None;
}
};
......
......@@ -5366,8 +5366,9 @@ pub fn enum_variants<'tcx>(cx: &ctxt<'tcx>, id: ast::DefId)
"expected signed integer constant");
}
Err(err) => {
span_err!(cx.sess, e.span, E0305,
"expected constant: {}", err);
span_err!(cx.sess, err.span, E0305,
"constant evaluation error: {}",
err.description().as_slice());
}
}
} else {
......
......@@ -1390,14 +1390,22 @@ pub fn ast_ty_to_ty<'tcx>(this: &AstConv<'tcx>,
ty::mk_vec(tcx, ast_ty_to_ty(this, rscope, &**ty),
Some(i as uint)),
_ => {
span_fatal!(tcx.sess, ast_ty.span, E0249,
"expected constant expr for array length");
span_err!(tcx.sess, ast_ty.span, E0249,
"expected constant expr for array length");
this.tcx().types.err
}
}
}
Err(r) => {
span_fatal!(tcx.sess, ast_ty.span, E0250,
"expected constant expr for array length: {}", r);
Err(ref r) => {
let subspan =
ast_ty.span.lo <= r.span.lo && r.span.hi <= ast_ty.span.hi;
span_err!(tcx.sess, ast_ty.span, E0250,
"array length constant evaluation error: {}",
r.description().as_slice());
if !subspan {
span_note!(tcx.sess, ast_ty.span, "for array length here")
}
this.tcx().types.err
}
}
}
......
......@@ -4604,8 +4604,9 @@ fn do_check<'a, 'tcx>(ccx: &CrateCtxt<'a, 'tcx>,
"expected signed integer constant");
}
Err(ref err) => {
span_err!(ccx.tcx.sess, e.span, E0080,
"expected constant: {}", *err);
span_err!(ccx.tcx.sess, err.span, E0080,
"constant evaluation error: {}",
err.description().as_slice());
}
}
},
......
......@@ -9,8 +9,8 @@
// except according to those terms.
enum test {
div_zero = 1/0, //~ERROR expected constant: attempted to divide by zero
rem_zero = 1%0 //~ERROR expected constant: attempted remainder with a divisor of zero
div_zero = 1/0, //~ERROR constant evaluation error: attempted to divide by zero
rem_zero = 1%0 //~ERROR constant evaluation error: attempted remainder with a divisor of zero
}
fn main() {}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册