Skip to content

Commit 4e04d57

Browse files
committed
Added type-specific overflow checks when computing enum discriminant values.
Moved such overflow checking into one place (in `rustc::middle::ty`, since it needs to be run on-demand during `const_eval` in some scenarios), and revised `rustc_typeck` accordingly. (Note that we only check for overflow if program did not provide a discriminant value explicitly.) Fix rust-lang#23030 Fix rust-lang#23221 Fix rust-lang#23235
1 parent d754722 commit 4e04d57

File tree

4 files changed

+279
-118
lines changed

4 files changed

+279
-118
lines changed

src/librustc/diagnostics.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ register_diagnostics! {
6868
E0019,
6969
E0020,
7070
E0022,
71+
E0079, // enum variant: expected signed integer constant
72+
E0080, // enum variant: constant evaluation error
7173
E0109,
7274
E0110,
7375
E0133,
@@ -128,7 +130,8 @@ register_diagnostics! {
128130
E0313, // lifetime of borrowed pointer outlives lifetime of captured variable
129131
E0314, // closure outlives stack frame
130132
E0315, // cannot invoke closure outside of its lifetime
131-
E0316 // nested quantification of lifetimes
133+
E0316, // nested quantification of lifetimes
134+
E0370 // discriminant overflow
132135
}
133136

134137
__build_diagnostic_array! { DIAGNOSTICS }

src/librustc/middle/ty.rs

+257-48
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ use std::cmp;
7373
use std::fmt;
7474
use std::hash::{Hash, SipHasher, Hasher};
7575
use std::mem;
76+
use std::num::ToPrimitive;
77+
use std::num::wrapping::WrappingOps;
7678
use std::ops;
7779
use std::rc::Rc;
7880
use std::vec::IntoIter;
@@ -83,9 +85,11 @@ use syntax::ast::{CrateNum, DefId, Ident, ItemTrait, LOCAL_CRATE};
8385
use syntax::ast::{MutImmutable, MutMutable, Name, NamedField, NodeId};
8486
use syntax::ast::{StmtExpr, StmtSemi, StructField, UnnamedField, Visibility};
8587
use syntax::ast_util::{self, is_local, lit_is_str, local_def};
86-
use syntax::attr::{self, AttrMetaMethods};
88+
use syntax::attr::{self, AttrMetaMethods, SignedInt, UnsignedInt};
8789
use syntax::codemap::Span;
8890
use syntax::parse::token::{self, InternedString, special_idents};
91+
use syntax::print::pprust;
92+
use syntax::ptr::P;
8993
use syntax::{ast, ast_map};
9094

9195
pub type Disr = u64;
@@ -5489,63 +5493,268 @@ pub fn type_is_empty(cx: &ctxt, ty: Ty) -> bool {
54895493
}
54905494
}
54915495

