0001: /*
0002: * FindBugs - Find bugs in Java programs
0003: * Copyright (C) 2003-2005 University of Maryland
0004: *
0005: * This library is free software; you can redistribute it and/or
0006: * modify it under the terms of the GNU Lesser General Public
0007: * License as published by the Free Software Foundation; either
0008: * version 2.1 of the License, or (at your option) any later version.
0009: *
0010: * This library is distributed in the hope that it will be useful,
0011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0013: * Lesser General Public License for more details.
0014: *
0015: * You should have received a copy of the GNU Lesser General Public
0016: * License along with this library; if not, write to the Free Software
0017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0018: */
0019:
0020: package edu.umd.cs.findbugs;
0021:
0022: import java.io.IOException;
0023: import java.io.Serializable;
0024: import java.math.BigInteger;
0025: import java.security.MessageDigest;
0026: import java.util.ArrayList;
0027: import java.util.Collection;
0028: import java.util.Collections;
0029: import java.util.HashSet;
0030: import java.util.Iterator;
0031: import java.util.NoSuchElementException;
0032: import java.util.Set;
0033: import java.util.StringTokenizer;
0034:
0035: import org.apache.bcel.Constants;
0036: import org.apache.bcel.classfile.JavaClass;
0037: import org.apache.bcel.classfile.Method;
0038: import org.apache.bcel.generic.ConstantPoolGen;
0039: import org.apache.bcel.generic.InstructionHandle;
0040: import org.apache.bcel.generic.InvokeInstruction;
0041: import org.apache.bcel.generic.MethodGen;
0042: import org.objectweb.asm.tree.ClassNode;
0043:
0044: import edu.umd.cs.findbugs.annotations.CheckForNull;
0045: import edu.umd.cs.findbugs.annotations.NonNull;
0046: import edu.umd.cs.findbugs.annotations.Nullable;
0047: import edu.umd.cs.findbugs.ba.AnalysisContext;
0048: import edu.umd.cs.findbugs.ba.ClassContext;
0049: import edu.umd.cs.findbugs.ba.JavaClassAndMethod;
0050: import edu.umd.cs.findbugs.ba.Location;
0051: import edu.umd.cs.findbugs.ba.XFactory;
0052: import edu.umd.cs.findbugs.ba.XField;
0053: import edu.umd.cs.findbugs.ba.XMethod;
0054: import edu.umd.cs.findbugs.ba.bcp.FieldVariable;
0055: import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
0056: import edu.umd.cs.findbugs.classfile.ClassDescriptor;
0057: import edu.umd.cs.findbugs.classfile.FieldDescriptor;
0058: import edu.umd.cs.findbugs.classfile.Global;
0059: import edu.umd.cs.findbugs.classfile.IAnalysisCache;
0060: import edu.umd.cs.findbugs.classfile.MethodDescriptor;
0061: import edu.umd.cs.findbugs.util.ClassName;
0062: import edu.umd.cs.findbugs.visitclass.DismantleBytecode;
0063: import edu.umd.cs.findbugs.visitclass.PreorderVisitor;
0064: import edu.umd.cs.findbugs.xml.XMLAttributeList;
0065: import edu.umd.cs.findbugs.xml.XMLOutput;
0066:
0067: /**
0068: * An instance of a bug pattern.
0069: * A BugInstance consists of several parts:
0070: * <p/>
0071: * <ul>
0072: * <li> the type, which is a string indicating what kind of bug it is;
0073: * used as a key for the FindBugsMessages resource bundle
0074: * <li> the priority; how likely this instance is to actually be a bug
0075: * <li> a list of <em>annotations</em>
0076: * </ul>
0077: * <p/>
0078: * The annotations describe classes, methods, fields, source locations,
0079: * and other relevant context information about the bug instance.
0080: * Every BugInstance must have at least one ClassAnnotation, which
0081: * describes the class in which the instance was found. This is the
0082: * "primary class annotation".
0083: * <p/>
0084: * <p> BugInstance objects are built up by calling a string of <code>add</code>
0085: * methods. (These methods all "return this", so they can be chained).
0086: * Some of the add methods are specialized to get information automatically from
0087: * a BetterVisitor or DismantleBytecode object.
0088: *
0089: * @author David Hovemeyer
0090: * @see BugAnnotation
0091: */
0092: public class BugInstance implements Comparable<BugInstance>,
0093: XMLWriteableWithMessages, Serializable, Cloneable {
0094: private static final long serialVersionUID = 1L;
0095:
0096: private String type;
0097: private int priority;
0098: private ArrayList<BugAnnotation> annotationList;
0099: private int cachedHashCode;
0100: private @CheckForNull
0101: BugDesignation userDesignation;
0102: private BugProperty propertyListHead, propertyListTail;
0103: private String uniqueId;
0104: private String oldInstanceHash;
0105: private String instanceHash;
0106: private int instanceOccurrenceNum;
0107: private int instanceOccurrenceMax;
0108:
0109: /*
0110: * The following fields are used for tracking Bug instances across multiple versions of software.
0111: * They are meaningless in a BugCollection for just one version of software.
0112: */
0113: private long firstVersion = 0;
0114: private long lastVersion = -1;
0115: private boolean introducedByChangeOfExistingClass;
0116: private boolean removedByChangeOfPersistingClass;
0117:
0118: /**
0119: * This value is used to indicate that the cached hashcode
0120: * is invalid, and should be recomputed.
0121: */
0122: private static final int INVALID_HASH_CODE = 0;
0123:
0124: /**
0125: * This value is used to indicate whether BugInstances should be reprioritized very low,
0126: * when the BugPattern is marked as experimental
0127: */
0128: private static boolean adjustExperimental = false;
0129:
0130: private static Set<String> bugTypes = Collections
0131: .synchronizedSet(new HashSet<String>());
0132:
0133: /**
0134: * Constructor.
0135: *
0136: * @param type the bug type
0137: * @param priority the bug priority
0138: */
0139: public BugInstance(String type, int priority) {
0140: this .type = type;
0141: this .priority = priority;
0142: annotationList = new ArrayList<BugAnnotation>(4);
0143: cachedHashCode = INVALID_HASH_CODE;
0144:
0145: if (bugTypes.add(type)) {
0146: BugPattern p = I18N.instance().lookupBugPattern(type);
0147: if (p == null) {
0148: String msg = "Can't find definition of bug type "
0149: + type;
0150: AnalysisContext.logError(msg,
0151: new IllegalArgumentException(msg));
0152: }
0153: }
0154: if (adjustExperimental && isExperimental())
0155: this .priority = Detector.EXP_PRIORITY;
0156: boundPriority();
0157: }
0158:
0159: private void boundPriority() {
0160: priority = boundedPriority(priority);
0161: }
0162:
0163: @Override
0164: public Object clone() {
0165: BugInstance dup;
0166:
0167: try {
0168: dup = (BugInstance) super .clone();
0169:
0170: // Do deep copying of mutable objects
0171: for (int i = 0; i < dup.annotationList.size(); ++i) {
0172: dup.annotationList.set(i,
0173: (BugAnnotation) dup.annotationList.get(i)
0174: .clone());
0175: }
0176: dup.propertyListHead = dup.propertyListTail = null;
0177: for (Iterator<BugProperty> i = propertyIterator(); i
0178: .hasNext();) {
0179: dup.addProperty((BugProperty) i.next().clone());
0180: }
0181:
0182: return dup;
0183: } catch (CloneNotSupportedException e) {
0184: throw new AssertionError(e);
0185: }
0186: }
0187:
0188: /**
0189: * Create a new BugInstance.
0190: * This is the constructor that should be used by Detectors.
0191: *
0192: * @param detector the Detector that is reporting the BugInstance
0193: * @param type the bug type
0194: * @param priority the bug priority
0195: */
0196: public BugInstance(Detector detector, String type, int priority) {
0197: this (type, priority);
0198:
0199: if (detector != null) {
0200: // Adjust priority if required
0201: DetectorFactory factory = DetectorFactoryCollection
0202: .instance().getFactoryByClassName(
0203: detector.getClass().getName());
0204: if (factory != null) {
0205: this .priority += factory.getPriorityAdjustment();
0206: boundPriority();
0207: }
0208: }
0209:
0210: }
0211:
0212: /**
0213: * Create a new BugInstance.
0214: * This is the constructor that should be used by Detectors.
0215: *
0216: * @param detector the Detector2 that is reporting the BugInstance
0217: * @param type the bug type
0218: * @param priority the bug priority
0219: */
0220: public BugInstance(Detector2 detector, String type, int priority) {
0221: this (type, priority);
0222:
0223: if (detector != null) {
0224: // Adjust priority if required
0225: DetectorFactory factory = DetectorFactoryCollection
0226: .instance().getFactoryByClassName(
0227: detector.getDetectorClassName());
0228: if (factory != null) {
0229: this .priority += factory.getPriorityAdjustment();
0230: boundPriority();
0231: }
0232: }
0233:
0234: }
0235:
0236: public static void setAdjustExperimental(boolean adjust) {
0237: adjustExperimental = adjust;
0238: }
0239:
0240: /* ----------------------------------------------------------------------
0241: * Accessors
0242: * ---------------------------------------------------------------------- */
0243:
0244: /**
0245: * Get the bug type.
0246: */
0247: public String getType() {
0248: return type;
0249: }
0250:
0251: /**
0252: * Get the BugPattern.
0253: */
0254: public @NonNull
0255: BugPattern getBugPattern() {
0256: BugPattern result = I18N.instance().lookupBugPattern(getType());
0257: if (result != null)
0258: return result;
0259: AnalysisContext
0260: .logError("Unable to find description of bug pattern "
0261: + getType());
0262: result = I18N.instance().lookupBugPattern("UNKNOWN");
0263: if (result != null)
0264: return result;
0265: return BugPattern.REALLY_UNKNOWN;
0266: }
0267:
0268: /**
0269: * Get the bug priority.
0270: */
0271: public int getPriority() {
0272: return priority;
0273: }
0274:
0275: /**
0276: * Get a string describing the bug priority and type.
0277: * e.g. "High Priority Correctness"
0278: * @return a string describing the bug priority and type
0279: */
0280: public String getPriorityTypeString() {
0281: String priorityString = getPriorityString();
0282: BugPattern bugPattern = this .getBugPattern();
0283: //then get the category and put everything together
0284: String categoryString;
0285: if (bugPattern == null)
0286: categoryString = "Unknown category for " + getType();
0287: else
0288: categoryString = I18N.instance().getBugCategoryDescription(
0289: bugPattern.getCategory());
0290: return priorityString + " Priority " + categoryString;
0291: //TODO: internationalize the word "Priority"
0292: }
0293:
0294: public String getPriorityTypeAbbreviation() {
0295: String priorityString = getPriorityAbbreviation();
0296: return priorityString + " " + getCategoryAbbrev();
0297: }
0298:
0299: public String getCategoryAbbrev() {
0300: BugPattern bugPattern = getBugPattern();
0301: if (bugPattern == null)
0302: return "?";
0303: return bugPattern.getCategoryAbbrev();
0304: }
0305:
0306: public String getPriorityString() {
0307: //first, get the priority
0308: int value = this .getPriority();
0309: String priorityString;
0310: if (value == Detector.HIGH_PRIORITY)
0311: priorityString = edu.umd.cs.findbugs.L10N.getLocalString(
0312: "sort.priority_high", "High");
0313: else if (value == Detector.NORMAL_PRIORITY)
0314: priorityString = edu.umd.cs.findbugs.L10N.getLocalString(
0315: "sort.priority_normal", "Medium");
0316: else if (value == Detector.LOW_PRIORITY)
0317: priorityString = edu.umd.cs.findbugs.L10N.getLocalString(
0318: "sort.priority_low", "Low");
0319: else if (value == Detector.EXP_PRIORITY)
0320: priorityString = edu.umd.cs.findbugs.L10N.getLocalString(
0321: "sort.priority_experimental", "Experimental");
0322: else
0323: priorityString = edu.umd.cs.findbugs.L10N.getLocalString(
0324: "sort.priority_ignore", "Ignore"); // This probably shouldn't ever happen, but what the hell, let's be complete
0325: return priorityString;
0326: }
0327:
0328: public String getPriorityAbbreviation() {
0329: return getPriorityString().substring(0, 1);
0330: }
0331:
0332: /**
0333: * Set the bug priority.
0334: */
0335: public void setPriority(int p) {
0336: priority = boundedPriority(p);
0337: }
0338:
0339: private int boundedPriority(int p) {
0340: return Math.max(Detector.HIGH_PRIORITY, Math.min(
0341: Detector.IGNORE_PRIORITY, p));
0342: }
0343:
0344: public void raisePriority() {
0345: priority = boundedPriority(priority - 1);
0346:
0347: }
0348:
0349: public void lowerPriority() {
0350: priority = boundedPriority(priority + 1);
0351: }
0352:
0353: public void lowerPriorityALot() {
0354: priority = boundedPriority(priority + 2);
0355: }
0356:
0357: /**
0358: * Is this bug instance the result of an experimental detector?
0359: */
0360: public boolean isExperimental() {
0361: BugPattern pattern = getBugPattern();
0362: return (pattern != null) && pattern.isExperimental();
0363: }
0364:
0365: /**
0366: * Get the primary class annotation, which indicates where the bug occurs.
0367: */
0368: public ClassAnnotation getPrimaryClass() {
0369: return findAnnotationOfType(ClassAnnotation.class);
0370: }
0371:
0372: /**
0373: * Get the primary method annotation, which indicates where the bug occurs.
0374: */
0375: public MethodAnnotation getPrimaryMethod() {
0376: return findAnnotationOfType(MethodAnnotation.class);
0377: }
0378:
0379: /**
0380: * Get the primary method annotation, which indicates where the bug occurs.
0381: */
0382: public FieldAnnotation getPrimaryField() {
0383: return findAnnotationOfType(FieldAnnotation.class);
0384: }
0385:
0386: public BugInstance lowerPriorityIfDeprecated() {
0387: MethodAnnotation m = getPrimaryMethod();
0388: if (m != null && XFactory.createXMethod(m).isDeprecated())
0389: lowerPriority();
0390: FieldAnnotation f = getPrimaryField();
0391: if (f != null && XFactory.createXField(f).isDeprecated())
0392: lowerPriority();
0393: return this ;
0394: }
0395:
0396: /**
0397: * Find the first BugAnnotation in the list of annotations
0398: * that is the same type or a subtype as the given Class parameter.
0399: *
0400: * @param cls the Class parameter
0401: * @return the first matching BugAnnotation of the given type,
0402: * or null if there is no such BugAnnotation
0403: */
0404: private <T extends BugAnnotation> T findAnnotationOfType(
0405: Class<T> cls) {
0406: for (Iterator<BugAnnotation> i = annotationIterator(); i
0407: .hasNext();) {
0408: BugAnnotation annotation = i.next();
0409: if (cls.isAssignableFrom(annotation.getClass()))
0410: return (T) annotation;
0411: }
0412: return null;
0413: }
0414:
0415: public LocalVariableAnnotation getPrimaryLocalVariableAnnotation() {
0416: for (BugAnnotation annotation : annotationList)
0417: if (annotation instanceof LocalVariableAnnotation)
0418: return (LocalVariableAnnotation) annotation;
0419: return null;
0420: }
0421:
0422: /**
0423: * Get the primary source line annotation.
0424: * There is guaranteed to be one (unless some Detector constructed
0425: * an invalid BugInstance).
0426: *
0427: * @return the source line annotation
0428: */
0429: public SourceLineAnnotation getPrimarySourceLineAnnotation() {
0430: // Highest priority: return the first top level source line annotation
0431: for (BugAnnotation annotation : annotationList) {
0432: if (annotation instanceof SourceLineAnnotation)
0433: return (SourceLineAnnotation) annotation;
0434: }
0435:
0436: // Next: Try primary method, primary field, primary class
0437: SourceLineAnnotation srcLine;
0438: if ((srcLine = inspectPackageMemberSourceLines(getPrimaryMethod())) != null)
0439: return srcLine;
0440: if ((srcLine = inspectPackageMemberSourceLines(getPrimaryField())) != null)
0441: return srcLine;
0442: if ((srcLine = inspectPackageMemberSourceLines(getPrimaryClass())) != null)
0443: return srcLine;
0444:
0445: // Last resort: throw exception
0446: throw new IllegalStateException(
0447: "BugInstance must contain at least one class, method, or field annotation");
0448: }
0449:
0450: public String getInstanceKey() {
0451: StringBuffer buf = new StringBuffer(type);
0452: for (BugAnnotation annotation : annotationList) {
0453: if (annotation instanceof SourceLineAnnotation
0454: || annotation instanceof MethodAnnotation
0455: && !annotation.isSignificant()) {
0456: // do nothing
0457: } else {
0458: buf.append(":");
0459: buf.append(annotation.format("hash", null));
0460: }
0461: }
0462: return buf.toString();
0463: }
0464:
0465: /**
0466: * If given PackageMemberAnnotation is non-null, return its
0467: * SourceLineAnnotation.
0468: *
0469: * @param packageMember
0470: * a PackageMemberAnnotation
0471: * @return the PackageMemberAnnotation's SourceLineAnnotation, or null if
0472: * there is no SourceLineAnnotation
0473: */
0474: private SourceLineAnnotation inspectPackageMemberSourceLines(
0475: PackageMemberAnnotation packageMember) {
0476: return (packageMember != null) ? packageMember.getSourceLines()
0477: : null;
0478: }
0479:
0480: /**
0481: * Get an Iterator over all bug annotations.
0482: */
0483: public Iterator<BugAnnotation> annotationIterator() {
0484: return annotationList.iterator();
0485: }
0486:
0487: /**
0488: * Get the abbreviation of this bug instance's BugPattern.
0489: * This is the same abbreviation used by the BugCode which
0490: * the BugPattern is a particular species of.
0491: */
0492: public String getAbbrev() {
0493: BugPattern pattern = getBugPattern();
0494: return pattern != null ? pattern.getAbbrev()
0495: : "<unknown bug pattern>";
0496: }
0497:
0498: /** set the user designation object. This will clobber any
0499: * existing annotationText (or any other BugDesignation field). */
0500: public void setUserDesignation(BugDesignation bd) {
0501: userDesignation = bd;
0502: }
0503:
0504: /** return the user designation object, which may be null.
0505: *
0506: * A previous calls to getSafeUserDesignation(), setAnnotationText(),
0507: * or setUserDesignation() will ensure it will be non-null
0508: * [barring an intervening setUserDesignation(null)].
0509: * @see #getNonnullUserDesignation() */
0510: @Nullable
0511: public BugDesignation getUserDesignation() {
0512: return userDesignation;
0513: }
0514:
0515: /** return the user designation object, creating one if
0516: * necessary. So calling
0517: * <code>getSafeUserDesignation().setDesignation("HARMLESS")</code>
0518: * will always work without the possibility of a NullPointerException.
0519: * @see #getUserDesignation() */
0520: @NonNull
0521: public BugDesignation getNonnullUserDesignation() {
0522: if (userDesignation == null)
0523: userDesignation = new BugDesignation();
0524: return userDesignation;
0525: }
0526:
0527: /** Get the user designation key.
0528: * E.g., "MOSTLY_HARMLESS", "CRITICAL", "NOT_A_BUG", etc.
0529: *
0530: * If the user designation object is null,returns UNCLASSIFIED.
0531: *
0532: * To set the user designation key, call
0533: * <code>getSafeUserDesignation().setDesignation("HARMLESS")</code>.
0534: *
0535: * @see I18N#getUserDesignation(String key)
0536: * @return the user designation key
0537: */
0538: @NonNull
0539: public String getUserDesignationKey() {
0540: BugDesignation userDesignation = this .userDesignation;
0541: if (userDesignation == null)
0542: return BugDesignation.UNCLASSIFIED;
0543: return userDesignation.getDesignationKey();
0544: }
0545:
0546: /**
0547: * Set the user annotation text.
0548: *
0549: * @param annotationText the user annotation text
0550: */
0551: public void setAnnotationText(String annotationText) {
0552: getNonnullUserDesignation().setAnnotationText(annotationText);
0553: }
0554:
0555: /**
0556: * Get the user annotation text.
0557: *
0558: * @return the user annotation text
0559: */
0560: @NonNull
0561: public String getAnnotationText() {
0562: BugDesignation userDesignation = this .userDesignation;
0563: if (userDesignation == null)
0564: return "";
0565: String s = userDesignation.getAnnotationText();
0566: if (s == null)
0567: return "";
0568: return s;
0569: }
0570:
0571: /**
0572: * Determine whether or not the annotation text contains
0573: * the given word.
0574: *
0575: * @param word the word
0576: * @return true if the annotation text contains the word, false otherwise
0577: */
0578: public boolean annotationTextContainsWord(String word) {
0579: return getTextAnnotationWords().contains(word);
0580: }
0581:
0582: /**
0583: * Get set of words in the text annotation.
0584: */
0585: public Set<String> getTextAnnotationWords() {
0586: HashSet<String> result = new HashSet<String>();
0587:
0588: StringTokenizer tok = new StringTokenizer(getAnnotationText(),
0589: " \t\r\n\f.,:;-");
0590: while (tok.hasMoreTokens()) {
0591: result.add(tok.nextToken());
0592: }
0593: return result;
0594: }
0595:
0596: /**
0597: * Get the BugInstance's unique id.
0598: *
0599: * @return the unique id, or null if no unique id has been assigned
0600: *
0601: * Deprecated, since it isn't persistent
0602: */
0603: @Deprecated
0604: public String getUniqueId() {
0605: return uniqueId;
0606: }
0607:
0608: /**
0609: * Set the unique id of the BugInstance.
0610: *
0611: * @param uniqueId the unique id
0612: *
0613: * * Deprecated, since it isn't persistent
0614: */
0615: @Deprecated
0616: void setUniqueId(String uniqueId) {
0617: this .uniqueId = uniqueId;
0618: }
0619:
0620: /* ----------------------------------------------------------------------
0621: * Property accessors
0622: * ---------------------------------------------------------------------- */
0623:
0624: private class BugPropertyIterator implements Iterator<BugProperty> {
0625: private BugProperty prev, cur;
0626: private boolean removed;
0627:
0628: /* (non-Javadoc)
0629: * @see java.util.Iterator#hasNext()
0630: */
0631: public boolean hasNext() {
0632: return findNext() != null;
0633: }
0634:
0635: /* (non-Javadoc)
0636: * @see java.util.Iterator#next()
0637: */
0638: public BugProperty next() {
0639: BugProperty next = findNext();
0640: if (next == null)
0641: throw new NoSuchElementException();
0642: prev = cur;
0643: cur = next;
0644: removed = false;
0645: return cur;
0646: }
0647:
0648: /* (non-Javadoc)
0649: * @see java.util.Iterator#remove()
0650: */
0651: public void remove() {
0652: if (cur == null || removed)
0653: throw new IllegalStateException();
0654: if (prev == null) {
0655: propertyListHead = cur.getNext();
0656: } else {
0657: prev.setNext(cur.getNext());
0658: }
0659: if (cur == propertyListTail) {
0660: propertyListTail = prev;
0661: }
0662: removed = true;
0663: }
0664:
0665: private BugProperty findNext() {
0666: return cur == null ? propertyListHead : cur.getNext();
0667: }
0668:
0669: }
0670:
0671: /**
0672: * Get value of given property.
0673: *
0674: * @param name name of the property to get
0675: * @return the value of the named property, or null if
0676: * the property has not been set
0677: */
0678: public String getProperty(String name) {
0679: BugProperty prop = lookupProperty(name);
0680: return prop != null ? prop.getValue() : null;
0681: }
0682:
0683: /**
0684: * Get value of given property, returning given default
0685: * value if the property has not been set.
0686: *
0687: * @param name name of the property to get
0688: * @param defaultValue default value to return if propery is not set
0689: * @return the value of the named property, or the default
0690: * value if the property has not been set
0691: */
0692: public String getProperty(String name, String defaultValue) {
0693: String value = getProperty(name);
0694: return value != null ? value : defaultValue;
0695: }
0696:
0697: /**
0698: * Get an Iterator over the properties defined in this BugInstance.
0699: *
0700: * @return Iterator over properties
0701: */
0702: public Iterator<BugProperty> propertyIterator() {
0703: return new BugPropertyIterator();
0704: }
0705:
0706: /**
0707: * Set value of given property.
0708: *
0709: * @param name name of the property to set
0710: * @param value the value of the property
0711: * @return this object, so calls can be chained
0712: */
0713: public BugInstance setProperty(String name, String value) {
0714: BugProperty prop = lookupProperty(name);
0715: if (prop != null) {
0716: prop.setValue(value);
0717: } else {
0718: prop = new BugProperty(name, value);
0719: addProperty(prop);
0720: }
0721: return this ;
0722: }
0723:
0724: /**
0725: * Look up a property by name.
0726: *
0727: * @param name name of the property to look for
0728: * @return the BugProperty with the given name,
0729: * or null if the property has not been set
0730: */
0731: public BugProperty lookupProperty(String name) {
0732: BugProperty prop = propertyListHead;
0733:
0734: while (prop != null) {
0735: if (prop.getName().equals(name))
0736: break;
0737: prop = prop.getNext();
0738: }
0739:
0740: return prop;
0741: }
0742:
0743: /**
0744: * Delete property with given name.
0745: *
0746: * @param name name of the property to delete
0747: * @return true if a property with that name was deleted,
0748: * or false if there is no such property
0749: */
0750: public boolean deleteProperty(String name) {
0751: BugProperty prev = null;
0752: BugProperty prop = propertyListHead;
0753:
0754: while (prop != null) {
0755: if (prop.getName().equals(name))
0756: break;
0757: prev = prop;
0758: prop = prop.getNext();
0759: }
0760:
0761: if (prop != null) {
0762: if (prev != null) {
0763: // Deleted node in interior or at tail of list
0764: prev.setNext(prop.getNext());
0765: } else {
0766: // Deleted node at head of list
0767: propertyListHead = prop.getNext();
0768: }
0769:
0770: if (prop.getNext() == null) {
0771: // Deleted node at end of list
0772: propertyListTail = prev;
0773: }
0774:
0775: return true;
0776: } else {
0777: // No such property
0778: return false;
0779: }
0780: }
0781:
0782: private void addProperty(BugProperty prop) {
0783: if (propertyListTail != null) {
0784: propertyListTail.setNext(prop);
0785: propertyListTail = prop;
0786: } else {
0787: propertyListHead = propertyListTail = prop;
0788: }
0789: prop.setNext(null);
0790: }
0791:
0792: /* ----------------------------------------------------------------------
0793: * Generic BugAnnotation adders
0794: * ---------------------------------------------------------------------- */
0795:
0796: /**
0797: * Add a Collection of BugAnnotations.
0798: *
0799: * @param annotationCollection Collection of BugAnnotations
0800: */
0801: public BugInstance addAnnotations(
0802: Collection<? extends BugAnnotation> annotationCollection) {
0803: for (BugAnnotation annotation : annotationCollection) {
0804: add(annotation);
0805: }
0806: return this ;
0807: }
0808:
0809: /* ----------------------------------------------------------------------
0810: * Combined annotation adders
0811: * ---------------------------------------------------------------------- */
0812:
0813: public BugInstance addClassAndMethod(
0814: MethodDescriptor methodDescriptor) {
0815: addClass(methodDescriptor.getSlashedClassName());
0816: add(MethodAnnotation.fromMethodDescriptor(methodDescriptor));
0817: return this ;
0818: }
0819:
0820: /**
0821: * Add a class annotation and a method annotation for the class and method
0822: * which the given visitor is currently visiting.
0823: *
0824: * @param visitor the BetterVisitor
0825: * @return this object
0826: */
0827: public BugInstance addClassAndMethod(PreorderVisitor visitor) {
0828: addClass(visitor);
0829: addMethod(visitor);
0830: return this ;
0831: }
0832:
0833: /**
0834: * Add class and method annotations for given method.
0835: *
0836: * @param methodAnnotation the method
0837: * @return this object
0838: */
0839: public BugInstance addClassAndMethod(
0840: MethodAnnotation methodAnnotation) {
0841: addClass(methodAnnotation.getClassName());
0842: addMethod(methodAnnotation);
0843: return this ;
0844: }
0845:
0846: /**
0847: * Add class and method annotations for given method.
0848: *
0849: * @param methodGen the method
0850: * @param sourceFile source file the method is defined in
0851: * @return this object
0852: */
0853: public BugInstance addClassAndMethod(MethodGen methodGen,
0854: String sourceFile) {
0855: addClass(methodGen.getClassName());
0856: addMethod(methodGen, sourceFile);
0857: return this ;
0858: }
0859:
0860: /**
0861: * Add class and method annotations for given class and method.
0862: *
0863: * @param javaClass the class
0864: * @param method the method
0865: * @return this object
0866: */
0867: public BugInstance addClassAndMethod(JavaClass javaClass,
0868: Method method) {
0869: addClass(javaClass.getClassName());
0870: addMethod(javaClass, method);
0871: return this ;
0872: }
0873:
0874: /* ----------------------------------------------------------------------
0875: * Class annotation adders
0876: * ---------------------------------------------------------------------- */
0877:
0878: /**
0879: * Add a class annotation. If this is the first class annotation added,
0880: * it becomes the primary class annotation.
0881: *
0882: * @param className the name of the class
0883: * @param sourceFileName the source file of the class
0884: * @return this object
0885: * @deprecated use addClass(String) instead
0886: */
0887: public BugInstance addClass(String className, String sourceFileName) {
0888: ClassAnnotation classAnnotation = new ClassAnnotation(className);
0889: add(classAnnotation);
0890: return this ;
0891: }
0892:
0893: /**
0894: * Add a class annotation. If this is the first class annotation added,
0895: * it becomes the primary class annotation.
0896: *
0897: * @param className the name of the class
0898: * @return this object
0899: */
0900: public BugInstance addClass(String className) {
0901: className = ClassName.toDottedClassName(className);
0902: ClassAnnotation classAnnotation = new ClassAnnotation(className);
0903: add(classAnnotation);
0904: return this ;
0905: }
0906:
0907: /**
0908: * Add a class annotation for the classNode.
0909: *
0910: * @param classNode the ASM visitor
0911: * @return this object
0912: */
0913: public BugInstance addClass(ClassNode classNode) {
0914: ClassAnnotation classAnnotation = new ClassAnnotation(
0915: classNode.name);
0916: add(classAnnotation);
0917: return this ;
0918: }
0919:
0920: /**
0921: * Add a class annotation. If this is the first class annotation added,
0922: * it becomes the primary class annotation.
0923: *
0924: * @param classDescriptor the class to add
0925: * @return this object
0926: */
0927: public BugInstance addClass(ClassDescriptor classDescriptor) {
0928: add(ClassAnnotation.fromClassDescriptor(classDescriptor));
0929: return this ;
0930: }
0931:
0932: /**
0933: * Add a class annotation. If this is the first class annotation added,
0934: * it becomes the primary class annotation.
0935: *
0936: * @param jclass the JavaClass object for the class
0937: * @return this object
0938: */
0939: public BugInstance addClass(JavaClass jclass) {
0940: addClass(jclass.getClassName());
0941: return this ;
0942: }
0943:
0944: /**
0945: * Add a class annotation for the class that the visitor is currently visiting.
0946: *
0947: * @param visitor the BetterVisitor
0948: * @return this object
0949: */
0950: public BugInstance addClass(PreorderVisitor visitor) {
0951: String className = visitor.getDottedClassName();
0952: addClass(className);
0953: return this ;
0954: }
0955:
0956: /**
0957: * Add a class annotation for the superclass of the class the visitor
0958: * is currently visiting.
0959: *
0960: * @param visitor the BetterVisitor
0961: * @return this object
0962: */
0963: public BugInstance addSuperclass(PreorderVisitor visitor) {
0964: String className = visitor.getSuperclassName();
0965: addClass(className);
0966: return this ;
0967: }
0968:
0969: /* ----------------------------------------------------------------------
0970: * Type annotation adders
0971: * ---------------------------------------------------------------------- */
0972:
0973: /**
0974: * Add a type annotation. Handy for referring to array types.
0975: *
0976: * <p>For information on type descriptors,
0977: * <br>see http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#14152
0978: * <br>or http://www.murrayc.com/learning/java/java_classfileformat.shtml#TypeDescriptors
0979: *
0980: * @param typeDescriptor a jvm type descriptor, such as "[I"
0981: * @return this object
0982: */
0983: public BugInstance addType(String typeDescriptor) {
0984: TypeAnnotation typeAnnotation = new TypeAnnotation(
0985: typeDescriptor);
0986: add(typeAnnotation);
0987: return this ;
0988: }
0989:
0990: public BugInstance addFoundAndExpectedType(String foundType,
0991: String expectedType) {
0992: add(new TypeAnnotation(foundType)).describe(
0993: TypeAnnotation.FOUND_ROLE);
0994: add(new TypeAnnotation(expectedType)).describe(
0995: TypeAnnotation.EXPECTED_ROLE);
0996: return this ;
0997: }
0998:
0999: public BugInstance addTypeOfNamedClass(String typeName) {
1000: TypeAnnotation typeAnnotation = new TypeAnnotation("L"
1001: + typeName.replace('.', '/') + ";");
1002: add(typeAnnotation);
1003: return this ;
1004: }
1005:
1006: /* ----------------------------------------------------------------------
1007: * Field annotation adders
1008: * ---------------------------------------------------------------------- */
1009:
1010: /**
1011: * Add a field annotation.
1012: *
1013: * @param className name of the class containing the field
1014: * @param fieldName the name of the field
1015: * @param fieldSig type signature of the field
1016: * @param isStatic whether or not the field is static
1017: * @return this object
1018: */
1019: public BugInstance addField(String className, String fieldName,
1020: String fieldSig, boolean isStatic) {
1021: addField(new FieldAnnotation(className, fieldName, fieldSig,
1022: isStatic));
1023: return this ;
1024: }
1025:
1026: /**
1027: * Add a field annotation.
1028: *
1029: * @param className name of the class containing the field
1030: * @param fieldName the name of the field
1031: * @param fieldSig type signature of the field
1032: * @param isStatic whether or not the field is static
1033: * @return this object
1034: */
1035: public BugInstance addField(String className, String fieldName,
1036: String fieldSig, int accessFlags) {
1037: addField(new FieldAnnotation(className, fieldName, fieldSig,
1038: accessFlags));
1039: return this ;
1040: }
1041:
1042: public BugInstance addField(PreorderVisitor visitor) {
1043: FieldAnnotation fieldAnnotation = FieldAnnotation
1044: .fromVisitedField(visitor);
1045: return addField(fieldAnnotation);
1046: }
1047:
1048: /**
1049: * Add a field annotation
1050: *
1051: * @param fieldAnnotation the field annotation
1052: * @return this object
1053: */
1054: public BugInstance addField(FieldAnnotation fieldAnnotation) {
1055: add(fieldAnnotation);
1056: return this ;
1057: }
1058:
1059: /**
1060: * Add a field annotation for a FieldVariable matched in a ByteCodePattern.
1061: *
1062: * @param field the FieldVariable
1063: * @return this object
1064: */
1065: public BugInstance addField(FieldVariable field) {
1066: return addField(field.getClassName(), field.getFieldName(),
1067: field.getFieldSig(), field.isStatic());
1068: }
1069:
1070: /**
1071: * Add a field annotation for an XField.
1072: *
1073: * @param xfield the XField
1074: * @return this object
1075: */
1076: public BugInstance addOptionalField(@CheckForNull
1077: XField xfield) {
1078: if (xfield == null)
1079: return this ;
1080: return addField(xfield.getClassName(), xfield.getName(), xfield
1081: .getSignature(), xfield.isStatic());
1082: }
1083:
1084: /**
1085: * Add a field annotation for an XField.
1086: *
1087: * @param xfield the XField
1088: * @return this object
1089: */
1090: public BugInstance addField(XField xfield) {
1091: return addField(xfield.getClassName(), xfield.getName(), xfield
1092: .getSignature(), xfield.isStatic());
1093: }
1094:
1095: /**
1096: * Add a field annotation for a FieldDescriptor.
1097: *
1098: * @param fieldDescriptor the FieldDescriptor
1099: * @return this object
1100: */
1101: public BugInstance addField(FieldDescriptor fieldDescriptor) {
1102: FieldAnnotation fieldAnnotation = FieldAnnotation
1103: .fromFieldDescriptor(fieldDescriptor);
1104: add(fieldAnnotation);
1105: return this ;
1106: }
1107:
1108: /**
1109: * Add a field annotation for the field which has just been accessed
1110: * by the method currently being visited by given visitor.
1111: * Assumes that a getfield/putfield or getstatic/putstatic
1112: * has just been seen.
1113: *
1114: * @param visitor the DismantleBytecode object
1115: * @return this object
1116: */
1117: public BugInstance addReferencedField(DismantleBytecode visitor) {
1118: FieldAnnotation f = FieldAnnotation
1119: .fromReferencedField(visitor);
1120: addField(f);
1121: return this ;
1122: }
1123:
1124: /**
1125: * Add a field annotation for the field referenced by the FieldAnnotation parameter
1126: */
1127: public BugInstance addReferencedField(FieldAnnotation fa) {
1128: addField(fa);
1129: return this ;
1130: }
1131:
1132: /**
1133: * Add a field annotation for the field which is being visited by
1134: * given visitor.
1135: *
1136: * @param visitor the visitor
1137: * @return this object
1138: */
1139: public BugInstance addVisitedField(PreorderVisitor visitor) {
1140: FieldAnnotation f = FieldAnnotation.fromVisitedField(visitor);
1141: addField(f);
1142: return this ;
1143: }
1144:
1145: /* ----------------------------------------------------------------------
1146: * Method annotation adders
1147: * ---------------------------------------------------------------------- */
1148:
1149: /**
1150: * Add a method annotation. If this is the first method annotation added,
1151: * it becomes the primary method annotation.
1152: *
1153: * @param className name of the class containing the method
1154: * @param methodName name of the method
1155: * @param methodSig type signature of the method
1156: * @param isStatic true if the method is static, false otherwise
1157: * @return this object
1158: */
1159: public BugInstance addMethod(String className, String methodName,
1160: String methodSig, boolean isStatic) {
1161: addMethod(MethodAnnotation.fromForeignMethod(className,
1162: methodName, methodSig, isStatic));
1163: return this ;
1164: }
1165:
1166: /**
1167: * Add a method annotation. If this is the first method annotation added,
1168: * it becomes the primary method annotation.
1169: *
1170: * @param className name of the class containing the method
1171: * @param methodName name of the method
1172: * @param methodSig type signature of the method
1173: * @param accessFlags accessFlags for the method
1174: * @return this object
1175: */
1176: public BugInstance addMethod(String className, String methodName,
1177: String methodSig, int accessFlags) {
1178: addMethod(MethodAnnotation.fromForeignMethod(className,
1179: methodName, methodSig, accessFlags));
1180: return this ;
1181: }
1182:
1183: /**
1184: * Add a method annotation. If this is the first method annotation added,
1185: * it becomes the primary method annotation.
1186: * If the method has source line information, then a SourceLineAnnotation
1187: * is added to the method.
1188: *
1189: * @param methodGen the MethodGen object for the method
1190: * @param sourceFile source file method is defined in
1191: * @return this object
1192: */
1193: public BugInstance addMethod(MethodGen methodGen, String sourceFile) {
1194: String className = methodGen.getClassName();
1195: MethodAnnotation methodAnnotation = new MethodAnnotation(
1196: className, methodGen.getName(), methodGen
1197: .getSignature(), methodGen.isStatic());
1198: addMethod(methodAnnotation);
1199: addSourceLinesForMethod(methodAnnotation, SourceLineAnnotation
1200: .fromVisitedMethod(methodGen, sourceFile));
1201: return this ;
1202: }
1203:
1204: /**
1205: * Add a method annotation. If this is the first method annotation added,
1206: * it becomes the primary method annotation.
1207: * If the method has source line information, then a SourceLineAnnotation
1208: * is added to the method.
1209: *
1210: * @param javaClass the class the method is defined in
1211: * @param method the method
1212: * @return this object
1213: */
1214: public BugInstance addMethod(JavaClass javaClass, Method method) {
1215: MethodAnnotation methodAnnotation = new MethodAnnotation(
1216: javaClass.getClassName(), method.getName(), method
1217: .getSignature(), method.isStatic());
1218: SourceLineAnnotation methodSourceLines = SourceLineAnnotation
1219: .forEntireMethod(javaClass, method);
1220: methodAnnotation.setSourceLines(methodSourceLines);
1221: addMethod(methodAnnotation);
1222: return this ;
1223: }
1224:
1225: /**
1226: * Add a method annotation. If this is the first method annotation added,
1227: * it becomes the primary method annotation.
1228: * If the method has source line information, then a SourceLineAnnotation
1229: * is added to the method.
1230: *
1231: * @param classAndMethod JavaClassAndMethod identifying the method to add
1232: * @return this object
1233: */
1234: public BugInstance addMethod(JavaClassAndMethod classAndMethod) {
1235: return addMethod(classAndMethod.getJavaClass(), classAndMethod
1236: .getMethod());
1237: }
1238:
1239: /**
1240: * Add a method annotation for the method which the given visitor is currently visiting.
1241: * If the method has source line information, then a SourceLineAnnotation
1242: * is added to the method.
1243: *
1244: * @param visitor the BetterVisitor
1245: * @return this object
1246: */
1247: public BugInstance addMethod(PreorderVisitor visitor) {
1248: MethodAnnotation methodAnnotation = MethodAnnotation
1249: .fromVisitedMethod(visitor);
1250: addMethod(methodAnnotation);
1251: addSourceLinesForMethod(methodAnnotation, SourceLineAnnotation
1252: .fromVisitedMethod(visitor));
1253: return this ;
1254: }
1255:
1256: /**
1257: * Add a method annotation for the method which has been called
1258: * by the method currently being visited by given visitor.
1259: * Assumes that the visitor has just looked at an invoke instruction
1260: * of some kind.
1261: *
1262: * @param visitor the DismantleBytecode object
1263: * @return this object
1264: */
1265: public BugInstance addCalledMethod(DismantleBytecode visitor) {
1266: return addMethod(MethodAnnotation.fromCalledMethod(visitor))
1267: .describe("METHOD_CALLED");
1268: }
1269:
1270: /**
1271: * Add a method annotation.
1272: *
1273: * @param className name of class containing called method
1274: * @param methodName name of called method
1275: * @param methodSig signature of called method
1276: * @param isStatic true if called method is static, false if not
1277: * @return this object
1278: */
1279: public BugInstance addCalledMethod(String className,
1280: String methodName, String methodSig, boolean isStatic) {
1281: return addMethod(
1282: MethodAnnotation.fromCalledMethod(className,
1283: methodName, methodSig, isStatic)).describe(
1284: "METHOD_CALLED");
1285: }
1286:
1287: /**
1288: * Add a method annotation for the method which is called by given
1289: * instruction.
1290: *
1291: * @param methodGen the method containing the call
1292: * @param inv the InvokeInstruction
1293: * @return this object
1294: */
1295: public BugInstance addCalledMethod(MethodGen methodGen,
1296: InvokeInstruction inv) {
1297: ConstantPoolGen cpg = methodGen.getConstantPool();
1298: String className = inv.getClassName(cpg);
1299: String methodName = inv.getMethodName(cpg);
1300: String methodSig = inv.getSignature(cpg);
1301: addMethod(className, methodName, methodSig,
1302: inv.getOpcode() == Constants.INVOKESTATIC);
1303: describe("METHOD_CALLED");
1304: return this ;
1305: }
1306:
1307: /**
1308: * Add a MethodAnnotation from an XMethod.
1309: *
1310: * @param xmethod the XMethod
1311: * @return this object
1312: */
1313: public BugInstance addMethod(XMethod xmethod) {
1314: addMethod(MethodAnnotation.fromXMethod(xmethod));
1315: return this ;
1316: }
1317:
1318: /**
1319: * Add a method annotation. If this is the first method annotation added,
1320: * it becomes the primary method annotation.
1321: *
1322: * @param methodAnnotation the method annotation
1323: * @return this object
1324: */
1325: public BugInstance addMethod(MethodAnnotation methodAnnotation) {
1326: add(methodAnnotation);
1327: return this ;
1328: }
1329:
1330: /* ----------------------------------------------------------------------
1331: * Integer annotation adders
1332: * ---------------------------------------------------------------------- */
1333:
1334: /**
1335: * Add an integer annotation.
1336: *
1337: * @param value the integer value
1338: * @return this object
1339: */
1340: public BugInstance addInt(int value) {
1341: add(new IntAnnotation(value));
1342: return this ;
1343: }
1344:
1345: /**
1346: * Add a String annotation.
1347: *
1348: * @param value the String value
1349: * @return this object
1350: */
1351: public BugInstance addString(String value) {
1352: add(new StringAnnotation(value));
1353: return this ;
1354: }
1355:
1356: /* ----------------------------------------------------------------------
1357: * Source line annotation adders
1358: * ---------------------------------------------------------------------- */
1359:
1360: /**
1361: * Add a source line annotation.
1362: *
1363: * @param sourceLine the source line annotation
1364: * @return this object
1365: */
1366: public BugInstance addSourceLine(SourceLineAnnotation sourceLine) {
1367: add(sourceLine);
1368: return this ;
1369: }
1370:
1371: /**
1372: * Add a source line annotation for instruction whose PC is given
1373: * in the method that the given visitor is currently visiting.
1374: * Note that if the method does not have line number information, then
1375: * no source line annotation will be added.
1376: *
1377: * @param visitor a BytecodeScanningDetector that is currently visiting the method
1378: * @param pc bytecode offset of the instruction
1379: * @return this object
1380: */
1381: public BugInstance addSourceLine(BytecodeScanningDetector visitor,
1382: int pc) {
1383: SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation
1384: .fromVisitedInstruction(visitor.getClassContext(),
1385: visitor, pc);
1386: if (sourceLineAnnotation != null)
1387: add(sourceLineAnnotation);
1388: return this ;
1389: }
1390:
1391: /**
1392: * Add a source line annotation for instruction whose PC is given
1393: * in the method that the given visitor is currently visiting.
1394: * Note that if the method does not have line number information, then
1395: * no source line annotation will be added.
1396: *
1397: * @param classContext the ClassContext
1398: * @param visitor a PreorderVisitor that is currently visiting the method
1399: * @param pc bytecode offset of the instruction
1400: * @return this object
1401: */
1402: public BugInstance addSourceLine(ClassContext classContext,
1403: PreorderVisitor visitor, int pc) {
1404: SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation
1405: .fromVisitedInstruction(classContext, visitor, pc);
1406: if (sourceLineAnnotation != null)
1407: add(sourceLineAnnotation);
1408: return this ;
1409: }
1410:
1411: /**
1412: * Add a source line annotation for the given instruction in the given method.
1413: * Note that if the method does not have line number information, then
1414: * no source line annotation will be added.
1415: *
1416: * @param classContext the ClassContext
1417: * @param methodGen the method being visited
1418: * @param sourceFile source file the method is defined in
1419: * @param handle the InstructionHandle containing the visited instruction
1420: * @return this object
1421: */
1422: public BugInstance addSourceLine(ClassContext classContext,
1423: MethodGen methodGen, String sourceFile, @NonNull
1424: InstructionHandle handle) {
1425: SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation
1426: .fromVisitedInstruction(classContext, methodGen,
1427: sourceFile, handle);
1428: if (sourceLineAnnotation != null)
1429: add(sourceLineAnnotation);
1430: return this ;
1431: }
1432:
1433: /**
1434: * Add a source line annotation describing a range of instructions.
1435: *
1436: * @param classContext the ClassContext
1437: * @param methodGen the method
1438: * @param sourceFile source file the method is defined in
1439: * @param start the start instruction in the range
1440: * @param end the end instruction in the range (inclusive)
1441: * @return this object
1442: */
1443: public BugInstance addSourceLine(ClassContext classContext,
1444: MethodGen methodGen, String sourceFile,
1445: InstructionHandle start, InstructionHandle end) {
1446: // Make sure start and end are really in the right order.
1447: if (start.getPosition() > end.getPosition()) {
1448: InstructionHandle tmp = start;
1449: start = end;
1450: end = tmp;
1451: }
1452: SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation
1453: .fromVisitedInstructionRange(classContext, methodGen,
1454: sourceFile, start, end);
1455: if (sourceLineAnnotation != null)
1456: add(sourceLineAnnotation);
1457: return this ;
1458: }
1459:
1460: /**
1461: * Add source line annotation for given Location in a method.
1462: *
1463: * @param classContext the ClassContext
1464: * @param method the Method
1465: * @param location the Location in the method
1466: * @return this BugInstance
1467: */
1468: public BugInstance addSourceLine(ClassContext classContext,
1469: Method method, Location location) {
1470: return addSourceLine(classContext, method, location.getHandle());
1471: }
1472:
1473: /**
1474: * Add source line annotation for given Location in a method.
1475: *
1476: * @param methodDescriptor the method
1477: * @param location the Location in the method
1478: * @return this BugInstance
1479: */
1480: public BugInstance addSourceLine(MethodDescriptor methodDescriptor,
1481: Location location) {
1482: try {
1483: IAnalysisCache analysisCache = Global.getAnalysisCache();
1484: ClassContext classContext = analysisCache.getClassAnalysis(
1485: ClassContext.class, methodDescriptor
1486: .getClassDescriptor());
1487: Method method = analysisCache.getMethodAnalysis(
1488: Method.class, methodDescriptor);
1489: return addSourceLine(classContext, method, location);
1490: } catch (CheckedAnalysisException e) {
1491: return addSourceLine(SourceLineAnnotation
1492: .createReallyUnknown(methodDescriptor
1493: .getClassDescriptor().toDottedClassName()));
1494: }
1495: }
1496:
1497: /**
1498: * Add source line annotation for given Location in a method.
1499: *
1500: * @param classContext the ClassContext
1501: * @param method the Method
1502: * @param handle InstructionHandle of an instruction in the method
1503: * @return this BugInstance
1504: */
1505: public BugInstance addSourceLine(ClassContext classContext,
1506: Method method, InstructionHandle handle) {
1507: SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation
1508: .fromVisitedInstruction(classContext, method, handle
1509: .getPosition());
1510: if (sourceLineAnnotation != null)
1511: add(sourceLineAnnotation);
1512: return this ;
1513: }
1514:
1515: /**
1516: * Add a source line annotation describing the
1517: * source line numbers for a range of instructions in the method being
1518: * visited by the given visitor.
1519: * Note that if the method does not have line number information, then
1520: * no source line annotation will be added.
1521: *
1522: * @param visitor a BetterVisitor which is visiting the method
1523: * @param startPC the bytecode offset of the start instruction in the range
1524: * @param endPC the bytecode offset of the end instruction in the range
1525: * @return this object
1526: */
1527: public BugInstance addSourceLineRange(
1528: BytecodeScanningDetector visitor, int startPC, int endPC) {
1529: SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation
1530: .fromVisitedInstructionRange(visitor.getClassContext(),
1531: visitor, startPC, endPC);
1532: if (sourceLineAnnotation != null)
1533: add(sourceLineAnnotation);
1534: return this ;
1535: }
1536:
1537: /**
1538: * Add a source line annotation describing the
1539: * source line numbers for a range of instructions in the method being
1540: * visited by the given visitor.
1541: * Note that if the method does not have line number information, then
1542: * no source line annotation will be added.
1543: *
1544: * @param classContext the ClassContext
1545: * @param visitor a BetterVisitor which is visiting the method
1546: * @param startPC the bytecode offset of the start instruction in the range
1547: * @param endPC the bytecode offset of the end instruction in the range
1548: * @return this object
1549: */
1550: public BugInstance addSourceLineRange(ClassContext classContext,
1551: PreorderVisitor visitor, int startPC, int endPC) {
1552: SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation
1553: .fromVisitedInstructionRange(classContext, visitor,
1554: startPC, endPC);
1555: if (sourceLineAnnotation != null)
1556: add(sourceLineAnnotation);
1557: return this ;
1558: }
1559:
1560: /**
1561: * Add a source line annotation for instruction currently being visited
1562: * by given visitor.
1563: * Note that if the method does not have line number information, then
1564: * no source line annotation will be added.
1565: *
1566: * @param visitor a BytecodeScanningDetector visitor that is currently visiting the instruction
1567: * @return this object
1568: */
1569: public BugInstance addSourceLine(BytecodeScanningDetector visitor) {
1570: SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation
1571: .fromVisitedInstruction(visitor);
1572: if (sourceLineAnnotation != null)
1573: add(sourceLineAnnotation);
1574: return this ;
1575: }
1576:
1577: /**
1578: * Add a non-specific source line annotation.
1579: * This will result in the entire source file being displayed.
1580: *
1581: * @param className the class name
1582: * @param sourceFile the source file name
1583: * @return this object
1584: */
1585: public BugInstance addUnknownSourceLine(String className,
1586: String sourceFile) {
1587: SourceLineAnnotation sourceLineAnnotation = SourceLineAnnotation
1588: .createUnknown(className, sourceFile);
1589: if (sourceLineAnnotation != null)
1590: add(sourceLineAnnotation);
1591: return this ;
1592: }
1593:
1594: /* ----------------------------------------------------------------------
1595: * Formatting support
1596: * ---------------------------------------------------------------------- */
1597:
1598: /**
1599: * Format a string describing this bug instance.
1600: *
1601: * @return the description
1602: */
1603: public String getMessageWithoutPrefix() {
1604: BugPattern bugPattern = getBugPattern();
1605: String pattern, shortPattern;
1606:
1607: pattern = getLongDescription();
1608: shortPattern = bugPattern.getShortDescription();
1609: try {
1610: FindBugsMessageFormat format = new FindBugsMessageFormat(
1611: pattern);
1612: return format.format(annotationList
1613: .toArray(new BugAnnotation[annotationList.size()]),
1614: getPrimaryClass());
1615: } catch (RuntimeException e) {
1616: AnalysisContext.logError("Error generating bug msg ", e);
1617: return shortPattern
1618: + " [Error generating customized description]";
1619: }
1620: }
1621:
1622: String getLongDescription() {
1623: return getBugPattern().getLongDescription().replaceAll(
1624: "BUG_PATTERN", type);
1625: }
1626:
1627: public String getAbridgedMessage() {
1628: BugPattern bugPattern = getBugPattern();
1629: String pattern, shortPattern;
1630: if (bugPattern == null)
1631: shortPattern = pattern = "Error2: missing bug pattern for key "
1632: + type;
1633: else {
1634: pattern = getLongDescription()
1635: .replaceAll(" in \\{1\\}", "");
1636: shortPattern = bugPattern.getShortDescription();
1637: }
1638: try {
1639: FindBugsMessageFormat format = new FindBugsMessageFormat(
1640: pattern);
1641: return format.format(annotationList
1642: .toArray(new BugAnnotation[annotationList.size()]),
1643: getPrimaryClass());
1644: } catch (RuntimeException e) {
1645: AnalysisContext.logError("Error generating bug msg ", e);
1646: return shortPattern
1647: + " [Error3 generating customized description]";
1648: }
1649: }
1650:
1651: /**
1652: * Format a string describing this bug instance.
1653: *
1654: * @return the description
1655: */
1656: public String getMessage() {
1657: BugPattern bugPattern = getBugPattern();
1658: String pattern = bugPattern.getAbbrev() + ": "
1659: + getLongDescription();
1660: FindBugsMessageFormat format = new FindBugsMessageFormat(
1661: pattern);
1662: try {
1663: return format.format(annotationList
1664: .toArray(new BugAnnotation[annotationList.size()]),
1665: getPrimaryClass());
1666: } catch (RuntimeException e) {
1667: AnalysisContext.logError("Error generating bug msg ", e);
1668: return bugPattern.getShortDescription()
1669: + " [Error generating customized description]";
1670: }
1671: }
1672:
1673: /**
1674: * Format a string describing this bug pattern, with the priority and type at the beginning.
1675: * e.g. "(High Priority Correctness) Guaranteed null pointer dereference..."
1676: */
1677: public String getMessageWithPriorityType() {
1678: return "(" + this .getPriorityTypeString() + ") "
1679: + this .getMessage();
1680: }
1681:
1682: public String getMessageWithPriorityTypeAbbreviation() {
1683: return this .getPriorityTypeAbbreviation() + " "
1684: + this .getMessage();
1685: }
1686:
1687: /**
1688: * Add a description to the most recently added bug annotation.
1689: *
1690: * @param description the description to add
1691: * @return this object
1692: */
1693: public BugInstance describe(String description) {
1694: annotationList.get(annotationList.size() - 1).setDescription(
1695: description);
1696: return this ;
1697: }
1698:
1699: /**
1700: * Convert to String.
1701: * This method returns the "short" message describing the bug,
1702: * as opposed to the longer format returned by getMessage().
1703: * The short format is appropriate for the tree view in a GUI,
1704: * where the annotations are listed separately as part of the overall
1705: * bug instance.
1706: */
1707: @Override
1708: public String toString() {
1709: return I18N.instance().getShortMessage(type);
1710: }
1711:
1712: /* ----------------------------------------------------------------------
1713: * XML Conversion support
1714: * ---------------------------------------------------------------------- */
1715:
1716: public void writeXML(XMLOutput xmlOutput) throws IOException {
1717: writeXML(xmlOutput, false);
1718: }
1719:
1720: public void writeXML(XMLOutput xmlOutput, boolean addMessages)
1721: throws IOException {
1722: XMLAttributeList attributeList = new XMLAttributeList()
1723: .addAttribute("type", type).addAttribute("priority",
1724: String.valueOf(priority));
1725:
1726: BugPattern pattern = getBugPattern();
1727: if (pattern != null) {
1728: // The bug abbreviation and pattern category are
1729: // emitted into the XML for informational purposes only.
1730: // (The information is redundant, but might be useful
1731: // for processing tools that want to make sense of
1732: // bug instances without looking at the plugin descriptor.)
1733: attributeList.addAttribute("abbrev", pattern.getAbbrev());
1734: attributeList.addAttribute("category", pattern
1735: .getCategory());
1736: }
1737:
1738: if (addMessages) {
1739: // Add a uid attribute, if we have a unique id.
1740: if (getUniqueId() != null) {
1741: attributeList.addAttribute("uid", getUniqueId());
1742: }
1743: attributeList.addAttribute("instanceHash",
1744: getInstanceHash());
1745: attributeList.addAttribute("instanceOccurrenceNum", Integer
1746: .toString(getInstanceOccurrenceNum()));
1747: attributeList.addAttribute("instanceOccurrenceMax", Integer
1748: .toString(getInstanceOccurrenceMax()));
1749:
1750: }
1751: if (firstVersion > 0)
1752: attributeList.addAttribute("first", Long
1753: .toString(firstVersion));
1754: if (lastVersion >= 0)
1755: attributeList.addAttribute("last", Long
1756: .toString(lastVersion));
1757: if (introducedByChangeOfExistingClass)
1758: attributeList.addAttribute("introducedByChange", "true");
1759: if (removedByChangeOfPersistingClass)
1760: attributeList.addAttribute("removedByChange", "true");
1761:
1762: xmlOutput.openTag(ELEMENT_NAME, attributeList);
1763:
1764: if (userDesignation != null) {
1765: userDesignation.writeXML(xmlOutput);
1766: }
1767:
1768: if (addMessages) {
1769: BugPattern bugPattern = getBugPattern();
1770:
1771: xmlOutput.openTag("ShortMessage");
1772: xmlOutput.writeText(bugPattern != null ? bugPattern
1773: .getShortDescription() : this .toString());
1774: xmlOutput.closeTag("ShortMessage");
1775:
1776: xmlOutput.openTag("LongMessage");
1777: if (FindBugsDisplayFeatures.isAbridgedMessages())
1778: xmlOutput.writeText(this .getAbridgedMessage());
1779: else
1780: xmlOutput.writeText(this .getMessageWithoutPrefix());
1781: xmlOutput.closeTag("LongMessage");
1782: }
1783:
1784: boolean foundSourceAnnotation = false;
1785: for (BugAnnotation annotation : annotationList) {
1786: if (annotation instanceof SourceLineAnnotation)
1787: foundSourceAnnotation = true;
1788: annotation.writeXML(xmlOutput, addMessages);
1789: }
1790: if (!foundSourceAnnotation && addMessages) {
1791: SourceLineAnnotation synth = getPrimarySourceLineAnnotation();
1792: if (synth != null) {
1793: synth.setSynthetic(true);
1794: synth.writeXML(xmlOutput, addMessages);
1795: }
1796: }
1797:
1798: if (propertyListHead != null) {
1799: BugProperty prop = propertyListHead;
1800: while (prop != null) {
1801: prop.writeXML(xmlOutput);
1802: prop = prop.getNext();
1803: }
1804: }
1805:
1806: xmlOutput.closeTag(ELEMENT_NAME);
1807: }
1808:
1809: private static final String ELEMENT_NAME = "BugInstance";
1810: private static final String USER_ANNOTATION_ELEMENT_NAME = "UserAnnotation";
1811:
1812: /* ----------------------------------------------------------------------
1813: * Implementation
1814: * ---------------------------------------------------------------------- */
1815:
1816: public BugInstance addOptionalAnnotation(@CheckForNull
1817: BugAnnotation annotation) {
1818: if (annotation == null)
1819: return this ;
1820: return add(annotation);
1821: }
1822:
1823: public BugInstance add(BugAnnotation annotation) {
1824: if (annotation == null)
1825: throw new IllegalStateException("Missing BugAnnotation!");
1826:
1827: // Add to list
1828: annotationList.add(annotation);
1829:
1830: // This object is being modified, so the cached hashcode
1831: // must be invalidated
1832: cachedHashCode = INVALID_HASH_CODE;
1833: return this ;
1834: }
1835:
1836: private void addSourceLinesForMethod(
1837: MethodAnnotation methodAnnotation,
1838: SourceLineAnnotation sourceLineAnnotation) {
1839: if (sourceLineAnnotation != null) {
1840: // Note: we don't add the source line annotation directly to
1841: // the bug instance. Instead, we stash it in the MethodAnnotation.
1842: // It is much more useful there, and it would just be distracting
1843: // if it were displayed in the UI, since it would compete for attention
1844: // with the actual bug location source line annotation (which is much
1845: // more important and interesting).
1846: methodAnnotation.setSourceLines(sourceLineAnnotation);
1847: }
1848: }
1849:
1850: @Override
1851: public int hashCode() {
1852: if (cachedHashCode == INVALID_HASH_CODE) {
1853: int hashcode = type.hashCode() + priority;
1854: Iterator<BugAnnotation> i = annotationIterator();
1855: while (i.hasNext())
1856: hashcode += i.next().hashCode();
1857: if (hashcode == INVALID_HASH_CODE)
1858: hashcode = INVALID_HASH_CODE + 1;
1859: cachedHashCode = hashcode;
1860: }
1861:
1862: return cachedHashCode;
1863: }
1864:
1865: @Override
1866: public boolean equals(Object o) {
1867: if (!(o instanceof BugInstance))
1868: return false;
1869: BugInstance other = (BugInstance) o;
1870: if (!type.equals(other.type) || priority != other.priority)
1871: return false;
1872: if (annotationList.size() != other.annotationList.size())
1873: return false;
1874: int numAnnotations = annotationList.size();
1875: for (int i = 0; i < numAnnotations; ++i) {
1876: BugAnnotation lhs = annotationList.get(i);
1877: BugAnnotation rhs = other.annotationList.get(i);
1878: if (!lhs.equals(rhs))
1879: return false;
1880: }
1881:
1882: return true;
1883: }
1884:
1885: public int compareTo(BugInstance other) {
1886: int cmp;
1887: cmp = type.compareTo(other.type);
1888: if (cmp != 0)
1889: return cmp;
1890: cmp = priority - other.priority;
1891: if (cmp != 0)
1892: return cmp;
1893:
1894: // Compare BugAnnotations lexicographically
1895: int pfxLen = Math.min(annotationList.size(),
1896: other.annotationList.size());
1897: for (int i = 0; i < pfxLen; ++i) {
1898: BugAnnotation lhs = annotationList.get(i);
1899: BugAnnotation rhs = other.annotationList.get(i);
1900: cmp = lhs.compareTo(rhs);
1901: if (cmp != 0)
1902: return cmp;
1903: }
1904:
1905: // All elements in prefix were the same,
1906: // so use number of elements to decide
1907: return annotationList.size() - other.annotationList.size();
1908: }
1909:
1910: /**
1911: * @param firstVersion The firstVersion to set.
1912: */
1913: public void setFirstVersion(long firstVersion) {
1914: this .firstVersion = firstVersion;
1915: if (lastVersion >= 0 && firstVersion > lastVersion)
1916: throw new IllegalArgumentException(firstVersion + ".."
1917: + lastVersion);
1918: }
1919:
1920: /**
1921: * @return Returns the firstVersion.
1922: */
1923: public long getFirstVersion() {
1924: return firstVersion;
1925: }
1926:
1927: /**
1928: * @param lastVersion The lastVersion to set.
1929: */
1930: public void setLastVersion(long lastVersion) {
1931: if (lastVersion >= 0 && firstVersion > lastVersion)
1932: throw new IllegalArgumentException(firstVersion + ".."
1933: + lastVersion);
1934: this .lastVersion = lastVersion;
1935: }
1936:
1937: /**
1938: * @return Returns the lastVersion.
1939: */
1940: public long getLastVersion() {
1941: return lastVersion;
1942: }
1943:
1944: /**
1945: * @param introducedByChangeOfExistingClass The introducedByChangeOfExistingClass to set.
1946: */
1947: public void setIntroducedByChangeOfExistingClass(
1948: boolean introducedByChangeOfExistingClass) {
1949: this .introducedByChangeOfExistingClass = introducedByChangeOfExistingClass;
1950: }
1951:
1952: /**
1953: * @return Returns the introducedByChangeOfExistingClass.
1954: */
1955: public boolean isIntroducedByChangeOfExistingClass() {
1956: return introducedByChangeOfExistingClass;
1957: }
1958:
1959: /**
1960: * @param removedByChangeOfPersistingClass The removedByChangeOfPersistingClass to set.
1961: */
1962: public void setRemovedByChangeOfPersistingClass(
1963: boolean removedByChangeOfPersistingClass) {
1964: this .removedByChangeOfPersistingClass = removedByChangeOfPersistingClass;
1965: }
1966:
1967: /**
1968: * @return Returns the removedByChangeOfPersistingClass.
1969: */
1970: public boolean isRemovedByChangeOfPersistingClass() {
1971: return removedByChangeOfPersistingClass;
1972: }
1973:
1974: /**
1975: * @param instanceHash The instanceHash to set.
1976: */
1977: public void setInstanceHash(String instanceHash) {
1978: this .instanceHash = instanceHash;
1979: }
1980:
1981: /**
1982: * @param oldInstanceHash The oldInstanceHash to set.
1983: */
1984: public void setOldInstanceHash(String oldInstanceHash) {
1985: this .oldInstanceHash = oldInstanceHash;
1986: }
1987:
1988: private static final boolean DONT_HASH = SystemProperties
1989: .getBoolean("findbugs.dontHash");
1990:
1991: /**
1992: * @return Returns the instanceHash.
1993: */
1994: public String getInstanceHash() {
1995: if (instanceHash != null)
1996: return instanceHash;
1997: MessageDigest digest = null;
1998: try {
1999: digest = MessageDigest.getInstance("MD5");
2000: } catch (Exception e2) {
2001: // OK, we won't digest
2002: }
2003: instanceHash = getInstanceKey();
2004: if (digest != null && !DONT_HASH) {
2005: byte[] data = digest.digest(instanceHash.getBytes());
2006: String tmp = new BigInteger(1, data).toString(16);
2007: instanceHash = tmp;
2008: }
2009: return instanceHash;
2010: }
2011:
2012: public boolean isInstanceHashConsistent() {
2013: return oldInstanceHash == null
2014: || instanceHash.equals(oldInstanceHash);
2015: }
2016:
2017: /**
2018: * @param instanceOccurrenceNum The instanceOccurrenceNum to set.
2019: */
2020: public void setInstanceOccurrenceNum(int instanceOccurrenceNum) {
2021: this .instanceOccurrenceNum = instanceOccurrenceNum;
2022: }
2023:
2024: /**
2025: * @return Returns the instanceOccurrenceNum.
2026: */
2027: public int getInstanceOccurrenceNum() {
2028: return instanceOccurrenceNum;
2029: }
2030:
2031: /**
2032: * @param instanceOccurrenceMax The instanceOccurrenceMax to set.
2033: */
2034: public void setInstanceOccurrenceMax(int instanceOccurrenceMax) {
2035: this .instanceOccurrenceMax = instanceOccurrenceMax;
2036: }
2037:
2038: /**
2039: * @return Returns the instanceOccurrenceMax.
2040: */
2041: public int getInstanceOccurrenceMax() {
2042: return instanceOccurrenceMax;
2043: }
2044: }
2045:
2046: // vim:ts=4
|