View Javadoc

1   /*
2    $Id: VariableScopeVisitor.java 4607 2006-12-22 22:19:01Z blackdrag $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
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     // helper methods   
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         // a scope in a closure is never really static
125         // the checking needs this to be as the surrounding
126         // method to correctly check the access to variables.
127         // But a closure and all nested scopes are a result
128         // of calling a non static method, so the context
129         // is not static.
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             // if we are in a class and no variable is declared until
175             // now, then we can break the loop, because we are allowed
176             // to declare a variable of the same name as a class member
177             if (scope.getClassScope()!=null) break;
178             
179             Map declares = scope.getDeclaredVariables();
180             if (declares.get(var.getName())!=null) {
181                 // variable already declared
182                 addError(msg.toString(), expr);
183                 break;
184             }
185         }
186         // declare the variable even if there was an error to allow more checks
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     // different Variable based checks  
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         // try to find a declaration of a variable
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             // no matching scope found
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             // no matching scope found, but there was a scope that
305             // resolves dynamic
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         // declare a static variable to be able to continue the check
343         DynamicVariable v2 = new DynamicVariable(v.getName(),currentScope.isInStaticContext());
344         currentScope.getDeclaredVariables().put(v.getName(),v2);
345     }
346     
347     // ------------------------------
348     // code visit  
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         // visit right side first to avoid the usage of a 
370         // variable before its declaration
371         expression.getRightExpression().visit(this);
372         // no need to visit left side, just get the variable name
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         // as result of the Paris meeting Closure resolves
392         // always dynamically
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         //TODO: change that to get the correct scope
424         Variable v = checkVariableNameForDeclaration(name,expression);
425         checkVariableContextAccess(v,expression);  
426     }
427     
428     // ------------------------------
429     // class visit  
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 }