edu.umd.cs.findbugs.ba.XFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spotbugs Show documentation
Show all versions of spotbugs Show documentation
SpotBugs: Because it's easy!
/*
* FindBugs - Find Bugs in Java programs
* Copyright (C) 2005, 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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.apache.bcel.Const;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldInstruction;
import org.apache.bcel.generic.INVOKEDYNAMIC;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.objectweb.asm.Opcodes;
import edu.umd.cs.findbugs.FieldAnnotation;
import edu.umd.cs.findbugs.MethodAnnotation;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.ba.obl.Obligation;
import edu.umd.cs.findbugs.ba.obl.ObligationPolicyDatabase;
import edu.umd.cs.findbugs.ba.obl.ObligationPolicyDatabaseEntry;
import edu.umd.cs.findbugs.ba.obl.ObligationPolicyDatabaseEntryType;
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.FieldDescriptor;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.classfile.IAnalysisCache;
import edu.umd.cs.findbugs.classfile.MethodDescriptor;
import edu.umd.cs.findbugs.classfile.analysis.FieldInfo;
import edu.umd.cs.findbugs.classfile.analysis.MethodInfo;
import edu.umd.cs.findbugs.detect.BuildObligationPolicyDatabase;
import edu.umd.cs.findbugs.internalAnnotations.DottedClassName;
import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName;
import edu.umd.cs.findbugs.util.ClassName;
import edu.umd.cs.findbugs.util.SplitCamelCaseIdentifier;
import edu.umd.cs.findbugs.util.Values;
import edu.umd.cs.findbugs.visitclass.DismantleBytecode;
import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
/**
* Factory methods for creating XMethod objects.
*
* @author David Hovemeyer
*/
public class XFactory {
public static final boolean DEBUG_UNRESOLVED = SystemProperties.getBoolean("findbugs.xfactory.debugunresolved");
private final Set reflectiveClasses = new HashSet<>();
private final Map methods = new HashMap<>();
private final Map fields = new HashMap<>();
private final Set calledMethods = new HashSet<>();
private final Set emptyArrays = new HashSet<>();
/**
* @deprecated This field is not updated by any code in the project. Will be removed in 5.x release.
*/
@Deprecated
private final Set calledMethodSignatures = new HashSet<>();
private final Set functionsThatMightBeMistakenForProcedures = new HashSet<>();
public void canonicalizeAll() {
DescriptorFactory descriptorFactory = DescriptorFactory.instance();
for (XMethod m : methods.values()) {
if (m instanceof MethodDescriptor) {
descriptorFactory.canonicalize((MethodDescriptor) m);
}
}
for (XField f : fields.values()) {
if (f instanceof FieldDescriptor) {
descriptorFactory.canonicalize((FieldDescriptor) f);
}
}
}
/**
* Constructor.
*/
public XFactory() {
}
public void intern(XClass c) {
for (XMethod m : c.getXMethods()) {
MethodInfo mi = (MethodInfo) m;
methods.put(mi, mi);
}
for (XField f : c.getXFields()) {
FieldInfo fi = (FieldInfo) f;
fields.put(fi, fi);
}
}
public Collection allFields() {
return fields.values();
}
public void addCalledMethod(MethodDescriptor m) {
assert m.getClassDescriptor().getClassName().indexOf('.') == -1;
calledMethods.add(createXMethod(m));
}
public void addEmptyArrayField(XField f) {
emptyArrays.add(f);
}
public boolean isEmptyArrayField(@CheckForNull XField f) {
return emptyArrays.contains(f);
}
public boolean isCalled(XMethod m) {
if (Const.STATIC_INITIALIZER_NAME.equals(m.getName())) {
return true;
}
return calledMethods.contains(m);
}
public Set getCalledMethods() {
return calledMethods;
}
public void addFunctionThatMightBeMistakenForProcedures(MethodDescriptor m) {
functionsThatMightBeMistakenForProcedures.add(m);
}
public boolean isFunctionshatMightBeMistakenForProcedures(MethodDescriptor m) {
return functionsThatMightBeMistakenForProcedures.contains(m);
}
public Set getReflectiveClasses() {
return reflectiveClasses;
}
public boolean isReflectiveClass(ClassDescriptor c) {
return reflectiveClasses.contains(c);
}
public boolean addReflectiveClasses(ClassDescriptor c) {
return reflectiveClasses.add(c);
}
public boolean isCalledDirectlyOrIndirectly(XMethod m) {
if (isCalled(m)) {
return true;
}
if (m.isStatic() || m.isPrivate() || Const.CONSTRUCTOR_NAME.equals(m.getName())) {
return false;
}
try {
IAnalysisCache analysisCache = Global.getAnalysisCache();
XClass clazz = analysisCache.getClassAnalysis(XClass.class, m.getClassDescriptor());
if (isCalledDirectlyOrIndirectly(clazz.getSuperclassDescriptor(), m)) {
return true;
}
for (ClassDescriptor i : clazz.getInterfaceDescriptorList()) {
if (isCalledDirectlyOrIndirectly(i, m)) {
return true;
}
}
return false;
} catch (edu.umd.cs.findbugs.classfile.MissingClassException e) {
// AnalysisContext.reportMissingClass(e.getClassNotFoundException());
return false;
} catch (MissingClassException e) {
AnalysisContext.reportMissingClass(e.getClassNotFoundException());
return false;
} catch (Exception e) {
AnalysisContext.logError("Error checking to see if " + m + " is called (" + e.getClass().getCanonicalName() + ")", e);
return false;
}
}
private boolean isCalledDirectlyOrIndirectly(@CheckForNull ClassDescriptor clazzDescriptor, XMethod m)
throws CheckedAnalysisException {
if (clazzDescriptor == null) {
return false;
}
IAnalysisCache analysisCache = Global.getAnalysisCache();
XClass clazz = analysisCache.getClassAnalysis(XClass.class, clazzDescriptor);
XMethod m2 = clazz.findMethod(m.getName(), m.getSignature(), m.isStatic());
if (m2 != null && isCalled(m2)) {
return true;
}
if (isCalledDirectlyOrIndirectly(clazz.getSuperclassDescriptor(), m)) {
return true;
}
for (ClassDescriptor i : clazz.getInterfaceDescriptorList()) {
if (isCalledDirectlyOrIndirectly(i, m)) {
return true;
}
}
return false;
}
/**
* @deprecated This method does not work as expected. Will be removed in 5.x release.
*/
@Deprecated
public boolean nameAndSignatureIsCalled(XMethod m) {
return calledMethodSignatures.contains(getDetailedSignature(m));
}
private static String getDetailedSignature(XMethod m2) {
return m2.getName() + m2.getSignature() + m2.isStatic();
}
@Deprecated
public boolean isInterned(XMethod m) {
return m.isResolved();
}
/**
* @see DescriptorFactory#canonicalizeString(String)
*/
@Deprecated
public static String canonicalizeString(String s) {
return s;
}
/**
* Create an XMethod object from a BCEL Method.
*
* @param className
* the class to which the Method belongs
* @param method
* the Method
* @return an XMethod representing the Method
*/
public static XMethod createXMethod(String className, Method method) {
String methodName = method.getName();
String methodSig = method.getSignature();
int accessFlags = method.getAccessFlags();
return createXMethod(className, methodName, methodSig, accessFlags);
}
/*
* Create a new, never-before-seen, XMethod object and intern it.
*/
private static XMethod createXMethod(@DottedClassName String className, String methodName, String methodSig, int accessFlags) {
return createXMethod(className, methodName, methodSig, (accessFlags & Const.ACC_STATIC) != 0);
}
/**
* Create an XMethod object from a BCEL Method.
*
* @param javaClass
* the class to which the Method belongs
* @param method
* the Method
* @return an XMethod representing the Method
*/
public static XMethod createXMethod(JavaClass javaClass, Method method) {
if (method == null) {
throw new NullPointerException("method must not be null");
}
XMethod xmethod = createXMethod(javaClass.getClassName(), method);
assert xmethod.isResolved();
return xmethod;
}
public static void assertDottedClassName(@DottedClassName String className) {
assert className.indexOf('/') == -1;
}
public static void assertSlashedClassName(@SlashedClassName String className) {
assert className.indexOf('.') == -1;
}
/**
* @param className
* @param methodName
* @param methodSig
* @param isStatic
* @return the created XMethod
*/
public static XMethod createXMethodUsingSlashedClassName(@SlashedClassName String className, String methodName,
String methodSig, boolean isStatic) {
assertSlashedClassName(className);
MethodDescriptor desc = DescriptorFactory.instance().getMethodDescriptor(className, methodName, methodSig, isStatic);
return createXMethod(desc);
}
/**
* @param className
* @param methodName
* @param methodSig
* @param isStatic
* @return the created XMethod
*/
public static XMethod createXMethod(@DottedClassName String className, String methodName, String methodSig, boolean isStatic) {
assertDottedClassName(className);
MethodDescriptor desc = DescriptorFactory.instance().getMethodDescriptor(ClassName.toSlashedClassName(className),
methodName, methodSig, isStatic);
return createXMethod(desc);
}
public static XMethod createXMethod(MethodDescriptor desc) {
XFactory xFactory = AnalysisContext.currentXFactory();
XMethod m = xFactory.methods.get(desc);
if (m != null) {
return m;
}
m = xFactory.resolveXMethod(desc);
if (m instanceof MethodDescriptor) {
xFactory.methods.put((MethodDescriptor) m, m);
DescriptorFactory.instance().canonicalize((MethodDescriptor) m);
} else {
xFactory.methods.put(desc, m);
}
return m;
}
public static void profile() {
XFactory xFactory = AnalysisContext.currentXFactory();
int count = 0;
for (XMethod m : xFactory.methods.values()) {
if (m instanceof MethodInfo) {
count++;
}
}
System.out.printf("XFactory cached methods: %d/%d%n", count, xFactory.methods.size());
DescriptorFactory.instance().profile();
}
private XMethod resolveXMethod(MethodDescriptor originalDescriptor) {
MethodDescriptor desc = originalDescriptor;
try {
while (true) {
XMethod m = methods.get(desc);
if (m != null) {
return m;
}
XClass xClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, desc.getClassDescriptor());
if (xClass == null) {
break;
}
ClassDescriptor superClass = xClass.getSuperclassDescriptor();
if (superClass == null) {
break;
}
desc = DescriptorFactory.instance().getMethodDescriptor(superClass.getClassName(), desc.getName(),
desc.getSignature(), desc.isStatic());
}
} catch (CheckedAnalysisException e) {
assert true;
} catch (RuntimeException e) {
assert true;
}
UnresolvedXMethod xmethod = new UnresolvedXMethod(originalDescriptor);
ObligationPolicyDatabase database = Global.getAnalysisCache().getOptionalDatabase(ObligationPolicyDatabase.class);
if (BuildObligationPolicyDatabase.INFER_CLOSE_METHODS && database != null
&& !xmethod.getClassName().startsWith("java")) {
boolean methodHasCloseInName = false;
String methodName = xmethod.getName();
SplitCamelCaseIdentifier splitter = new SplitCamelCaseIdentifier(methodName);
methodHasCloseInName = splitter.split().contains("close");
Obligation[] paramObligationTypes = database.getFactory().getParameterObligationTypes(xmethod);
for (int i = 0; i < xmethod.getNumParams(); i++) {
Obligation obligationType = paramObligationTypes[i];
if (obligationType == null) {
continue;
}
if (methodHasCloseInName) {
// Method has "close" in its name.
// Assume that it deletes the obligation.
ObligationPolicyDatabaseEntry entry = database.addParameterDeletesObligationDatabaseEntry(xmethod,
obligationType, ObligationPolicyDatabaseEntryType.STRONG);
} else {
/*
* // Interesting case: we have a parameter which is // an
* Obligation type, but no annotation or other indication //
* what is done by the method with the obligation. // We'll
* create a "weak" database entry deleting the //
* obligation. If strict checking is performed, // weak
* entries are ignored.
*/
if (Const.CONSTRUCTOR_NAME.equals(methodName) || methodName.startsWith("access$") || xmethod.isStatic()
|| methodName.toLowerCase().indexOf("close") >= 0
|| xmethod.getSignature().toLowerCase().indexOf("Closeable") >= 0) {
ObligationPolicyDatabaseEntry entry = database.addParameterDeletesObligationDatabaseEntry(xmethod,
obligationType, ObligationPolicyDatabaseEntryType.WEAK);
}
}
}
}
return xmethod;
}
public static XMethod createXMethod(MethodAnnotation ma) {
return createXMethod(ma.getClassName(), ma.getMethodName(), ma.getMethodSignature(), ma.isStatic());
}
/**
* Create an XField object
*
* @param className
* @param fieldName
* @param fieldSignature
* @param isStatic
* @return the created XField
*/
public static XField createXFieldUsingSlashedClassName(@SlashedClassName String className, String fieldName,
String fieldSignature, boolean isStatic) {
FieldDescriptor fieldDesc = DescriptorFactory.instance().getFieldDescriptor(className, fieldName, fieldSignature,
isStatic);
return createXField(fieldDesc);
}
/**
* Create an XField object
*
* @param className
* @param fieldName
* @param fieldSignature
* @param isStatic
* @return the created XField
*/
public static XField createXField(@DottedClassName String className, String fieldName, String fieldSignature, boolean isStatic) {
FieldDescriptor fieldDesc = DescriptorFactory.instance().getFieldDescriptor(ClassName.toSlashedClassName(className),
fieldName, fieldSignature, isStatic);
return createXField(fieldDesc);
}
public final static boolean DEBUG_CIRCULARITY = SystemProperties.getBoolean("circularity.debug");
public static XField createXField(FieldInstruction fieldInstruction, ConstantPoolGen cpg) {
String className = fieldInstruction.getClassName(cpg);
String fieldName = fieldInstruction.getName(cpg);
String fieldSig = fieldInstruction.getSignature(cpg);
int opcode = fieldInstruction.getOpcode();
return createXField(className, fieldName, fieldSig, opcode == Const.GETSTATIC || opcode == Const.PUTSTATIC);
}
public static XField createReferencedXField(DismantleBytecode visitor) {
int seen = visitor.getOpcode();
if (seen != Opcodes.GETFIELD && seen != Opcodes.GETSTATIC && seen != Opcodes.PUTFIELD && seen != Opcodes.PUTSTATIC) {
throw new IllegalArgumentException("Not at a field reference");
}
return createXFieldUsingSlashedClassName(visitor.getClassConstantOperand(), visitor.getNameConstantOperand(),
visitor.getSigConstantOperand(), visitor.getRefFieldIsStatic());
}
public static XMethod createReferencedXMethod(DismantleBytecode visitor) {
XMethod m = createXMethodUsingSlashedClassName(visitor.getClassConstantOperand(), visitor.getNameConstantOperand(),
visitor.getSigConstantOperand(), visitor.getOpcode() == Const.INVOKESTATIC);
return m.resolveAccessMethodForMethod();
}
public static XField createXField(FieldAnnotation f) {
return createXField(f.getClassName(), f.getFieldName(), f.getFieldSignature(), f.isStatic());
}
public static XField createXField(JavaClass javaClass, Field field) {
return createXField(javaClass.getClassName(), field);
}
/**
* Create an XField object from a BCEL Field.
*
* @param className
* the name of the Java class containing the field
* @param field
* the Field within the JavaClass
* @return the created XField
*/
public static XField createXField(String className, Field field) {
String fieldName = field.getName();
String fieldSig = field.getSignature();
XField xfield = getExactXField(className, fieldName, fieldSig, field.isStatic());
assert xfield.isResolved() : "Could not exactly resolve " + xfield;
return xfield;
}
/**
* Get an XField object exactly matching given class, name, and signature.
* May return an unresolved object (if the class can't be found, or does not
* directly declare named field).
*
* @param className
* name of class containing the field
* @param name
* name of field
* @param signature
* field signature
* @param isStatic
* field access flags
* @return XField exactly matching class name, field name, and field
* signature
*/
public static XField getExactXField(@SlashedClassName String className, String name, String signature, boolean isStatic) {
FieldDescriptor fieldDesc = DescriptorFactory.instance().getFieldDescriptor(ClassName.toSlashedClassName(className),
name, signature, isStatic);
return getExactXField(fieldDesc);
}
public static @Nonnull XField getExactXField(@SlashedClassName String className, Field f) {
FieldDescriptor fd = DescriptorFactory.instance().getFieldDescriptor(className, f);
return getExactXField(fd);
}
public static @Nonnull XField getExactXField(FieldDescriptor desc) {
XFactory xFactory = AnalysisContext.currentXFactory();
XField f = xFactory.fields.get(desc);
if (f == null) {
return new UnresolvedXField(desc);
}
return f;
}
public static XField createXField(FieldDescriptor desc) {
XFactory xFactory = AnalysisContext.currentXFactory();
XField m = xFactory.fields.get(desc);
if (m != null) {
return m;
}
m = xFactory.resolveXField(desc);
xFactory.fields.put(desc, m);
return m;
}
private XField resolveXField(FieldDescriptor originalDescriptor) {
FieldDescriptor desc = originalDescriptor;
LinkedList worklist = new LinkedList<>();
ClassDescriptor originalClassDescriptor = desc.getClassDescriptor();
worklist.add(originalClassDescriptor);
try {
while (!worklist.isEmpty()) {
ClassDescriptor d = worklist.removeFirst();
if (!d.equals(originalClassDescriptor)) {
desc = DescriptorFactory.instance().getFieldDescriptor(d.getClassName(), desc.getName(), desc.getSignature(),
desc.isStatic());
}
XField f = fields.get(desc);
if (f != null) {
return f;
}
XClass xClass = Global.getAnalysisCache().getClassAnalysis(XClass.class, d);
if (xClass == null) {
break;
}
ClassDescriptor superClass = xClass.getSuperclassDescriptor();
if (superClass != null) {
worklist.add(superClass);
}
if (originalDescriptor.isStatic()) {
Collections.addAll(worklist, xClass.getInterfaceDescriptorList());
}
}
} catch (CheckedAnalysisException e) {
AnalysisContext.logError("Error resolving " + originalDescriptor, e);
}
return new UnresolvedXField(originalDescriptor);
}
/**
* Create an XMethod object from an InvokeInstruction.
*
* @param invokeInstruction
* the InvokeInstruction
* @param cpg
* ConstantPoolGen from the class containing the instruction
* @return XMethod representing the method called by the InvokeInstruction
*/
public static XMethod createXMethod(InvokeInstruction invokeInstruction, ConstantPoolGen cpg) {
String className = invokeInstruction.getClassName(cpg);
String methodName = invokeInstruction.getName(cpg);
String methodSig = invokeInstruction.getSignature(cpg);
if (invokeInstruction instanceof INVOKEDYNAMIC) {
// XXX the lambda representation makes no sense for XMethod
// "classical" instruction attributes are filled with garbage, causing
// the code later to produce crazy errors (looking for non existing types etc)
// We should NOT be called here from our code, but 3rd party code still may
// use this method. So *at least* provide a valid class name, which is
// (don't ask me why) is encoded in the first argument type of the lambda
// className = invokeInstruction.getArgumentTypes(cpg)[0].toString();
className = Values.DOTTED_JAVA_LANG_OBJECT;
}
return createXMethod(className, methodName, methodSig, invokeInstruction.getOpcode() == Const.INVOKESTATIC);
}
/**
* Create an XMethod object from the method currently being visited by the
* given PreorderVisitor.
*
* @param visitor
* the PreorderVisitor
* @return the XMethod representing the method currently being visited
*/
public static XMethod createXMethod(PreorderVisitor visitor) {
JavaClass javaClass = visitor.getThisClass();
Method method = visitor.getMethod();
XMethod m = createXMethod(javaClass, method);
return m;
}
/**
* Create an XField object from the field currently being visited by the
* given PreorderVisitor.
*
* @param visitor
* the PreorderVisitor
* @return the XField representing the method currently being visited
*/
public static XField createXField(PreorderVisitor visitor) {
JavaClass javaClass = visitor.getThisClass();
Field field = visitor.getField();
XField f = createXField(javaClass, field);
return f;
}
public static XMethod createXMethod(MethodGen methodGen) {
String className = methodGen.getClassName();
String methodName = methodGen.getName();
String methodSig = methodGen.getSignature();
int accessFlags = methodGen.getAccessFlags();
return createXMethod(className, methodName, methodSig, accessFlags);
}
public static XMethod createXMethod(JavaClassAndMethod classAndMethod) {
return createXMethod(classAndMethod.getJavaClass(), classAndMethod.getMethod());
}
/**
* Get the XClass object providing information about the class named by the
* given ClassDescriptor.
*
* @param classDescriptor
* a ClassDescriptor
* @return an XClass object providing information about the class, or null
* if the class cannot be found
*/
public @CheckForNull XClass getXClass(ClassDescriptor classDescriptor) {
try {
IAnalysisCache analysisCache = Global.getAnalysisCache();
return analysisCache.getClassAnalysis(XClass.class, classDescriptor);
} catch (CheckedAnalysisException e) {
return null;
}
}
/**
* Compare XMethod or XField object objects.
* All methods that implement XMethod or XField should
* delegate to this method when implementing compareTo(Object)
* if the right-hand object implements XField or XMethod.
*
* @param lhs
* an XMethod or XField
* @param rhs
* an XMethod or XField
* @return comparison of lhs and rhs
*/
public static int compare(E lhs, E rhs) {
int cmp;
cmp = lhs.getClassName().compareTo(rhs.getClassName());
if (cmp != 0) {
return cmp;
}
cmp = lhs.getName().compareTo(rhs.getName());
if (cmp != 0) {
return cmp;
}
cmp = lhs.getSignature().compareTo(rhs.getSignature());
if (cmp != 0) {
return cmp;
}
return (lhs.isStatic() ? 1 : 0) - (rhs.isStatic() ? 1 : 0);
}
}