/* * FindBugs - Find Bugs in Java programs * Copyright (C) 2006, University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.ba; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.bcel.generic.ArrayType; import org.apache.bcel.generic.BasicType; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.ReferenceType; import org.apache.bcel.generic.Type; import edu.umd.cs.findbugs.Priorities; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.ba.ch.Subtypes2; import edu.umd.cs.findbugs.ba.generic.GenericObjectType; import edu.umd.cs.findbugs.classfile.CheckedAnalysisException; import edu.umd.cs.findbugs.classfile.ClassDescriptor; import edu.umd.cs.findbugs.classfile.DescriptorFactory; import edu.umd.cs.findbugs.classfile.Global; import edu.umd.cs.findbugs.classfile.IAnalysisCache; public class IncompatibleTypes { private static final ObjectType GWT_JAVASCRIPTOBJECT_TYPE = ObjectTypeFactory.getInstance("com.google.gwt.core.client.JavaScriptObject"); private static final ObjectType COLLECTION_TYPE = ObjectTypeFactory.getInstance("java.util.Collection"); private static final ObjectType MAP_TYPE = ObjectTypeFactory.getInstance("java.util.Map"); private static final ClassDescriptor LIST_DESCRIPTOR = DescriptorFactory.createClassDescriptor(List.class); private static final ClassDescriptor MAP_DESCRIPTOR = DescriptorFactory.createClassDescriptor(Map.class); private static final ClassDescriptor SET_DESCRIPTOR = DescriptorFactory.createClassDescriptor(Set.class); final int priority; final String msg; private IncompatibleTypes(String msg, int priority) { this.msg = msg; this.priority = priority; } public int getPriority() { return priority; } public String getMsg() { return msg; } @Override public String toString() { return msg; } public static final IncompatibleTypes SEEMS_OK = new IncompatibleTypes("Seems OK", Priorities.IGNORE_PRIORITY); public static final IncompatibleTypes ARRAY_AND_NON_ARRAY = new IncompatibleTypes("Array and non array", Priorities.HIGH_PRIORITY); public static final IncompatibleTypes PRIMATIVE_ARRAY_AND_OTHER_ARRAY = new IncompatibleTypes("Primitive array and a non-primitive array", Priorities.HIGH_PRIORITY); public static final IncompatibleTypes INCOMPATIBLE_PRIMATIVE_ARRAYS = new IncompatibleTypes("Incompatible primitive arrays", Priorities.HIGH_PRIORITY); public static final IncompatibleTypes UNCHECKED = new IncompatibleTypes("Actual compile type time of argument is Object, unchecked", Priorities.IGNORE_PRIORITY); public static final IncompatibleTypes ARRAY_AND_OBJECT = new IncompatibleTypes("Array and Object", Priorities.IGNORE_PRIORITY); public static final IncompatibleTypes INCOMPATIBLE_CLASSES = new IncompatibleTypes("Incompatible classes", Priorities.HIGH_PRIORITY); public static final IncompatibleTypes UNRELATED_CLASS_AND_INTERFACE = new IncompatibleTypes("Unrelated class and interface", Priorities.NORMAL_PRIORITY); public static final IncompatibleTypes UNRELATED_FINAL_CLASS_AND_INTERFACE = new IncompatibleTypes( "Unrelated final class and interface", Priorities.HIGH_PRIORITY); public static final IncompatibleTypes UNRELATED_INTERFACES = new IncompatibleTypes("Unrelated interfaces", Priorities.NORMAL_PRIORITY); public static final IncompatibleTypes UNRELATED_UTIL_INTERFACE = new IncompatibleTypes("Unrelated java.util interface", Priorities.HIGH_PRIORITY); public static final IncompatibleTypes UNRELATED_TYPES_BUT_MATCHES_TYPE_PARAMETER = new IncompatibleTypes("Unrelated types but one type matches type parameter of the other", Priorities.HIGH_PRIORITY); static public @NonNull IncompatibleTypes getPriorityForAssumingCompatible(GenericObjectType genericType, Type plainType) { IncompatibleTypes result = IncompatibleTypes.getPriorityForAssumingCompatible(genericType.getObjectType(), plainType); List<? extends ReferenceType> parameters = genericType.getParameters(); if (result.getPriority() == Priorities.NORMAL_PRIORITY && parameters != null && parameters.contains(plainType)) { result = UNRELATED_TYPES_BUT_MATCHES_TYPE_PARAMETER; } return result; } static public @NonNull IncompatibleTypes getPriorityForAssumingCompatible(Type lhsType, Type rhsType) { return getPriorityForAssumingCompatible(lhsType, rhsType, false); } static public @NonNull IncompatibleTypes getPriorityForAssumingCompatible(Type expectedType, Type actualType, boolean pointerEquality) { if (!(expectedType instanceof ReferenceType)) return SEEMS_OK; if (!(actualType instanceof ReferenceType)) return SEEMS_OK; if (expectedType instanceof BasicType ^ actualType instanceof BasicType) { return INCOMPATIBLE_CLASSES; } while (expectedType instanceof ArrayType && actualType instanceof ArrayType) { expectedType = ((ArrayType) expectedType).getElementType(); actualType = ((ArrayType) actualType).getElementType(); } if (expectedType instanceof BasicType ^ actualType instanceof BasicType) { return PRIMATIVE_ARRAY_AND_OTHER_ARRAY; } if (expectedType instanceof BasicType && actualType instanceof BasicType) { if (!expectedType.equals(actualType)) return INCOMPATIBLE_PRIMATIVE_ARRAYS; else return SEEMS_OK; } if (expectedType instanceof ArrayType) { return getPriorityForAssumingCompatibleWithArray(actualType); } if (actualType instanceof ArrayType) { return getPriorityForAssumingCompatibleWithArray(expectedType); } if (expectedType.equals(actualType)) return SEEMS_OK; // For now, ignore the case where either reference is not // of an object type. (It could be either an array or null.) if (!(expectedType instanceof ObjectType) || !(actualType instanceof ObjectType)) return SEEMS_OK; return getPriorityForAssumingCompatible((ObjectType) expectedType, (ObjectType) actualType, pointerEquality); } private static IncompatibleTypes getPriorityForAssumingCompatibleWithArray(Type rhsType) { if (rhsType.equals(ObjectType.OBJECT)) return ARRAY_AND_OBJECT; String sig = rhsType.getSignature(); if (sig.equals("Ljava/io/Serializable;") || sig.equals("Ljava/lang/Cloneable;")) return SEEMS_OK; return ARRAY_AND_NON_ARRAY; } static @NonNull XMethod getInvokedMethod(XClass xClass, String name, String sig, boolean isStatic) throws CheckedAnalysisException { IAnalysisCache cache = Global.getAnalysisCache(); while (true) { XMethod result = xClass.findMethod(name, sig, isStatic); if (result != null) return result; if (isStatic) throw new CheckedAnalysisException(); ClassDescriptor superclassDescriptor = xClass.getSuperclassDescriptor(); if (superclassDescriptor == null) throw new CheckedAnalysisException(); xClass = cache.getClassAnalysis(XClass.class, superclassDescriptor); } } static public @NonNull IncompatibleTypes getPriorityForAssumingCompatible(ObjectType expectedType, ObjectType actualType, boolean pointerEquality) { if (expectedType.equals(actualType)) return SEEMS_OK; if (actualType.equals(ObjectType.OBJECT)) return IncompatibleTypes.UNCHECKED; try { if (!Hierarchy.isSubtype(expectedType, actualType) && !Hierarchy.isSubtype(actualType, expectedType)) { if (Hierarchy.isSubtype(expectedType, GWT_JAVASCRIPTOBJECT_TYPE) && Hierarchy.isSubtype(actualType, GWT_JAVASCRIPTOBJECT_TYPE)) return SEEMS_OK; // See if the types are related by inheritance. ClassDescriptor lhsDescriptor = DescriptorFactory.createClassDescriptorFromDottedClassName(expectedType.getClassName()); ClassDescriptor rhsDescriptor = DescriptorFactory.createClassDescriptorFromDottedClassName(actualType.getClassName()); return getPriorityForAssumingCompatible(pointerEquality, lhsDescriptor, rhsDescriptor); } if (expectedType instanceof GenericObjectType && actualType instanceof GenericObjectType && (Hierarchy.isSubtype(expectedType, COLLECTION_TYPE) || Hierarchy.isSubtype(expectedType, MAP_TYPE))) { List<? extends ReferenceType> lhsParameters = ((GenericObjectType)expectedType).getParameters(); List<? extends ReferenceType> rhsParameters = ((GenericObjectType)actualType).getParameters(); if (lhsParameters != null && rhsParameters != null && lhsParameters.size() == rhsParameters.size()) for(int i = 0; i < lhsParameters.size(); i++) { IncompatibleTypes r = getPriorityForAssumingCompatible(lhsParameters.get(i), rhsParameters.get(i), pointerEquality); if (r.getPriority() <= Priorities.NORMAL_PRIORITY) return r; } } } catch (ClassNotFoundException e) { AnalysisContext.reportMissingClass(e); } catch (MissingClassException e) { AnalysisContext.reportMissingClass(e.getClassNotFoundException()); } catch (CheckedAnalysisException e) { AnalysisContext.logError("Error checking for incompatible types", e); } return SEEMS_OK; } /** * @param pointerEquality * @param lhsDescriptor * @param rhsDescriptor * @throws CheckedAnalysisException * @throws ClassNotFoundException */ public static IncompatibleTypes getPriorityForAssumingCompatible(boolean pointerEquality, ClassDescriptor lhsDescriptor, ClassDescriptor rhsDescriptor) throws CheckedAnalysisException, ClassNotFoundException { if (lhsDescriptor.equals(rhsDescriptor)) return SEEMS_OK; AnalysisContext analysisContext = AnalysisContext.currentAnalysisContext(); Subtypes2 subtypes2 = analysisContext.getSubtypes2(); IAnalysisCache cache = Global.getAnalysisCache(); XClass lhs = cache.getClassAnalysis(XClass.class, lhsDescriptor); XClass rhs = cache.getClassAnalysis(XClass.class, rhsDescriptor); // Look up the classes XMethod lhsEquals = getInvokedMethod(lhs, "equals", "(Ljava/lang/Object;)Z", false); XMethod rhsEquals = getInvokedMethod(rhs, "equals", "(Ljava/lang/Object;)Z", false); String lhsClassName = lhsEquals.getClassName(); if (lhsEquals.equals(rhsEquals)) { if (lhsClassName.equals("java.lang.Enum")) return INCOMPATIBLE_CLASSES; if (!pointerEquality && !lhsClassName.equals("java.lang.Object")) return SEEMS_OK; } if ((subtypes2.isSubtype(lhsDescriptor, SET_DESCRIPTOR) && subtypes2.isSubtype(rhsDescriptor, SET_DESCRIPTOR) || subtypes2.isSubtype(lhsDescriptor, MAP_DESCRIPTOR) && subtypes2.isSubtype(rhsDescriptor, MAP_DESCRIPTOR) || subtypes2.isSubtype(lhsDescriptor, LIST_DESCRIPTOR) && subtypes2.isSubtype(rhsDescriptor, LIST_DESCRIPTOR))) return SEEMS_OK; if (!lhs.isInterface() && !rhs.isInterface()) { // Both are class types, and therefore there is no possible // way // the compared objects can have the same runtime type. return INCOMPATIBLE_CLASSES; } else { // Look up the common subtypes of the two types. If the // intersection does not contain at least one instantiable // class, // then issue a warning of the appropriate type. Set<ClassDescriptor> commonSubtypes = subtypes2.getTransitiveCommonSubtypes( lhsDescriptor, rhsDescriptor); if (!containsAtLeastOneInstantiableClass(commonSubtypes)) { if (lhs.isFinal() || rhs.isFinal()) return UNRELATED_FINAL_CLASS_AND_INTERFACE; if (lhsDescriptor.getClassName().startsWith("java/util/") || rhsDescriptor.getClassName().startsWith("java/util/")) return UNRELATED_UTIL_INTERFACE; if (lhs.isInterface() && rhs.isInterface()) return UNRELATED_INTERFACES; return UNRELATED_CLASS_AND_INTERFACE; } } return SEEMS_OK; } private static boolean containsAtLeastOneInstantiableClass(Set<ClassDescriptor> commonSubtypes) throws CheckedAnalysisException { IAnalysisCache cache = Global.getAnalysisCache(); for (ClassDescriptor classDescriptor : commonSubtypes) { XClass xclass = cache.getClassAnalysis(XClass.class, classDescriptor); if (!xclass.isInterface() && !xclass.isAbstract()) return true; } return false; } }