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 package org.codehaus.groovy.classgen;
47
48 import java.util.Iterator;
49 import java.util.LinkedList;
50 import java.util.List;
51 import java.util.Map;
52
53 import org.codehaus.groovy.GroovyBugError;
54 import org.codehaus.groovy.ast.ASTNode;
55 import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
56 import org.codehaus.groovy.ast.ClassHelper;
57 import org.codehaus.groovy.ast.ClassNode;
58 import org.codehaus.groovy.ast.FieldNode;
59 import org.codehaus.groovy.ast.MethodNode;
60 import org.codehaus.groovy.ast.Parameter;
61 import org.codehaus.groovy.ast.PropertyNode;
62 import org.codehaus.groovy.ast.DynamicVariable;
63 import org.codehaus.groovy.ast.Variable;
64 import org.codehaus.groovy.ast.VariableScope;
65 import org.codehaus.groovy.ast.expr.ClosureExpression;
66 import org.codehaus.groovy.ast.expr.ConstantExpression;
67 import org.codehaus.groovy.ast.expr.DeclarationExpression;
68 import org.codehaus.groovy.ast.expr.Expression;
69 import org.codehaus.groovy.ast.expr.FieldExpression;
70 import org.codehaus.groovy.ast.expr.MethodCallExpression;
71 import org.codehaus.groovy.ast.expr.VariableExpression;
72 import org.codehaus.groovy.ast.stmt.BlockStatement;
73 import org.codehaus.groovy.ast.stmt.CatchStatement;
74 import org.codehaus.groovy.ast.stmt.ForStatement;
75 import org.codehaus.groovy.control.SourceUnit;
76
77 /***
78 * goes through an AST and initializes the scopes
79 * @author Jochen Theodorou
80 */
81 public class VariableScopeVisitor extends ClassCodeVisitorSupport {
82 private VariableScope currentScope = null;
83 private VariableScope headScope = new VariableScope();
84 private ClassNode currentClass=null;
85 private SourceUnit source;
86 private boolean inClosure=false;
87
88 private LinkedList stateStack=new LinkedList();
89
90 private class StateStackElement {
91 VariableScope scope;
92 ClassNode clazz;
93 boolean dynamic;
94 boolean closure;
95
96 StateStackElement() {
97 scope = VariableScopeVisitor.this.currentScope;
98 clazz = VariableScopeVisitor.this.currentClass;
99 closure = VariableScopeVisitor.this.inClosure;
100 }
101 }
102
103 public VariableScopeVisitor(SourceUnit source) {
104 this.source = source;
105 currentScope = headScope;
106 }
107
108
109
110
111
112
113 private void pushState(boolean isStatic) {
114 stateStack.add(new StateStackElement());
115 currentScope = new VariableScope(currentScope);
116 currentScope.setInStaticContext(isStatic);
117 }
118
119 private void pushState() {
120 pushState(currentScope.isInStaticContext());
121 }
122
123 private void popState() {
124
125
126
127
128
129
130 if (inClosure) currentScope.setInStaticContext(false);
131
132 StateStackElement element = (StateStackElement) stateStack.removeLast();
133 currentScope = element.scope;
134 currentClass = element.clazz;
135 inClosure = element.closure;
136 }
137
138 private void declare(Parameter[] parameters, ASTNode node) {
139 for (int i = 0; i < parameters.length; i++) {
140 if (parameters[i].hasInitialExpression()) {
141 parameters[i].getInitialExpression().visit(this);
142 }
143 declare(parameters[i],node);
144 }
145 }
146
147 private void declare(VariableExpression expr) {
148 declare(expr,expr);
149 }
150
151 private void declare(Variable var, ASTNode expr) {
152 String scopeType = "scope";
153 String variableType = "variable";
154
155 if (expr.getClass()==FieldNode.class){
156 scopeType = "class";
157 variableType = "field";
158 } else if (expr.getClass()==PropertyNode.class){
159 scopeType = "class";
160 variableType = "property";
161 }
162
163 StringBuffer msg = new StringBuffer();
164 msg.append("The current ").append(scopeType);
165 msg.append(" does already contain a ").append(variableType);
166 msg.append(" of the name ").append(var.getName());
167
168 if (currentScope.getDeclaredVariable(var.getName())!=null) {
169 addError(msg.toString(),expr);
170 return;
171 }
172
173 for (VariableScope scope = currentScope.getParent(); scope!=null; scope = scope.getParent()) {
174
175
176
177 if (scope.getClassScope()!=null) break;
178
179 Map declares = scope.getDeclaredVariables();
180 if (declares.get(var.getName())!=null) {
181
182 addError(msg.toString(), expr);
183 break;
184 }
185 }
186
187 currentScope.getDeclaredVariables().put(var.getName(),var);
188 }
189
190 protected SourceUnit getSourceUnit() {
191 return source;
192 }
193
194 private Variable findClassMember(ClassNode cn, String name) {
195 if (cn == null) return null;
196 if (cn.isScript()) {
197 return new DynamicVariable(name,false);
198 }
199 List l = cn.getFields();
200 for (Iterator iter = l.iterator(); iter.hasNext();) {
201 FieldNode f = (FieldNode) iter.next();
202 if (f.getName().equals(name)) return f;
203 }
204
205 l = cn.getMethods();
206 for (Iterator iter = l.iterator(); iter.hasNext();) {
207 MethodNode f =(MethodNode) iter.next();
208 String methodName = f.getName();
209 String pName = getPropertyName(f);
210 if (pName == null) continue;
211 if (!pName.equals(name)) continue;
212 PropertyNode var = new PropertyNode(pName,f.getModifiers(),getPropertyType(f),cn,null,null,null);
213 return var;
214 }
215
216 l = cn.getProperties();
217 for (Iterator iter = l.iterator(); iter.hasNext();) {
218 PropertyNode f = (PropertyNode) iter.next();
219 if (f.getName().equals(name)) return f;
220 }
221
222 Variable ret = findClassMember(cn.getSuperClass(),name);
223 if (ret!=null) return ret;
224 return findClassMember(cn.getOuterClass(),name);
225 }
226
227 private ClassNode getPropertyType(MethodNode m) {
228 String name = m.getName();
229 if (m.getReturnType()!=ClassHelper.VOID_TYPE) {
230 return m.getReturnType();
231 }
232 return m.getParameters()[0].getType();
233 }
234
235 private String getPropertyName(MethodNode m) {
236 String name = m.getName();
237 if (!(name.startsWith("set") || name.startsWith("get"))) return null;
238 String pname = name.substring(3);
239 if (pname.length() == 0) return null;
240 String s = pname.substring(0, 1).toLowerCase();
241 String rest = pname.substring(1);
242 pname = s + rest;
243
244 if (name.startsWith("get") && m.getReturnType()==ClassHelper.VOID_TYPE) {
245 return null;
246 }
247 if (name.startsWith("set") && m.getParameters().length!=1) {
248 return null;
249 }
250 return pname;
251 }
252
253
254
255
256
257 private Variable checkVariableNameForDeclaration(String name, Expression expression) {
258 if ("super".equals(name) || "this".equals(name)) return null;
259
260 VariableScope scope = currentScope;
261 Variable var = new DynamicVariable(name,currentScope.isInStaticContext());
262 Variable dummyStart = var;
263
264 VariableScope dynamicScope = null;
265 while (!scope.isRoot()) {
266 if (dynamicScope==null && scope.isResolvingDynamic()) {
267 dynamicScope = scope;
268 }
269
270 Map declares = scope.getDeclaredVariables();
271 if (declares.get(var.getName())!=null) {
272 var = (Variable) declares.get(var.getName());
273 break;
274 }
275 Map localReferenced = scope.getReferencedLocalVariables();
276 if (localReferenced.get(var.getName())!=null) {
277 var = (Variable) localReferenced.get(var.getName());
278 break;
279 }
280
281 Map classReferenced = scope.getReferencedClassVariables();
282 if (classReferenced.get(var.getName())!=null) {
283 var = (Variable) classReferenced.get(var.getName());
284 break;
285 }
286
287 ClassNode classScope = scope.getClassScope();
288 if (classScope!=null) {
289 Variable member = findClassMember(classScope,var.getName());
290 if (member!=null && (currentScope.isInStaticContext() ^ member instanceof DynamicVariable)) var = member;
291 break;
292 }
293 scope = scope.getParent();
294 }
295
296 VariableScope end = scope;
297
298 if (scope.isRoot() && dynamicScope==null) {
299
300 declare(var,expression);
301 addError("The variable " + var.getName() +
302 " is undefined in the current scope", expression);
303 } else if (scope.isRoot() && dynamicScope!=null) {
304
305
306 scope = dynamicScope;
307 }
308
309 if (!scope.isRoot()) {
310 scope = currentScope;
311 while (scope != end) {
312 Map references = null;
313 if (end.isClassScope() || end.isRoot() ||
314 (end.isReferencedClassVariable(name) && end.getDeclaredVariable(name)==null))
315 {
316 references = scope.getReferencedClassVariables();
317 } else {
318 references = scope.getReferencedLocalVariables();
319 var.setClosureSharedVariable(var.isClosureSharedVariable() || inClosure);
320 }
321 references.put(var.getName(),var);
322 scope = scope.getParent();
323 }
324 if (end.isResolvingDynamic()) {
325 if (end.getDeclaredVariable(var.getName())==null) {
326 end.getDeclaredVariables().put(var.getName(),var);
327 }
328 }
329 }
330
331 return var;
332 }
333
334 private void checkVariableContextAccess(Variable v, Expression expr) {
335 if (v.isInStaticContext() || !currentScope.isInStaticContext()) return;
336
337 String msg = v.getName()+
338 " is declared in a dynamic context, but you tried to"+
339 " access it from a static context.";
340 addError(msg,expr);
341
342
343 DynamicVariable v2 = new DynamicVariable(v.getName(),currentScope.isInStaticContext());
344 currentScope.getDeclaredVariables().put(v.getName(),v2);
345 }
346
347
348
349
350
351 public void visitBlockStatement(BlockStatement block) {
352 pushState();
353 block.setVariableScope(currentScope);
354 super.visitBlockStatement(block);
355 popState();
356 }
357
358 public void visitForLoop(ForStatement forLoop) {
359 pushState();
360 forLoop.setVariableScope(currentScope);
361 Parameter p = (Parameter) forLoop.getVariable();
362 p.setInStaticContext(currentScope.isInStaticContext());
363 declare(p, forLoop);
364 super.visitForLoop(forLoop);
365 popState();
366 }
367
368 public void visitDeclarationExpression(DeclarationExpression expression) {
369
370
371 expression.getRightExpression().visit(this);
372
373 VariableExpression vex = expression.getVariableExpression();
374 vex.setInStaticContext(currentScope.isInStaticContext());
375 declare(vex);
376 vex.setAccessedVariable(vex);
377 }
378
379 public void visitVariableExpression(VariableExpression expression) {
380 String name = expression.getName();
381 Variable v = checkVariableNameForDeclaration(name,expression);
382 if (v==null) return;
383 expression.setAccessedVariable(v);
384 checkVariableContextAccess(v,expression);
385 }
386
387 public void visitClosureExpression(ClosureExpression expression) {
388 pushState();
389
390 inClosure=true;
391
392
393 currentScope.setDynamicResolving(true);
394
395 expression.setVariableScope(currentScope);
396
397 if (expression.isParameterSpecified()) {
398 Parameter[] parameters = expression.getParameters();
399 for (int i = 0; i < parameters.length; i++) {
400 parameters[i].setInStaticContext(currentScope.isInStaticContext());
401 declare(parameters[i],expression);
402 }
403 } else if (expression.getParameters()!=null){
404 DynamicVariable var = new DynamicVariable("it",currentScope.isInStaticContext());
405 currentScope.getDeclaredVariables().put("it",var);
406 }
407
408 super.visitClosureExpression(expression);
409 popState();
410 }
411
412 public void visitCatchStatement(CatchStatement statement) {
413 pushState();
414 Parameter p = (Parameter) statement.getVariable();
415 p.setInStaticContext(currentScope.isInStaticContext());
416 declare(p, statement);
417 super.visitCatchStatement(statement);
418 popState();
419 }
420
421 public void visitFieldExpression(FieldExpression expression) {
422 String name = expression.getFieldName();
423
424 Variable v = checkVariableNameForDeclaration(name,expression);
425 checkVariableContextAccess(v,expression);
426 }
427
428
429
430
431
432 public void visitClass(ClassNode node) {
433 pushState();
434 boolean dynamicMode = node.isScript();
435 currentScope.setDynamicResolving(dynamicMode);
436 currentScope.setClassScope(node);
437
438 super.visitClass(node);
439 popState();
440 }
441
442 protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) {
443 pushState(node.isStatic());
444
445 node.setVariableScope(currentScope);
446 declare(node.getParameters(),node);
447
448 super.visitConstructorOrMethod(node, isConstructor);
449 popState();
450 }
451
452 public void visitMethodCallExpression(MethodCallExpression call) {
453 if (call.isImplicitThis() && call.getMethod() instanceof ConstantExpression) {
454 Object value = ((ConstantExpression) call.getMethod()).getText();
455 if (! (value instanceof String)) {
456 throw new GroovyBugError("tried to make a method call with an constant as"+
457 " name, but the constant was no String.");
458 }
459 String methodName = (String) value;
460 Variable v = checkVariableNameForDeclaration(methodName,call);
461 if (v!=null && !(v instanceof DynamicVariable)) {
462 checkVariableContextAccess(v,call);
463 }
464 }
465 super.visitMethodCallExpression(call);
466 }
467 }