This is an automated email from the ASF dual-hosted git repository. paulk pushed a commit to branch GROOVY_2_5_X in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 6f2131091d80d3394e64cdacd846b4ef4c12f6ca Author: Paul King <pa...@asert.com.au> AuthorDate: Fri Feb 1 14:43:30 2019 +1000 GROOVY-8959: Avoid NPE in StaticVerifier --- .../codehaus/groovy/control/StaticVerifier.java | 407 ++++++++++----------- 1 file changed, 203 insertions(+), 204 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/control/StaticVerifier.java b/src/main/java/org/codehaus/groovy/control/StaticVerifier.java index 6bf3908..a0442a4 100644 --- a/src/main/java/org/codehaus/groovy/control/StaticVerifier.java +++ b/src/main/java/org/codehaus/groovy/control/StaticVerifier.java @@ -1,204 +1,203 @@ -/* - * 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.control; - -import org.codehaus.groovy.ast.ClassCodeVisitorSupport; -import org.codehaus.groovy.ast.ClassNode; -import org.codehaus.groovy.ast.CodeVisitorSupport; -import org.codehaus.groovy.ast.DynamicVariable; -import org.codehaus.groovy.ast.FieldNode; -import org.codehaus.groovy.ast.MethodNode; -import org.codehaus.groovy.ast.Parameter; -import org.codehaus.groovy.ast.Variable; -import org.codehaus.groovy.ast.expr.ClosureExpression; -import org.codehaus.groovy.ast.expr.ConstructorCallExpression; -import org.codehaus.groovy.ast.expr.Expression; -import org.codehaus.groovy.ast.expr.MethodCallExpression; -import org.codehaus.groovy.ast.expr.PropertyExpression; -import org.codehaus.groovy.ast.expr.VariableExpression; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static org.apache.groovy.ast.tools.ClassNodeUtils.isInnerClass; - -/** - * Verifier to check non-static access in static contexts - */ -public class StaticVerifier extends ClassCodeVisitorSupport { - private boolean inSpecialConstructorCall; - private boolean inPropertyExpression; // TODO use it or lose it - private boolean inClosure; - private MethodNode currentMethod; - private SourceUnit source; - - public void visitClass(ClassNode node, SourceUnit source) { - this.source = source; - super.visitClass(node); - } - - @Override - public void visitVariableExpression(VariableExpression ve) { - Variable v = ve.getAccessedVariable(); - if (v instanceof DynamicVariable) { - if (!inPropertyExpression || inSpecialConstructorCall) addStaticVariableError(ve); - } - } - - @Override - public void visitClosureExpression(ClosureExpression ce) { - boolean oldInClosure = inClosure; - inClosure = true; - super.visitClosureExpression(ce); - inClosure = oldInClosure; - } - - @Override - public void visitConstructorCallExpression(ConstructorCallExpression cce) { - boolean oldIsSpecialConstructorCall = inSpecialConstructorCall; - inSpecialConstructorCall = cce.isSpecialCall(); - super.visitConstructorCallExpression(cce); - inSpecialConstructorCall = oldIsSpecialConstructorCall; - } - - @Override - public void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { - MethodNode oldCurrentMethod = currentMethod; - currentMethod = node; - super.visitConstructorOrMethod(node, isConstructor); - if (isConstructor) { - final Set<String> exceptions = new HashSet<String>(); - for (final Parameter param : node.getParameters()) { - exceptions.add(param.getName()); - if (param.hasInitialExpression()) { - param.getInitialExpression().visit(new CodeVisitorSupport() { - @Override - public void visitVariableExpression(VariableExpression ve) { - if (exceptions.contains(ve.getName())) return; - Variable av = ve.getAccessedVariable(); - if (av instanceof DynamicVariable || !av.isInStaticContext()) { - addVariableError(ve); - } - } - - @Override - public void visitMethodCallExpression(MethodCallExpression call) { - Expression objectExpression = call.getObjectExpression(); - if (objectExpression instanceof VariableExpression) { - VariableExpression ve = (VariableExpression) objectExpression; - if (ve.isThisExpression()) { - addError("Can't access instance method '" + call.getMethodAsString() + "' for a constructor parameter default value", param); - return; - } - } - super.visitMethodCallExpression(call); - } - - @Override - public void visitClosureExpression(ClosureExpression expression) { - //skip contents, because of dynamic scope - } - }); - } - } - } - currentMethod = oldCurrentMethod; - } - - @Override - public void visitMethodCallExpression(MethodCallExpression mce) { - if (inSpecialConstructorCall && !isInnerClass(currentMethod.getDeclaringClass())) { - Expression objectExpression = mce.getObjectExpression(); - if (objectExpression instanceof VariableExpression) { - VariableExpression ve = (VariableExpression) objectExpression; - if (ve.isThisExpression()) { - addError("Can't access instance method '" + mce.getMethodAsString() + "' before the class is constructed", mce); - return; - } - } - } - super.visitMethodCallExpression(mce); - } - - @Override - public void visitPropertyExpression(PropertyExpression pe) { - if (!inSpecialConstructorCall) checkStaticScope(pe); - } - - @Override - protected SourceUnit getSourceUnit() { - return source; - } - - - private void checkStaticScope(PropertyExpression pe) { - if (inClosure) return; - for (Expression it = pe; it != null; it = ((PropertyExpression) it).getObjectExpression()) { - if (it instanceof PropertyExpression) continue; - if (it instanceof VariableExpression) { - addStaticVariableError((VariableExpression) it); - } - return; - } - } - - private void addStaticVariableError(VariableExpression ve) { - // closures are always dynamic - // propertyExpressions will handle the error a bit differently - if (!inSpecialConstructorCall && (inClosure || !ve.isInStaticContext())) return; - if (ve.isThisExpression() || ve.isSuperExpression()) return; - Variable v = ve.getAccessedVariable(); - if (currentMethod != null && currentMethod.isStatic()) { - FieldNode fieldNode = getDeclaredOrInheritedField(currentMethod.getDeclaringClass(), ve.getName()); - if (fieldNode != null && fieldNode.isStatic()) return; - } - if (v != null && !(v instanceof DynamicVariable) && v.isInStaticContext()) return; - addVariableError(ve); - } - - private void addVariableError(VariableExpression ve) { - addError("Apparent variable '" + ve.getName() + "' was found in a static scope but doesn't refer" + - " to a local variable, static field or class. Possible causes:\n" + - "You attempted to reference a variable in the binding or an instance variable from a static context.\n" + - "You misspelled a classname or statically imported field. Please check the spelling.\n" + - "You attempted to use a method '" + ve.getName() + - "' but left out brackets in a place not allowed by the grammar.", ve); - } - - private static FieldNode getDeclaredOrInheritedField(ClassNode cn, String fieldName) { - ClassNode node = cn; - while (node != null) { - FieldNode fn = node.getDeclaredField(fieldName); - if (fn != null) return fn; - List<ClassNode> interfacesToCheck = new ArrayList<ClassNode>(Arrays.asList(node.getInterfaces())); - while (!interfacesToCheck.isEmpty()) { - ClassNode nextInterface = interfacesToCheck.remove(0); - fn = nextInterface.getDeclaredField(fieldName); - if (fn != null) return fn; - interfacesToCheck.addAll(Arrays.asList(nextInterface.getInterfaces())); - } - node = node.getSuperClass(); - } - return null; - } - -} +/* + * 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.control; + +import org.codehaus.groovy.ast.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.DynamicVariable; +import org.codehaus.groovy.ast.FieldNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.Variable; +import org.codehaus.groovy.ast.expr.ClosureExpression; +import org.codehaus.groovy.ast.expr.ConstructorCallExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.MethodCallExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.apache.groovy.ast.tools.ClassNodeUtils.isInnerClass; + +/** + * Verifier to check non-static access in static contexts + */ +public class StaticVerifier extends ClassCodeVisitorSupport { + private boolean inSpecialConstructorCall; + private boolean inPropertyExpression; // TODO use it or lose it + private boolean inClosure; + private MethodNode currentMethod; + private SourceUnit source; + + public void visitClass(ClassNode node, SourceUnit source) { + this.source = source; + super.visitClass(node); + } + + @Override + public void visitVariableExpression(VariableExpression ve) { + Variable v = ve.getAccessedVariable(); + if (v instanceof DynamicVariable) { + if (!inPropertyExpression || inSpecialConstructorCall) addStaticVariableError(ve); + } + } + + @Override + public void visitClosureExpression(ClosureExpression ce) { + boolean oldInClosure = inClosure; + inClosure = true; + super.visitClosureExpression(ce); + inClosure = oldInClosure; + } + + @Override + public void visitConstructorCallExpression(ConstructorCallExpression cce) { + boolean oldIsSpecialConstructorCall = inSpecialConstructorCall; + inSpecialConstructorCall = cce.isSpecialCall(); + super.visitConstructorCallExpression(cce); + inSpecialConstructorCall = oldIsSpecialConstructorCall; + } + + @Override + public void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { + MethodNode oldCurrentMethod = currentMethod; + currentMethod = node; + super.visitConstructorOrMethod(node, isConstructor); + if (isConstructor) { + final Set<String> exceptions = new HashSet<String>(); + for (final Parameter param : node.getParameters()) { + exceptions.add(param.getName()); + if (param.hasInitialExpression()) { + param.getInitialExpression().visit(new CodeVisitorSupport() { + @Override + public void visitVariableExpression(VariableExpression ve) { + if (exceptions.contains(ve.getName())) return; + if (ve.getAccessedVariable() instanceof DynamicVariable || !ve.isInStaticContext()) { + addVariableError(ve); + } + } + + @Override + public void visitMethodCallExpression(MethodCallExpression call) { + Expression objectExpression = call.getObjectExpression(); + if (objectExpression instanceof VariableExpression) { + VariableExpression ve = (VariableExpression) objectExpression; + if (ve.isThisExpression()) { + addError("Can't access instance method '" + call.getMethodAsString() + "' for a constructor parameter default value", param); + return; + } + } + super.visitMethodCallExpression(call); + } + + @Override + public void visitClosureExpression(ClosureExpression expression) { + //skip contents, because of dynamic scope + } + }); + } + } + } + currentMethod = oldCurrentMethod; + } + + @Override + public void visitMethodCallExpression(MethodCallExpression mce) { + if (inSpecialConstructorCall && !isInnerClass(currentMethod.getDeclaringClass())) { + Expression objectExpression = mce.getObjectExpression(); + if (objectExpression instanceof VariableExpression) { + VariableExpression ve = (VariableExpression) objectExpression; + if (ve.isThisExpression()) { + addError("Can't access instance method '" + mce.getMethodAsString() + "' before the class is constructed", mce); + return; + } + } + } + super.visitMethodCallExpression(mce); + } + + @Override + public void visitPropertyExpression(PropertyExpression pe) { + if (!inSpecialConstructorCall) checkStaticScope(pe); + } + + @Override + protected SourceUnit getSourceUnit() { + return source; + } + + + private void checkStaticScope(PropertyExpression pe) { + if (inClosure) return; + for (Expression it = pe; it != null; it = ((PropertyExpression) it).getObjectExpression()) { + if (it instanceof PropertyExpression) continue; + if (it instanceof VariableExpression) { + addStaticVariableError((VariableExpression) it); + } + return; + } + } + + private void addStaticVariableError(VariableExpression ve) { + // closures are always dynamic + // propertyExpressions will handle the error a bit differently + if (!inSpecialConstructorCall && (inClosure || !ve.isInStaticContext())) return; + if (ve.isThisExpression() || ve.isSuperExpression()) return; + Variable v = ve.getAccessedVariable(); + if (currentMethod != null && currentMethod.isStatic()) { + FieldNode fieldNode = getDeclaredOrInheritedField(currentMethod.getDeclaringClass(), ve.getName()); + if (fieldNode != null && fieldNode.isStatic()) return; + } + if (v != null && !(v instanceof DynamicVariable) && v.isInStaticContext()) return; + addVariableError(ve); + } + + private void addVariableError(VariableExpression ve) { + addError("Apparent variable '" + ve.getName() + "' was found in a static scope but doesn't refer" + + " to a local variable, static field or class. Possible causes:\n" + + "You attempted to reference a variable in the binding or an instance variable from a static context.\n" + + "You misspelled a classname or statically imported field. Please check the spelling.\n" + + "You attempted to use a method '" + ve.getName() + + "' but left out brackets in a place not allowed by the grammar.", ve); + } + + private static FieldNode getDeclaredOrInheritedField(ClassNode cn, String fieldName) { + ClassNode node = cn; + while (node != null) { + FieldNode fn = node.getDeclaredField(fieldName); + if (fn != null) return fn; + List<ClassNode> interfacesToCheck = new ArrayList<ClassNode>(Arrays.asList(node.getInterfaces())); + while (!interfacesToCheck.isEmpty()) { + ClassNode nextInterface = interfacesToCheck.remove(0); + fn = nextInterface.getDeclaredField(fieldName); + if (fn != null) return fn; + interfacesToCheck.addAll(Arrays.asList(nextInterface.getInterfaces())); + } + node = node.getSuperClass(); + } + return null; + } + +}