View Javadoc

1   /*
2    $Id: VariableScopeVisitor.java,v 1.4 2006/06/15 17:21:33 blackdrag Exp $
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.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     // helper methods   
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         // a scope in a closure is never really static
123         // the checking needs this to be as the surrounding
124         // method to correctly check the access to variables.
125         // But a closure and all nested scopes are a result
126         // of calling a non static method, so the context
127         // is not static.
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             // if we are in a class and no variable is declared until
173             // now, then we can break the loop, because we are allowed
174             // to declare a variable of the same name as a class member
175             if (scope.getClassScope()!=null) break;
176             
177             Map declares = scope.getDeclaredVariables();
178             if (declares.get(var.getName())!=null) {
179                 // variable already declared
180                 addError(msg.toString(), expr);
181                 break;
182             }
183         }
184         // declare the variable even if there was an error to allow more checks
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     // different Variable based checks  
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         // try to find a declaration of a variable
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             // no matching scope found
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             // no matching scope found, but there was a scope that
303             // resolves dynamic
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         // declare a static variable to be able to continue the check
339         DynamicVariable v2 = new DynamicVariable(v.getName(),currentScope.isInStaticContext());
340         currentScope.getDeclaredVariables().put(v.getName(),v2);
341     }
342     
343     // ------------------------------
344     // code visit  
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         // visit right side first to avoid the usage of a 
366         // variable before its declaration
367         expression.getRightExpression().visit(this);
368         // no need to visit left side, just get the variable name
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         // as result of the Paris meeting Closure resolves
388         // always dynamically
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         //TODO: change that to get the correct scope
420         Variable v = checkVariableNameForDeclaration(name,expression);
421         checkVariableContextAccess(v,expression);  
422     }
423     
424     // ------------------------------
425     // class visit  
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 }