All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.umd.cs.findbugs.BugInstance Maven / Gradle / Ivy

There is a newer version: 4.8.6
Show newest version
/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2003-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;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.Objects.requireNonNull;

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.meta.When;

import org.apache.bcel.Const;
import org.apache.bcel.classfile.AnnotationEntry;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;
import org.objectweb.asm.tree.ClassNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.CFGBuilderException;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.ba.DataflowAnalysisException;
import edu.umd.cs.findbugs.ba.Hierarchy2;
import edu.umd.cs.findbugs.ba.JavaClassAndMethod;
import edu.umd.cs.findbugs.ba.Location;
import edu.umd.cs.findbugs.ba.OpcodeStackScanner;
import edu.umd.cs.findbugs.ba.OpcodeStackScanner.UnreachableCodeException;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XField;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.ba.bcp.FieldVariable;
import edu.umd.cs.findbugs.ba.vna.ValueNumberSourceInfo;
import edu.umd.cs.findbugs.bytecode.MemberUtils;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
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.internalAnnotations.DottedClassName;
import edu.umd.cs.findbugs.internalAnnotations.SlashedClassName;
import edu.umd.cs.findbugs.util.ClassName;
import edu.umd.cs.findbugs.util.Util;
import edu.umd.cs.findbugs.visitclass.DismantleBytecode;
import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
import edu.umd.cs.findbugs.xml.XMLAttributeList;
import edu.umd.cs.findbugs.xml.XMLOutput;
import edu.umd.cs.findbugs.xml.XMLWriteable;

