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 }