forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathstatic_recursion.rs
290 lines (272 loc) · 11.9 KB
/
static_recursion.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
// Copyright 2014 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.
// This compiler pass detects constants that refer to themselves
// recursively.
use rustc::front::map as ast_map;
use rustc::session::Session;
use rustc::middle::def::{Def, DefMap};
use rustc::util::nodemap::NodeMap;
use syntax::{ast};
use syntax::codemap::Span;
use syntax::feature_gate::{GateIssue, emit_feature_err};
use rustc_front::intravisit::{self, Visitor};
use rustc_front::hir;
use std::cell::RefCell;
struct CheckCrateVisitor<'a, 'ast: 'a> {
sess: &'a Session,
def_map: &'a DefMap,
ast_map: &'a ast_map::Map<'ast>,
// `discriminant_map` is a cache that associates the `NodeId`s of local
// variant definitions with the discriminant expression that applies to
// each one. If the variant uses the default values (starting from `0`),
// then `None` is stored.
discriminant_map: RefCell<NodeMap<Option<&'ast hir::Expr>>>,
}
impl<'a, 'ast: 'a> Visitor<'ast> for CheckCrateVisitor<'a, 'ast> {
fn visit_item(&mut self, it: &'ast hir::Item) {
match it.node {
hir::ItemStatic(..) |
hir::ItemConst(..) => {
let mut recursion_visitor =
CheckItemRecursionVisitor::new(self, &it.span);
recursion_visitor.visit_item(it);
},
hir::ItemEnum(ref enum_def, ref generics) => {
// We could process the whole enum, but handling the variants
// with discriminant expressions one by one gives more specific,
// less redundant output.
for variant in &enum_def.variants {
if let Some(_) = variant.node.disr_expr {
let mut recursion_visitor =
CheckItemRecursionVisitor::new(self, &variant.span);
recursion_visitor.populate_enum_discriminants(enum_def);
recursion_visitor.visit_variant(variant, generics, it.id);
}
}
}
_ => {}
}
intravisit::walk_item(self, it)
}
fn visit_trait_item(&mut self, ti: &'ast hir::TraitItem) {
match ti.node {
hir::ConstTraitItem(_, ref default) => {
if let Some(_) = *default {
let mut recursion_visitor =
CheckItemRecursionVisitor::new(self, &ti.span);
recursion_visitor.visit_trait_item(ti);
}
}
_ => {}
}
intravisit::walk_trait_item(self, ti)
}
fn visit_impl_item(&mut self, ii: &'ast hir::ImplItem) {
match ii.node {
hir::ImplItemKind::Const(..) => {
let mut recursion_visitor =
CheckItemRecursionVisitor::new(self, &ii.span);
recursion_visitor.visit_impl_item(ii);
}
_ => {}
}
intravisit::walk_impl_item(self, ii)
}
}
pub fn check_crate<'ast>(sess: &Session,
krate: &'ast hir::Crate,
def_map: &DefMap,
ast_map: &ast_map::Map<'ast>) {
let mut visitor = CheckCrateVisitor {
sess: sess,
def_map: def_map,
ast_map: ast_map,
discriminant_map: RefCell::new(NodeMap()),
};
sess.abort_if_new_errors(|| {
krate.visit_all_items(&mut visitor);
});
}
struct CheckItemRecursionVisitor<'a, 'ast: 'a> {
root_span: &'a Span,
sess: &'a Session,
ast_map: &'a ast_map::Map<'ast>,
def_map: &'a DefMap,
discriminant_map: &'a RefCell<NodeMap<Option<&'ast hir::Expr>>>,
idstack: Vec<ast::NodeId>,
}
impl<'a, 'ast: 'a> CheckItemRecursionVisitor<'a, 'ast> {
fn new(v: &'a CheckCrateVisitor<'a, 'ast>, span: &'a Span)
-> CheckItemRecursionVisitor<'a, 'ast> {
CheckItemRecursionVisitor {
root_span: span,
sess: v.sess,
ast_map: v.ast_map,
def_map: v.def_map,
discriminant_map: &v.discriminant_map,
idstack: Vec::new(),
}
}
fn with_item_id_pushed<F>(&mut self, id: ast::NodeId, f: F)
where F: Fn(&mut Self) {
if self.idstack.iter().any(|&x| x == id) {
let any_static = self.idstack.iter().any(|&x| {
if let ast_map::NodeItem(item) = self.ast_map.get(x) {
if let hir::ItemStatic(..) = item.node {
true
} else {
false
}
} else {
false
}
});
if any_static {
if !self.sess.features.borrow().static_recursion {
emit_feature_err(&self.sess.parse_sess.span_diagnostic,
"static_recursion",
*self.root_span, GateIssue::Language, "recursive static");
}
} else {
span_err!(self.sess, *self.root_span, E0265, "recursive constant");
}
return;
}
self.idstack.push(id);
f(self);
self.idstack.pop();
}
// If a variant has an expression specifying its discriminant, then it needs
// to be checked just like a static or constant. However, if there are more
// variants with no explicitly specified discriminant, those variants will
// increment the same expression to get their values.
//
// So for every variant, we need to track whether there is an expression
// somewhere in the enum definition that controls its discriminant. We do
// this by starting from the end and searching backward.
fn populate_enum_discriminants(&self, enum_definition: &'ast hir::EnumDef) {
// Get the map, and return if we already processed this enum or if it
// has no variants.
let mut discriminant_map = self.discriminant_map.borrow_mut();
match enum_definition.variants.first() {
None => { return; }
Some(variant) if discriminant_map.contains_key(&variant.node.data.id()) => {
return;
}
_ => {}
}
// Go through all the variants.
let mut variant_stack: Vec<ast::NodeId> = Vec::new();
for variant in enum_definition.variants.iter().rev() {
variant_stack.push(variant.node.data.id());
// When we find an expression, every variant currently on the stack
// is affected by that expression.
if let Some(ref expr) = variant.node.disr_expr {
for id in &variant_stack {
discriminant_map.insert(*id, Some(expr));
}
variant_stack.clear()
}
}
// If we are at the top, that always starts at 0, so any variant on the
// stack has a default value and does not need to be checked.
for id in &variant_stack {
discriminant_map.insert(*id, None);
}
}
}
impl<'a, 'ast: 'a> Visitor<'ast> for CheckItemRecursionVisitor<'a, 'ast> {
fn visit_item(&mut self, it: &'ast hir::Item) {
self.with_item_id_pushed(it.id, |v| intravisit::walk_item(v, it));
}
fn visit_enum_def(&mut self, enum_definition: &'ast hir::EnumDef,
generics: &'ast hir::Generics, item_id: ast::NodeId, _: Span) {
self.populate_enum_discriminants(enum_definition);
intravisit::walk_enum_def(self, enum_definition, generics, item_id);
}
fn visit_variant(&mut self, variant: &'ast hir::Variant,
_: &'ast hir::Generics, _: ast::NodeId) {
let variant_id = variant.node.data.id();
let maybe_expr;
if let Some(get_expr) = self.discriminant_map.borrow().get(&variant_id) {
// This is necessary because we need to let the `discriminant_map`
// borrow fall out of scope, so that we can reborrow farther down.
maybe_expr = (*get_expr).clone();
} else {
self.sess.span_bug(variant.span,
"`check_static_recursion` attempted to visit \
variant with unknown discriminant")
}
// If `maybe_expr` is `None`, that's because no discriminant is
// specified that affects this variant. Thus, no risk of recursion.
if let Some(expr) = maybe_expr {
self.with_item_id_pushed(expr.id, |v| intravisit::walk_expr(v, expr));
}
}
fn visit_trait_item(&mut self, ti: &'ast hir::TraitItem) {
self.with_item_id_pushed(ti.id, |v| intravisit::walk_trait_item(v, ti));
}
fn visit_impl_item(&mut self, ii: &'ast hir::ImplItem) {
self.with_item_id_pushed(ii.id, |v| intravisit::walk_impl_item(v, ii));
}
fn visit_expr(&mut self, e: &'ast hir::Expr) {
match e.node {
hir::ExprPath(..) => {
match self.def_map.get(&e.id).map(|d| d.base_def) {
Some(Def::Static(def_id, _)) |
Some(Def::AssociatedConst(def_id)) |
Some(Def::Const(def_id)) => {
if let Some(node_id) = self.ast_map.as_local_node_id(def_id) {
match self.ast_map.get(node_id) {
ast_map::NodeItem(item) =>
self.visit_item(item),
ast_map::NodeTraitItem(item) =>
self.visit_trait_item(item),
ast_map::NodeImplItem(item) =>
self.visit_impl_item(item),
ast_map::NodeForeignItem(_) => {},
_ => {
self.sess.span_bug(
e.span,
&format!("expected item, found {}",
self.ast_map.node_to_string(node_id)));
}
}
}
}
// For variants, we only want to check expressions that
// affect the specific variant used, but we need to check
// the whole enum definition to see what expression that
// might be (if any).
Some(Def::Variant(enum_id, variant_id)) => {
if let Some(enum_node_id) = self.ast_map.as_local_node_id(enum_id) {
if let hir::ItemEnum(ref enum_def, ref generics) =
self.ast_map.expect_item(enum_node_id).node
{
self.populate_enum_discriminants(enum_def);
let enum_id = self.ast_map.as_local_node_id(enum_id).unwrap();
let variant_id = self.ast_map.as_local_node_id(variant_id).unwrap();
let variant = self.ast_map.expect_variant(variant_id);
self.visit_variant(variant, generics, enum_id);
} else {
self.sess.span_bug(e.span,
"`check_static_recursion` found \
non-enum in Def::Variant");
}
}
}
_ => ()
}
},
_ => ()
}
intravisit::walk_expr(self, e);
}
}