/**
 * 

An instance of a bug pattern. A BugInstance consists of several parts:

*
    *
  • the type, which is a string indicating what kind of bug it is; used as a * key for the FindBugsMessages resource bundle
  • *
  • the priority; how likely this instance is to actually be a bug
  • *
  • a list of annotations
  • *
*

* The annotations describe classes, methods, fields, source locations, and * other relevant context information about the bug instance. Every BugInstance * must have at least one ClassAnnotation, which describes the class in which * the instance was found. This is the "primary class annotation". *

*

* BugInstance objects are built up by calling a string of add * methods. (These methods all "return this", so they can be chained). Some of * the add methods are specialized to get information automatically from a * BetterVisitor or DismantleBytecode object.

* * @author David Hovemeyer * @see BugAnnotation */ public class BugInstance implements Comparable, XMLWriteable, Cloneable { private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private final String type; private int priority; private final ArrayList annotationList; private int cachedHashCode; private BugProperty propertyListHead, propertyListTail; private String oldInstanceHash; private String instanceHash; private int instanceOccurrenceNum; private int instanceOccurrenceMax; @CheckForNull private DetectorFactory detectorFactory; /* * The following fields are used for tracking Bug instances across multiple * versions of software. They are meaningless in a BugCollection for just * one version of software. */ private long firstVersion; private long lastVersion; private boolean introducedByChangeOfExistingClass; private boolean removedByChangeOfPersistingClass; /** * This value is used to indicate that the cached hashcode is invalid, and * should be recomputed. */ private static final int INVALID_HASH_CODE = 0; private static final String ELEMENT_NAME = "BugInstance"; /** * This value is used to indicate whether BugInstances should be * reprioritized very low, when the BugPattern is marked as experimental */ private static boolean adjustExperimental; private static Set missingBugTypes = Collections.synchronizedSet(new HashSet()); public static class NoSuchBugPattern extends IllegalArgumentException { public final String type; public NoSuchBugPattern(String type) { super("Can't find definition of bug type " + type); this.type = type; } } /** * Constructor. * * @param type * the bug type * @param priority * the bug priority */ public BugInstance(String type, int priority) { this.type = type.intern(); this.priority = priority; lastVersion = -1; annotationList = new ArrayList<>(4); cachedHashCode = INVALID_HASH_CODE; BugPattern p = DetectorFactoryCollection.instance().lookupBugPattern(type); if (p == null) { if (missingBugTypes.add(type)) { String msg = "Can't find definition of bug type " + type; AnalysisContext.logError(msg, new NoSuchBugPattern(type)); } } else { this.priority += p.getPriorityAdjustment(); } if (adjustExperimental && isExperimental()) { this.priority = Priorities.EXP_PRIORITY; } boundPriority(); } public static DateFormat firstSeenXMLFormat() { return new SimpleDateFormat("M/d/yy h:mm a", Locale.ENGLISH); } /* private boolean isFakeBugType(String type) { return "MISSING".equals(type) || "FOUND".equals(type); } */ private void boundPriority() { priority = boundedPriority(priority); } @Override public Object clone() { BugInstance dup; try { dup = (BugInstance) super.clone(); // Do deep copying of mutable objects for (int i = 0; i < dup.annotationList.size(); ++i) { dup.annotationList.set(i, (BugAnnotation) dup.annotationList.get(i).clone()); } dup.propertyListHead = dup.propertyListTail = null; for (Iterator i = propertyIterator(); i.hasNext();) { dup.addProperty((BugProperty) i.next().clone()); } return dup; } catch (CloneNotSupportedException e) { throw new AssertionError(e); } } /** * Create a new BugInstance. This is the constructor that should be used by * Detectors. * * @param detector * the Detector that is reporting the BugInstance * @param type * the bug type * @param priority * the bug priority */ public BugInstance(Detector detector, String type, int priority) { this(type, priority); if (detector != null) { // Adjust priority if required String detectorName = detector.getClass().getName(); adjustForDetector(detectorName); } } /** * @param detectorName */ public void adjustForDetector(String detectorName) { DetectorFactory factory = DetectorFactoryCollection.instance().getFactoryByClassName(detectorName); detectorFactory = factory; if (factory != null) { this.priority += factory.getPriorityAdjustment(); boundPriority(); BugPattern bugPattern = getBugPattern(); if (SystemProperties.ASSERTIONS_ENABLED && !"EXPERIMENTAL".equals(bugPattern.getCategory()) && !factory.getReportedBugPatterns().contains(bugPattern)) { AnalysisContext.logError(factory.getShortName() + " doesn't note that it reports " + bugPattern + " in category " + bugPattern.getCategory()); } } } /** * Create a new BugInstance. This is the constructor that should be used by * Detectors. * * @param detector * the Detector2 that is reporting the BugInstance * @param type * the bug type * @param priority * the bug priority */ public BugInstance(Detector2 detector, String type, int priority) { this(type, priority); if (detector != null) { // Adjust priority if required String detectorName = detector.getDetectorClassName(); adjustForDetector(detectorName); } } public static void setAdjustExperimental(boolean adjust) { adjustExperimental = adjust; } /* * ---------------------------------------------------------------------- * Accessors * ---------------------------------------------------------------------- */ /** * Get the bug pattern name (e.g., IL_INFINITE_RECURSIVE_LOOP) */ public String getType() { return type; } /** * Get the BugPattern. */ public @Nonnull BugPattern getBugPattern() { BugPattern result = DetectorFactoryCollection.instance().lookupBugPattern(getType()); if (result != null) { return result; } AnalysisContext.logError("Unable to find description of bug pattern " + getType()); result = DetectorFactoryCollection.instance().lookupBugPattern("UNKNOWN"); if (result != null) { return result; } return BugPattern.REALLY_UNKNOWN; } /** * Get the bug priority. */ public int getPriority() { return priority; } public int getBugRank() { return BugRanker.findRank(this); } public BugRankCategory getBugRankCategory() { return BugRankCategory.getRank(getBugRank()); } /** * Get a string describing the bug priority and type. e.g. * "High Priority Correctness" * * @return a string describing the bug priority and type */ public String getPriorityTypeString() { String priorityString = getPriorityString(); BugPattern bugPattern = this.getBugPattern(); // then get the category and put everything together String categoryString = I18N.instance().getBugCategoryDescription(bugPattern.getCategory()); return priorityString + " Confidence " + categoryString; // TODO: internationalize the word "Confidence" } public String getPriorityTypeAbbreviation() { String priorityString = getPriorityAbbreviation(); return priorityString + " " + getCategoryAbbrev(); } public String getCategoryAbbrev() { BugPattern bugPattern = getBugPattern(); return bugPattern.getCategoryAbbrev(); } public String getPriorityString() { // first, get the priority int value = this.getPriority(); String priorityString; if (value == Priorities.HIGH_PRIORITY) { priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_high", "High"); } else if (value == Priorities.NORMAL_PRIORITY) { priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_normal", "Medium"); } else if (value == Priorities.LOW_PRIORITY) { priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_low", "Low"); } else if (value == Priorities.EXP_PRIORITY) { priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_experimental", "Experimental"); } else { priorityString = edu.umd.cs.findbugs.L10N.getLocalString("sort.priority_ignore", "Ignore"); // This } // probably shouldn't ever happen, but what the hell, let's be complete return priorityString; } public String getPriorityAbbreviation() { return getPriorityString().substring(0, 1); } /** * Set the bug priority. */ public void setPriority(int p) { priority = boundedPriority(p); } private int boundedPriority(int p) { return Math.max(Priorities.HIGH_PRIORITY, Math.min(Priorities.IGNORE_PRIORITY, p)); } public void raisePriority() { priority = boundedPriority(priority - 1); } public void lowerPriority() { priority = boundedPriority(priority + 1); } public void lowerPriorityALot() { priority = boundedPriority(priority + 2); } /** * Is this bug instance the result of an experimental detector? */ public boolean isExperimental() { BugPattern pattern = getBugPattern(); return pattern.isExperimental(); } /** * Get the primary class annotation, which indicates where the bug occurs. */ public ClassAnnotation getPrimaryClass() { return findPrimaryAnnotationOfType(ClassAnnotation.class); } /** * Get the primary type annotation, which indicates where the bug occurs. */ @CheckForNull public TypeAnnotation getPrimaryType() { return findPrimaryAnnotationOfType(TypeAnnotation.class); } /** * Get the primary method annotation, which indicates where the bug occurs. */ @CheckForNull public MethodAnnotation getPrimaryMethod() { return findPrimaryAnnotationOfType(MethodAnnotation.class); } /** * Get the primary field annotation, which indicates where the bug occurs. */ @CheckForNull public FieldAnnotation getPrimaryField() { return findPrimaryAnnotationOfType(FieldAnnotation.class); } @Nonnull public BugInstance lowerPriorityIfDeprecated() { MethodAnnotation m = getPrimaryMethod(); if (m != null && XFactory.createXMethod(m).isDeprecated()) { lowerPriority(); } FieldAnnotation f = getPrimaryField(); if (f != null && XFactory.createXField(f).isDeprecated()) { lowerPriority(); } return this; } /** * Find the first BugAnnotation in the list of annotations that is the same * type or a subtype as the given Class parameter. * * @param cls * the Class parameter * @return the first matching BugAnnotation of the given type, or null if * there is no such BugAnnotation */ @CheckForNull private T findPrimaryAnnotationOfType(Class cls) { T firstMatch = null; for (Iterator i = annotationIterator(); i.hasNext();) { BugAnnotation annotation = i.next(); if (cls.isAssignableFrom(annotation.getClass())) { if (annotation.getDescription().endsWith("DEFAULT")) { return cls.cast(annotation); } else if (firstMatch == null) { firstMatch = cls.cast(annotation); } } } return firstMatch; } public LocalVariableAnnotation getPrimaryLocalVariableAnnotation() { for (BugAnnotation annotation : annotationList) { if (annotation instanceof LocalVariableAnnotation) { return (LocalVariableAnnotation) annotation; } } return null; } /** * Get the primary source line annotation. There is guaranteed to be one * (unless some Detector constructed an invalid BugInstance). * * @return the source line annotation */ @Nonnull public SourceLineAnnotation getPrimarySourceLineAnnotation() { // Highest priority: return the first top level source line annotation for (BugAnnotation annotation : annotationList) { if (annotation instanceof SourceLineAnnotation && SourceLineAnnotation.DEFAULT_ROLE.equals(annotation.getDescription()) && !((SourceLineAnnotation) annotation).isUnknown()) { return (SourceLineAnnotation) annotation; } } for (BugAnnotation annotation : annotationList) { if (annotation instanceof SourceLineAnnotation && !((SourceLineAnnotation) annotation).isUnknown()) { return (SourceLineAnnotation) annotation; } } // Next: Try primary method, primary field, primary class SourceLineAnnotation srcLine; if ((srcLine = inspectPackageMemberSourceLines(getPrimaryMethod())) != null) { return srcLine; } if ((srcLine = inspectPackageMemberSourceLines(getPrimaryField())) != null) { return srcLine; } if ((srcLine = inspectPackageMemberSourceLines(getPrimaryClass())) != null) { return srcLine; } // Last resort: throw exception throw new IllegalStateException("BugInstance for " + getType() + " must contain at least one class, method, or field annotation"); } public Collection getAnotherInstanceSourceLineAnnotations() { // Highest priority: return the first top level source line annotation Collection result = new ArrayList<>(); for (BugAnnotation annotation : annotationList) { if (annotation instanceof SourceLineAnnotation && SourceLineAnnotation.ROLE_ANOTHER_INSTANCE.equals(annotation.getDescription()) && !((SourceLineAnnotation) annotation).isUnknown()) { result.add((SourceLineAnnotation) annotation); } } return result; } public String getInstanceKey() { String newValue = getInstanceKeyNew(); return newValue; } private String getInstanceKeyNew() { StringBuilder buf = new StringBuilder(type); for (BugAnnotation annotation : annotationList) { if (annotation.isSignificant() || annotation instanceof IntAnnotation || annotation instanceof LocalVariableAnnotation) { buf.append(":"); buf.append(annotation.format("hash", null)); } } return buf.toString(); } /** * If given PackageMemberAnnotation is non-null, return its * SourceLineAnnotation. * * @param packageMember * a PackageMemberAnnotation * @return the PackageMemberAnnotation's SourceLineAnnotation, or null if * there is no SourceLineAnnotation */ private SourceLineAnnotation inspectPackageMemberSourceLines(PackageMemberAnnotation packageMember) { return (packageMember != null) ? packageMember.getSourceLines() : null; } /** * Get an Iterator over all bug annotations. */ public Iterator annotationIterator() { return annotationList.iterator(); } /** * Get an Iterator over all bug annotations. */ public List getAnnotations() { return annotationList; } /** Get the first bug annotation with the specified class and role; return null if no * such annotation exists; */ public @CheckForNull A getAnnotationWithRole(Class c, String role) { for (BugAnnotation a : annotationList) { if (c.isInstance(a) && Objects.equals(role, a.getDescription())) { return c.cast(a); } } return null; } /** * Get the abbreviation of this bug instance's BugPattern. This is the same * abbreviation used by the BugCode which the BugPattern is a particular * species of. */ public String getAbbrev() { BugPattern pattern = getBugPattern(); return pattern.getAbbrev(); } /* * ---------------------------------------------------------------------- * Property accessors * ---------------------------------------------------------------------- */ private class BugPropertyIterator implements Iterator { private BugProperty prev, cur; private boolean removed; @Override public boolean hasNext() { return findNext() != null; } @Override public BugProperty next() { BugProperty next = findNext(); if (next == null) { throw new NoSuchElementException(); } prev = cur; cur = next; removed = false; return cur; } @Override public void remove() { if (cur == null || removed) { throw new IllegalStateException(); } if (prev == null) { propertyListHead = cur.getNext(); } else { prev.setNext(cur.getNext()); } if (cur == propertyListTail) { propertyListTail = prev; } removed = true; } private BugProperty findNext() { return cur == null ? propertyListHead : cur.getNext(); } } /** * Get value of given property. * * @param name * name of the property to get * @return the value of the named property, or null if the property has not * been set */ public String getProperty(String name) { BugProperty prop = lookupProperty(name); return prop != null ? prop.getValue() : null; } /** * Get value of given property, returning given default value if the * property has not been set. * * @param name * name of the property to get * @param defaultValue * default value to return if propery is not set * @return the value of the named property, or the default value if the * property has not been set */ public String getProperty(String name, String defaultValue) { String value = getProperty(name); return value != null ? value : defaultValue; } /** * Get an Iterator over the properties defined in this BugInstance. * * @return Iterator over properties */ public Iterator propertyIterator() { return new BugPropertyIterator(); } /** * Set value of given property. * * @param name * name of the property to set * @param value * the value of the property * @return this object, so calls can be chained */ @Nonnull public BugInstance setProperty(String name, String value) { BugProperty prop = lookupProperty(name); if (prop != null) { prop.setValue(value); } else { prop = new BugProperty(name, value); addProperty(prop); } return this; } /** * Look up a property by name. * * @param name * name of the property to look for * @return the BugProperty with the given name, or null if the property has * not been set */ public BugProperty lookupProperty(String name) { BugProperty prop = propertyListHead; while (prop != null) { if (prop.getName().equals(name)) { break; } prop = prop.getNext(); } return prop; } /** * Delete property with given name. * * @param name * name of the property to delete * @return true if a property with that name was deleted, or false if there * is no such property */ public boolean deleteProperty(String name) { BugProperty prev = null; BugProperty prop = propertyListHead; while (prop != null) { if (prop.getName().equals(name)) { break; } prev = prop; prop = prop.getNext(); } if (prop != null) { if (prev != null) { // Deleted node in interior or at tail of list prev.setNext(prop.getNext()); } else { // Deleted node at head of list propertyListHead = prop.getNext(); } if (prop.getNext() == null) { // Deleted node at end of list propertyListTail = prev; } return true; } else { // No such property return false; } } private void addProperty(BugProperty prop) { if (propertyListTail != null) { propertyListTail.setNext(prop); propertyListTail = prop; } else { propertyListHead = propertyListTail = prop; } prop.setNext(null); } /* * ---------------------------------------------------------------------- * Generic BugAnnotation adders * ---------------------------------------------------------------------- */ /** * Add a Collection of BugAnnotations. * * @param annotationCollection * Collection of BugAnnotations */ @Nonnull public BugInstance addAnnotations(Collection annotationCollection) { for (BugAnnotation annotation : annotationCollection) { add(annotation); } return this; } /* * ---------------------------------------------------------------------- * Combined annotation adders * ---------------------------------------------------------------------- */ @Nonnull public BugInstance addClassAndMethod(MethodDescriptor methodDescriptor) { addClass(ClassName.toDottedClassName(methodDescriptor.getSlashedClassName())); add(MethodAnnotation.fromMethodDescriptor(methodDescriptor)); return this; } public BugInstance addClassAndMethod(XMethod xMethod) { return addClassAndMethod(xMethod.getMethodDescriptor()); } /** * Add a class annotation and a method annotation for the class and method * which the given visitor is currently visiting. * * @param visitor * the BetterVisitor * @return this object */ @Nonnull public BugInstance addClassAndMethod(PreorderVisitor visitor) { addClass(visitor); XMethod m = visitor.getXMethod(); addMethod(visitor); if (!MemberUtils.isUserGenerated(m)) { foundInAutogeneratedMethod(); } return this; } private void foundInAutogeneratedMethod() { if (annotationList.size() != 2) { return; } priority += 2; setProperty("FOUND_IN_SYNTHETIC_METHOD", "true"); if (SystemProperties.ASSERTIONS_ENABLED && AnalysisContext.analyzingApplicationClass() && priority <= 3) { AnalysisContext.logError("Adding error " + getBugPattern().getType() + " to synthetic method " + getPrimaryMethod()); } } /** * Add class and method annotations for given method. * * @param methodAnnotation * the method * @return this object */ @Nonnull public BugInstance addClassAndMethod(MethodAnnotation methodAnnotation) { addClass(methodAnnotation.getClassName()); addMethod(methodAnnotation); return this; } /** * Add class and method annotations for given method. * * @param methodGen * the method * @param sourceFile * source file the method is defined in * @return this object */ @Nonnull public BugInstance addClassAndMethod(MethodGen methodGen, String sourceFile) { addClass(methodGen.getClassName()); addMethod(methodGen, sourceFile); if (!MemberUtils.isUserGenerated(methodGen)) { foundInAutogeneratedMethod(); } return this; } /** * Add class and method annotations for given class and method. * * @param javaClass * the class * @param method * the method * @return this object */ @Nonnull public BugInstance addClassAndMethod(JavaClass javaClass, Method method) { addClass(javaClass.getClassName()); addMethod(javaClass, method); if (!MemberUtils.isUserGenerated(method)) { foundInAutogeneratedMethod(); } return this; } /* * ---------------------------------------------------------------------- * Class annotation adders * ---------------------------------------------------------------------- */ /** * Add a class annotation. If this is the first class annotation added, it * becomes the primary class annotation. * * @param className * the name of the class * @param sourceFileName * the source file of the class * @return this object */ @Nonnull public BugInstance addClass(String className, String sourceFileName) { ClassAnnotation classAnnotation = new ClassAnnotation(className, sourceFileName); add(classAnnotation); return this; } /** * Add a class annotation. If this is the first class annotation added, it * becomes the primary class annotation. * * @param className * the name of the class * @return this object */ @Nonnull public BugInstance addClass(@SlashedClassName(when = When.UNKNOWN) String className) { ClassAnnotation classAnnotation = new ClassAnnotation(ClassName.toDottedClassName(className)); add(classAnnotation); return this; } /** * Add a class annotation for the classNode. * * @param classNode * the ASM visitor * @return this object */ @Nonnull public BugInstance addClass(ClassNode classNode) { String dottedClassName = ClassName.toDottedClassName(classNode.name); ClassAnnotation classAnnotation = new ClassAnnotation(dottedClassName); add(classAnnotation); return this; } /** * Add a class annotation. If this is the first class annotation added, it * becomes the primary class annotation. * * @param classDescriptor * the class to add * @return this object */ @Nonnull public BugInstance addClass(ClassDescriptor classDescriptor) { add(ClassAnnotation.fromClassDescriptor(classDescriptor)); return this; } /** * Add a class annotation. If this is the first class annotation added, it * becomes the primary class annotation. * * @param jclass * the JavaClass object for the class * @return this object */ @Nonnull public BugInstance addClass(JavaClass jclass) { addClass(jclass.getClassName()); return this; } /** * Add a class annotation for the class that the visitor is currently * visiting. * * @param visitor * the BetterVisitor * @return this object */ @Nonnull public BugInstance addClass(PreorderVisitor visitor) { String className = visitor.getDottedClassName(); addClass(className); return this; } /** * Add a class annotation for the superclass of the class the visitor is * currently visiting. * * @param visitor * the BetterVisitor * @return this object */ @Nonnull public BugInstance addSuperclass(PreorderVisitor visitor) { String className = ClassName.toDottedClassName(visitor.getSuperclassName()); addClass(className); return this; } /* * ---------------------------------------------------------------------- * Type annotation adders * ---------------------------------------------------------------------- */ /** * Add a type annotation. Handy for referring to array types. * *

* For information on type descriptors,
* see http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc. * html#14152
* or http://www.murrayc.com/learning/java/java_classfileformat.shtml# * TypeDescriptors * * @param typeDescriptor * a jvm type descriptor, such as "[I" * @return this object */ @Nonnull public BugInstance addType(String typeDescriptor) { TypeAnnotation typeAnnotation = new TypeAnnotation(typeDescriptor); add(typeAnnotation); return this; } @Nonnull public BugInstance addType(Type type) { TypeAnnotation typeAnnotation = new TypeAnnotation(type); add(typeAnnotation); return this; } @Nonnull public BugInstance addFoundAndExpectedType(Type foundType, Type expectedType) { add(new TypeAnnotation(foundType, TypeAnnotation.FOUND_ROLE)); add(new TypeAnnotation(expectedType, TypeAnnotation.EXPECTED_ROLE)); return this; } @Nonnull public BugInstance addFoundAndExpectedType(String foundType, String expectedType) { add(new TypeAnnotation(foundType, TypeAnnotation.FOUND_ROLE)); add(new TypeAnnotation(expectedType, TypeAnnotation.EXPECTED_ROLE)); return this; } @Nonnull public BugInstance addEqualsMethodUsed(ClassDescriptor expectedClass) { try { Set targets = Hierarchy2.resolveVirtualMethodCallTargets(expectedClass, "equals", "(Ljava/lang/Object;)Z", false, false); addEqualsMethodUsed(targets); } catch (ClassNotFoundException e) { AnalysisContext.reportMissingClass(e); } return this; } @Nonnull public BugInstance addEqualsMethodUsed(@CheckForNull Collection equalsMethods) { if (equalsMethods == null) { return this; } if (equalsMethods.size() < 5) { for (XMethod m : equalsMethods) { addMethod(m).describe(MethodAnnotation.METHOD_EQUALS_USED); } } else { addMethod(equalsMethods.iterator().next()).describe(MethodAnnotation.METHOD_EQUALS_USED); } return this; } @Nonnull public BugInstance addTypeOfNamedClass(@DottedClassName String typeName) { TypeAnnotation typeAnnotation = new TypeAnnotation("L" + typeName.replace('.', '/') + ";"); add(typeAnnotation); return this; } @Nonnull public BugInstance addType(ClassDescriptor c) { TypeAnnotation typeAnnotation = new TypeAnnotation(c.getSignature()); add(typeAnnotation); return this; } /* * ---------------------------------------------------------------------- * Field annotation adders * ---------------------------------------------------------------------- */ /** * Add a field annotation. * * @param className * name of the class containing the field * @param fieldName * the name of the field * @param fieldSig * type signature of the field * @param isStatic * whether or not the field is static * @return this object */ @Nonnull public BugInstance addField(String className, String fieldName, String fieldSig, boolean isStatic) { addField(new FieldAnnotation(className, fieldName, fieldSig, isStatic)); return this; } /** * Add a field annotation. * * @param className * name of the class containing the field * @param fieldName * the name of the field * @param fieldSig * type signature of the field * @param accessFlags * access flags for the field * @return this object */ @Nonnull public BugInstance addField(String className, String fieldName, String fieldSig, int accessFlags) { addField(new FieldAnnotation(className, fieldName, fieldSig, accessFlags)); return this; } @Nonnull public BugInstance addField(PreorderVisitor visitor) { FieldAnnotation fieldAnnotation = FieldAnnotation.fromVisitedField(visitor); return addField(fieldAnnotation); } /** * Add a field annotation * * @param fieldAnnotation * the field annotation * @return this object */ @Nonnull public BugInstance addField(FieldAnnotation fieldAnnotation) { add(fieldAnnotation); return this; } /** * Add a field annotation for a FieldVariable matched in a ByteCodePattern. * * @param field * the FieldVariable * @return this object */ @Nonnull public BugInstance addField(FieldVariable field) { return addField(field.getClassName(), field.getFieldName(), field.getFieldSig(), field.isStatic()); } /** * Add a field annotation for an XField. * * @param xfield * the XField * @return this object */ @Nonnull public BugInstance addOptionalField(@CheckForNull XField xfield) { if (xfield == null) { return this; } return addField(xfield.getClassName(), xfield.getName(), xfield.getSignature(), xfield.isStatic()); } /** * Add a field annotation for an XField. * * @param xfield * the XField * @return this object */ @Nonnull public BugInstance addField(XField xfield) { return addField(xfield.getClassName(), xfield.getName(), xfield.getSignature(), xfield.isStatic()); } /** * Add a field annotation for a FieldDescriptor. * * @param fieldDescriptor * the FieldDescriptor * @return this object */ @Nonnull public BugInstance addField(FieldDescriptor fieldDescriptor) { FieldAnnotation fieldAnnotation = FieldAnnotation.fromFieldDescriptor(fieldDescriptor); add(fieldAnnotation); return this; } /** * Add a field annotation for the field which has just been accessed by the * method currently being visited by given visitor. Assumes that a * getfield/putfield or getstatic/putstatic has just been seen. * * @param visitor * the DismantleBytecode object * @return this object */ @Nonnull public BugInstance addReferencedField(DismantleBytecode visitor) { FieldAnnotation f = FieldAnnotation.fromReferencedField(visitor); addField(f); return this; } /** * Add a field annotation for the field referenced by the FieldAnnotation * parameter */ @Nonnull public BugInstance addReferencedField(FieldAnnotation fa) { addField(fa); return this; } /** * Add a field annotation for the field which is being visited by given * visitor. * * @param visitor * the visitor * @return this object */ @Nonnull public BugInstance addVisitedField(PreorderVisitor visitor) { FieldAnnotation f = FieldAnnotation.fromVisitedField(visitor); addField(f); return this; } /** * Local variable adders */ @Nonnull public BugInstance addOptionalLocalVariable(DismantleBytecode dbc, OpcodeStack.Item item) { int register = item.getRegisterNumber(); if (register >= 0) { this.add(LocalVariableAnnotation.getLocalVariableAnnotation(dbc.getMethod(), register, dbc.getPC() - 1, dbc.getPC())); } return this; } /* * ---------------------------------------------------------------------- * Method annotation adders * ---------------------------------------------------------------------- */ /** * Add a method annotation. If this is the first method annotation added, it * becomes the primary method annotation. * * @param className * name of the class containing the method * @param methodName * name of the method * @param methodSig * type signature of the method * @param isStatic * true if the method is static, false otherwise * @return this object */ @Nonnull public BugInstance addMethod(String className, String methodName, String methodSig, boolean isStatic) { addMethod(MethodAnnotation.fromForeignMethod(className, methodName, methodSig, isStatic)); return this; } /** * Add a method annotation. If this is the first method annotation added, it * becomes the primary method annotation. * * @param className * name of the class containing the method * @param methodName * name of the method * @param methodSig * type signature of the method * @param accessFlags * accessFlags for the method * @return this object */ @Nonnull public BugInstance addMethod(@SlashedClassName String className, String methodName, String methodSig, int accessFlags) { addMethod(MethodAnnotation.fromForeignMethod(className, methodName, methodSig, accessFlags)); return this; } /** * Add a method annotation. If this is the first method annotation added, it * becomes the primary method annotation. If the method has source line * information, then a SourceLineAnnotation is added to the method. * * @param methodGen * the MethodGen object for the method * @param sourceFile * source file method is defined in * @return this object */ @Nonnull public BugInstance addMethod(MethodGen methodGen, String sourceFile) { String className = methodGen.getClassName(); MethodAnnotation methodAnnotation = new MethodAnnotation(className, methodGen.getName(), methodGen.getSignature(), methodGen.isStatic()); addMethod(methodAnnotation); addSourceLinesForMethod(methodAnnotation, SourceLineAnnotation.fromVisitedMethod(methodGen, sourceFile)); return this; } /** * Add a method annotation. If this is the first method annotation added, it * becomes the primary method annotation. If the method has source line * information, then a SourceLineAnnotation is added to the method. * * @param javaClass * the class the method is defined in * @param method * the method * @return this object */ @Nonnull public BugInstance addMethod(JavaClass javaClass, Method method) { MethodAnnotation methodAnnotation = new MethodAnnotation(javaClass.getClassName(), method.getName(), method.getSignature(), method.isStatic()); SourceLineAnnotation methodSourceLines = SourceLineAnnotation.forEntireMethod(javaClass, method); methodAnnotation.setSourceLines(methodSourceLines); addMethod(methodAnnotation); return this; } /** * Add a method annotation. If this is the first method annotation added, it * becomes the primary method annotation. If the method has source line * information, then a SourceLineAnnotation is added to the method. * * @param classAndMethod * JavaClassAndMethod identifying the method to add * @return this object */ @Nonnull public BugInstance addMethod(JavaClassAndMethod classAndMethod) { return addMethod(classAndMethod.getJavaClass(), classAndMethod.getMethod()); } /** * Add a method annotation for the method which the given visitor is * currently visiting. If the method has source line information, then a * SourceLineAnnotation is added to the method. * * @param visitor * the BetterVisitor * @return this object */ @Nonnull public BugInstance addMethod(PreorderVisitor visitor) { MethodAnnotation methodAnnotation = MethodAnnotation.fromVisitedMethod(visitor); addMethod(methodAnnotation); addSourceLinesForMethod(methodAnnotation, SourceLineAnnotation.fromVisitedMethod(visitor)); return this; } /** * Add a method annotation for the method which has been called by the * method currently being visited by given visitor. Assumes that the visitor * has just looked at an invoke instruction of some kind. * * @param visitor * the DismantleBytecode object * @return this object */ @Nonnull public BugInstance addCalledMethod(DismantleBytecode visitor) { return addMethod(MethodAnnotation.fromCalledMethod(visitor)).describe(MethodAnnotation.METHOD_CALLED); } @Nonnull public BugInstance addCalledMethod(XMethod m) { return addMethod(m).describe(MethodAnnotation.METHOD_CALLED); } /** * Add a method annotation. * * @param className * name of class containing called method * @param methodName * name of called method * @param methodSig * signature of called method * @param isStatic * true if called method is static, false if not * @return this object */ @Nonnull public BugInstance addCalledMethod(String className, String methodName, String methodSig, boolean isStatic) { return addMethod(MethodAnnotation.fromCalledMethod(className, methodName, methodSig, isStatic)).describe( MethodAnnotation.METHOD_CALLED); } /** * Add a method annotation for the method which is called by given * instruction. * * @param cpg * the constant pool for the method containing the call * @param inv * the InvokeInstruction * @return this object */ @Nonnull public BugInstance addCalledMethod(ConstantPoolGen cpg, InvokeInstruction inv) { String className = inv.getClassName(cpg); String methodName = inv.getMethodName(cpg); String methodSig = inv.getSignature(cpg); addMethod(className, methodName, methodSig, inv.getOpcode() == Const.INVOKESTATIC); describe(MethodAnnotation.METHOD_CALLED); return this; } /** * Add a method annotation for the method which is called by given * instruction. * * @param methodGen * the method containing the call * @param inv * the InvokeInstruction * @return this object */ @Nonnull public BugInstance addCalledMethod(MethodGen methodGen, InvokeInstruction inv) { ConstantPoolGen cpg = methodGen.getConstantPool(); return addCalledMethod(cpg, inv); } /** * Add a MethodAnnotation from an XMethod. * * @param xmethod * the XMethod * @return this object */ @Nonnull public BugInstance addMethod(XMethod xmethod) { addMethod(MethodAnnotation.fromXMethod(xmethod)); return this; } /** * Add a MethodAnnotation from an MethodDescriptor. * * @param method * the method * @return this object */ @Nonnull public BugInstance addMethod(MethodDescriptor method) { addMethod(MethodAnnotation.fromMethodDescriptor(method)); return this; } /** * Add a method annotation. If this is the first method annotation added, it * becomes the primary method annotation. * * @param methodAnnotation * the method annotation * @return this object */ @Nonnull public BugInstance addMethod(MethodAnnotation methodAnnotation) { add(methodAnnotation); return this; } /* * ---------------------------------------------------------------------- * Integer annotation adders * ---------------------------------------------------------------------- */ /** * Add an integer annotation. * * @param value * the integer value * @return this object */ @Nonnull public BugInstance addInt(int value) { add(new IntAnnotation(value)); return this; } /** * Add an annotation about a parameter * * @param index parameter index, starting from 0 * * @param role the role used to describe the parameter */ @Nonnull public BugInstance addParameterAnnotation(int index, String role) { return addInt(index + 1).describe(role); } /** * Add a String annotation. * * @param value * the String value * @return this object */ @Nonnull public BugInstance addString(String value) { add(StringAnnotation.fromRawString(value)); return this; } /** * Add a String annotation. * * @param c * the char value * @return this object */ @Nonnull public BugInstance addString(char c) { add(StringAnnotation.fromRawString(Character.toString(c))); return this; } /* * ---------------------------------------------------------------------- * Source line annotation adders * ---------------------------------------------------------------------- */ /** * Add a source line annotation. * * @param sourceLine * the source line annotation * @return this object */ @Nonnull public BugInstance addSourceLine(SourceLineAnnotation sourceLine) { add(sourceLine); return this; } /** * Add a source line annotation for instruction whose PC is given in the * method that the given visitor is currently visiting. Note that if the * method does not have line number information, then no source line * annotation will be added. * * @param visitor * a BytecodeScanningDetector that is currently visiting the * method * @param pc * bytecode offset of the instruction * @return this object */ @Nonnull public BugInstance addSourceLine(BytecodeScanningDetector visitor, int pc) { SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstruction(visitor.getClassContext(), visitor, pc); if (sourceLineAnnotation != null) { add(sourceLineAnnotation); } return this; } /** * Add a source line annotation for instruction whose PC is given in the * method that the given visitor is currently visiting. Note that if the * method does not have line number information, then no source line * annotation will be added. * * @param classContext * the ClassContext * @param visitor * a PreorderVisitor that is currently visiting the method * @param pc * bytecode offset of the instruction * @return this object */ @Nonnull public BugInstance addSourceLine(ClassContext classContext, PreorderVisitor visitor, int pc) { SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstruction(classContext, visitor, pc); if (sourceLineAnnotation != null) { add(sourceLineAnnotation); } return this; } /** * Add a source line annotation for the given instruction in the given * method. Note that if the method does not have line number information, * then no source line annotation will be added. * * @param classContext * the ClassContext * @param methodGen * the method being visited * @param sourceFile * source file the method is defined in * @param handle * the InstructionHandle containing the visited instruction * @return this object */ @Nonnull public BugInstance addSourceLine(ClassContext classContext, MethodGen methodGen, String sourceFile, @Nonnull InstructionHandle handle) { SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstruction(classContext, methodGen, sourceFile, handle); if (sourceLineAnnotation != null) { add(sourceLineAnnotation); } return this; } /** * Add a source line annotation describing a range of instructions. * * @param classContext * the ClassContext * @param methodGen * the method * @param sourceFile * source file the method is defined in * @param start * the start instruction in the range * @param end * the end instruction in the range (inclusive) * @return this object */ @Nonnull public BugInstance addSourceLine(ClassContext classContext, MethodGen methodGen, String sourceFile, InstructionHandle start, InstructionHandle end) { // Make sure start and end are really in the right order. if (start.getPosition() > end.getPosition()) { InstructionHandle tmp = start; start = end; end = tmp; } SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstructionRange(classContext, methodGen, sourceFile, start, end); if (sourceLineAnnotation != null) { add(sourceLineAnnotation); } return this; } /** * Add source line annotation for given Location in a method. * * @param classContext * the ClassContext * @param method * the Method * @param location * the Location in the method * @return this BugInstance */ @Nonnull public BugInstance addSourceLine(ClassContext classContext, Method method, Location location) { return addSourceLine(classContext, method, location.getHandle()); } /** * Add source line annotation for given Location in a method. * * @param methodDescriptor * the method * @param location * the Location in the method * @return this BugInstance */ @Nonnull public BugInstance addSourceLine(MethodDescriptor methodDescriptor, Location location) { try { IAnalysisCache analysisCache = Global.getAnalysisCache(); ClassContext classContext = analysisCache.getClassAnalysis(ClassContext.class, methodDescriptor.getClassDescriptor()); Method method = analysisCache.getMethodAnalysis(Method.class, methodDescriptor); return addSourceLine(classContext, method, location); } catch (CheckedAnalysisException e) { return addSourceLine(SourceLineAnnotation.createReallyUnknown(methodDescriptor.getClassDescriptor() .toDottedClassName())); } } /** * Add source line annotation for given Location in a method. * * @param classContext * the ClassContext * @param method * the Method * @param handle * InstructionHandle of an instruction in the method * @return this BugInstance */ @Nonnull public BugInstance addSourceLine(ClassContext classContext, Method method, InstructionHandle handle) { SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstruction(classContext, method, handle.getPosition()); if (sourceLineAnnotation != null) { add(sourceLineAnnotation); } return this; } /** * Add a source line annotation describing the source line numbers for a * range of instructions in the method being visited by the given visitor. * Note that if the method does not have line number information, then no * source line annotation will be added. * * @param visitor * a BetterVisitor which is visiting the method * @param startPC * the bytecode offset of the start instruction in the range * @param endPC * the bytecode offset of the end instruction in the range * @return this object */ @Nonnull public BugInstance addSourceLineRange(BytecodeScanningDetector visitor, int startPC, int endPC) { SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstructionRange(visitor.getClassContext(), visitor, startPC, endPC); requireNonNull(sourceLineAnnotation); add(sourceLineAnnotation); return this; } /** * Add a source line annotation describing the source line numbers for a * range of instructions in the method being visited by the given visitor. * Note that if the method does not have line number information, then no * source line annotation will be added. * * @param classContext * the ClassContext * @param visitor * a BetterVisitor which is visiting the method * @param startPC * the bytecode offset of the start instruction in the range * @param endPC * the bytecode offset of the end instruction in the range * @return this object */ @Nonnull public BugInstance addSourceLineRange(ClassContext classContext, PreorderVisitor visitor, int startPC, int endPC) { SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstructionRange(classContext, visitor, startPC, endPC); requireNonNull(sourceLineAnnotation); add(sourceLineAnnotation); return this; } /** * Add a source line annotation for instruction currently being visited by * given visitor. Note that if the method does not have line number * information, then no source line annotation will be added. * * @param visitor * a BytecodeScanningDetector visitor that is currently visiting * the instruction * @return this object */ @Nonnull public BugInstance addSourceLine(BytecodeScanningDetector visitor) { SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstruction(visitor); if (sourceLineAnnotation != null) { add(sourceLineAnnotation); } return this; } /** * Add a non-specific source line annotation. This will result in the entire * source file being displayed. * * @param className * the class name * @param sourceFile * the source file name * @return this object */ @Nonnull public BugInstance addUnknownSourceLine(String className, String sourceFile) { SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.createUnknown(className, sourceFile); if (sourceLineAnnotation != null) { add(sourceLineAnnotation); } return this; } /* * ---------------------------------------------------------------------- * Formatting support * ---------------------------------------------------------------------- */ /** * Format a string describing this bug instance. * * @return the description */ @Nonnull public String getMessageWithoutPrefix() { BugPattern bugPattern = getBugPattern(); String pattern, shortPattern; pattern = getLongDescription(); shortPattern = bugPattern.getShortDescription(); try { FindBugsMessageFormat format = new FindBugsMessageFormat(pattern); return format.format(annotationList.toArray(new BugAnnotation[0]), getPrimaryClass()); } catch (RuntimeException e) { AnalysisContext.logError("Error generating bug msg ", e); return shortPattern + " [Error generating customized description]"; } } String getLongDescription() { return getBugPattern().getLongDescription().replaceAll("BUG_PATTERN", type); } public String getAbridgedMessage() { BugPattern bugPattern = getBugPattern(); String pattern = getLongDescription().replaceAll(" in \\{1\\}", ""); String shortPattern = bugPattern.getShortDescription(); try { FindBugsMessageFormat format = new FindBugsMessageFormat(pattern); return format.format(annotationList.toArray(new BugAnnotation[0]), getPrimaryClass(), true); } catch (RuntimeException e) { AnalysisContext.logError("Error generating bug msg ", e); return shortPattern + " [Error3 generating customized description]"; } } /** * Format a string describing this bug instance. * * @return the description */ public String getMessage() { BugPattern bugPattern = getBugPattern(); String pattern = bugPattern.getAbbrev() + ": " + getLongDescription(); FindBugsMessageFormat format = new FindBugsMessageFormat(pattern); try { return format.format(annotationList.toArray(new BugAnnotation[0]), getPrimaryClass()); } catch (RuntimeException e) { AnalysisContext.logError("Error generating bug msg ", e); return bugPattern.getShortDescription() + " [Error generating customized description]"; } } /** * Format a string describing this bug pattern, with the priority and type * at the beginning. e.g. * "(High Priority Correctness) Guaranteed null pointer dereference..." */ public String getMessageWithPriorityType() { return "(" + this.getPriorityTypeString() + ") " + this.getMessage(); } public String getMessageWithPriorityTypeAbbreviation() { return this.getPriorityTypeAbbreviation() + " " + this.getMessage(); } /** * Add a description to the most recently added bug annotation. * * @param description * the description to add * @return this object */ @Nonnull public BugInstance describe(String description) { annotationList.get(annotationList.size() - 1).setDescription(description); return this; } /** * Convert to String. This method returns the "short" message describing the * bug, as opposed to the longer format returned by getMessage(). The short * format is appropriate for the tree view in a GUI, where the annotations * are listed separately as part of the overall bug instance. */ @Override public String toString() { return I18N.instance().getShortMessage(type); } /* * ---------------------------------------------------------------------- * XML Conversion support * ---------------------------------------------------------------------- */ @Override public void writeXML(XMLOutput xmlOutput) throws IOException { writeXML(xmlOutput, null, false); } public int getCWEid() { BugPattern pattern = getBugPattern(); int cweid = pattern.getCWEid(); if (cweid != 0) { return cweid; } BugCode bugCode = pattern.getBugCode(); return bugCode.getCWEid(); } public void writeXML(XMLOutput xmlOutput, BugCollection bugCollection, boolean addMessages) throws IOException { XMLAttributeList attributeList = new XMLAttributeList().addAttribute("type", type).addAttribute("priority", String.valueOf(priority)); // Always add the rank attribute. attributeList.addAttribute("rank", Integer.toString(getBugRank())); BugPattern pattern = getBugPattern(); // The bug abbreviation and pattern category are // emitted into the XML for informational purposes only. // (The information is redundant, but might be useful // for processing tools that want to make sense of // bug instances without looking at the plugin descriptor.) attributeList.addAttribute("abbrev", pattern.getAbbrev()); attributeList.addAttribute("category", pattern.getCategory()); if (addMessages) { // Add a uid attribute, if we have a unique id. attributeList.addAttribute("instanceHash", getInstanceHash()); attributeList.addAttribute("instanceOccurrenceNum", Integer.toString(getInstanceOccurrenceNum())); attributeList.addAttribute("instanceOccurrenceMax", Integer.toString(getInstanceOccurrenceMax())); int cweid = getCWEid(); if (cweid != 0) { attributeList.addAttribute("cweid", Integer.toString(cweid)); } } else if (oldInstanceHash != null && !isInstanceHashConsistent()) { attributeList.addAttribute("oldInstanceHash", oldInstanceHash); } if (firstVersion > 0) { attributeList.addAttribute("first", Long.toString(firstVersion)); } if (lastVersion >= 0) { attributeList.addAttribute("last", Long.toString(lastVersion)); } if (introducedByChangeOfExistingClass) { attributeList.addAttribute("introducedByChange", "true"); } if (removedByChangeOfPersistingClass) { attributeList.addAttribute("removedByChange", "true"); } xmlOutput.openTag(ELEMENT_NAME, attributeList); if (addMessages) { BugPattern bugPattern = getBugPattern(); xmlOutput.openTag("ShortMessage"); xmlOutput.writeText(bugPattern.getShortDescription()); xmlOutput.closeTag("ShortMessage"); xmlOutput.openTag("LongMessage"); if (FindBugsDisplayFeatures.isAbridgedMessages()) { xmlOutput.writeText(this.getAbridgedMessage()); } else { xmlOutput.writeText(this.getMessageWithoutPrefix()); } xmlOutput.closeTag("LongMessage"); } Map primaryAnnotations; if (addMessages) { primaryAnnotations = new IdentityHashMap<>(); primaryAnnotations.put(getPrimarySourceLineAnnotation(), null); primaryAnnotations.put(getPrimaryClass(), null); primaryAnnotations.put(getPrimaryField(), null); primaryAnnotations.put(getPrimaryMethod(), null); } else { primaryAnnotations = Collections.emptyMap(); } boolean foundSourceAnnotation = false; for (BugAnnotation annotation : annotationList) { if (annotation instanceof SourceLineAnnotation) { foundSourceAnnotation = true; } annotation.writeXML(xmlOutput, addMessages, primaryAnnotations.containsKey(annotation)); } if (!foundSourceAnnotation && addMessages) { SourceLineAnnotation synth = getPrimarySourceLineAnnotation(); synth.setSynthetic(true); synth.writeXML(xmlOutput, addMessages, false); } if (propertyListHead != null) { List props = new ArrayList<>(); for (BugProperty prop = propertyListHead; prop != null; prop = prop.getNext()) { props.add(prop); } Collections.sort(props, (o1, o2) -> o1.getName().compareTo(o2.getName())); for (BugProperty prop : props) { prop.writeXML(xmlOutput); } } xmlOutput.closeTag(ELEMENT_NAME); } private int ageInDays(BugCollection bugCollection, long firstSeen) { long age = bugCollection.getAnalysisTimestamp() - firstSeen; if (age < 0) { age = 0; } int ageInDays = (int) (age / 1000 / 3600 / 24); return ageInDays; } /* * ---------------------------------------------------------------------- * Implementation * ---------------------------------------------------------------------- */ public BugInstance addOptionalAnnotation(@CheckForNull BugAnnotation annotation) { if (annotation == null) { return this; } return add(annotation); } public BugInstance addOptionalAnnotation(@CheckForNull BugAnnotation annotation, String role) { if (annotation == null) { return this; } return add(annotation).describe(role); } private void addJavaAnnotationNames(BugAnnotation annotation) { try { IAnalysisCache analysisCache = Global.getAnalysisCache(); if (analysisCache == null) { // Note: IAnalysisCache is not available during testing or spotbugs:gui return; } PackageMemberAnnotation pma = (PackageMemberAnnotation) annotation; ClassDescriptor classDescriptor = pma.getClassDescriptor(); ClassContext classContext = analysisCache.getClassAnalysis(ClassContext.class, classDescriptor); JavaClass javaClass = classContext.getJavaClass(); AnnotationEntry[] annotationEntries = javaClass.getAnnotationEntries(); List javaAnnotationNames = Arrays.asList(annotationEntries).stream().map((AnnotationEntry ae) -> { // map annotation entry type to dotted class name, for example // Lorg/immutables/value/Generated; -> org.immutables.value.Generated String annotationType = ae.getAnnotationType().substring(1).replace("/", ".").replace(";", ""); return annotationType; }).collect(Collectors.toList()); pma.setJavaAnnotationNames(javaAnnotationNames); } catch (Exception e) { LOG.debug(e.getMessage(), e); } } public BugInstance add(@Nonnull BugAnnotation annotation) { requireNonNull(annotation, "Missing BugAnnotation!"); // The java annotations for the class were not stored before, // thus we add a post-hook to lookup the java annotations for // bug annotation types that have a class descriptor. Then // post-analysis matcher can easily filter based on the java // annotations (without having to look at the bytecode again). if (annotation instanceof PackageMemberAnnotation) { addJavaAnnotationNames(annotation); } // Add to list annotationList.add(annotation); // This object is being modified, so the cached hashcode // must be invalidated cachedHashCode = INVALID_HASH_CODE; return this; } public BugInstance addSomeSourceForTopTwoStackValues(ClassContext classContext, Method method, Location location) { int pc = location.getHandle().getPosition(); try { OpcodeStack stack = OpcodeStackScanner.getStackAt(classContext.getJavaClass(), method, pc); BugAnnotation a1 = getSomeSource(classContext, method, location, stack, 1); BugAnnotation a0 = getSomeSource(classContext, method, location, stack, 0); addOptionalUniqueAnnotations(a0, a1); } catch (UnreachableCodeException e) { if (SystemProperties.ASSERTIONS_ENABLED) { AnalysisContext.logError(e.getMessage(), e); } assert true; } return this; } public BugInstance addSourceForTopStackValue(ClassContext classContext, Method method, Location location) { BugAnnotation b = getSourceForTopStackValue(classContext, method, location); return this.addOptionalAnnotation(b); } public static @CheckForNull BugAnnotation getSourceForTopStackValue(ClassContext classContext, Method method, Location location) { return getSourceForStackValue(classContext, method, location, 0); } public static @CheckForNull BugAnnotation getSourceForStackValue(ClassContext classContext, Method method, Location location, int depth) { try { int pc = location.getHandle().getPosition(); OpcodeStack stack = OpcodeStackScanner.getStackAt(classContext.getJavaClass(), method, pc); BugAnnotation a0 = getSomeSource(classContext, method, location, stack, depth); return a0; } catch (UnreachableCodeException e) { if (SystemProperties.ASSERTIONS_ENABLED) { AnalysisContext.logError(e.getMessage(), e); } return null; } } public static @CheckForNull BugAnnotation getSomeSource(ClassContext classContext, Method method, Location location, OpcodeStack stack, int stackPos) { if (stack.isTop()) { return null; } int pc = location.getHandle().getPosition(); try { BugAnnotation result = ValueNumberSourceInfo.getFromValueNumber(classContext, method, location, stackPos); if (result != null) { return result; } } catch (DataflowAnalysisException e) { AnalysisContext.logError("Couldn't find value source", e); } catch (CFGBuilderException e) { AnalysisContext.logError("Couldn't find value source", e); } return getValueSource(stack.getStackItem(stackPos), method, pc); } public static @CheckForNull BugAnnotation getValueSource(OpcodeStack.Item item, Method method, int pc) { LocalVariableAnnotation lv = LocalVariableAnnotation.getLocalVariableAnnotation(method, item, pc); if (lv != null && lv.isNamed()) { return lv; } BugAnnotation a = getFieldOrMethodValueSource(item); if (a != null) { return a; } Object c = item.getConstant(); if (c instanceof String) { a = new StringAnnotation((String) c); a.setDescription(StringAnnotation.STRING_CONSTANT_ROLE); return a; } if (c instanceof Integer && !item.isArray()) { a = new IntAnnotation((Integer) c); a.setDescription(IntAnnotation.INT_VALUE); return a; } return null; } public BugInstance addValueSource(@CheckForNull OpcodeStack.Item item, DismantleBytecode dbc) { if (item != null) { addValueSource(item, dbc.getMethod(), dbc.getPC()); } return this; } public BugInstance addValueSource(OpcodeStack.Item item, Method method, int pc) { addOptionalAnnotation(getValueSource(item, method, pc)); return this; } public BugInstance addFieldOrMethodValueSource(OpcodeStack.Item item) { addOptionalAnnotation(getFieldOrMethodValueSource(item)); return this; } public BugInstance addOptionalUniqueAnnotations(BugAnnotation... annotations) { HashSet added = new HashSet<>(); for (BugAnnotation a : annotations) { if (a != null && added.add(a)) { add(a); } } return this; } public boolean tryAddingOptionalUniqueAnnotations(BugAnnotation... annotations) { HashSet added = new HashSet<>(); for (BugAnnotation a : annotations) { if (a != null && added.add(a)) { add(a); } } return !added.isEmpty(); } public BugInstance addOptionalUniqueAnnotationsWithFallback(BugAnnotation fallback, BugAnnotation... annotations) { HashSet added = new HashSet<>(); for (BugAnnotation a : annotations) { if (a != null && added.add(a)) { add(a); } } if (added.isEmpty()) { add(fallback); } return this; } public static @CheckForNull BugAnnotation getFieldOrMethodValueSource(@CheckForNull OpcodeStack.Item item) { if (item == null) { return null; } XField xField = item.getXField(); if (xField != null) { FieldAnnotation a = FieldAnnotation.fromXField(xField); a.setDescription(FieldAnnotation.LOADED_FROM_ROLE); return a; } XMethod xMethod = item.getReturnValueOf(); if (xMethod != null) { MethodAnnotation a = MethodAnnotation.fromXMethod(xMethod); a.setDescription(MethodAnnotation.METHOD_RETURN_VALUE_OF); return a; } return null; } private void addSourceLinesForMethod(MethodAnnotation methodAnnotation, SourceLineAnnotation sourceLineAnnotation) { if (sourceLineAnnotation != null) { // Note: we don't add the source line annotation directly to // the bug instance. Instead, we stash it in the MethodAnnotation. // It is much more useful there, and it would just be distracting // if it were displayed in the UI, since it would compete for // attention // with the actual bug location source line annotation (which is // much // more important and interesting). methodAnnotation.setSourceLines(sourceLineAnnotation); } } @Override public int hashCode() { if (cachedHashCode == INVALID_HASH_CODE) { int hashcode = type.hashCode() + priority; Iterator i = annotationIterator(); while (i.hasNext()) { hashcode += i.next().hashCode(); } if (hashcode == INVALID_HASH_CODE) { hashcode = INVALID_HASH_CODE + 1; } cachedHashCode = hashcode; } return cachedHashCode; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof BugInstance)) { return false; } BugInstance other = (BugInstance) o; if (!type.equals(other.type) || priority != other.priority) { return false; } if (annotationList.size() != other.annotationList.size()) { return false; } int numAnnotations = annotationList.size(); for (int i = 0; i < numAnnotations; ++i) { BugAnnotation lhs = annotationList.get(i); BugAnnotation rhs = other.annotationList.get(i); if (!lhs.equals(rhs)) { return false; } } return true; } @Override public int compareTo(BugInstance other) { int cmp; cmp = type.compareTo(other.type); if (cmp != 0) { return cmp; } cmp = priority - other.priority; if (cmp != 0) { return cmp; } // Compare BugAnnotations lexicographically int pfxLen = Math.min(annotationList.size(), other.annotationList.size()); for (int i = 0; i < pfxLen; ++i) { BugAnnotation lhs = annotationList.get(i); BugAnnotation rhs = other.annotationList.get(i); cmp = lhs.compareTo(rhs); if (cmp != 0) { return cmp; } } // All elements in prefix were the same, // so use number of elements to decide return annotationList.size() - other.annotationList.size(); } public void setFirstVersion(long firstVersion) { if (lastVersion >= 0 && firstVersion > lastVersion) { throw new IllegalArgumentException(firstVersion + ".." + lastVersion); } this.firstVersion = firstVersion; } public void clearHistory() { setFirstVersion(0); setLastVersion(-1); setIntroducedByChangeOfExistingClass(false); setRemovedByChangeOfPersistingClass(false); } public long getFirstVersion() { return firstVersion; } public void setHistory(BugInstance from) { long first = from.getFirstVersion(); long last = from.getLastVersion(); if (first > 0 && last >= 0 && first > last) { throw new IllegalArgumentException("from has version range " + first + "..." + last + " in " + from.getBugPattern() + "\n" + from.getMessage()); } setFirstVersion(first); setLastVersion(last); this.removedByChangeOfPersistingClass = from.removedByChangeOfPersistingClass; this.introducedByChangeOfExistingClass = from.introducedByChangeOfExistingClass; } public void setLastVersion(long lastVersion) { if (lastVersion >= 0 && firstVersion > lastVersion) { throw new IllegalArgumentException(firstVersion + ".." + lastVersion); } this.lastVersion = lastVersion; } /** Mark the bug instance is being alive (still present in the last version) */ public void setLive() { this.lastVersion = -1; } public long getLastVersion() { return lastVersion; } public boolean isDead() { return lastVersion != -1; } public void setIntroducedByChangeOfExistingClass(boolean introducedByChangeOfExistingClass) { this.introducedByChangeOfExistingClass = introducedByChangeOfExistingClass; } public boolean isIntroducedByChangeOfExistingClass() { return introducedByChangeOfExistingClass; } public void setRemovedByChangeOfPersistingClass(boolean removedByChangeOfPersistingClass) { this.removedByChangeOfPersistingClass = removedByChangeOfPersistingClass; } public boolean isRemovedByChangeOfPersistingClass() { return removedByChangeOfPersistingClass; } public void setInstanceHash(String instanceHash) { this.instanceHash = instanceHash; } public void setOldInstanceHash(String oldInstanceHash) { this.oldInstanceHash = oldInstanceHash; } public String getInstanceHash() { String hash = instanceHash; if (hash != null) { return hash; } MessageDigest digest = Util.getMD5Digest(); String key = getInstanceKey(); byte[] data; data = digest.digest(key.getBytes(StandardCharsets.UTF_8)); hash = new BigInteger(1, data).toString(16); instanceHash = hash; return hash; } public boolean isInstanceHashConsistent() { return oldInstanceHash == null || getInstanceHash().equals(oldInstanceHash); } public void setInstanceOccurrenceNum(int instanceOccurrenceNum) { this.instanceOccurrenceNum = instanceOccurrenceNum; } public int getInstanceOccurrenceNum() { return instanceOccurrenceNum; } public void setInstanceOccurrenceMax(int instanceOccurrenceMax) { this.instanceOccurrenceMax = instanceOccurrenceMax; } public int getInstanceOccurrenceMax() { return instanceOccurrenceMax; } @CheckForNull public DetectorFactory getDetectorFactory() { return detectorFactory; } private void optionalAdd(Collection c, BugAnnotation a) { if (a != null) { c.add(a); } } public List getAnnotationsForMessage(boolean showContext) { ArrayList result = new ArrayList<>(); HashSet primaryAnnotations = new HashSet<>(); // This ensures the order of the primary annotations of the bug FieldAnnotation primeField = getPrimaryField(); MethodAnnotation primeMethod = getPrimaryMethod(); ClassAnnotation primeClass = getPrimaryClass(); SourceLineAnnotation primarySourceLineAnnotation = getPrimarySourceLineAnnotation(); optionalAdd(primaryAnnotations, primarySourceLineAnnotation); optionalAdd(primaryAnnotations, primeMethod); optionalAdd(primaryAnnotations, primeField); optionalAdd(primaryAnnotations, primeClass); if ((showContext || !SourceLineAnnotation.DEFAULT_ROLE.equals(primarySourceLineAnnotation.getDescription()))) { result.add(primarySourceLineAnnotation); } if (primeMethod != null && (showContext || !MethodAnnotation.DEFAULT_ROLE.equals(primeMethod.getDescription()))) { result.add(primeMethod); } optionalAdd(result, primeField); String fieldClass = ""; String methodClass = ""; if (primeField != null) { fieldClass = primeField.getClassName(); } if (primeMethod != null) { methodClass = primeMethod.getClassName(); } if (showContext && (primaryAnnotations.size() < 2) || (!(primeClass.getClassName().equals(fieldClass) || primeClass.getClassName().equals(methodClass)))) { optionalAdd(result, primeClass); } for (BugAnnotation b : getAnnotations()) { if (primaryAnnotations.contains(b)) { continue; } if (b instanceof LocalVariableAnnotation && !((LocalVariableAnnotation) b).isNamed()) { continue; } if (b instanceof SourceLineAnnotation && ((SourceLineAnnotation) b).isUnknown()) { continue; } result.add(b); } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy