/*
* 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.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nonnull;
import org.apache.bcel.Constants;
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 edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
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.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.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.cloud.Cloud;
import edu.umd.cs.findbugs.cloud.Cloud.UserDesignation;
import edu.umd.cs.findbugs.core.Priorities;
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:
* <p/>
* <ul>
* <li>the type, which is a string indicating what kind of bug it is; used as a
* key for the FindBugsMessages resource bundle
* <li>the priority; how likely this instance is to actually be a bug
* <li>a list of <em>annotations</em>
* </ul>
* <p/>
* 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".
* <p/>
* <p>
* BugInstance objects are built up by calling a string of <code>add</code>
* 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<BugInstance>, XMLWriteable, Serializable, Cloneable {
private static final long serialVersionUID = 1L;
private final String type;
private int priority;
private final ArrayList<BugAnnotation> annotationList;
private int cachedHashCode;
private @CheckForNull
BugDesignation userDesignation;
private BugProperty propertyListHead, propertyListTail;
private String oldInstanceHash;
private String instanceHash;
private int instanceOccurrenceNum;
private int instanceOccurrenceMax;
private DetectorFactory detectorFactory;
private final AtomicReference<XmlProps> xmlProps = new AtomicReference<XmlProps>();
/*
* 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 = 0;
private long lastVersion = -1;
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;
/**
* This value is used to indicate whether BugInstances should be
* reprioritized very low, when the BugPattern is marked as experimental
*/
private static boolean adjustExperimental = false;
private static Set<String> missingBugTypes = Collections.synchronizedSet(new HashSet<String>());
public static DateFormat firstSeenXMLFormat() {
return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.ENGLISH);
}
/**
* Constructor.
*
* @param type
* the bug type
* @param priority
* the bug priority
*/
public BugInstance(String type, int priority) {
this.type = type.intern();
this.priority = priority;
annotationList = new ArrayList<BugAnnotation>(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 IllegalArgumentException(msg));
}
} else
this.priority += p.getPriorityAdjustment();
if (adjustExperimental && isExperimental())
this.priority = Priorities.EXP_PRIORITY;
boundPriority();
}
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<BugProperty> 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
detectorFactory = DetectorFactoryCollection.instance().getFactoryByClassName(detector.getClass().getName());
if (detectorFactory != null) {
this.priority += detectorFactory.getPriorityAdjustment();
boundPriority();
BugPattern bugPattern = getBugPattern();
if (SystemProperties.ASSERTIONS_ENABLED && !bugPattern.getCategory().equals("EXPERIMENTAL")
&& !detectorFactory.getReportedBugPatterns().contains(bugPattern))
AnalysisContext.logError(detectorFactory.getShortName() + " doesn't note that it reports " + bugPattern);
}
}
}
/**
* 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
detectorFactory = DetectorFactoryCollection.instance().getFactoryByClassName(detector.getDetectorClassName());
if (detectorFactory != null) {
this.priority += detectorFactory.getPriorityAdjustment();
boundPriority();
if (SystemProperties.ASSERTIONS_ENABLED && !detectorFactory.getReportedBugPatterns().contains(getBugPattern()))
AnalysisContext.logError(detectorFactory.getShortName() + " doesn't note that it reports " + getBugPattern());
}
}
}
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);
}
/**
* 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;
if (bugPattern == null)
categoryString = "Unknown category for " + getType();
else
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();
if (bugPattern == null)
return "?";
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 != null) && pattern.isExperimental();
}
/**
* Get the primary class annotation, which indicates where the bug occurs.
*/
public ClassAnnotation getPrimaryClass() {
ClassAnnotation result = findPrimaryAnnotationOfType(ClassAnnotation.class);
if (result == null) {
System.out.println("huh");
result = findPrimaryAnnotationOfType(ClassAnnotation.class);
}
return result;
}
/**
* Get the primary method annotation, which indicates where the bug occurs.
*/
public MethodAnnotation getPrimaryMethod() {
return findPrimaryAnnotationOfType(MethodAnnotation.class);
}
/**
* Get the primary method annotation, which indicates where the bug occurs.
*/
public FieldAnnotation getPrimaryField() {
return findPrimaryAnnotationOfType(FieldAnnotation.class);
}
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
*/
private <T extends BugAnnotation> T findPrimaryAnnotationOfType(Class<T> cls) {
T bestMatch = null;
for (Iterator<BugAnnotation> i = annotationIterator(); i.hasNext();) {
BugAnnotation annotation = i.next();
if (cls.isAssignableFrom(annotation.getClass())) {
if (annotation.getDescription().endsWith("DEFAULT"))
return cls.cast(annotation);
else
bestMatch = cls.cast(annotation);
}
}
return bestMatch;
}
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
*/
public SourceLineAnnotation getPrimarySourceLineAnnotation() {
// Highest priority: return the first top level source line annotation
for (BugAnnotation annotation : annotationList) {
if (annotation instanceof SourceLineAnnotation
&& annotation.getDescription().equals(SourceLineAnnotation.DEFAULT_ROLE)
&& !((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 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<BugAnnotation> annotationIterator() {
return annotationList.iterator();
}
/**
* Get an Iterator over all bug annotations.
*/
public List<? extends BugAnnotation> getAnnotations() {
return annotationList;
}
/** Get the first bug annotation with the specified class and role; return null if no
* such annotation exists;
* @param role
* @return
*/
public @CheckForNull <A extends BugAnnotation> A getAnnotationWithRole(Class<A> c, String role) {
for(BugAnnotation a : annotationList) {
if (c.isInstance(a) && Util.nullSafeEquals(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 != null ? pattern.getAbbrev() : "<unknown bug pattern>";
}
/** clear the user designation. */
public void clearUserDesignation() {
userDesignation = null;
}
/**
* set the user designation object. This will clobber any existing
* annotationText (or any other BugDesignation field).
*/
@Deprecated
public void setUserDesignation(BugDesignation bd) {
userDesignation = bd;
}
/**
* return the user designation object, which may be null.
*
* A previous calls to getSafeUserDesignation(), setAnnotationText(), or
* setUserDesignation() will ensure it will be non-null [barring an
* intervening setUserDesignation(null)].
*
* @see #getNonnullUserDesignation()
*/
@Deprecated
@Nullable
public BugDesignation getUserDesignation() {
return userDesignation;
}
/**
* return the user designation object, creating one if necessary. So calling
* <code>getSafeUserDesignation().setDesignation("HARMLESS")</code> will
* always work without the possibility of a NullPointerException.
*
* @see #getUserDesignation()
*/
@Deprecated
@NonNull
public BugDesignation getNonnullUserDesignation() {
if (userDesignation == null)
userDesignation = new BugDesignation();
return userDesignation;
}
/**
* Get the user designation key. E.g., "MOSTLY_HARMLESS", "CRITICAL",
* "NOT_A_BUG", etc.
*
* If the user designation object is null,returns UNCLASSIFIED.
*
* To set the user designation key, call
* <code>getSafeUserDesignation().setDesignation("HARMLESS")</code>.
*
* @see I18N#getUserDesignation(String key)
* @return the user designation key
*/
@NonNull
public String getUserDesignationKey() {
if (userDesignation == null)
return BugDesignation.UNCLASSIFIED;
return userDesignation.getDesignationKey();
}
public @CheckForNull
String getUserName() {
if (userDesignation == null)
return null;
return userDesignation.getUser();
}
public long getUserTimestamp() {
if (userDesignation == null)
return Long.MAX_VALUE;
return userDesignation.getTimestamp();
}
@NonNull
public int getUserDesignationKeyIndex() {
return I18N.instance().getUserDesignationKeys(true).indexOf(getUserDesignationKey());
}
/**
* @param key
* @param bugCollection
* TODO
*/
public void setUserDesignationKey(String key, @CheckForNull BugCollection bugCollection) {
BugDesignation userDesignation = getNonnullUserDesignation();
if (userDesignation.getDesignationKey().equals(key))
return;
userDesignation.setDesignationKey(key);
Cloud plugin = bugCollection != null ? bugCollection.getCloud() : null;
if (plugin != null)
plugin.storeUserAnnotation(this);
}
/**
* s
*
* @param index
* @param bugCollection
* TODO
*/
public void setUserDesignationKeyIndex(int index, @CheckForNull BugCollection bugCollection) {
setUserDesignationKey(I18N.instance().getUserDesignationKey(index), bugCollection);
}
/**
* Set the user annotation text.
*
* @param annotationText
* the user annotation text
* @param bugCollection
* TODO
*/
public void setAnnotationText(String annotationText, @CheckForNull BugCollection bugCollection) {
final BugDesignation u = getNonnullUserDesignation();
String existingText = u.getAnnotationText();
if (existingText != null && existingText.equals(annotationText))
return;
u.setAnnotationText(annotationText);
Cloud plugin = bugCollection != null ? bugCollection.getCloud() : null;
if (plugin != null)
plugin.storeUserAnnotation(this);
}
/**
* Get the user annotation text.
*
* @return the user annotation text
*/
@NonNull
public String getAnnotationText() {
BugDesignation userDesignation = this.userDesignation;
if (userDesignation == null)
return "";
String s = userDesignation.getAnnotationText();
if (s == null)
return "";
return s;
}
public void setUser(String user) {
BugDesignation userDesignation = getNonnullUserDesignation();
userDesignation.setUser(user);
}
public void setUserAnnotationTimestamp(long timestamp) {
BugDesignation userDesignation = getNonnullUserDesignation();
userDesignation.setTimestamp(timestamp);
}
/**
* Determine whether or not the annotation text contains the given word.
*
* @param word
* the word
* @return true if the annotation text contains the word, false otherwise
*/
public boolean annotationTextContainsWord(String word) {
return getTextAnnotationWords().contains(word);
}
/**
* Get set of words in the text annotation.
*/
public Set<String> getTextAnnotationWords() {
HashSet<String> result = new HashSet<String>();
StringTokenizer tok = new StringTokenizer(getAnnotationText(), " \t\r\n\f.,:;-");
while (tok.hasMoreTokens()) {
result.add(tok.nextToken());
}
return result;
}
public boolean hasXmlProps() {
XmlProps props = xmlProps.get();
return props != null;
}
public XmlProps getXmlProps() {
XmlProps props = xmlProps.get();
if (props != null)
return props;
props = new XmlProps();
while (xmlProps.get() == null)
xmlProps.compareAndSet(null, props);
return xmlProps.get();
}
public boolean hasSomeUserAnnotation() {
return !getAnnotationText().equals("")
|| !getUserDesignationKey().equals(BugDesignation.UNCLASSIFIED);
}
/*
* ----------------------------------------------------------------------
* Property accessors
* ----------------------------------------------------------------------
*/
private class BugPropertyIterator implements Iterator<BugProperty> {
private BugProperty prev, cur;
private boolean removed;
/*
* (non-Javadoc)
*
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return findNext() != null;
}
/*
* (non-Javadoc)
*
* @see java.util.Iterator#next()
*/
public BugProperty next() {
BugProperty next = findNext();
if (next == null)
throw new NoSuchElementException();
prev = cur;
cur = next;
removed = false;
return cur;
}
/*
* (non-Javadoc)
*
* @see java.util.Iterator#remove()
*/
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<BugProperty> 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
*/
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
*/
public BugInstance addAnnotations(Collection<? extends BugAnnotation> annotationCollection) {
for (BugAnnotation annotation : annotationCollection) {
add(annotation);
}
return this;
}
/*
* ----------------------------------------------------------------------
* Combined annotation adders
* ----------------------------------------------------------------------
*/
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
*/
public BugInstance addClassAndMethod(PreorderVisitor visitor) {
addClass(visitor);
addMethod(visitor);
return this;
}
/**
* Add class and method annotations for given method.
*
* @param methodAnnotation
* the method
* @return this object
*/
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
*/
public BugInstance addClassAndMethod(MethodGen methodGen, String sourceFile) {
addClass(methodGen.getClassName());
addMethod(methodGen, sourceFile);
return this;
}
/**
* Add class and method annotations for given class and method.
*
* @param javaClass
* the class
* @param method
* the method
* @return this object
*/
public BugInstance addClassAndMethod(JavaClass javaClass, Method method) {
addClass(javaClass.getClassName());
addMethod(javaClass, method);
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
*/
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
*/
public BugInstance addClass(@DottedClassName 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
*/
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
*/
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
*/
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
*/
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
*/
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.
*
* <p>
* For information on type descriptors, <br>
* see http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.
* html#14152 <br>
* or http://www.murrayc.com/learning/java/java_classfileformat.shtml#
* TypeDescriptors
*
* @param typeDescriptor
* a jvm type descriptor, such as "[I"
* @return this object
*/
public BugInstance addType(String typeDescriptor) {
TypeAnnotation typeAnnotation = new TypeAnnotation(typeDescriptor);
add(typeAnnotation);
return this;
}
public BugInstance addType(Type type) {
TypeAnnotation typeAnnotation = new TypeAnnotation(type);
add(typeAnnotation);
return this;
}
public BugInstance addFoundAndExpectedType(Type foundType, Type expectedType) {
add(new TypeAnnotation(foundType, TypeAnnotation.FOUND_ROLE));
add(new TypeAnnotation(expectedType, TypeAnnotation.EXPECTED_ROLE));
return this;
}
public BugInstance addFoundAndExpectedType(String foundType, String expectedType) {
add(new TypeAnnotation(foundType, TypeAnnotation.FOUND_ROLE));
add(new TypeAnnotation(expectedType, TypeAnnotation.EXPECTED_ROLE));
return this;
}
public BugInstance addEqualsMethodUsed(ClassDescriptor expectedClass) {
try {
Set<XMethod> targets = Hierarchy2.resolveVirtualMethodCallTargets(expectedClass, "equals", "(Ljava/lang/Object;)Z",
false, false);
addEqualsMethodUsed(targets);
} catch (ClassNotFoundException e) {
AnalysisContext.reportMissingClass(e);
}
return this;
}
public BugInstance addEqualsMethodUsed(@CheckForNull Collection<XMethod> 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;
}
public BugInstance addTypeOfNamedClass(@DottedClassName String typeName) {
TypeAnnotation typeAnnotation = new TypeAnnotation("L" + typeName.replace('.', '/') + ";");
add(typeAnnotation);
return this;
}
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
*/
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
*/
public BugInstance addField(String className, String fieldName, String fieldSig, int accessFlags) {
addField(new FieldAnnotation(className, fieldName, fieldSig, accessFlags));
return this;
}
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
public BugInstance addVisitedField(PreorderVisitor visitor) {
FieldAnnotation f = FieldAnnotation.fromVisitedField(visitor);
addField(f);
return this;
}
/**
* Local variable adders
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
public BugInstance addCalledMethod(DismantleBytecode visitor) {
return addMethod(MethodAnnotation.fromCalledMethod(visitor)).describe(MethodAnnotation.METHOD_CALLED);
}
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
*/
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
*/
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() == Constants.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
*/
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
*/
public BugInstance addMethod(XMethod xmethod) {
addMethod(MethodAnnotation.fromXMethod(xmethod));
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
*/
public BugInstance addMethod(MethodAnnotation methodAnnotation) {
add(methodAnnotation);
return this;
}
/*
* ----------------------------------------------------------------------
* Integer annotation adders
* ----------------------------------------------------------------------
*/
/**
* Add an integer annotation.
*
* @param value
* the integer value
* @return this object
*/
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
*/
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
*/
public BugInstance addString(String value) {
add(StringAnnotation.fromRawString(value));
return this;
}
/**
* Add a String annotation.
*
* @param c
* the char value
* @return this object
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
public BugInstance addSourceLineRange(BytecodeScanningDetector visitor, int startPC, int endPC) {
SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstructionRange(visitor.getClassContext(),
visitor, startPC, endPC);
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 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
*/
public BugInstance addSourceLineRange(ClassContext classContext, PreorderVisitor visitor, int startPC, int endPC) {
SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation.fromVisitedInstructionRange(classContext, visitor,
startPC, endPC);
if (sourceLineAnnotation != null)
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
*/
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
*/
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
*/
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[annotationList.size()]), 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, shortPattern;
if (bugPattern == null)
shortPattern = pattern = "Error2: missing bug pattern for key " + type;
else {
pattern = getLongDescription().replaceAll(" in \\{1\\}", "");
shortPattern = bugPattern.getShortDescription();
}
try {
FindBugsMessageFormat format = new FindBugsMessageFormat(pattern);
return format.format(annotationList.toArray(new BugAnnotation[annotationList.size()]), 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[annotationList.size()]), 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
*/
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
* ----------------------------------------------------------------------
*/
public void writeXML(XMLOutput xmlOutput) throws IOException {
writeXML(xmlOutput, null, false);
}
public void writeXML(XMLOutput xmlOutput, BugCollection bugCollection, boolean addMessages) throws IOException {
XMLAttributeList attributeList = new XMLAttributeList().addAttribute("type", type).addAttribute("priority",
String.valueOf(priority));
BugPattern pattern = getBugPattern();
if (pattern != null) {
// 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()));
attributeList.addAttribute("rank", Integer.toString(getBugRank()));
} 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");
if (bugCollection != null) {
Cloud cloud = bugCollection.getCloudLazily();
if (cloud != null && cloud.communicationInitiated()) {
long firstSeen = cloud.getFirstSeen(this);
attributeList.addAttribute("firstSeen", firstSeenXMLFormat().format(firstSeen));
int reviews = cloud.getNumberReviewers(this);
UserDesignation consensus = cloud.getConsensusDesignation(this);
if (!cloud.isInCloud(this)) {
attributeList.addAttribute("isInCloud", "false");
}
if (reviews > 0) {
attributeList.addAttribute("reviews", Integer.toString(reviews));
if (consensus != UserDesignation.UNCLASSIFIED)
attributeList.addAttribute("consensus", consensus.toString());
}
if (addMessages) {
long age = bugCollection.getAnalysisTimestamp() - firstSeen;
if (age < 0)
age = 0;
int ageInDays = (int) (age / 1000 / 3600 / 24);
attributeList.addAttribute("ageInDays", Integer.toString(ageInDays));
if (reviews > 0 && consensus != UserDesignation.UNCLASSIFIED) {
if (consensus.score() < 0)
attributeList.addAttribute("notAProblem", "true");
if (consensus.score() > 0)
attributeList.addAttribute("shouldFix", "true");
}
}
}
}
xmlOutput.openTag(ELEMENT_NAME, attributeList);
// write out the user's designation & comment
if (userDesignation != null) {
userDesignation.writeXML(xmlOutput);
}
if (addMessages) {
BugPattern bugPattern = getBugPattern();
xmlOutput.openTag("ShortMessage");
xmlOutput.writeText(bugPattern != null ? bugPattern.getShortDescription() : this.toString());
xmlOutput.closeTag("ShortMessage");
xmlOutput.openTag("LongMessage");
if (FindBugsDisplayFeatures.isAbridgedMessages())
xmlOutput.writeText(this.getAbridgedMessage());
else
xmlOutput.writeText(this.getMessageWithoutPrefix());
xmlOutput.closeTag("LongMessage");
}
Map<BugAnnotation, Void> primaryAnnotations;
if (addMessages) {
primaryAnnotations = new IdentityHashMap<BugAnnotation, Void>();
primaryAnnotations.put(getPrimarySourceLineAnnotation(), null);
primaryAnnotations.put(getPrimaryClass(), null);
primaryAnnotations.put(getPrimaryField(), null);
primaryAnnotations.put(getPrimaryMethod(), null);
} else {
primaryAnnotations = Collections.<BugAnnotation, Void> 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();
if (synth != null) {
synth.setSynthetic(true);
synth.writeXML(xmlOutput, addMessages, false);
}
}
if (propertyListHead != null) {
BugProperty prop = propertyListHead;
while (prop != null) {
prop.writeXML(xmlOutput);
prop = prop.getNext();
}
}
xmlOutput.closeTag(ELEMENT_NAME);
}
private static final String ELEMENT_NAME = "BugInstance";
/*
* ----------------------------------------------------------------------
* 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);
}
public BugInstance add(@Nonnull BugAnnotation annotation) {
if (annotation == null)
throw new IllegalArgumentException("Missing BugAnnotation!");
// 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();
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);
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) {
int pc = location.getHandle().getPosition();
OpcodeStack stack = OpcodeStackScanner.getStackAt(classContext.getJavaClass(), method, pc);
BugAnnotation a0 = getSomeSource(classContext, method, location, stack, depth);
return a0;
}
public static @CheckForNull
BugAnnotation getSomeSource(ClassContext classContext, Method method, Location location, OpcodeStack stack, int stackPos) {
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) {
a = new IntAnnotation((Integer) c);
a.setDescription(IntAnnotation.INT_VALUE);
return a;
}
return null;
}
public BugInstance addValueSource(OpcodeStack.Item item, DismantleBytecode dbc) {
return addValueSource(item, dbc.getMethod(), dbc.getPC());
}
public BugInstance addValueSource(OpcodeStack.Item item, Method method, int pc) {
addOptionalAnnotation(getValueSource(item, method, pc));
return this;
}
/**
* @param item
*/
public BugInstance addFieldOrMethodValueSource(OpcodeStack.Item item) {
addOptionalAnnotation(getFieldOrMethodValueSource(item));
return this;
}
public BugInstance addOptionalUniqueAnnotations(BugAnnotation... annotations) {
HashSet<BugAnnotation> added = new HashSet<BugAnnotation>();
for (BugAnnotation a : annotations)
if (a != null && added.add(a))
add(a);
return this;
}
public BugInstance addOptionalUniqueAnnotationsWithFallback(BugAnnotation fallback, BugAnnotation... annotations) {
HashSet<BugAnnotation> added = new HashSet<BugAnnotation>();
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<BugAnnotation> 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;
}
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();
}
/**
* @param firstVersion
* The firstVersion to set.
*/
public void setFirstVersion(long firstVersion) {
if (lastVersion >= 0 && firstVersion > lastVersion)
throw new IllegalArgumentException(firstVersion + ".." + lastVersion);
this.firstVersion = firstVersion;
}
/**
* @return Returns the firstVersion.
*/
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;
}
/**
* @param lastVersion
* The lastVersion to set.
*/
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;
}
/**
* @return Returns the lastVersion.
*/
public long getLastVersion() {
return lastVersion;
}
public boolean isDead() {
return lastVersion != -1;
}
/**
* @param introducedByChangeOfExistingClass
* The introducedByChangeOfExistingClass to set.
*/
public void setIntroducedByChangeOfExistingClass(boolean introducedByChangeOfExistingClass) {
this.introducedByChangeOfExistingClass = introducedByChangeOfExistingClass;
}
/**
* @return Returns the introducedByChangeOfExistingClass.
*/
public boolean isIntroducedByChangeOfExistingClass() {
return introducedByChangeOfExistingClass;
}
/**
* @param removedByChangeOfPersistingClass
* The removedByChangeOfPersistingClass to set.
*/
public void setRemovedByChangeOfPersistingClass(boolean removedByChangeOfPersistingClass) {
this.removedByChangeOfPersistingClass = removedByChangeOfPersistingClass;
}
/**
* @return Returns the removedByChangeOfPersistingClass.
*/
public boolean isRemovedByChangeOfPersistingClass() {
return removedByChangeOfPersistingClass;
}
/**
* @param instanceHash
* The instanceHash to set.
*/
public void setInstanceHash(String instanceHash) {
this.instanceHash = instanceHash;
}
/**
* @param oldInstanceHash
* The oldInstanceHash to set.
*/
public void setOldInstanceHash(String oldInstanceHash) {
this.oldInstanceHash = oldInstanceHash;
}
/**
* @return Returns the instanceHash.
*/
public String getInstanceHash() {
String hash = instanceHash;
if (hash != null)
return hash;
MessageDigest digest = Util.getMD5Digest();
String key = getInstanceKey();
byte[] data;
try {
data = digest.digest(key.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
hash = new BigInteger(1, data).toString(16);
instanceHash = hash;
return hash;
}
public boolean isInstanceHashConsistent() {
return oldInstanceHash == null || getInstanceHash().equals(oldInstanceHash);
}
/**
* @param instanceOccurrenceNum
* The instanceOccurrenceNum to set.
*/
public void setInstanceOccurrenceNum(int instanceOccurrenceNum) {
this.instanceOccurrenceNum = instanceOccurrenceNum;
}
/**
* @return Returns the instanceOccurrenceNum.
*/
public int getInstanceOccurrenceNum() {
return instanceOccurrenceNum;
}
/**
* @param instanceOccurrenceMax
* The instanceOccurrenceMax to set.
*/
public void setInstanceOccurrenceMax(int instanceOccurrenceMax) {
this.instanceOccurrenceMax = instanceOccurrenceMax;
}
/**
* @return Returns the instanceOccurrenceMax.
*/
public int getInstanceOccurrenceMax() {
return instanceOccurrenceMax;
}
public DetectorFactory getDetectorFactory() {
return detectorFactory;
}
private void optionalAdd(Collection<BugAnnotation> c, BugAnnotation a) {
if (a != null)
c.add(a);
}
public Iterable<BugAnnotation> getAnnotationsForMessage(boolean showContext) {
ArrayList<BugAnnotation> result = new ArrayList<BugAnnotation>();
HashSet<BugAnnotation> primaryAnnotations = new HashSet<BugAnnotation>();
// 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 (primarySourceLineAnnotation != null
&& (showContext || !primarySourceLineAnnotation.getDescription().equals(SourceLineAnnotation.DEFAULT_ROLE)))
result.add(primarySourceLineAnnotation);
if (primeMethod != null && (showContext || !primeMethod.getDescription().equals(MethodAnnotation.DEFAULT_ROLE)))
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;
}
/**
* These are properties read from an analysis XML file. These properties
* should not take precedence over information from the Cloud - rather, this
* should be used when the cloud is unavailable, or when communicating with
* it is not desired for performance or complexity reasons.
*/
static public class XmlProps {
private Date firstSeen = null;
private int reviewCount = 0;
private boolean isInCloud = true;
private String consensus;
public Date getFirstSeen() {
return firstSeen;
}
public int getReviewCount() {
return reviewCount;
}
public String getConsensus() {
return consensus;
}
public boolean isInCloud() {
return isInCloud;
}
public void setFirstSeen(Date firstSeen) {
this.firstSeen = firstSeen;
}
public void setReviewCount(int reviewCount) {
this.reviewCount = reviewCount;
}
public void setConsensus(String consensus) {
this.consensus = consensus;
}
public void setIsInCloud(boolean inCloud) {
isInCloud = inCloud;
}
}
}
// vim:ts=4