001 /* 002 $Id: VariableScopeVisitor.java 4607 2006-12-22 22:19:01Z blackdrag $ 003 004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. 005 006 Redistribution and use of this software and associated documentation 007 ("Software"), with or without modification, are permitted provided 008 that the following conditions are met: 009 010 1. Redistributions of source code must retain copyright 011 statements and notices. Redistributions must also contain a 012 copy of this document. 013 014 2. Redistributions in binary form must reproduce the 015 above copyright notice, this list of conditions and the 016 following disclaimer in the documentation and/or other 017 materials provided with the distribution. 018 019 3. The name "groovy" must not be used to endorse or promote 020 products derived from this Software without prior written 021 permission of The Codehaus. For written permission, 022 please contact info@codehaus.org. 023 024 4. Products derived from this Software may not be called "groovy" 025 nor may "groovy" appear in their names without prior written 026 permission of The Codehaus. "groovy" is a registered 027 trademark of The Codehaus. 028 029 5. Due credit should be given to The Codehaus - 030 http://groovy.codehaus.org/ 031 032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS 033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 043 OF THE POSSIBILITY OF SUCH DAMAGE. 044 045 */ 046 package org.codehaus.groovy.classgen; 047 048 import java.util.Iterator; 049 import java.util.LinkedList; 050 import java.util.List; 051 import java.util.Map; 052 053 import org.codehaus.groovy.GroovyBugError; 054 import org.codehaus.groovy.ast.ASTNode; 055 import org.codehaus.groovy.ast.ClassCodeVisitorSupport; 056 import org.codehaus.groovy.ast.ClassHelper; 057 import org.codehaus.groovy.ast.ClassNode; 058 import org.codehaus.groovy.ast.FieldNode; 059 import org.codehaus.groovy.ast.MethodNode; 060 import org.codehaus.groovy.ast.Parameter; 061 import org.codehaus.groovy.ast.PropertyNode; 062 import org.codehaus.groovy.ast.DynamicVariable; 063 import org.codehaus.groovy.ast.Variable; 064 import org.codehaus.groovy.ast.VariableScope; 065 import org.codehaus.groovy.ast.expr.ClosureExpression; 066 import org.codehaus.groovy.ast.expr.ConstantExpression; 067 import org.codehaus.groovy.ast.expr.DeclarationExpression; 068 import org.codehaus.groovy.ast.expr.Expression; 069 import org.codehaus.groovy.ast.expr.FieldExpression; 070 import org.codehaus.groovy.ast.expr.MethodCallExpression; 071 import org.codehaus.groovy.ast.expr.VariableExpression; 072 import org.codehaus.groovy.ast.stmt.BlockStatement; 073 import org.codehaus.groovy.ast.stmt.CatchStatement; 074 import org.codehaus.groovy.ast.stmt.ForStatement; 075 import org.codehaus.groovy.control.SourceUnit; 076 077 /** 078 * goes through an AST and initializes the scopes 079 * @author Jochen Theodorou 080 */ 081 public class VariableScopeVisitor extends ClassCodeVisitorSupport { 082 private VariableScope currentScope = null; 083 private VariableScope headScope = new VariableScope(); 084 private ClassNode currentClass=null; 085 private SourceUnit source; 086 private boolean inClosure=false; 087 088 private LinkedList stateStack=new LinkedList(); 089 090 private class StateStackElement { 091 VariableScope scope; 092 ClassNode clazz; 093 boolean dynamic; 094 boolean closure; 095 096 StateStackElement() { 097 scope = VariableScopeVisitor.this.currentScope; 098 clazz = VariableScopeVisitor.this.currentClass; 099 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 }