Skip to content

Commit faf7302

Browse files
committed
Fix issue #23302, ICE on recursively defined enum variant discriminant.
1 parent dedd430 commit faf7302

File tree

4 files changed

+162
-36
lines changed

4 files changed

+162
-36
lines changed

src/librustc/diagnostics.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,6 @@ register_diagnostics! {
10121012
E0138,
10131013
E0139,
10141014
E0264, // unknown external lang item
1015-
E0266, // expected item
10161015
E0269, // not all control paths return a value
10171016
E0270, // computation may converge in a function marked as diverging
10181017
E0272, // rustc_on_unimplemented attribute refers to non-existent type parameter

src/librustc/middle/check_static_recursion.rs

Lines changed: 136 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,68 +13,86 @@
1313

1414
use ast_map;
1515
use session::Session;
16-
use middle::def::{DefStatic, DefConst, DefAssociatedConst, DefMap};
16+
use middle::def::{DefStatic, DefConst, DefAssociatedConst, DefVariant, DefMap};
17+
use util::nodemap::NodeMap;
1718

1819
use syntax::{ast, ast_util};
1920
use syntax::codemap::Span;
2021
use syntax::visit::Visitor;
2122
use syntax::visit;
2223

24+
use std::cell::RefCell;
25+
2326
struct CheckCrateVisitor<'a, 'ast: 'a> {
2427
sess: &'a Session,
2528
def_map: &'a DefMap,
26-
ast_map: &'a ast_map::Map<'ast>
29+
ast_map: &'a ast_map::Map<'ast>,
30+
discriminant_map: RefCell<NodeMap<Option<&'ast ast::Expr>>>,
2731
}
2832

