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