/*
* 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;
}
}