5496+
trait IntTypeExt {
5497+
fn to_ty<'tcx>(&self, cx: &ctxt<'tcx>) -> Ty<'tcx>;
5498+
fn i64_to_disr(&self, val: i64) -> Option<Disr>;
5499+
fn u64_to_disr(&self, val: u64) -> Option<Disr>;
5500+
fn disr_incr(&self, val: Disr) -> Option<Disr>;
5501+
fn disr_string(&self, val: Disr) -> String;
5502+
fn disr_wrap_incr(&self, val: Option<Disr>) -> Disr;
5503+
}
5504+
5505+
impl IntTypeExt for attr::IntType {
5506+
fn to_ty<'tcx>(&self, cx: &ctxt<'tcx>) -> Ty<'tcx> {
5507+
match *self {
5508+
SignedInt(ast::TyI8) => cx.types.i8,
5509+
SignedInt(ast::TyI16) => cx.types.i16,
5510+
SignedInt(ast::TyI32) => cx.types.i32,
5511+
SignedInt(ast::TyI64) => cx.types.i64,
5512+
SignedInt(ast::TyIs) => cx.types.int,
5513+
UnsignedInt(ast::TyU8) => cx.types.u8,
5514+
UnsignedInt(ast::TyU16) => cx.types.u16,
5515+
UnsignedInt(ast::TyU32) => cx.types.u32,
5516+
UnsignedInt(ast::TyU64) => cx.types.u64,
5517+
UnsignedInt(ast::TyUs) => cx.types.uint,
5518+
}
5519+
}
5520+
5521+
fn i64_to_disr(&self, val: i64) -> Option<Disr> {
5522+
match *self {
5523+
SignedInt(ast::TyI8) => val.to_i8() .map(|v| v as Disr),
5524+
SignedInt(ast::TyI16) => val.to_i16() .map(|v| v as Disr),
5525+
SignedInt(ast::TyI32) => val.to_i32() .map(|v| v as Disr),
5526+
SignedInt(ast::TyI64) => val.to_i64() .map(|v| v as Disr),
5527+
UnsignedInt(ast::TyU8) => val.to_u8() .map(|v| v as Disr),
5528+
UnsignedInt(ast::TyU16) => val.to_u16() .map(|v| v as Disr),
5529+
UnsignedInt(ast::TyU32) => val.to_u32() .map(|v| v as Disr),
5530+
UnsignedInt(ast::TyU64) => val.to_u64() .map(|v| v as Disr),
5531+
5532+
UnsignedInt(ast::TyUs) |
5533+
SignedInt(ast::TyIs) => unreachable!(),
5534+
}
5535+
}
5536+
5537+
fn u64_to_disr(&self, val: u64) -> Option<Disr> {
5538+
match *self {
5539+
SignedInt(ast::TyI8) => val.to_i8() .map(|v| v as Disr),
5540+
SignedInt(ast::TyI16) => val.to_i16() .map(|v| v as Disr),
5541+
SignedInt(ast::TyI32) => val.to_i32() .map(|v| v as Disr),
5542+
SignedInt(ast::TyI64) => val.to_i64() .map(|v| v as Disr),
5543+
UnsignedInt(ast::TyU8) => val.to_u8() .map(|v| v as Disr),
5544+
UnsignedInt(ast::TyU16) => val.to_u16() .map(|v| v as Disr),
5545+
UnsignedInt(ast::TyU32) => val.to_u32() .map(|v| v as Disr),
5546+
UnsignedInt(ast::TyU64) => val.to_u64() .map(|v| v as Disr),
5547+
5548+
UnsignedInt(ast::TyUs) |
5549+
SignedInt(ast::TyIs) => unreachable!(),
5550+
}
5551+
}
5552+
5553+
fn disr_incr(&self, val: Disr) -> Option<Disr> {
5554+
macro_rules! add1 {
5555+
($e:expr) => { $e.and_then(|v|v.checked_add(1)).map(|v| v as Disr) }
5556+
}
5557+
match *self {
5558+
// SignedInt repr means we *want* to reinterpret the bits
5559+
// treating the highest bit of Disr as a sign-bit, so
5560+
// cast to i64 before range-checking.
5561+
SignedInt(ast::TyI8) => add1!((val as i64).to_i8()),
5562+
SignedInt(ast::TyI16) => add1!((val as i64).to_i16()),
5563+
SignedInt(ast::TyI32) => add1!((val as i64).to_i32()),
5564+
SignedInt(ast::TyI64) => add1!(Some(val as i64)),
5565+
5566+
UnsignedInt(ast::TyU8) => add1!(val.to_u8()),
5567+
UnsignedInt(ast::TyU16) => add1!(val.to_u16()),
5568+
UnsignedInt(ast::TyU32) => add1!(val.to_u32()),
5569+
UnsignedInt(ast::TyU64) => add1!(Some(val)),
5570+
5571+
UnsignedInt(ast::TyUs) |
5572+
SignedInt(ast::TyIs) => unreachable!(),
5573+
}
5574+
}
5575+
5576+
// This returns a String because (1.) it is only used for
5577+
// rendering an error message and (2.) a string can represent the
5578+
// full range from `i64::MIN` through `u64::MAX`.
5579+
fn disr_string(&self, val: Disr) -> String {
5580+
match *self {
5581+
SignedInt(ast::TyI8) => format!("{}", val as i8 ),
5582+
SignedInt(ast::TyI16) => format!("{}", val as i16),
5583+
SignedInt(ast::TyI32) => format!("{}", val as i32),
5584+
SignedInt(ast::TyI64) => format!("{}", val as i64),
5585+
UnsignedInt(ast::TyU8) => format!("{}", val as u8 ),
5586+
UnsignedInt(ast::TyU16) => format!("{}", val as u16),
5587+
UnsignedInt(ast::TyU32) => format!("{}", val as u32),
5588+
UnsignedInt(ast::TyU64) => format!("{}", val as u64),
5589+
5590+
UnsignedInt(ast::TyUs) |
5591+
SignedInt(ast::TyIs) => unreachable!(),
5592+
}
5593+
}
5594+
5595+
fn disr_wrap_incr(&self, val: Option<Disr>) -> Disr {
5596+
macro_rules! add1 {
5597+
($e:expr) => { ($e).wrapping_add(1) as Disr }
5598+
}
5599+
let val = val.unwrap_or(ty::INITIAL_DISCRIMINANT_VALUE);
5600+
match *self {
5601+
SignedInt(ast::TyI8) => add1!(val as i8 ),
5602+
SignedInt(ast::TyI16) => add1!(val as i16),
5603+
SignedInt(ast::TyI32) => add1!(val as i32),
5604+
SignedInt(ast::TyI64) => add1!(val as i64),
5605+
UnsignedInt(ast::TyU8) => add1!(val as u8 ),
5606+
UnsignedInt(ast::TyU16) => add1!(val as u16),
5607+
UnsignedInt(ast::TyU32) => add1!(val as u32),
5608+
UnsignedInt(ast::TyU64) => add1!(val as u64),
5609+
5610+
UnsignedInt(ast::TyUs) |
5611+
SignedInt(ast::TyIs) => unreachable!(),
5612+
}
5613+
}
5614+
}
5615+
5616+
/// Returns `(normalized_type, ty)`, where `normalized_type` is the
5617+
/// IntType representation of one of {i64,i32,i16,i8,u64,u32,u16,u8},
5618+
/// and `ty` is the original type (i.e. may include `isize` or
5619+
/// `usize`).
5620+
pub fn enum_repr_type<'tcx>(cx: &ctxt<'tcx>,
5621+
opt_hint: Option<&attr::ReprAttr>)
5622+
-> (attr::IntType, Ty<'tcx>)
5623+
{
5624+
let repr_type = match opt_hint {
5625+
// Feed in the given type
5626+
Some(&attr::ReprInt(_, int_t)) => int_t,
5627+
// ... but provide sensible default if none provided
5628+
//
5629+
// NB. Historically `fn enum_variants` generate i64 here, while
5630+
// rustc_typeck::check would generate isize.
5631+
_ => SignedInt(ast::TyIs),
5632+
};
5633+
5634+
let repr_type_ty = repr_type.to_ty(cx);
5635+
let repr_type = match repr_type {
5636+
SignedInt(ast::TyIs) =>
5637+
SignedInt(cx.sess.target.int_type),
5638+
UnsignedInt(ast::TyUs) =>
5639+
UnsignedInt(cx.sess.target.uint_type),
5640+
other => other
5641+
};
5642+
5643+
(repr_type, repr_type_ty)
5644+
}
5645+
5646+
fn report_discrim_overflow(cx: &ctxt,
5647+
variant_span: Span,
5648+
variant_name: &str,
5649+
repr_type: attr::IntType,
5650+
prev_val: Disr) {
5651+
let computed_value = repr_type.disr_wrap_incr(Some(prev_val));
5652+
let computed_value = repr_type.disr_string(computed_value);
5653+
let prev_val = repr_type.disr_string(prev_val);
5654+
let repr_type = repr_type.to_ty(cx).user_string(cx);
5655+
span_err!(cx.sess, variant_span, E0370,
5656+
"enum discriminant overflowed on value after {}: {}; \
5657+
set explicitly via {} = {} if that is desired outcome",
5658+
prev_val, repr_type, variant_name, computed_value);
5659+
}
5660+
5661+
// This computes the discriminant values for the sequence of Variants
5662+
// attached to a particular enum, taking into account the #[repr] (if
5663+
// any) provided via the `opt_hint`.
5664+
fn compute_enum_variants<'tcx>(cx: &ctxt<'tcx>,
5665+
vs: &'tcx [P<ast::Variant>],
5666+
opt_hint: Option<&attr::ReprAttr>)
5667+
-> Vec<Rc<ty::VariantInfo<'tcx>>> {
5668+
let mut variants: Vec<Rc<ty::VariantInfo>> = Vec::new();
5669+
let mut prev_disr_val: Option<ty::Disr> = None;
5670+
5671+
let (repr_type, repr_type_ty) = ty::enum_repr_type(cx, opt_hint);
5672+
5673+
for v in vs {
5674+
// If the discriminant value is specified explicitly in the
5675+
// enum, check whether the initialization expression is valid,
5676+
// otherwise use the last value plus one.
5677+
let current_disr_val;
5678+
5679+
// This closure marks cases where, when an error occurs during
5680+
// the computation, attempt to assign a (hopefully) fresh
5681+
// value to avoid spurious error reports downstream.
5682+
let attempt_fresh_value = move || -> Disr {
5683+
repr_type.disr_wrap_incr(prev_disr_val)
5684+
};
5685+
5686+
match v.node.disr_expr {
5687+
Some(ref e) => {
5688+
debug!("disr expr, checking {}", pprust::expr_to_string(&**e));
5689+
5690+
// check_expr (from check_const pass) doesn't guarantee
5691+
// that the expression is in a form that eval_const_expr can
5692+
// handle, so we may still get an internal compiler error
5693+
//
5694+
// pnkfelix: The above comment was transcribed from
5695+
// the version of this code taken from rustc_typeck.
5696+
// Presumably the implication is that we need to deal
5697+
// with such ICE's as they arise.
5698+
//
5699+
// Since this can be called from `ty::enum_variants`
5700+
// anyway, best thing is to make `eval_const_expr`
5701+
// more robust (on case-by-case basis).
5702+
5703+
match const_eval::eval_const_expr_partial(cx, &**e, Some(repr_type_ty)) {
5704+
Ok(const_eval::const_int(val)) => current_disr_val = val as Disr,
5705+
Ok(const_eval::const_uint(val)) => current_disr_val = val as Disr,
5706+
Ok(_) => {
5707+
span_err!(cx.sess, e.span, E0079,
5708+
"expected signed integer constant");
5709+
current_disr_val = attempt_fresh_value();
5710+
}
5711+
Err(ref err) => {
5712+
span_err!(cx.sess, err.span, E0080,
5713+
"constant evaluation error: {}",
5714+
err.description());
5715+
current_disr_val = attempt_fresh_value();
5716+
}
5717+
}
5718+
},
5719+
None => {
5720+
current_disr_val = match prev_disr_val {
5721+
Some(prev_disr_val) => {
5722+
if let Some(v) = repr_type.disr_incr(prev_disr_val) {
5723+
v
5724+
} else {
5725+
report_discrim_overflow(cx, v.span, v.node.name.as_str(),
5726+
repr_type, prev_disr_val);
5727+
attempt_fresh_value()
5728+
}
5729+
}
5730+
None => ty::INITIAL_DISCRIMINANT_VALUE
5731+
}
5732+
}
5733+
}
5734+
5735+
let variant_info = Rc::new(VariantInfo::from_ast_variant(cx, &**v, current_disr_val));
5736+
prev_disr_val = Some(current_disr_val);
5737+
5738+
variants.push(variant_info);
5739+
}
5740+
5741+
return variants;
5742+
}
5743+
54925744
pub fn enum_variants<'tcx>(cx: &ctxt<'tcx>, id: ast::DefId)
54935745
-> Rc<Vec<Rc<VariantInfo<'tcx>>>> {
54945746
memoized(&cx.enum_var_cache, id, |id: ast::DefId| {
54955747
if ast::LOCAL_CRATE != id.krate {
54965748
Rc::new(csearch::get_enum_variants(cx, id))
54975749
} else {
5498-
/*
5499-
Although both this code and check_enum_variants in typeck/check
5500-
call eval_const_expr, it should never get called twice for the same
5501-
expr, since check_enum_variants also updates the enum_var_cache
5502-
*/
55035750
match cx.map.get(id.node) {
55045751
ast_map::NodeItem(ref item) => {
55055752
match item.node {
55065753
ast::ItemEnum(ref enum_definition, _) => {
5507-
let mut last_discriminant: Option<Disr> = None;
5508-
Rc::new(enum_definition.variants.iter().map(|variant| {
5509-
5510-
let mut discriminant = INITIAL_DISCRIMINANT_VALUE;
5511-
if let Some(ref e) = variant.node.disr_expr {
5512-
// Preserve all values, and prefer signed.
5513-
let ty = Some(cx.types.i64);
5514-
match const_eval::eval_const_expr_partial(cx, &**e, ty) {
5515-
Ok(const_eval::const_int(val)) => {
5516-
discriminant = val as Disr;
5517-
}
5518-
Ok(const_eval::const_uint(val)) => {
5519-
discriminant = val as Disr;
5520-
}
5521-
Ok(_) => {
5522-
span_err!(cx.sess, e.span, E0304,
5523-
"expected signed integer constant");
5524-
}
5525-
Err(err) => {
5526-
span_err!(cx.sess, err.span, E0305,
5527-
"constant evaluation error: {}",
5528-
err.description());
5529-
}
5530-
}
5531-
} else {
5532-
if let Some(val) = last_discriminant {
5533-
if let Some(v) = val.checked_add(1) {
5534-
discriminant = v
5535-
} else {
5536-
cx.sess.span_err(
5537-
variant.span,
5538-
&format!("Discriminant overflowed!"));
5539-
}
5540-
} else {
5541-
discriminant = INITIAL_DISCRIMINANT_VALUE;
5542-
}
5543-
}
5544-
5545-
last_discriminant = Some(discriminant);
5546-
Rc::new(VariantInfo::from_ast_variant(cx, &**variant,
5547-
discriminant))
5548-
}).collect())
5754+
Rc::new(compute_enum_variants(
5755+
cx,
5756+
&enum_definition.variants,
5757+
lookup_repr_hints(cx, id).get(0)))
55495758
}
55505759
_ => {
55515760
cx.sess.bug("enum_variants: id not bound to an enum")

0 commit comments

Comments
 (0)