| /* |
| * 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.apache.groovy.ast.tools.ClassNodeUtils; |
| import org.codehaus.groovy.ast.ASTNode; |
| import org.codehaus.groovy.ast.ClassCodeVisitorSupport; |
| import org.codehaus.groovy.ast.ClassHelper; |
| import org.codehaus.groovy.ast.ClassNode; |
| import org.codehaus.groovy.ast.ConstructorNode; |
| 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.expr.BinaryExpression; |
| import org.codehaus.groovy.ast.expr.ConstantExpression; |
| import org.codehaus.groovy.ast.expr.DeclarationExpression; |
| import org.codehaus.groovy.ast.expr.Expression; |
| import org.codehaus.groovy.ast.expr.GStringExpression; |
| import org.codehaus.groovy.ast.expr.MapEntryExpression; |
| 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.CatchStatement; |
| import org.codehaus.groovy.ast.tools.GeneralUtils; |
| import org.codehaus.groovy.control.SourceUnit; |
| import org.codehaus.groovy.syntax.Types; |
| import org.codehaus.groovy.transform.trait.Traits; |
| |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| |
| import static java.lang.reflect.Modifier.isAbstract; |
| import static java.lang.reflect.Modifier.isFinal; |
| import static java.lang.reflect.Modifier.isInterface; |
| import static java.lang.reflect.Modifier.isNative; |
| import static java.lang.reflect.Modifier.isPrivate; |
| import static java.lang.reflect.Modifier.isStatic; |
| import static java.lang.reflect.Modifier.isStrict; |
| import static java.lang.reflect.Modifier.isSynchronized; |
| import static java.lang.reflect.Modifier.isTransient; |
| import static java.lang.reflect.Modifier.isVolatile; |
| import static org.apache.groovy.util.BeanUtils.capitalize; |
| import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE; |
| import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; |
| import static org.objectweb.asm.Opcodes.ACC_FINAL; |
| import static org.objectweb.asm.Opcodes.ACC_INTERFACE; |
| import static org.objectweb.asm.Opcodes.ACC_NATIVE; |
| import static org.objectweb.asm.Opcodes.ACC_PRIVATE; |
| import static org.objectweb.asm.Opcodes.ACC_PROTECTED; |
| import static org.objectweb.asm.Opcodes.ACC_PUBLIC; |
| import static org.objectweb.asm.Opcodes.ACC_STATIC; |
| import static org.objectweb.asm.Opcodes.ACC_STRICT; |
| import static org.objectweb.asm.Opcodes.ACC_SYNCHRONIZED; |
| import static org.objectweb.asm.Opcodes.ACC_TRANSIENT; |
| import static org.objectweb.asm.Opcodes.ACC_VOLATILE; |
| /** |
| * Checks that a class satisfies various conditions including: |
| * <ul> |
| * <li>Incorrect class or method access modifiers</li> |
| * <li>No abstract methods appear in a non-abstract class</li> |
| * <li>Existence and correct visibility for inherited members</li> |
| * <li>Invalid attempts to override final members</li> |
| * </ul> |
| */ |
| public class ClassCompletionVerifier extends ClassCodeVisitorSupport { |
| private static final String[] INVALID_NAME_CHARS = {".", ":", "/", ";", "[", "<", ">"}; |
| // the groovy.compiler.strictNames system property is experimental and may change default value or be removed in a future version of Groovy |
| private final boolean strictNames = Boolean.parseBoolean(System.getProperty("groovy.compiler.strictNames", "false")); |
| private ClassNode currentClass; |
| private final SourceUnit source; |
| private boolean inConstructor = false; |
| private boolean inStaticConstructor = false; |
| |
| public ClassCompletionVerifier(SourceUnit source) { |
| this.source = source; |
| } |
| |
| public ClassNode getClassNode() { |
| return currentClass; |
| } |
| |
| public void visitClass(ClassNode node) { |
| ClassNode oldClass = currentClass; |
| currentClass = node; |
| checkImplementsAndExtends(node); |
| if (source != null && !source.getErrorCollector().hasErrors()) { |
| checkClassForIncorrectModifiers(node); |
| checkInterfaceMethodVisibility(node); |
| checkAbstractMethodVisibility(node); |
| checkClassForOverwritingFinal(node); |
| checkMethodsForIncorrectModifiers(node); |
| checkMethodsForIncorrectName(node); |
| checkMethodsForWeakerAccess(node); |
| checkMethodsForOverridingFinal(node); |
| checkNoAbstractMethodsNonabstractClass(node); |
| checkClassExtendsAllSelfTypes(node); |
| checkNoStaticMethodWithSameSignatureAsNonStatic(node); |
| checkGenericsUsage(node, node.getUnresolvedInterfaces()); |
| checkGenericsUsage(node, node.getUnresolvedSuperClass()); |
| } |
| super.visitClass(node); |
| currentClass = oldClass; |
| } |
| |
| private void checkNoStaticMethodWithSameSignatureAsNonStatic(final ClassNode node) { |
| ClassNode parent = node.getSuperClass(); |
| Map<String, MethodNode> result; |
| // start with methods from the parent if any |
| if (parent != null) { |
| result = parent.getDeclaredMethodsMap(); |
| } else { |
| result = new HashMap<String, MethodNode>(); |
| } |
| // add in unimplemented abstract methods from the interfaces |
| ClassNodeUtils.addDeclaredMethodsFromInterfaces(node, result); |
| for (MethodNode methodNode : node.getMethods()) { |
| MethodNode mn = result.get(methodNode.getTypeDescriptor()); |
| if (mn != null && (mn.isStatic() ^ methodNode.isStatic()) && !methodNode.isStaticConstructor()) { |
| if (!mn.isAbstract()) continue; |
| ClassNode declaringClass = mn.getDeclaringClass(); |
| ClassNode cn = declaringClass.getOuterClass(); |
| if (cn == null && declaringClass.isResolved()) { |
| // in case of a precompiled class, the outerclass is unknown |
| Class typeClass = declaringClass.getTypeClass(); |
| typeClass = typeClass.getEnclosingClass(); |
| if (typeClass != null) { |
| cn = ClassHelper.make(typeClass); |
| } |
| } |
| if (!Traits.isTrait(cn)) { |
| ASTNode errorNode = methodNode; |
| String name = mn.getName(); |
| if (errorNode.getLineNumber() == -1) { |
| // try to get a better error message location based on the property |
| for (PropertyNode propertyNode : node.getProperties()) { |
| if (name.startsWith("set") || name.startsWith("get") || name.startsWith("is")) { |
| String propName = Verifier.capitalize(propertyNode.getField().getName()); |
| String shortName = name.substring(name.startsWith("is") ? 2 : 3); |
| if (propName.equals(shortName)) { |
| errorNode = propertyNode; |
| break; |
| } |
| } |
| } |
| } |
| addError("The " + getDescription(methodNode) + " is already defined in " + getDescription(node) + |
| ". You cannot have both a static and an instance method with the same signature", errorNode); |
| } |
| } |
| result.put(methodNode.getTypeDescriptor(), methodNode); |
| } |
| } |
| |
| private void checkInterfaceMethodVisibility(ClassNode node) { |
| if (!node.isInterface()) return; |
| for (MethodNode method : node.getMethods()) { |
| if (method.isPrivate()) { |
| addError("Method '" + method.getName() + "' is private but should be public in " + getDescription(currentClass) + ".", method); |
| } else if (method.isProtected()) { |
| addError("Method '" + method.getName() + "' is protected but should be public in " + getDescription(currentClass) + ".", method); |
| } |
| } |
| } |
| |
| private void checkAbstractMethodVisibility(ClassNode node) { |
| // we only do check abstract classes (including enums), no interfaces or non-abstract classes |
| if (!isAbstract(node.getModifiers()) || isInterface(node.getModifiers())) return; |
| |
| for (MethodNode method : node.getAbstractMethods()) { |
| if (method.isPrivate()) { |
| addError("Method '" + method.getName() + "' from " + getDescription(node) + |
| " must not be private as it is declared as an abstract method.", method); |
| } |
| } |
| } |
| |
| private void checkNoAbstractMethodsNonabstractClass(ClassNode node) { |
| if (isAbstract(node.getModifiers())) return; |
| for (MethodNode method : node.getAbstractMethods()) { |
| MethodNode sameArgsMethod = node.getMethod(method.getName(), method.getParameters()); |
| if (null == sameArgsMethod) { |
| sameArgsMethod = ClassHelper.GROOVY_OBJECT_TYPE.getMethod(method.getName(), method.getParameters()); |
| if (null != sameArgsMethod && !sameArgsMethod.isAbstract() && method.getReturnType().equals(sameArgsMethod.getReturnType())) { |
| return; |
| } |
| } |
| |
| if (sameArgsMethod==null || method.getReturnType().equals(sameArgsMethod.getReturnType())) { |
| addError("Can't have an abstract method in a non-abstract class." + |
| " The " + getDescription(node) + " must be declared abstract or" + |
| " the " + getDescription(method) + " must be implemented.", node); |
| } else { |
| addError("Abstract "+getDescription(method)+" is not implemented but a " + |
| "method of the same name but different return type is defined: "+ |
| (sameArgsMethod.isStatic()?"static ":"")+ |
| getDescription(sameArgsMethod), method |
| ); |
| } |
| } |
| } |
| |
| private void checkClassExtendsAllSelfTypes(ClassNode node) { |
| int modifiers = node.getModifiers(); |
| if (!isInterface(modifiers)) { |
| for (ClassNode anInterface : GeneralUtils.getInterfacesAndSuperInterfaces(node)) { |
| if (Traits.isTrait(anInterface)) { |
| LinkedHashSet<ClassNode> selfTypes = new LinkedHashSet<ClassNode>(); |
| for (ClassNode type : Traits.collectSelfTypes(anInterface, selfTypes, true, false)) { |
| if (type.isInterface() && !node.implementsInterface(type)) { |
| addError(getDescription(node) |
| + " implements " + getDescription(anInterface) |
| + " but does not implement self type " + getDescription(type), |
| anInterface); |
| } else if (!type.isInterface() && !node.isDerivedFrom(type)) { |
| addError(getDescription(node) |
| + " implements " + getDescription(anInterface) |
| + " but does not extend self type " + getDescription(type), |
| anInterface); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void checkClassForIncorrectModifiers(ClassNode node) { |
| checkClassForAbstractAndFinal(node); |
| checkClassForOtherModifiers(node); |
| } |
| |
| private void checkClassForAbstractAndFinal(ClassNode node) { |
| if (!isAbstract(node.getModifiers())) return; |
| if (!isFinal(node.getModifiers())) return; |
| if (node.isInterface()) { |
| addError("The " + getDescription(node) + " must not be final. It is by definition abstract.", node); |
| } else { |
| addError("The " + getDescription(node) + " must not be both final and abstract.", node); |
| } |
| } |
| |
| private void checkClassForOtherModifiers(ClassNode node) { |
| checkClassForModifier(node, isTransient(node.getModifiers()), "transient"); |
| checkClassForModifier(node, isVolatile(node.getModifiers()), "volatile"); |
| checkClassForModifier(node, isNative(node.getModifiers()), "native"); |
| if (!(node instanceof InnerClassNode)) { |
| checkClassForModifier(node, isStatic(node.getModifiers()), "static"); |
| checkClassForModifier(node, isPrivate(node.getModifiers()), "private"); |
| } |
| // don't check synchronized here as it overlaps with ACC_SUPER |
| } |
| |
| private void checkMethodForModifier(MethodNode node, boolean condition, String modifierName) { |
| if (!condition) return; |
| addError("The " + getDescription(node) + " has an incorrect modifier " + modifierName + ".", node); |
| } |
| |
| private void checkClassForModifier(ClassNode node, boolean condition, String modifierName) { |
| if (!condition) return; |
| addError("The " + getDescription(node) + " has an incorrect modifier " + modifierName + ".", node); |
| } |
| |
| private static String getDescription(ClassNode node) { |
| return (node.isInterface() ? (Traits.isTrait(node)?"trait":"interface") : "class") + " '" + node.getName() + "'"; |
| } |
| |
| private static String getDescription(MethodNode node) { |
| return "method '" + node.getTypeDescriptor() + "'"; |
| } |
| |
| private static String getDescription(FieldNode node) { |
| return "field '" + node.getName() + "'"; |
| } |
| |
| private static String getDescription(Parameter node) { |
| return "parameter '" + node.getName() + "'"; |
| } |
| |
| private void checkAbstractDeclaration(MethodNode methodNode) { |
| if (!methodNode.isAbstract()) return; |
| if (isAbstract(currentClass.getModifiers())) return; |
| addError("Can't have an abstract method in a non-abstract class." + |
| " The " + getDescription(currentClass) + " must be declared abstract or the method '" + |
| methodNode.getTypeDescriptor() + "' must not be abstract.", methodNode); |
| } |
| |
| private void checkClassForOverwritingFinal(ClassNode cn) { |
| ClassNode superCN = cn.getSuperClass(); |
| if (superCN == null) return; |
| if (!isFinal(superCN.getModifiers())) return; |
| String msg = "You are not allowed to overwrite the final " + getDescription(superCN) + "."; |
| addError(msg, cn); |
| } |
| |
| private void checkImplementsAndExtends(ClassNode node) { |
| ClassNode cn = node.getSuperClass(); |
| if (cn.isInterface() && !node.isInterface()) { |
| addError("You are not allowed to extend the " + getDescription(cn) + ", use implements instead.", node); |
| } |
| for (ClassNode anInterface : node.getInterfaces()) { |
| cn = anInterface; |
| if (!cn.isInterface()) { |
| addError("You are not allowed to implement the " + getDescription(cn) + ", use extends instead.", node); |
| } |
| } |
| } |
| |
| private void checkMethodsForIncorrectName(ClassNode cn) { |
| if (!strictNames) return; |
| List<MethodNode> methods = cn.getAllDeclaredMethods(); |
| for (MethodNode mNode : methods) { |
| String name = mNode.getName(); |
| if (name.equals("<init>") || name.equals("<clinit>")) continue; |
| // Groovy allows more characters than Character.isValidJavaIdentifier() would allow |
| // if we find a good way to encode special chars we could remove (some of) these checks |
| for (String ch : INVALID_NAME_CHARS) { |
| if (name.contains(ch)) { |
| addError("You are not allowed to have '" + ch + "' in a method name", mNode); |
| } |
| } |
| } |
| } |
| |
| private void checkMethodsForIncorrectModifiers(ClassNode cn) { |
| if (!cn.isInterface()) return; |
| for (MethodNode method : cn.getMethods()) { |
| if (method.isFinal()) { |
| addError("The " + getDescription(method) + " from " + getDescription(cn) + |
| " must not be final. It is by definition abstract.", method); |
| } |
| if (method.isStatic() && !isConstructor(method)) { |
| addError("The " + getDescription(method) + " from " + getDescription(cn) + |
| " must not be static. Only fields may be static in an interface.", method); |
| } |
| } |
| } |
| |
| private void checkMethodsForWeakerAccess(ClassNode cn) { |
| for (MethodNode method : cn.getMethods()) { |
| checkMethodForWeakerAccessPrivileges(method, cn); |
| } |
| } |
| |
| private static boolean isConstructor(MethodNode method) { |
| return method.getName().equals("<clinit>"); |
| } |
| |
| private void checkMethodsForOverridingFinal(ClassNode cn) { |
| for (MethodNode method : cn.getMethods()) { |
| Parameter[] params = method.getParameters(); |
| for (MethodNode superMethod : cn.getSuperClass().getMethods(method.getName())) { |
| Parameter[] superParams = superMethod.getParameters(); |
| if (!hasEqualParameterTypes(params, superParams)) continue; |
| if (!superMethod.isFinal()) break; |
| addInvalidUseOfFinalError(method, params, superMethod.getDeclaringClass()); |
| return; |
| } |
| } |
| } |
| |
| private void addInvalidUseOfFinalError(MethodNode method, Parameter[] parameters, ClassNode superCN) { |
| StringBuilder msg = new StringBuilder(); |
| msg.append("You are not allowed to override the final method ").append(method.getName()); |
| appendParamsDescription(parameters, msg); |
| msg.append(" from ").append(getDescription(superCN)); |
| msg.append("."); |
| addError(msg.toString(), method); |
| } |
| |
| private void appendParamsDescription(Parameter[] parameters, StringBuilder msg) { |
| msg.append("("); |
| boolean needsComma = false; |
| for (Parameter parameter : parameters) { |
| if (needsComma) { |
| msg.append(","); |
| } else { |
| needsComma = true; |
| } |
| msg.append(parameter.getType()); |
| } |
| msg.append(")"); |
| } |
| |
| private void addWeakerAccessError(ClassNode cn, MethodNode method, Parameter[] parameters, MethodNode superMethod) { |
| StringBuilder msg = new StringBuilder(); |
| msg.append(method.getName()); |
| appendParamsDescription(parameters, msg); |
| msg.append(" in "); |
| msg.append(cn.getName()); |
| msg.append(" cannot override "); |
| msg.append(superMethod.getName()); |
| msg.append(" in "); |
| msg.append(superMethod.getDeclaringClass().getName()); |
| msg.append("; attempting to assign weaker access privileges; was "); |
| msg.append(superMethod.isPublic() ? "public" : (superMethod.isProtected() ? "protected" : "package-private")); |
| addError(msg.toString(), method); |
| } |
| |
| private static boolean hasEqualParameterTypes(Parameter[] first, Parameter[] second) { |
| if (first.length != second.length) return false; |
| for (int i = 0; i < first.length; i++) { |
| String ft = first[i].getType().getName(); |
| String st = second[i].getType().getName(); |
| if (ft.equals(st)) continue; |
| return false; |
| } |
| return true; |
| } |
| |
| protected SourceUnit getSourceUnit() { |
| return source; |
| } |
| |
| public void visitMethod(MethodNode node) { |
| inConstructor = false; |
| inStaticConstructor = node.isStaticConstructor(); |
| checkAbstractDeclaration(node); |
| checkRepetitiveMethod(node); |
| checkOverloadingPrivateAndPublic(node); |
| checkMethodModifiers(node); |
| checkGenericsUsage(node, node.getParameters()); |
| checkGenericsUsage(node, node.getReturnType()); |
| for (Parameter param : node.getParameters()) { |
| if (param.getType().equals(VOID_TYPE)) { |
| addError("The " + getDescription(param) + " in " + getDescription(node) + " has invalid type void", param); |
| } |
| } |
| super.visitMethod(node); |
| } |
| |
| private void checkMethodModifiers(MethodNode node) { |
| // don't check volatile here as it overlaps with ACC_BRIDGE |
| // additional modifiers not allowed for interfaces |
| if ((this.currentClass.getModifiers() & ACC_INTERFACE) != 0) { |
| checkMethodForModifier(node, isStrict(node.getModifiers()), "strictfp"); |
| checkMethodForModifier(node, isSynchronized(node.getModifiers()), "synchronized"); |
| checkMethodForModifier(node, isNative(node.getModifiers()), "native"); |
| } |
| } |
| |
| private void checkMethodForWeakerAccessPrivileges(MethodNode mn, ClassNode cn) { |
| if (mn.isPublic()) return; |
| Parameter[] params = mn.getParameters(); |
| for (MethodNode superMethod : cn.getSuperClass().getMethods(mn.getName())) { |
| Parameter[] superParams = superMethod.getParameters(); |
| if (!hasEqualParameterTypes(params, superParams)) continue; |
| if ((mn.isPrivate() && !superMethod.isPrivate()) |
| || (mn.isProtected() && !superMethod.isProtected() && !superMethod.isPackageScope() && !superMethod.isPrivate()) |
| || (!mn.isPrivate() && !mn.isProtected() && !mn.isPublic() && (superMethod.isPublic() || superMethod.isProtected()))) { |
| addWeakerAccessError(cn, mn, params, superMethod); |
| return; |
| } |
| } |
| } |
| |
| private void checkOverloadingPrivateAndPublic(MethodNode node) { |
| if (isConstructor(node)) return; |
| boolean hasPrivate = node.isPrivate(); |
| boolean hasPublic = node.isPublic(); |
| for (MethodNode method : currentClass.getMethods(node.getName())) { |
| if (method == node) continue; |
| if (!method.getDeclaringClass().equals(node.getDeclaringClass())) continue; |
| if (method.isPublic() || method.isProtected()) { |
| hasPublic = true; |
| } else { |
| hasPrivate = true; |
| } |
| if (hasPrivate && hasPublic) break; |
| } |
| if (hasPrivate && hasPublic) { |
| addError("Mixing private and public/protected methods of the same name causes multimethods to be disabled and is forbidden to avoid surprising behaviour. Renaming the private methods will solve the problem.", node); |
| } |
| } |
| |
| private void checkRepetitiveMethod(MethodNode node) { |
| if (isConstructor(node)) return; |
| for (MethodNode method : currentClass.getMethods(node.getName())) { |
| if (method == node) continue; |
| if (!method.getDeclaringClass().equals(node.getDeclaringClass())) continue; |
| Parameter[] p1 = node.getParameters(); |
| Parameter[] p2 = method.getParameters(); |
| if (p1.length != p2.length) continue; |
| addErrorIfParamsAndReturnTypeEqual(p2, p1, node, method); |
| } |
| } |
| |
| private void addErrorIfParamsAndReturnTypeEqual(Parameter[] p2, Parameter[] p1, |
| MethodNode node, MethodNode element) { |
| boolean isEqual = true; |
| for (int i = 0; i < p2.length; i++) { |
| isEqual &= p1[i].getType().equals(p2[i].getType()); |
| if (!isEqual) break; |
| } |
| isEqual &= node.getReturnType().equals(element.getReturnType()); |
| if (isEqual) { |
| addError("Repetitive method name/signature for " + getDescription(node) + |
| " in " + getDescription(currentClass) + ".", node); |
| } |
| } |
| |
| public void visitField(FieldNode node) { |
| if (currentClass.getDeclaredField(node.getName()) != node) { |
| addError("The " + getDescription(node) + " is declared multiple times.", node); |
| } |
| checkInterfaceFieldModifiers(node); |
| checkGenericsUsage(node, node.getType()); |
| if (node.getType().equals(VOID_TYPE)) { |
| addError("The " + getDescription(node) + " has invalid type void", node); |
| } |
| super.visitField(node); |
| } |
| |
| public void visitProperty(PropertyNode node) { |
| checkDuplicateProperties(node); |
| checkGenericsUsage(node, node.getType()); |
| super.visitProperty(node); |
| } |
| |
| private void checkDuplicateProperties(PropertyNode node) { |
| ClassNode cn = node.getDeclaringClass(); |
| String name = node.getName(); |
| String getterName = "get" + capitalize(name); |
| if (Character.isUpperCase(name.charAt(0))) { |
| for (PropertyNode propNode : cn.getProperties()) { |
| String otherName = propNode.getField().getName(); |
| String otherGetterName = "get" + capitalize(otherName); |
| if (node != propNode && getterName.equals(otherGetterName)) { |
| String msg = "The field " + name + " and " + otherName + " on the class " + |
| cn.getName() + " will result in duplicate JavaBean properties, which is not allowed"; |
| addError(msg, node); |
| } |
| } |
| } |
| } |
| |
| private void checkInterfaceFieldModifiers(FieldNode node) { |
| if (!currentClass.isInterface()) return; |
| if ((node.getModifiers() & (ACC_PUBLIC | ACC_STATIC | ACC_FINAL)) == 0 || |
| (node.getModifiers() & (ACC_PRIVATE | ACC_PROTECTED)) != 0) { |
| addError("The " + getDescription(node) + " is not 'public static final' but is defined in " + |
| getDescription(currentClass) + ".", node); |
| } |
| } |
| |
| public void visitBinaryExpression(BinaryExpression expression) { |
| if (expression.getOperation().getType() == Types.LEFT_SQUARE_BRACKET && |
| expression.getRightExpression() instanceof MapEntryExpression) { |
| addError("You tried to use a map entry for an index operation, this is not allowed. " + |
| "Maybe something should be set in parentheses or a comma is missing?", |
| expression.getRightExpression()); |
| } |
| super.visitBinaryExpression(expression); |
| |
| if (Types.isAssignment(expression.getOperation().getType())) { |
| checkFinalFieldAccess(expression.getLeftExpression()); |
| checkSuperOrThisOnLHS(expression.getLeftExpression()); |
| } |
| } |
| |
| private void checkSuperOrThisOnLHS(Expression expression) { |
| if (!(expression instanceof VariableExpression)) return; |
| VariableExpression ve = (VariableExpression) expression; |
| if (ve.isThisExpression()) { |
| addError("cannot have 'this' as LHS of an assignment", expression); |
| } else if (ve.isSuperExpression()) { |
| addError("cannot have 'super' as LHS of an assignment", expression); |
| } |
| } |
| |
| private void checkFinalFieldAccess(Expression expression) { |
| if (!(expression instanceof VariableExpression) && !(expression instanceof PropertyExpression)) return; |
| Variable v = null; |
| if (expression instanceof VariableExpression) { |
| VariableExpression ve = (VariableExpression) expression; |
| v = ve.getAccessedVariable(); |
| } else { |
| PropertyExpression propExp = ((PropertyExpression) expression); |
| Expression objectExpression = propExp.getObjectExpression(); |
| if (objectExpression instanceof VariableExpression) { |
| VariableExpression varExp = (VariableExpression) objectExpression; |
| if (varExp.isThisExpression()) { |
| v = currentClass.getDeclaredField(propExp.getPropertyAsString()); |
| } |
| } |
| } |
| if (v instanceof FieldNode) { |
| FieldNode fn = (FieldNode) v; |
| |
| /* |
| * if it is static final but not accessed inside a static constructor, or, |
| * if it is an instance final but not accessed inside a instance constructor, it is an error |
| */ |
| boolean isFinal = fn.isFinal(); |
| boolean isStatic = fn.isStatic(); |
| boolean error = isFinal && ((isStatic && !inStaticConstructor) || (!isStatic && !inConstructor)); |
| |
| if (error) addError("cannot modify" + (isStatic ? " static" : "") + " final field '" + fn.getName() + |
| "' outside of " + (isStatic ? "static initialization block." : "constructor."), expression); |
| } |
| } |
| |
| public void visitConstructor(ConstructorNode node) { |
| inConstructor = true; |
| inStaticConstructor = node.isStaticConstructor(); |
| checkGenericsUsage(node, node.getParameters()); |
| super.visitConstructor(node); |
| } |
| |
| public void visitCatchStatement(CatchStatement cs) { |
| if (!(cs.getExceptionType().isDerivedFrom(ClassHelper.make(Throwable.class)))) { |
| addError("Catch statement parameter type is not a subclass of Throwable.", cs); |
| } |
| super.visitCatchStatement(cs); |
| } |
| |
| public void visitMethodCallExpression(MethodCallExpression mce) { |
| super.visitMethodCallExpression(mce); |
| Expression aexp = mce.getArguments(); |
| if (aexp instanceof TupleExpression) { |
| TupleExpression arguments = (TupleExpression) aexp; |
| for (Expression e : arguments.getExpressions()) { |
| checkForInvalidDeclaration(e); |
| } |
| } else { |
| checkForInvalidDeclaration(aexp); |
| } |
| } |
| |
| @Override |
| public void visitDeclarationExpression(DeclarationExpression expression) { |
| super.visitDeclarationExpression(expression); |
| if (expression.isMultipleAssignmentDeclaration()) return; |
| checkInvalidDeclarationModifier(expression, ACC_ABSTRACT, "abstract"); |
| checkInvalidDeclarationModifier(expression, ACC_NATIVE, "native"); |
| checkInvalidDeclarationModifier(expression, ACC_PRIVATE, "private"); |
| checkInvalidDeclarationModifier(expression, ACC_PROTECTED, "protected"); |
| checkInvalidDeclarationModifier(expression, ACC_PUBLIC, "public"); |
| checkInvalidDeclarationModifier(expression, ACC_STATIC, "static"); |
| checkInvalidDeclarationModifier(expression, ACC_STRICT, "strictfp"); |
| checkInvalidDeclarationModifier(expression, ACC_SYNCHRONIZED, "synchronized"); |
| checkInvalidDeclarationModifier(expression, ACC_TRANSIENT, "transient"); |
| checkInvalidDeclarationModifier(expression, ACC_VOLATILE, "volatile"); |
| if (expression.getVariableExpression().getOriginType().equals(VOID_TYPE)) { |
| addError("The variable '" + expression.getVariableExpression().getName() + "' has invalid type void", expression); |
| } |
| } |
| |
| private void checkInvalidDeclarationModifier(DeclarationExpression expression, int modifier, String modName) { |
| if ((expression.getVariableExpression().getModifiers() & modifier) != 0) { |
| addError("Modifier '" + modName + "' not allowed here.", expression); |
| } |
| } |
| |
| private void checkForInvalidDeclaration(Expression exp) { |
| if (!(exp instanceof DeclarationExpression)) return; |
| addError("Invalid use of declaration inside method call.", exp); |
| } |
| |
| public void visitConstantExpression(ConstantExpression expression) { |
| super.visitConstantExpression(expression); |
| checkStringExceedingMaximumLength(expression); |
| } |
| |
| public void visitGStringExpression(GStringExpression expression) { |
| super.visitGStringExpression(expression); |
| for (ConstantExpression ce : expression.getStrings()) { |
| checkStringExceedingMaximumLength(ce); |
| } |
| } |
| |
| private void checkStringExceedingMaximumLength(ConstantExpression expression) { |
| Object value = expression.getValue(); |
| if (value instanceof String) { |
| String s = (String) value; |
| if (s.length() > 65535) { |
| addError("String too long. The given string is " + s.length() + " Unicode code units long, but only a maximum of 65535 is allowed.", expression); |
| } |
| } |
| } |
| |
| private void checkGenericsUsage(ASTNode ref, ClassNode[] nodes) { |
| for (ClassNode node : nodes) { |
| checkGenericsUsage(ref, node); |
| } |
| } |
| |
| private void checkGenericsUsage(ASTNode ref, Parameter[] params) { |
| for (Parameter p : params) { |
| checkGenericsUsage(ref, p.getType()); |
| } |
| } |
| |
| private void checkGenericsUsage(ASTNode ref, ClassNode node) { |
| if (node.isArray()) { |
| checkGenericsUsage(ref, node.getComponentType()); |
| } else if (!node.isRedirectNode() && node.isUsingGenerics()) { |
| addError( |
| "A transform used a generics containing ClassNode "+ node + " " + |
| "for "+getRefDescriptor(ref) + |
| "directly. You are not supposed to do this. " + |
| "Please create a new ClassNode referring to the old ClassNode " + |
| "and use the new ClassNode instead of the old one. Otherwise " + |
| "the compiler will create wrong descriptors and a potential " + |
| "NullPointerException in TypeResolver in the OpenJDK. If this is " + |
| "not your own doing, please report this bug to the writer of the " + |
| "transform.", |
| ref); |
| } |
| } |
| |
| private static String getRefDescriptor(ASTNode ref) { |
| if (ref instanceof FieldNode) { |
| FieldNode f = (FieldNode) ref; |
| return "the field "+f.getName()+" "; |
| } else if (ref instanceof PropertyNode) { |
| PropertyNode p = (PropertyNode) ref; |
| return "the property "+p.getName()+" "; |
| } else if (ref instanceof ConstructorNode) { |
| return "the constructor "+ref.getText()+" "; |
| } else if (ref instanceof MethodNode) { |
| return "the method "+ref.getText()+" "; |
| } else if (ref instanceof ClassNode) { |
| return "the super class "+ref+" "; |
| } |
| return "<unknown with class "+ref.getClass()+"> "; |
| } |
| |
| } |