You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by cc...@apache.org on 2017/12/14 21:56:51 UTC

[26/57] [abbrv] [partial] groovy git commit: Move Java source set into `src/main/java`

http://git-wip-us.apache.org/repos/asf/groovy/blob/b25d0e55/src/main/java/org/codehaus/groovy/classgen/VariableScopeVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/VariableScopeVisitor.java b/src/main/java/org/codehaus/groovy/classgen/VariableScopeVisitor.java
new file mode 100644
index 0000000..1863c1f
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/VariableScopeVisitor.java
@@ -0,0 +1,611 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.classgen;
+
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.DynamicVariable;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.Variable;
+import org.codehaus.groovy.ast.VariableScope;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.DeclarationExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.FieldExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.CatchStatement;
+import org.codehaus.groovy.ast.stmt.ForStatement;
+import org.codehaus.groovy.ast.stmt.IfStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.syntax.Types;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static java.lang.reflect.Modifier.isFinal;
+
+/**
+ * goes through an AST and initializes the scopes
+ */
+public class VariableScopeVisitor extends ClassCodeVisitorSupport {
+
+    private VariableScope currentScope = null;
+    private final VariableScope headScope = new VariableScope();
+    private ClassNode currentClass = null;
+    private final SourceUnit source;
+    private boolean isSpecialConstructorCall = false;
+    private boolean inConstructor = false;
+    private final boolean recurseInnerClasses;
+
+    private final LinkedList stateStack = new LinkedList();
+
+    private class StateStackElement {
+        final VariableScope scope;
+        final ClassNode clazz;
+        final boolean inConstructor;
+
+        StateStackElement() {
+            scope = VariableScopeVisitor.this.currentScope;
+            clazz = VariableScopeVisitor.this.currentClass;
+            inConstructor = VariableScopeVisitor.this.inConstructor;
+        }
+    }
+
+    public VariableScopeVisitor(SourceUnit source, boolean recurseInnerClasses) {
+        this.source = source;
+        currentScope = headScope;
+        this.recurseInnerClasses = recurseInnerClasses;
+    }
+
+
+    public VariableScopeVisitor(SourceUnit source) {
+        this(source, false);
+    }
+
+    // ------------------------------
+    // helper methods
+    //------------------------------
+
+    private void pushState(boolean isStatic) {
+        stateStack.add(new StateStackElement());
+        currentScope = new VariableScope(currentScope);
+        currentScope.setInStaticContext(isStatic);
+    }
+
+    private void pushState() {
+        pushState(currentScope.isInStaticContext());
+    }
+
+    private void popState() {
+        StateStackElement element = (StateStackElement) stateStack.removeLast();
+        currentScope = element.scope;
+        currentClass = element.clazz;
+        inConstructor = element.inConstructor;
+    }
+
+    private void declare(Parameter[] parameters, ASTNode node) {
+        for (Parameter parameter : parameters) {
+            if (parameter.hasInitialExpression()) {
+                parameter.getInitialExpression().visit(this);
+            }
+            declare(parameter, node);
+        }
+    }
+
+    private void declare(VariableExpression vex) {
+        vex.setInStaticContext(currentScope.isInStaticContext());
+        declare(vex, vex);
+        vex.setAccessedVariable(vex);
+    }
+
+    private void declare(Variable var, ASTNode expr) {
+        String scopeType = "scope";
+        String variableType = "variable";
+
+        if (expr.getClass() == FieldNode.class) {
+            scopeType = "class";
+            variableType = "field";
+        } else if (expr.getClass() == PropertyNode.class) {
+            scopeType = "class";
+            variableType = "property";
+        }
+
+        StringBuilder msg = new StringBuilder();
+        msg.append("The current ").append(scopeType);
+        msg.append(" already contains a ").append(variableType);
+        msg.append(" of the name ").append(var.getName());
+
+        if (currentScope.getDeclaredVariable(var.getName()) != null) {
+            addError(msg.toString(), expr);
+            return;
+        }
+
+        for (VariableScope scope = currentScope.getParent(); scope != null; scope = scope.getParent()) {
+            // if we are in a class and no variable is declared until
+            // now, then we can break the loop, because we are allowed
+            // to declare a variable of the same name as a class member
+            if (scope.getClassScope() != null) break;
+
+            if (scope.getDeclaredVariable(var.getName()) != null) {
+                // variable already declared
+                addError(msg.toString(), expr);
+                break;
+            }
+        }
+        // declare the variable even if there was an error to allow more checks
+        currentScope.putDeclaredVariable(var);
+    }
+
+    protected SourceUnit getSourceUnit() {
+        return source;
+    }
+
+    private Variable findClassMember(ClassNode cn, String name) {
+        if (cn == null) return null;
+        if (cn.isScript()) {
+            return new DynamicVariable(name, false);
+        }
+
+        for (FieldNode fn : cn.getFields()) {
+            if (fn.getName().equals(name)) return fn;
+        }
+
+        for (MethodNode mn : cn.getMethods()) {
+            String pName = getPropertyName(mn);
+            if (pName != null && pName.equals(name))
+                return new PropertyNode(pName, mn.getModifiers(), ClassHelper.OBJECT_TYPE, cn, null, null, null);
+        }
+
+        for (PropertyNode pn : cn.getProperties()) {
+            if (pn.getName().equals(name)) return pn;
+        }
+
+        Variable ret = findClassMember(cn.getSuperClass(), name);
+        if (ret != null) return ret;
+        return findClassMember(cn.getOuterClass(), name);
+    }
+
+    private static String getPropertyName(MethodNode m) {
+        String name = m.getName();
+        if (!(name.startsWith("set") || name.startsWith("get"))) return null;
+        String pname = name.substring(3);
+        if (pname.length() == 0) return null;
+        pname = java.beans.Introspector.decapitalize(pname);
+
+        if (name.startsWith("get") && (m.getReturnType() == ClassHelper.VOID_TYPE || m.getParameters().length != 0)) {
+            return null;
+        }
+        if (name.startsWith("set") && m.getParameters().length != 1) {
+            return null;
+        }
+        return pname;
+    }
+
+    // -------------------------------
+    // different Variable based checks
+    // -------------------------------
+
+    private Variable checkVariableNameForDeclaration(String name, Expression expression) {
+        if ("super".equals(name) || "this".equals(name)) return null;
+
+        VariableScope scope = currentScope;
+        Variable var = new DynamicVariable(name, currentScope.isInStaticContext());
+        Variable orig = var;
+        // try to find a declaration of a variable
+        boolean crossingStaticContext = false;
+        while (true) {
+            crossingStaticContext = crossingStaticContext || scope.isInStaticContext();
+            Variable var1;
+            var1 = scope.getDeclaredVariable(var.getName());
+
+            if (var1 != null) {
+                var = var1;
+                break;
+            }
+
+            var1 = scope.getReferencedLocalVariable(var.getName());
+            if (var1 != null) {
+                var = var1;
+                break;
+            }
+
+            var1 = scope.getReferencedClassVariable(var.getName());
+            if (var1 != null) {
+                var = var1;
+                break;
+            }
+
+            ClassNode classScope = scope.getClassScope();
+            if (classScope != null) {
+                Variable member = findClassMember(classScope, var.getName());
+                if (member != null) {
+                    boolean staticScope = crossingStaticContext || isSpecialConstructorCall;
+                    boolean staticMember = member.isInStaticContext();
+                    // We don't allow a static context (e.g. a static method) to access
+                    // a non-static variable (e.g. a non-static field).
+                    if (!(staticScope && !staticMember))
+                        var = member;
+                }
+                break;
+            }
+            scope = scope.getParent();
+        }
+        if (var == orig && crossingStaticContext) {
+            var = new DynamicVariable(var.getName(), true);
+        }
+
+        VariableScope end = scope;
+
+        scope = currentScope;
+        while (scope != end) {
+            if (end.isClassScope() ||
+                    (end.isReferencedClassVariable(name) && end.getDeclaredVariable(name) == null)) {
+                scope.putReferencedClassVariable(var);
+            } else {
+                //var.setClosureSharedVariable(var.isClosureSharedVariable() || inClosure);
+                scope.putReferencedLocalVariable(var);
+            }
+            scope = scope.getParent();
+        }
+
+        return var;
+    }
+
+    /**
+     * a property on "this", like this.x is transformed to a
+     * direct field access, so we need to check the
+     * static context here
+     *
+     * @param pe the property expression to check
+     */
+    private void checkPropertyOnExplicitThis(PropertyExpression pe) {
+        if (!currentScope.isInStaticContext()) return;
+        Expression object = pe.getObjectExpression();
+        if (!(object instanceof VariableExpression)) return;
+        VariableExpression ve = (VariableExpression) object;
+        if (!ve.getName().equals("this")) return;
+        String name = pe.getPropertyAsString();
+        if (name == null || name.equals("class")) return;
+        Variable member = findClassMember(currentClass, name);
+        if (member == null) return;
+        checkVariableContextAccess(member, pe);
+    }
+
+    private void checkVariableContextAccess(Variable v, Expression expr) {
+        if (v.isInStaticContext() || !currentScope.isInStaticContext()) return;
+
+        String msg = v.getName() +
+                " is declared in a dynamic context, but you tried to" +
+                " access it from a static context.";
+        addError(msg, expr);
+
+        // declare a static variable to be able to continue the check
+        DynamicVariable v2 = new DynamicVariable(v.getName(), currentScope.isInStaticContext());
+        currentScope.putDeclaredVariable(v2);
+    }
+
+    // ------------------------------
+    // code visit
+    // ------------------------------
+
+    public void visitBlockStatement(BlockStatement block) {
+        pushState();
+        block.setVariableScope(currentScope);
+        super.visitBlockStatement(block);
+        popState();
+    }
+
+    public void visitForLoop(ForStatement forLoop) {
+        pushState();
+        forLoop.setVariableScope(currentScope);
+        Parameter p = forLoop.getVariable();
+        p.setInStaticContext(currentScope.isInStaticContext());
+        if (p != ForStatement.FOR_LOOP_DUMMY) declare(p, forLoop);
+        super.visitForLoop(forLoop);
+        popState();
+    }
+
+    public void visitIfElse(IfStatement ifElse) {
+        ifElse.getBooleanExpression().visit(this);
+        pushState();
+        ifElse.getIfBlock().visit(this);
+        popState();
+        pushState();
+        ifElse.getElseBlock().visit(this);
+        popState();
+    }
+
+    public void visitDeclarationExpression(DeclarationExpression expression) {
+        visitAnnotations(expression);
+        // visit right side first to avoid the usage of a
+        // variable before its declaration
+        expression.getRightExpression().visit(this);
+
+        if (expression.isMultipleAssignmentDeclaration()) {
+            TupleExpression list = expression.getTupleExpression();
+            for (Expression e : list.getExpressions()) {
+                declare((VariableExpression) e);
+            }
+        } else {
+            declare(expression.getVariableExpression());
+        }
+    }
+
+    @Override
+    public void visitBinaryExpression(BinaryExpression be) {
+        super.visitBinaryExpression(be);
+
+        if (Types.isAssignment(be.getOperation().getType())) {
+            checkFinalFieldAccess(be.getLeftExpression());
+        }
+    }
+
+    private void checkFinalFieldAccess(Expression expression) {
+        // currently not looking for PropertyExpression: dealt with at runtime using ReadOnlyPropertyException
+        if (!(expression instanceof VariableExpression) && !(expression instanceof TupleExpression)) return;
+        if (expression instanceof TupleExpression) {
+            TupleExpression list = (TupleExpression) expression;
+            for (Expression e : list.getExpressions()) {
+                checkForFinal(expression, (VariableExpression) e);
+            }
+        } else {
+            checkForFinal(expression, (VariableExpression) expression);
+        }
+    }
+
+    // TODO handle local variables
+    private void checkForFinal(final Expression expression, VariableExpression ve) {
+        Variable v = ve.getAccessedVariable();
+        if (v != null) {
+            boolean isFinal = isFinal(v.getModifiers());
+            boolean isParameter = v instanceof Parameter;
+            if (isFinal && isParameter) {
+                addError("Cannot assign a value to final variable '" + v.getName() + "'", expression);
+            }
+        }
+    }
+
+    public void visitVariableExpression(VariableExpression expression) {
+        String name = expression.getName();
+        Variable v = checkVariableNameForDeclaration(name, expression);
+        if (v == null) return;
+        expression.setAccessedVariable(v);
+        checkVariableContextAccess(v, expression);
+    }
+
+    public void visitPropertyExpression(PropertyExpression expression) {
+        expression.getObjectExpression().visit(this);
+        expression.getProperty().visit(this);
+        checkPropertyOnExplicitThis(expression);
+    }
+
+    public void visitClosureExpression(ClosureExpression expression) {
+        pushState();
+
+        expression.setVariableScope(currentScope);
+
+        if (expression.isParameterSpecified()) {
+            Parameter[] parameters = expression.getParameters();
+            for (Parameter parameter : parameters) {
+                parameter.setInStaticContext(currentScope.isInStaticContext());
+                if (parameter.hasInitialExpression()) {
+                    parameter.getInitialExpression().visit(this);
+                }
+                declare(parameter, expression);
+            }
+        } else if (expression.getParameters() != null) {
+            Parameter var = new Parameter(ClassHelper.OBJECT_TYPE, "it");
+            var.setInStaticContext(currentScope.isInStaticContext());
+            currentScope.putDeclaredVariable(var);
+        }
+
+        super.visitClosureExpression(expression);
+        markClosureSharedVariables();
+
+        popState();
+    }
+
+    private void markClosureSharedVariables() {
+        VariableScope scope = currentScope;
+        for (Iterator<Variable> it = scope.getReferencedLocalVariablesIterator(); it.hasNext(); ) {
+            it.next().setClosureSharedVariable(true);
+        }
+    }
+
+    public void visitCatchStatement(CatchStatement statement) {
+        pushState();
+        Parameter p = statement.getVariable();
+        p.setInStaticContext(currentScope.isInStaticContext());
+        declare(p, statement);
+        super.visitCatchStatement(statement);
+        popState();
+    }
+
+    public void visitFieldExpression(FieldExpression expression) {
+        String name = expression.getFieldName();
+        //TODO: change that to get the correct scope
+        Variable v = checkVariableNameForDeclaration(name, expression);
+        checkVariableContextAccess(v, expression);
+    }
+
+    // ------------------------------
+    // class visit
+    // ------------------------------
+
+    public void visitClass(ClassNode node) {
+        // AIC are already done, doing them here again will lead to wrong scopes
+        if (node instanceof InnerClassNode) {
+            InnerClassNode in = (InnerClassNode) node;
+            if (in.isAnonymous() && !in.isEnum()) return;
+        }
+
+        pushState();
+
+        prepareVisit(node);
+
+        super.visitClass(node);
+        if (recurseInnerClasses) {
+            Iterator<InnerClassNode> innerClasses = node.getInnerClasses();
+            while (innerClasses.hasNext()) {
+                visitClass(innerClasses.next());
+            }
+        }
+        popState();
+    }
+
+    /**
+     * Setup the current class node context.
+     * @param node
+     */
+    public void prepareVisit(ClassNode node) {
+        currentClass = node;
+        currentScope.setClassScope(node);
+    }
+
+    protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) {
+        pushState(node.isStatic());
+        inConstructor = isConstructor;
+        node.setVariableScope(currentScope);
+        visitAnnotations(node);
+        
+        // GROOVY-2156
+        Parameter[] parameters = node.getParameters();
+        for (Parameter parameter : parameters) {
+            visitAnnotations(parameter);
+        }
+
+        declare(node.getParameters(), node);
+        visitClassCodeContainer(node.getCode());
+
+        popState();
+    }
+
+    public void visitMethodCallExpression(MethodCallExpression call) {
+        if (call.isImplicitThis() && call.getMethod() instanceof ConstantExpression) {
+            ConstantExpression methodNameConstant = (ConstantExpression) call.getMethod();
+            Object value = methodNameConstant.getText();
+
+            if (!(value instanceof String)) {
+                throw new GroovyBugError("tried to make a method call with a non-String constant method name.");
+            }
+
+            String methodName = (String) value;
+            Variable v = checkVariableNameForDeclaration(methodName, call);
+            if (v != null && !(v instanceof DynamicVariable)) {
+                checkVariableContextAccess(v, call);
+            }
+
+            if (v instanceof VariableExpression || v instanceof Parameter) {
+                VariableExpression object = new VariableExpression(v);
+                object.setSourcePosition(methodNameConstant);
+                call.setObjectExpression(object);
+                ConstantExpression method = new ConstantExpression("call");
+                method.setSourcePosition(methodNameConstant); // important for GROOVY-4344
+                call.setImplicitThis(false);
+                call.setMethod(method);
+            }
+
+        }
+        super.visitMethodCallExpression(call);
+    }
+
+    public void visitConstructorCallExpression(ConstructorCallExpression call) {
+        isSpecialConstructorCall = call.isSpecialCall();
+        super.visitConstructorCallExpression(call);
+        isSpecialConstructorCall = false;
+        if (!call.isUsingAnonymousInnerClass()) return;
+
+        pushState();
+        InnerClassNode innerClass = (InnerClassNode) call.getType();
+        innerClass.setVariableScope(currentScope);
+        for (MethodNode method : innerClass.getMethods()) {
+            Parameter[] parameters = method.getParameters();
+            if (parameters.length == 0) parameters = null; // null means no implicit "it"
+            ClosureExpression cl = new ClosureExpression(parameters, method.getCode());
+            visitClosureExpression(cl);
+        }
+
+        for (FieldNode field : innerClass.getFields()) {
+            final Expression expression = field.getInitialExpression();
+            pushState(field.isStatic());
+            if (expression != null) {
+                if (expression instanceof VariableExpression) {
+                    VariableExpression vexp = (VariableExpression) expression;
+                    if (vexp.getAccessedVariable() instanceof Parameter) {
+                        // workaround for GROOVY-6834: accessing a parameter which is not yet seen in scope
+                        popState();
+                        continue;
+                    }
+                }
+                expression.visit(this);
+            }
+            popState();
+        }
+
+        for (Statement statement : innerClass.getObjectInitializerStatements()) {
+            statement.visit(this);
+        }
+        markClosureSharedVariables();
+        popState();
+    }
+
+    public void visitProperty(PropertyNode node) {
+        pushState(node.isStatic());
+        super.visitProperty(node);
+        popState();
+    }
+
+    public void visitField(FieldNode node) {
+        pushState(node.isStatic());
+        super.visitField(node);
+        popState();
+    }
+
+    public void visitAnnotations(AnnotatedNode node) {
+        List<AnnotationNode> annotations = node.getAnnotations();
+        if (annotations.isEmpty()) return;
+        for (AnnotationNode an : annotations) {
+            // skip built-in properties
+            if (an.isBuiltIn()) continue;
+            for (Map.Entry<String, Expression> member : an.getMembers().entrySet()) {
+                Expression annMemberValue = member.getValue();
+                annMemberValue.visit(this);
+            }
+        }
+    }
+}