From: Chris Jaekl Date: Mon, 28 Sep 2015 13:41:10 +0000 (+0900) Subject: Add computation of deltas (differences between Analysis runs). X-Git-Url: https://jaekl.net/gitweb/?a=commitdiff_plain;h=358d80a86ac7c79cd57b81a4f1708da80db2f0ec;p=cfb.git Add computation of deltas (differences between Analysis runs). --- diff --git a/prod/net/jaekl/cfb/analyze/Delta.java b/prod/net/jaekl/cfb/analyze/Delta.java new file mode 100644 index 0000000..e2853e0 --- /dev/null +++ b/prod/net/jaekl/cfb/analyze/Delta.java @@ -0,0 +1,57 @@ +package net.jaekl.cfb.analyze; + +import java.util.HashSet; + +import net.jaekl.cfb.xml.BugInstance; + +// Compute and store the delta (difference) between two analyses + +public class Delta { + HashSet m_fixed; // bugs that have been fixed + HashSet m_common; // bugs that are present in both versions + HashSet m_new; // bugs introduced in the new version + + public Delta(Analysis before, Analysis after) + { + m_fixed = new HashSet(); + m_common = new HashSet(); + m_new = new HashSet(); + + computeDelta(before, after); + } + + public BugInstance[] getFixed() { return m_fixed.toArray(new BugInstance[m_fixed.size()]); } + public int getNumFixed() { return m_fixed.size(); } + + public BugInstance[] getCommon() { return m_common.toArray(new BugInstance[m_common.size()]); } + public int getNumCommon() { return m_common.size(); } + + public BugInstance[] getNew() { return m_new.toArray(new BugInstance[m_new.size()]); } + public int getNumNew() { return m_new.size(); } + + void computeDelta(Analysis before, Analysis after) + { + m_fixed.clear(); + m_common.clear(); + m_new.clear(); + + HashSet beforeBugs = new HashSet(); + + beforeBugs.addAll(before.getBugCollection().getBugs()); + + for (BugInstance bug : after.getBugCollection().getBugs()) { + if (beforeBugs.contains(bug)) { + m_common.add(bug); + } + else { + m_new.add(bug); + } + } + + for (BugInstance bug : before.getBugCollection().getBugs()) { + if (! m_common.contains(bug)) { + m_fixed.add(bug); + } + } + } +} diff --git a/prod/net/jaekl/cfb/util/Util.java b/prod/net/jaekl/cfb/util/Util.java index 30f4c90..309e20d 100644 --- a/prod/net/jaekl/cfb/util/Util.java +++ b/prod/net/jaekl/cfb/util/Util.java @@ -6,10 +6,32 @@ import java.io.PrintWriter; import java.io.StringWriter; public class Util { - public static String stringify(Throwable thr) { + public static String stringify(Throwable thr) + { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); thr.printStackTrace(pw); return sw.toString(); } + + // Test for equality, while taking care to avoid + // dereferencing a null pointer. + // Note that two null pointers are considered equal. + public static boolean objsAreEqual(Object a, Object b) + { + if ((null == a) || (null == b)) { + return (a == b); + } + + return a.equals(b); + } + + // Return 1 if obj is null, or obj.hashCode() otherwise + public static int objHashCode(Object obj) + { + if (null == obj) { + return 1; + } + return obj.hashCode(); + } } diff --git a/prod/net/jaekl/cfb/xml/BugInstance.java b/prod/net/jaekl/cfb/xml/BugInstance.java index 3386e3f..100251f 100644 --- a/prod/net/jaekl/cfb/xml/BugInstance.java +++ b/prod/net/jaekl/cfb/xml/BugInstance.java @@ -7,6 +7,7 @@ import java.util.List; import org.xml.sax.Attributes; +import net.jaekl.cfb.util.Util; import net.jaekl.qd.xml.MissingAttributeException; import net.jaekl.qd.xml.ParseResult; import net.jaekl.qd.xml.XmlParseException; @@ -115,4 +116,77 @@ public class BugInstance extends ParseResult { sl.dump(pw, childIndent); } } + + // Note that this is a heuristic, "fuzzy", equals. + // Two BugInstances will be considered equal if: + // - they refer to the same bug type + // - they took place in the same class and method + // - the variable names referred to (if any) match + // In particular, this equality test does not check + // for line numbers being equal. This is by design; + // we want to consider two bugs to be the "same bug" + // even if other changes in the file have shifted + // line numbers a bit. + @Override + public boolean equals(Object obj) + { + if (null == obj) { + return false; + } + if (obj instanceof BugInstance) { + BugInstance that = (BugInstance)obj; + + if (! Util.objsAreEqual(this.m_type, that.m_type)) { + return false; + } + + if (! Util.objsAreEqual(this.m_category, that.m_category)) { + return false; + } + + BugMethod thisMethod = this.getPrincipalMethod(); + BugMethod thatMethod = that.getPrincipalMethod(); + if (null == thisMethod) { + if (null == thatMethod) { + return false; + } + } + else { + if (! Util.objsAreEqual(thisMethod.getClassName(), thatMethod.getClassName())) { + return false; + } + if (! Util.objsAreEqual(thisMethod.getMethodName(), thatMethod.getMethodName())) { + return false; + } + } + + if (! Util.objsAreEqual(this.getVariables(), that.getVariables())) { + return false; + } + + return true; + } + return false; + } + + @Override + public int hashCode() + { + int code = Util.objHashCode(m_type) + * Util.objHashCode(m_category) + * Util.objHashCode(getPrincipalMethod()) + * Util.objHashCode(getVariables()); + return code; + } + + // Get the "principal" method. + // This should be the place where the bug is reported. + BugMethod getPrincipalMethod() + { + List bugMethods = getMethods(); + if ((null == bugMethods) || (0 == bugMethods.size())) { + return null; + } + return bugMethods.get(0); + } } diff --git a/prod/net/jaekl/cfb/xml/LocalVariable.java b/prod/net/jaekl/cfb/xml/LocalVariable.java index a4d26d8..277bf2d 100644 --- a/prod/net/jaekl/cfb/xml/LocalVariable.java +++ b/prod/net/jaekl/cfb/xml/LocalVariable.java @@ -4,6 +4,7 @@ import java.io.PrintWriter; import org.xml.sax.Attributes; +import net.jaekl.cfb.util.Util; import net.jaekl.qd.xml.MissingAttributeException; import net.jaekl.qd.xml.ParseResult; import net.jaekl.qd.xml.XmlParseException; @@ -59,4 +60,24 @@ public class LocalVariable extends ParseResult { pw.println(tab + NAME + "=" + m_name); pw.println(tab + ROLE + "=" + m_role); } + + @Override + public boolean equals(Object obj) + { + if (null == obj) { + return false; + } + if (obj instanceof LocalVariable) { + LocalVariable that = (LocalVariable)obj; + return ( Util.objsAreEqual(this.m_name, that.m_name) + && Util.objsAreEqual(this.m_role, that.m_role) ); + } + return false; + } + + @Override + public int hashCode() + { + return ( (1 + Util.objHashCode(m_name)) * (1 + Util.objHashCode(m_role)) ); + } }