/** * */ package bixie.checker.report; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import util.Log; import bixie.checker.faultlocalization.FaultLocalizationThread; import bixie.checker.reportprinter.SourceLocation; import bixie.checker.transition_relation.AbstractTransitionRelation; import boogie.ProgramFactory; import boogie.ast.Attribute; import boogie.ast.NamedAttribute; import boogie.ast.statement.Statement; import boogie.controlflow.AbstractControlFlowFactory; import boogie.controlflow.BasicBlock; import boogie.controlflow.statement.CfgStatement; /** * @author schaef * */ public class Report { protected Map<Integer, Set<Set<BasicBlock>>> inconsistentBlocks = new LinkedHashMap<Integer, Set<Set<BasicBlock>>>(); protected AbstractTransitionRelation tr; protected Map<Integer, List<FaultExplanation>> faultExplanations = new LinkedHashMap<Integer, List<FaultExplanation>>(); public Report(AbstractTransitionRelation tr) { this.tr = tr; } /** * Report inconsistent code with severity level as suggested in the ATVA15 paper. * Fault localization will be applied for each severity level individually. * @param severity Severity level of inconsistent blocks with 0 being the highest * @param inconsistentBlocks Set of inconsistent codes to report */ public void reportInconsistentCode(Integer severity, Set<BasicBlock> inconsistentBlocks) { //Split the inconsistentBlocks into connected components. //Each connected component may be inconsistent for a separate //reason. Set<Set<BasicBlock>> inconsistentComponenets = findConnectedComponents(inconsistentBlocks); //remove the components that contain a noVerify tag. removeSkippedComponents(inconsistentComponenets); this.inconsistentBlocks.put(severity, inconsistentComponenets); } /** * Returns the fault explanations generated by the fault localization per severity level. * This map is empty if runFaultLocalization has not been called before. * @return */ public Map<Integer, List<FaultExplanation>> getReports() { return this.faultExplanations; } /** * Apply fault localization from the FSE13 paper to all reported * inconsistencies. * @param tr */ public void runFaultLocalization() { for (Entry<Integer, Set<Set<BasicBlock>>> entry : this.inconsistentBlocks.entrySet()) { for (Set<BasicBlock> inconsistency : entry.getValue()) { try { runFaultLocalization(entry.getKey(), inconsistency); } catch (Throwable e) { return; } } } } private void runFaultLocalization(Integer severity, Set<BasicBlock> inconsistency) throws Throwable { if (inconsistency.isEmpty()) { return; } FaultLocalizationThread flt = new FaultLocalizationThread(this.tr, inconsistency); ExecutorService executor = Executors.newSingleThreadExecutor(); final Future<?> future = executor.submit(flt); try { if (bixie.Options.v().getTimeout() > 0) { future.get(bixie.Options.v().getTimeout(), TimeUnit.SECONDS); } else { future.get(); } List<Map<CfgStatement, SourceLocation>> reportedLines = flt.getReports(); for (Map<CfgStatement, SourceLocation> lines : reportedLines) { if (lines.size()>0) { if (!this.faultExplanations.containsKey(severity)) { this.faultExplanations.put(severity, new LinkedList<FaultExplanation>()); } this.faultExplanations.get(severity).add(new FaultExplanation(lines)); } } } catch (TimeoutException e) { Log.error("fault localization timeout."); throw e; } catch (OutOfMemoryError e) { throw e; } catch (Throwable e) { e.printStackTrace(); throw e; } finally { if (flt != null) { flt.shutDownProver(); } if (!future.isDone()) { future.cancel(true); } executor.shutdown(); } //TODO: if the fault localization failed, // we can still return the lines of the // inconsistent blocks that we have. } protected LinkedList<Statement> collectStatements(AbstractControlFlowFactory cff, Set<BasicBlock> blocks) { LinkedList<Statement> astStatements = new LinkedList<Statement>(); for (BasicBlock b : blocks) { for (CfgStatement s : b.getStatements()) { Statement ast_stmt = cff.findAstStatement(s); if (ast_stmt!=null && ast_stmt.getLocation()!=null) { astStatements.add(ast_stmt); } } } return astStatements; } /** * Returns the set of connected components for a given * set of blocks. * @param blocks * @return */ private Set<Set<BasicBlock>> findConnectedComponents( Set<BasicBlock> blocks) { Set<Set<BasicBlock>> components = new HashSet<Set<BasicBlock>>(); LinkedList<BasicBlock> allblocks = new LinkedList<BasicBlock>(); allblocks.addAll(blocks); while (!allblocks.isEmpty()) { HashSet<BasicBlock> subprog = new HashSet<BasicBlock>(); LinkedList<BasicBlock> todo = new LinkedList<BasicBlock>(); todo.add(allblocks.pop()); while (!todo.isEmpty()) { BasicBlock current = todo.pop(); allblocks.remove(current); subprog.add(current); for (BasicBlock b : current.getPredecessors()) { if (!subprog.contains(b) && !todo.contains(b) && allblocks.contains(b)) { todo.add(b); } } for (BasicBlock b : current.getSuccessors()) { if (!subprog.contains(b) && !todo.contains(b) && allblocks.contains(b)) { todo.add(b); } } } if (subprog.size() > 0) { components.add(subprog); } } return components; } /** * Takes a set of connected components of blocks and removes * those components that contain a noVerify tag, or where every * block has a 'cloned' tag. * @param components * @return */ private void removeSkippedComponents(Set<Set<BasicBlock>> components) { for (Set<BasicBlock> component : new HashSet<Set<BasicBlock>>(components)) { boolean allSkipped = true; for (BasicBlock b : component) { if (containsNamedAttribute(b, ProgramFactory.NoVerifyTag)) { components.remove(component); break; } if (!containsNamedAttribute(b, ProgramFactory.Cloned)) { allSkipped = false; } } if (allSkipped) { components.remove(component); } } } protected boolean containsNoVerifyAttribute(BasicBlock b) { return containsNamedAttribute(b, ProgramFactory.NoVerifyTag); } protected boolean containsNamedAttribute(BasicBlock b, String name) { for (CfgStatement s : b.getStatements()) { if (s.getAttributes()!=null) { for (Attribute attr : s.getAttributes()) { if (attr instanceof NamedAttribute) { NamedAttribute na = (NamedAttribute)attr; if (na.getName().equals(name)) { return true; } } } } } return false; } /** * Class that holds the result of the fault localization. * @author schaef * */ static public class FaultExplanation { public Integer firstLine = -2; public String fileName = ""; public LinkedList<SourceLocation> locations = new LinkedList<SourceLocation>(); public HashSet<Integer> allLines = new HashSet<Integer>(); public LinkedList<SourceLocation> infeasibleLines = new LinkedList<SourceLocation>(); public LinkedList<SourceLocation> otherLines = new LinkedList<SourceLocation>(); public FaultExplanation(Map<CfgStatement, SourceLocation> report) { for (Entry<CfgStatement, SourceLocation> line : report.entrySet()) { if (firstLine==-2) { fileName = line.getValue().FileName; firstLine = line.getValue().StartLine; } else if (line.getValue().StartLine<firstLine) { firstLine = line.getValue().StartLine; } allLines.add(line.getValue().StartLine); SourceLocation sl = new SourceLocation(line.getValue()); if (line.getValue().inInfeasibleBlock) { this.infeasibleLines.add(sl); } else { this.otherLines.add(sl); } this.locations.add(sl); } } public boolean includes(FaultExplanation other) { return this.allLines.containsAll(other.allLines); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("File: "); sb.append(this.fileName); sb.append(", lines: "); String comma = ""; for (Integer i : this.allLines) { sb.append(comma); sb.append(i); comma = ", "; } sb.append("\n"); return sb.toString(); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (Entry<Integer, List<FaultExplanation>> entry : faultExplanations.entrySet()) { sb.append("Severity: "+entry.getKey()); sb.append("\n"); for (FaultExplanation fe : entry.getValue()) { sb.append(fe.toString()); } sb.append("==========\n"); } return sb.toString(); } }