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 }