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 }