SimpleBugIntroducerFinder.java 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. /*
  2. * MIT License
  3. *
  4. * Copyright (c) 2018 Axis Communications AB
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in all
  14. * copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. * SOFTWARE.
  23. */
  24. package heuristics;
  25. import data.Issues;
  26. import graph.AnnotationMap;
  27. import graph.FileAnnotationGraph;
  28. import java.io.*;
  29. import java.text.SimpleDateFormat;
  30. import java.util.*;
  31. import java.util.regex.Matcher;
  32. import java.util.regex.Pattern;
  33. import org.eclipse.jgit.api.errors.GitAPIException;
  34. import org.eclipse.jgit.lib.Repository;
  35. import org.eclipse.jgit.revwalk.RevCommit;
  36. import util.RevisionCombinationGenerator;
  37. /**
  38. * A simple bug introduce finder as described by Zeller et al.
  39. *
  40. * @author Oscar Svensson
  41. */
  42. public class SimpleBugIntroducerFinder implements BugIntroducerFinder {
  43. public static final int TIMEOUT_TO_PROCESS_INTRODUCER = 45000;
  44. private Issues issues;
  45. private Repository repo;
  46. private int depth;
  47. private Pattern partialFixPattern;
  48. public SimpleBugIntroducerFinder(
  49. Issues issues, Repository repo, int depth, String partialFixPattern) {
  50. this.issues = issues;
  51. this.repo = repo;
  52. this.depth = depth;
  53. this.partialFixPattern = Pattern.compile(partialFixPattern);
  54. }
  55. /**
  56. * Check if a commit is within a fix timeframe.
  57. *
  58. * @param fix the commit containing the fix.
  59. * @param commit the potential bug introducing commit.
  60. * @return if the commit is within the timeframe.
  61. */
  62. private boolean isWithinTimeframe(String fix, String commit) throws IOException, GitAPIException {
  63. Map<String, String> dates = this.issues.get(fix);
  64. RevCommit rCommit = this.repo.parseCommit(this.repo.resolve(commit));
  65. Date revisionDate = rCommit.getCommitterIdent().getWhen();
  66. String commitDateString = dates.get("creationdate");
  67. SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
  68. Date commitDate = null;
  69. try {
  70. commitDate = format.parse(commitDateString);
  71. } catch (Exception e) {
  72. e.printStackTrace();
  73. return false;
  74. }
  75. return revisionDate.before(commitDate);
  76. }
  77. /** Check if a commit is a partial fix. */
  78. private boolean isPartialFix(String commit) throws IOException, GitAPIException {
  79. RevCommit rCommit = this.repo.parseCommit(this.repo.resolve(commit));
  80. String message = rCommit.getFullMessage();
  81. Matcher fixMatch = this.partialFixPattern.matcher(commit);
  82. return fixMatch.find();
  83. }
  84. private Collection<FileAnnotationGraph> getSubGraphs(
  85. Collection<FileAnnotationGraph> root, int depth) {
  86. Collection<FileAnnotationGraph> sub = new LinkedList<>();
  87. if (depth == 1) return sub;
  88. for (FileAnnotationGraph subGraph : root) {
  89. if (depth > 2) {
  90. sub.addAll(getSubGraphs(subGraph.sub_graphs.values(), depth - 1));
  91. sub.addAll(subGraph.sub_graphs.values());
  92. }
  93. sub.add(subGraph);
  94. }
  95. return sub;
  96. }
  97. /**
  98. * Simple heuristics of the SZZ algorithm. Pick all commits that have made changes to a line but
  99. * take into consideration if they have been made before or after the bug was reported.
  100. *
  101. * @param graphs a graph containing all reported bugfixes.
  102. */
  103. public List<String[]> findBugIntroducingCommits(
  104. AnnotationMap<String, List<FileAnnotationGraph>> graphs) throws IOException, GitAPIException {
  105. List<String[]> bugIntroducers = new LinkedList<>();
  106. List<String[]> potentialBugIntroducers = new LinkedList<>();
  107. Map<String, List<String>> bucketIntroducers = new HashMap<String, List<String>>();
  108. Map<String, List<String>> bucketIssues = new HashMap<String, List<String>>();
  109. for (Map.Entry<String, List<FileAnnotationGraph>> entry : graphs.entrySet()) {
  110. List<FileAnnotationGraph> files = new LinkedList<>();
  111. String sCommitString = entry.getKey();
  112. files = entry.getValue();
  113. /*
  114. * Grab all commits that are seen as fixes or that have changed anything.
  115. * Only checks the first layer of commits.
  116. */
  117. Collection<FileAnnotationGraph> subGraphs = getSubGraphs(files, this.depth);
  118. subGraphs.addAll(files);
  119. for (FileAnnotationGraph fileGraph : subGraphs) {
  120. Iterator<String> revisions = fileGraph.revisions.iterator();
  121. if (!revisions.hasNext()) continue;
  122. revisions.next();
  123. if (!revisions.hasNext()) continue;
  124. while (revisions.hasNext()) {
  125. String rev = revisions.next();
  126. String[] pair = new String[2];
  127. pair[0] = sCommitString;
  128. pair[1] = rev;
  129. /*
  130. * Check if the timestamp is within the timeframe or not.
  131. */
  132. if (isWithinTimeframe(sCommitString, rev)) {
  133. bugIntroducers.add(pair);
  134. } else {
  135. if (!bucketIntroducers.containsKey(fileGraph.filePath)) {
  136. bucketIntroducers.put(fileGraph.filePath, new ArrayList<>());
  137. }
  138. bucketIntroducers.get(fileGraph.filePath).add(rev);
  139. if (!bucketIssues.containsKey(fileGraph.filePath)) {
  140. bucketIssues.put(fileGraph.filePath, new ArrayList<>());
  141. }
  142. bucketIssues.get(fileGraph.filePath).add(sCommitString);
  143. }
  144. }
  145. }
  146. }
  147. List<String[]> partial_fix_suspects = new LinkedList<>();
  148. Map<String, List<String>> partialIntroducers = new HashMap<String, List<String>>();
  149. Map<String, List<String>> partialIssues = new HashMap<String, List<String>>();
  150. /*
  151. * Now check if any of the potential bugintroducing commits are bugintroducers for any other fix commit, aka weak suspects.
  152. * This check should be made smarter...
  153. */
  154. for (Map.Entry<String, List<String>> entry : bucketIntroducers.entrySet()) {
  155. processBucketIntroducer(bugIntroducers, bucketIssues, partialIntroducers, partialIssues, entry);
  156. }
  157. /*
  158. * Now check for partial fixes. If a commit is flagged as a fix, it is a candidate to be a partial fix.
  159. */
  160. for (Map.Entry<String, List<String>> suspects : partialIntroducers.entrySet()) {
  161. processPartialIntroducer(bugIntroducers, partialIssues, suspects);
  162. }
  163. /*
  164. * All other pairs that hasn't been flagged as bug introducers are said to be hard suspects.
  165. */
  166. return bugIntroducers;
  167. }
  168. private void processPartialIntroducer(List<String[]> bugIntroducers, Map<String, List<String>> partialIssues, Map.Entry<String, List<String>> suspects) throws IOException, GitAPIException {
  169. long startTime = Calendar.getInstance().getTimeInMillis();
  170. List<String> introducers = suspects.getValue();
  171. List<String> issues = partialIssues.get(suspects.getKey());
  172. RevisionCombinationGenerator gen = new RevisionCombinationGenerator(introducers, issues, 2);
  173. gen = gen.iterator();
  174. while(gen.hasNext() && (Calendar.getInstance().getTimeInMillis() - startTime < TIMEOUT_TO_PROCESS_INTRODUCER)) {
  175. String[] pair = gen.getNextIndic();
  176. if (pair[0] == "" && pair[1] == "")
  177. continue;
  178. if (isPartialFix(pair[0])) {
  179. bugIntroducers.add(pair);
  180. }
  181. }
  182. }
  183. private void processBucketIntroducer(List<String[]> bugIntroducers, Map<String, List<String>> bucketIssues, Map<String, List<String>> partialIntroducers, Map<String, List<String>> partialIssues, Map.Entry<String, List<String>> entry) throws IOException, GitAPIException {
  184. List<String> introducers = entry.getValue();
  185. List<String> issues = bucketIssues.get(entry.getKey());
  186. long startTime = Calendar.getInstance().getTimeInMillis();
  187. RevisionCombinationGenerator gen = new RevisionCombinationGenerator(introducers, issues, 2);
  188. gen = gen.iterator();
  189. while(gen.hasNext() && (Calendar.getInstance().getTimeInMillis() - startTime < 30000)) {
  190. String[] pair = gen.getNextIndic();
  191. if (pair[0] == "" && pair[1] == "")
  192. continue;
  193. if (isWithinTimeframe(pair[1], pair[0])) {
  194. bugIntroducers.add(pair);
  195. } else {
  196. if (!partialIntroducers.containsKey(entry.getKey())) {
  197. partialIntroducers.put(entry.getKey(), new ArrayList<>());
  198. }
  199. partialIntroducers.get(entry.getKey()).add(pair[0]);
  200. if (!partialIssues.containsKey(entry.getKey())) {
  201. partialIssues.put(entry.getKey(), new ArrayList<>());
  202. }
  203. partialIssues.get(entry.getKey()).add(pair[1]);
  204. }
  205. }
  206. }
  207. }