29-
impl<'v, 'a, 'ast> Visitor<'v> for CheckCrateVisitor<'a, 'ast> {
30-
fn visit_item(&mut self, it: &ast::Item) {
33+
impl<'a, 'ast: 'a> Visitor<'ast> for CheckCrateVisitor<'a, 'ast> {
34+
fn visit_item(&mut self, it: &'ast ast::Item) {
3135
match it.node {
32-
ast::ItemStatic(_, _, ref expr) |
33-
ast::ItemConst(_, ref expr) => {
36+
ast::ItemStatic(..) |
37+
ast::ItemConst(..) => {
3438
let mut recursion_visitor =
3539
CheckItemRecursionVisitor::new(self, &it.span);
3640
recursion_visitor.visit_item(it);
37-
visit::walk_expr(self, &*expr)
3841
},
39-
_ => visit::walk_item(self, it)
42+
ast::ItemEnum(ref enum_def, ref generics) => {
43+
// We could process the whole enum, but handling the variants
44+
// with discriminant expressions one by one gives more specific,
45+
// less redundant output.
46+
for variant in &enum_def.variants {
47+
if let Some(_) = variant.node.disr_expr {
48+
let mut recursion_visitor =
49+
CheckItemRecursionVisitor::new(self, &variant.span);
50+
recursion_visitor.populate_enum_discriminants(enum_def);
51+
recursion_visitor.visit_variant(variant, generics);
52+
}
53+
}
54+
}
55+
_ => {}
4056
}
57+
visit::walk_item(self, it)
4158
}
4259

43-
fn visit_trait_item(&mut self, ti: &ast::TraitItem) {
60+
fn visit_trait_item(&mut self, ti: &'ast ast::TraitItem) {
4461
match ti.node {
4562
ast::ConstTraitItem(_, ref default) => {
46-
if let Some(ref expr) = *default {
63+
if let Some(_) = *default {
4764
let mut recursion_visitor =
4865
CheckItemRecursionVisitor::new(self, &ti.span);
4966
recursion_visitor.visit_trait_item(ti);
50-
visit::walk_expr(self, &*expr)
5167
}
5268
}
53-
_ => visit::walk_trait_item(self, ti)
69+
_ => {}
5470
}
71+
visit::walk_trait_item(self, ti)
5572
}
5673

57-
fn visit_impl_item(&mut self, ii: &ast::ImplItem) {
74+
fn visit_impl_item(&mut self, ii: &'ast ast::ImplItem) {
5875
match ii.node {
59-
ast::ConstImplItem(_, ref expr) => {
76+
ast::ConstImplItem(..) => {
6077
let mut recursion_visitor =
6178
CheckItemRecursionVisitor::new(self, &ii.span);
6279
recursion_visitor.visit_impl_item(ii);
63-
visit::walk_expr(self, &*expr)
6480
}
65-
_ => visit::walk_impl_item(self, ii)
81+
_ => {}
6682
}
83+
visit::walk_impl_item(self, ii)
6784
}
6885
}
6986

7087
pub fn check_crate<'ast>(sess: &Session,
71-
krate: &ast::Crate,
88+
krate: &'ast ast::Crate,
7289
def_map: &DefMap,
7390
ast_map: &ast_map::Map<'ast>) {
7491
let mut visitor = CheckCrateVisitor {
7592
sess: sess,
7693
def_map: def_map,
77-
ast_map: ast_map
94+
ast_map: ast_map,
95+
discriminant_map: RefCell::new(NodeMap()),
7896
};
7997
visit::walk_crate(&mut visitor, krate);
8098
sess.abort_if_errors();
@@ -85,53 +103,120 @@ struct CheckItemRecursionVisitor<'a, 'ast: 'a> {
85103
sess: &'a Session,
86104
ast_map: &'a ast_map::Map<'ast>,
87105
def_map: &'a DefMap,
88-
idstack: Vec<ast::NodeId>
106+
discriminant_map: &'a RefCell<NodeMap<Option<&'ast ast::Expr>>>,
107+
idstack: Vec<ast::NodeId>,
89108
}
90109

91110
impl<'a, 'ast: 'a> CheckItemRecursionVisitor<'a, 'ast> {
92-
fn new(v: &CheckCrateVisitor<'a, 'ast>, span: &'a Span)
111+
fn new(v: &'a CheckCrateVisitor<'a, 'ast>, span: &'a Span)
93112
-> CheckItemRecursionVisitor<'a, 'ast> {
94113
CheckItemRecursionVisitor {
95114
root_span: span,
96115
sess: v.sess,
97116
ast_map: v.ast_map,
98117
def_map: v.def_map,
99-
idstack: Vec::new()
118+
discriminant_map: &v.discriminant_map,
119+
idstack: Vec::new(),
100120
}
101121
}
102122
fn with_item_id_pushed<F>(&mut self, id: ast::NodeId, f: F)
103123
where F: Fn(&mut Self) {
104-
if self.idstack.iter().any(|x| x == &(id)) {
124+
if self.idstack.iter().any(|x| *x == id) {
105125
span_err!(self.sess, *self.root_span, E0265, "recursive constant");
106126
return;
107127
}
108128
self.idstack.push(id);
109129
f(self);
110130
self.idstack.pop();
111131
}
132+
// If a variant has an expression specifying its discriminant, then it needs
133+
// to be checked just like a static or constant. However, if there are more
134+
// variants with no explicitly specified discriminant, those variants will
135+
// increment the same expression to get their values.
136+
//
137+
// So for every variant, we need to track whether there is an expression
138+
// somewhere in the enum definition that controls its discriminant. We do
139+
// this by starting from the end and searching backward.
140+
fn populate_enum_discriminants(&self, enum_definition: &'ast ast::EnumDef) {
141+
// Get the map, and return if we already processed this enum or if it
142+
// has no variants.
143+
let mut discriminant_map = self.discriminant_map.borrow_mut();
144+
match enum_definition.variants.first() {
145+
None => { return; }
146+
Some(variant) if discriminant_map.contains_key(&variant.node.id) => {
147+
return;
148+
}
149+
_ => {}
150+
}
151+
152+
// Go through all the variants.
153+
let mut variant_stack: Vec<ast::NodeId> = Vec::new();
154+
for variant in enum_definition.variants.iter().rev() {
155+
variant_stack.push(variant.node.id);
156+
// When we find an expression, every variant currently on the stack
157+
// is affected by that expression.
158+
if let Some(ref expr) = variant.node.disr_expr {
159+
for id in &variant_stack {
160+
discriminant_map.insert(*id, Some(expr));
161+
}
162+
variant_stack.clear()
163+
}
164+
}
165+
// If we are at the top, that always starts at 0, so any variant on the
166+
// stack has a default value and does not need to be checked.
167+
for id in &variant_stack {
168+
discriminant_map.insert(*id, None);
169+
}
170+
}
112171
}
113172

114-
impl<'a, 'ast, 'v> Visitor<'v> for CheckItemRecursionVisitor<'a, 'ast> {
115-
fn visit_item(&mut self, it: &ast::Item) {
173+
impl<'a, 'ast: 'a> Visitor<'ast> for CheckItemRecursionVisitor<'a, 'ast> {
174+
fn visit_item(&mut self, it: &'ast ast::Item) {
116175
self.with_item_id_pushed(it.id, |v| visit::walk_item(v, it));
117176
}
118177

119-
fn visit_trait_item(&mut self, ti: &ast::TraitItem) {
178+
fn visit_enum_def(&mut self, enum_definition: &'ast ast::EnumDef,
179+
generics: &'ast ast::Generics) {
180+
self.populate_enum_discriminants(enum_definition);
181+
visit::walk_enum_def(self, enum_definition, generics);
182+
}
183+
184+
fn visit_variant(&mut self, variant: &'ast ast::Variant,
185+
_: &'ast ast::Generics) {
186+
let variant_id = variant.node.id;
187+
let maybe_expr;
188+
if let Some(get_expr) = self.discriminant_map.borrow().get(&variant_id) {
189+
// This is necessary because we need to let the `discriminant_map`
190+
// borrow fall out of scope, so that we can reborrow farther down.
191+
maybe_expr = (*get_expr).clone();
192+
} else {
193+
self.sess.span_bug(variant.span,
194+
"`check_static_recursion` attempted to visit \
195+
variant with unknown discriminant")
196+
}
197+
// If `maybe_expr` is `None`, that's because no discriminant is
198+
// specified that affects this variant. Thus, no risk of recursion.
199+
if let Some(expr) = maybe_expr {
200+
self.with_item_id_pushed(expr.id, |v| visit::walk_expr(v, expr));
201+
}
202+
}
203+
204+
fn visit_trait_item(&mut self, ti: &'ast ast::TraitItem) {
120205
self.with_item_id_pushed(ti.id, |v| visit::walk_trait_item(v, ti));
121206
}
122207

123-
fn visit_impl_item(&mut self, ii: &ast::ImplItem) {
208+
fn visit_impl_item(&mut self, ii: &'ast ast::ImplItem) {
124209
self.with_item_id_pushed(ii.id, |v| visit::walk_impl_item(v, ii));
125210
}
126211

127-
fn visit_expr(&mut self, e: &ast::Expr) {
212+
fn visit_expr(&mut self, e: &'ast ast::Expr) {
128213
match e.node {
129214
ast::ExprPath(..) => {
130215
match self.def_map.borrow().get(&e.id).map(|d| d.base_def) {
131216
Some(DefStatic(def_id, _)) |
132217
Some(DefAssociatedConst(def_id, _)) |
133-
Some(DefConst(def_id)) if
134-
ast_util::is_local(def_id) => {
218+
Some(DefConst(def_id))
219+
if ast_util::is_local(def_id) => {
135220
match self.ast_map.get(def_id.node) {
136221
ast_map::NodeItem(item) =>
137222
self.visit_item(item),
@@ -141,11 +226,28 @@ impl<'a, 'ast, 'v> Visitor<'v> for CheckItemRecursionVisitor<'a, 'ast> {
141226
self.visit_impl_item(item),
142227
ast_map::NodeForeignItem(_) => {},
143228
_ => {
144-
span_err!(self.sess, e.span, E0266,
145-
"expected item, found {}",
146-
self.ast_map.node_to_string(def_id.node));
147-
return;
148-
},
229+
self.sess.span_bug(
230+
e.span,
231+
&format!("expected item, found {}",
232+
self.ast_map.node_to_string(def_id.node)));
233+
}
234+
}
235+
}
236+
// For variants, we only want to check expressions that
237+
// affect the specific variant used, but we need to check
238+
// the whole enum definition to see what expression that
239+
// might be (if any).
240+
Some(DefVariant(enum_id, variant_id, false))
241+
if ast_util::is_local(enum_id) => {
242+
if let ast::ItemEnum(ref enum_def, ref generics) =
243+
self.ast_map.expect_item(enum_id.local_id()).node {
244+
self.populate_enum_discriminants(enum_def);
245+
let variant = self.ast_map.expect_variant(variant_id.local_id());
246+
self.visit_variant(variant, generics);
247+
} else {
248+
self.sess.span_bug(e.span,
249+
"`check_static_recursion` found \
250+
non-enum in DefVariant");
149251
}
150252
}
151253
_ => ()

src/libsyntax/visit.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ pub trait Visitor<'v> : Sized {
9090
walk_struct_def(self, s)
9191
}
9292
fn visit_struct_field(&mut self, s: &'v StructField) { walk_struct_field(self, s) }
93+
fn visit_enum_def(&mut self, enum_definition: &'v EnumDef,
94+
generics: &'v Generics) {
95+
walk_enum_def(self, enum_definition, generics)
96+
}
97+
9398
fn visit_variant(&mut self, v: &'v Variant, g: &'v Generics) { walk_variant(self, v, g) }
9499

95100
/// Visits an optional reference to a lifetime. The `span` is the span of some surrounding
@@ -268,7 +273,7 @@ pub fn walk_item<'v, V: Visitor<'v>>(visitor: &mut V, item: &'v Item) {
268273
}
269274
ItemEnum(ref enum_definition, ref type_parameters) => {
270275
visitor.visit_generics(type_parameters);
271-
walk_enum_def(visitor, enum_definition, type_parameters)
276+
visitor.visit_enum_def(enum_definition, type_parameters)
272277
}
273278
ItemDefaultImpl(_, ref trait_ref) => {
274279
visitor.visit_trait_ref(trait_ref)

src/test/compile-fail/issue-23302.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
enum X {
12+
A = X::A as isize, //~ ERROR E0265
13+
}
14+
15+
enum Y {
16+
A = Y::B as isize, //~ ERROR E0265
17+
B,
18+
}
19+
20+
fn main() { }

0 commit comments

Comments
 (0)