/* * 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.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import org.apache.bcel.Constants; 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.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.annotations.CheckForNull; 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.internalAnnotations.DottedClassName; import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName; import edu.umd.cs.findbugs.util.ClassName; 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 Set<ClassDescriptor> reflectiveClasses = new HashSet<ClassDescriptor>(); private Map<MethodDescriptor, XMethod> methods = new HashMap<MethodDescriptor, XMethod>(); private Map<FieldDescriptor, XField> fields = new HashMap<FieldDescriptor, XField>(); private Set<XMethod> calledMethods = new HashSet<XMethod>(); private Set<XField> emptyArrays = new HashSet<XField>(); private Set<String> calledMethodSignatures = new HashSet<String>(); 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<XField> 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 (m.getName().equals("<clinit>")) return true; return calledMethods.contains(m); } public Set<XMethod> getCalledMethods() { return calledMethods; } public Set<ClassDescriptor> 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() || m.getName().equals("<init>")) 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; } } /** * @param superclassDescriptor * @param m * @return * @throws CheckedAnalysisException */ 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; } public boolean nameAndSignatureIsCalled(XMethod m) { return calledMethodSignatures.contains(getDetailedSignature(m)); } /** * @param m2 * @return */ private static String getDetailedSignature(XMethod m2) { return m2.getName() + m2.getSignature() + m2.isStatic(); } @Deprecated public boolean isInterned(XMethod m) { return m.isResolved(); } public static String canonicalizeString(String s) { return DescriptorFactory.canonicalizeString(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 & Constants.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; } return new UnresolvedXMethod(originalDescriptor); } 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 == Constants.GETSTATIC || opcode == Constants.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) { return createXMethodUsingSlashedClassName(visitor.getClassConstantOperand(), visitor.getNameConstantOperand(), visitor.getSigConstantOperand(), visitor.getOpcode() == Constants.INVOKESTATIC); } 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<ClassDescriptor> worklist = new LinkedList<ClassDescriptor>(); 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()) for (ClassDescriptor i : xClass.getInterfaceDescriptorList()) worklist.add(i); } } 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); return createXMethod(className, methodName, methodSig, invokeInstruction.getOpcode() == Constants.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. * <em>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.</em> * * @param lhs * an XMethod or XField * @param rhs * an XMethod or XField * @return comparison of lhs and rhs */ public static <E extends ClassMember> 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); } }