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: // helper methods
110: //------------------------------
111:
112: private void pushState(boolean isStatic) {
113: stateStack.add(new StateStackElement());
114: currentScope = new VariableScope(currentScope);
115: currentScope.setInStaticContext(isStatic);
116: }
117:
118: private void pushState() {
119: pushState(currentScope.isInStaticContext());
120: }
121:
122: private void popState() {
123: // a scope in a closure is never really static
124: // the checking needs this to be as the surrounding
125: // method to correctly check the access to variables.
126: // But a closure and all nested scopes are a result
127: // of calling a non static method, so the context
128: // is not static.
129: if (inClosure)
130: currentScope.setInStaticContext(false);
131:
132: StateStackElement element = (StateStackElement) stateStack
133: .removeLast();
134: currentScope = element.scope;
135: currentClass = element.clazz;
136: inClosure = element.closure;
137: }
138:
139: private void declare(Parameter[] parameters, ASTNode node) {
140: for (int i = 0; i < parameters.length; i++) {
141: if (parameters[i].hasInitialExpression()) {
142: parameters[i].getInitialExpression().visit(this );
143: }
144: declare(parameters[i], node);
145: }
146: }
147:
148: private void declare(VariableExpression expr) {
149: declare(expr, expr);
150: }
151:
152: private void declare(Variable var, ASTNode expr) {
153: String scopeType = "scope";
154: String variableType = "variable";
155:
156: if (expr.getClass() == FieldNode.class) {
157: scopeType = "class";
158: variableType = "field";
159: } else if (expr.getClass() == PropertyNode.class) {
160: scopeType = "class";
161: variableType = "property";
162: }
163:
164: StringBuffer msg = new StringBuffer();
165: msg.append("The current ").append(scopeType);
166: msg.append(" does already contain a ").append(variableType);
167: msg.append(" of the name ").append(var.getName());
168:
169: if (currentScope.getDeclaredVariable(var.getName()) != null) {
170: addError(msg.toString(), expr);
171: return;
172: }
173:
174: for (VariableScope scope = currentScope.getParent(); scope != null; scope = scope
175: .getParent()) {
176: // if we are in a class and no variable is declared until
177: // now, then we can break the loop, because we are allowed
178: // to declare a variable of the same name as a class member
179: if (scope.getClassScope() != null)
180: break;
181:
182: Map declares = scope.getDeclaredVariables();
183: if (declares.get(var.getName()) != null) {
184: // variable already declared
185: addError(msg.toString(), expr);
186: break;
187: }
188: }
189: // declare the variable even if there was an error to allow more checks
190: currentScope.getDeclaredVariables().put(var.getName(), var);
191: }
192:
193: protected SourceUnit getSourceUnit() {
194: return source;
195: }
196:
197: private Variable findClassMember(ClassNode cn, String name) {
198: if (cn == null)
199: return null;
200: if (cn.isScript()) {
201: return new DynamicVariable(name, false);
202: }
203: List l = cn.getFields();
204: for (Iterator iter = l.iterator(); iter.hasNext();) {
205: FieldNode f = (FieldNode) iter.next();
206: if (f.getName().equals(name))
207: return f;
208: }
209:
210: l = cn.getMethods();
211: for (Iterator iter = l.iterator(); iter.hasNext();) {
212: MethodNode f = (MethodNode) iter.next();
213: String methodName = f.getName();
214: String pName = getPropertyName(f);
215: if (pName == null)
216: continue;
217: if (!pName.equals(name))
218: continue;
219: PropertyNode var = new PropertyNode(pName,
220: f.getModifiers(), getPropertyType(f), cn, null,
221: null, null);
222: return var;
223: }
224:
225: l = cn.getProperties();
226: for (Iterator iter = l.iterator(); iter.hasNext();) {
227: PropertyNode f = (PropertyNode) iter.next();
228: if (f.getName().equals(name))
229: return f;
230: }
231:
232: Variable ret = findClassMember(cn.getSuperClass(), name);
233: if (ret != null)
234: return ret;
235: return findClassMember(cn.getOuterClass(), name);
236: }
237:
238: private ClassNode getPropertyType(MethodNode m) {
239: String name = m.getName();
240: if (m.getReturnType() != ClassHelper.VOID_TYPE) {
241: return m.getReturnType();
242: }
243: return m.getParameters()[0].getType();
244: }
245:
246: private String getPropertyName(MethodNode m) {
247: String name = m.getName();
248: if (!(name.startsWith("set") || name.startsWith("get")))
249: return null;
250: String pname = name.substring(3);
251: if (pname.length() == 0)
252: return null;
253: String s = pname.substring(0, 1).toLowerCase();
254: String rest = pname.substring(1);
255: pname = s + rest;
256:
257: if (name.startsWith("get")
258: && m.getReturnType() == ClassHelper.VOID_TYPE) {
259: return null;
260: }
261: if (name.startsWith("set") && m.getParameters().length != 1) {
262: return null;
263: }
264: return pname;
265: }
266:
267: // -------------------------------
268: // different Variable based checks
269: // -------------------------------
270:
271: private Variable checkVariableNameForDeclaration(String name,
272: Expression expression) {
273: if ("super".equals(name) || "this".equals(name))
274: return null;
275:
276: VariableScope scope = currentScope;
277: Variable var = new DynamicVariable(name, currentScope
278: .isInStaticContext());
279: Variable dummyStart = var;
280: // try to find a declaration of a variable
281: VariableScope dynamicScope = null;
282: while (!scope.isRoot()) {
283: if (dynamicScope == null && scope.isResolvingDynamic()) {
284: dynamicScope = scope;
285: }
286:
287: Map declares = scope.getDeclaredVariables();
288: if (declares.get(var.getName()) != null) {
289: var = (Variable) declares.get(var.getName());
290: break;
291: }
292: Map localReferenced = scope.getReferencedLocalVariables();
293: if (localReferenced.get(var.getName()) != null) {
294: var = (Variable) localReferenced.get(var.getName());
295: break;
296: }
297:
298: Map classReferenced = scope.getReferencedClassVariables();
299: if (classReferenced.get(var.getName()) != null) {
300: var = (Variable) classReferenced.get(var.getName());
301: break;
302: }
303:
304: ClassNode classScope = scope.getClassScope();
305: if (classScope != null) {
306: Variable member = findClassMember(classScope, var
307: .getName());
308: if (member != null
309: && (currentScope.isInStaticContext() ^ member instanceof DynamicVariable))
310: var = member;
311: break;
312: }
313: scope = scope.getParent();
314: }
315:
316: VariableScope end = scope;
317:
318: if (scope.isRoot() && dynamicScope == null) {
319: // no matching scope found
320: declare(var, expression);
321: addError("The variable " + var.getName()
322: + " is undefined in the current scope", expression);
323: } else if (scope.isRoot() && dynamicScope != null) {
324: // no matching scope found, but there was a scope that
325: // resolves dynamic
326: scope = dynamicScope;
327: }
328:
329: if (!scope.isRoot()) {
330: scope = currentScope;
331: while (scope != end) {
332: Map references = null;
333: if (end.isClassScope()
334: || end.isRoot()
335: || (end.isReferencedClassVariable(name) && end
336: .getDeclaredVariable(name) == null)) {
337: references = scope.getReferencedClassVariables();
338: } else {
339: references = scope.getReferencedLocalVariables();
340: var.setClosureSharedVariable(var
341: .isClosureSharedVariable()
342: || inClosure);
343: }
344: references.put(var.getName(), var);
345: scope = scope.getParent();
346: }
347: if (end.isResolvingDynamic()) {
348: if (end.getDeclaredVariable(var.getName()) == null) {
349: end.getDeclaredVariables().put(var.getName(), var);
350: }
351: }
352: }
353:
354: return var;
355: }
356:
357: private void checkVariableContextAccess(Variable v, Expression expr) {
358: if (v.isInStaticContext() || !currentScope.isInStaticContext())
359: return;
360:
361: String msg = v.getName()
362: + " is declared in a dynamic context, but you tried to"
363: + " access it from a static context.";
364: addError(msg, expr);
365:
366: // declare a static variable to be able to continue the check
367: DynamicVariable v2 = new DynamicVariable(v.getName(),
368: currentScope.isInStaticContext());
369: currentScope.getDeclaredVariables().put(v.getName(), v2);
370: }
371:
372: // ------------------------------
373: // code visit
374: // ------------------------------
375:
376: public void visitBlockStatement(BlockStatement block) {
377: pushState();
378: block.setVariableScope(currentScope);
379: super .visitBlockStatement(block);
380: popState();
381: }
382:
383: public void visitForLoop(ForStatement forLoop) {
384: pushState();
385: forLoop.setVariableScope(currentScope);
386: Parameter p = (Parameter) forLoop.getVariable();
387: p.setInStaticContext(currentScope.isInStaticContext());
388: declare(p, forLoop);
389: super .visitForLoop(forLoop);
390: popState();
391: }
392:
393: public void visitDeclarationExpression(
394: DeclarationExpression expression) {
395: // visit right side first to avoid the usage of a
396: // variable before its declaration
397: expression.getRightExpression().visit(this );
398: // no need to visit left side, just get the variable name
399: VariableExpression vex = expression.getVariableExpression();
400: vex.setInStaticContext(currentScope.isInStaticContext());
401: declare(vex);
402: vex.setAccessedVariable(vex);
403: }
404:
405: public void visitVariableExpression(VariableExpression expression) {
406: String name = expression.getName();
407: Variable v = checkVariableNameForDeclaration(name, expression);
408: if (v == null)
409: return;
410: expression.setAccessedVariable(v);
411: checkVariableContextAccess(v, expression);
412: }
413:
414: public void visitClosureExpression(ClosureExpression expression) {
415: pushState();
416:
417: inClosure = true;
418: // as result of the Paris meeting Closure resolves
419: // always dynamically
420: currentScope.setDynamicResolving(true);
421:
422: expression.setVariableScope(currentScope);
423:
424: if (expression.isParameterSpecified()) {
425: Parameter[] parameters = expression.getParameters();
426: for (int i = 0; i < parameters.length; i++) {
427: parameters[i].setInStaticContext(currentScope
428: .isInStaticContext());
429: declare(parameters[i], expression);
430: }
431: } else if (expression.getParameters() != null) {
432: DynamicVariable var = new DynamicVariable("it",
433: currentScope.isInStaticContext());
434: currentScope.getDeclaredVariables().put("it", var);
435: }
436:
437: super .visitClosureExpression(expression);
438: popState();
439: }
440:
441: public void visitCatchStatement(CatchStatement statement) {
442: pushState();
443: Parameter p = (Parameter) statement.getVariable();
444: p.setInStaticContext(currentScope.isInStaticContext());
445: declare(p, statement);
446: super .visitCatchStatement(statement);
447: popState();
448: }
449:
450: public void visitFieldExpression(FieldExpression expression) {
451: String name = expression.getFieldName();
452: //TODO: change that to get the correct scope
453: Variable v = checkVariableNameForDeclaration(name, expression);
454: checkVariableContextAccess(v, expression);
455: }
456:
457: // ------------------------------
458: // class visit
459: // ------------------------------
460:
461: public void visitClass(ClassNode node) {
462: pushState();
463: boolean dynamicMode = node.isScript();
464: currentScope.setDynamicResolving(dynamicMode);
465: currentScope.setClassScope(node);
466:
467: super .visitClass(node);
468: popState();
469: }
470:
471: protected void visitConstructorOrMethod(MethodNode node,
472: boolean isConstructor) {
473: pushState(node.isStatic());
474:
475: node.setVariableScope(currentScope);
476: declare(node.getParameters(), node);
477:
478: super .visitConstructorOrMethod(node, isConstructor);
479: popState();
480: }
481:
482: public void visitMethodCallExpression(MethodCallExpression call) {
483: if (call.isImplicitThis()
484: && call.getMethod() instanceof ConstantExpression) {
485: Object value = ((ConstantExpression) call.getMethod())
486: .getText();
487: if (!(value instanceof String)) {
488: throw new GroovyBugError(
489: "tried to make a method call with an constant as"
490: + " name, but the constant was no String.");
491: }
492: String methodName = (String) value;
493: Variable v = checkVariableNameForDeclaration(methodName,
494: call);
495: if (v != null && !(v instanceof DynamicVariable)) {
496: checkVariableContextAccess(v, call);
497: }
498: }
499: super.visitMethodCallExpression(call);
500: }
501: }
|