aboutsummaryrefslogtreecommitdiff
path: root/lib/StaticAnalyzer/Core/BugReporter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/StaticAnalyzer/Core/BugReporter.cpp')
-rw-r--r--lib/StaticAnalyzer/Core/BugReporter.cpp651
1 files changed, 412 insertions, 239 deletions
diff --git a/lib/StaticAnalyzer/Core/BugReporter.cpp b/lib/StaticAnalyzer/Core/BugReporter.cpp
index c898d65a5f95..8f8eb3bb8502 100644
--- a/lib/StaticAnalyzer/Core/BugReporter.cpp
+++ b/lib/StaticAnalyzer/Core/BugReporter.cpp
@@ -12,29 +12,38 @@
//
//===----------------------------------------------------------------------===//
+#define DEBUG_TYPE "BugReporter"
+
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
-#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "clang/AST/ASTContext.h"
-#include "clang/Analysis/CFG.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ParentMap.h"
#include "clang/AST/StmtObjC.h"
-#include "clang/Basic/SourceManager.h"
+#include "clang/Analysis/CFG.h"
#include "clang/Analysis/ProgramPoint.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
-#include "llvm/Support/raw_ostream.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/SmallString.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/OwningPtr.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/OwningPtr.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/Support/raw_ostream.h"
#include <queue>
using namespace clang;
using namespace ento;
+STATISTIC(MaxBugClassSize,
+ "The maximum number of bug reports in the same equivalence class");
+STATISTIC(MaxValidBugClassSize,
+ "The maximum number of bug reports in the same equivalence class "
+ "where at least one report is valid (not suppressed)");
+
BugReporterVisitor::~BugReporterVisitor() {}
void BugReporterContext::anchor() {}
@@ -44,13 +53,13 @@ void BugReporterContext::anchor() {}
//===----------------------------------------------------------------------===//
static inline const Stmt *GetStmt(const ProgramPoint &P) {
- if (const StmtPoint* SP = dyn_cast<StmtPoint>(&P))
+ if (Optional<StmtPoint> SP = P.getAs<StmtPoint>())
return SP->getStmt();
- else if (const BlockEdge *BE = dyn_cast<BlockEdge>(&P))
+ if (Optional<BlockEdge> BE = P.getAs<BlockEdge>())
return BE->getSrc()->getTerminator();
- else if (const CallEnter *CE = dyn_cast<CallEnter>(&P))
+ if (Optional<CallEnter> CE = P.getAs<CallEnter>())
return CE->getCallExpr();
- else if (const CallExitEnd *CEE = dyn_cast<CallExitEnd>(&P))
+ if (Optional<CallExitEnd> CEE = P.getAs<CallExitEnd>())
return CEE->getCalleeContext()->getCallSite();
return 0;
@@ -191,9 +200,8 @@ static void removeRedundantMsgs(PathPieces &path) {
/// Recursively scan through a path and prune out calls and macros pieces
/// that aren't needed. Return true if afterwards the path contains
-/// "interesting stuff" which means it should be pruned from the parent path.
-bool BugReporter::RemoveUneededCalls(PathPieces &pieces, BugReport *R,
- PathDiagnosticCallPiece *CallWithLoc) {
+/// "interesting stuff" which means it shouldn't be pruned from the parent path.
+bool BugReporter::RemoveUnneededCalls(PathPieces &pieces, BugReport *R) {
bool containsSomethingInteresting = false;
const unsigned N = pieces.size();
@@ -203,7 +211,9 @@ bool BugReporter::RemoveUneededCalls(PathPieces &pieces, BugReport *R,
IntrusiveRefCntPtr<PathDiagnosticPiece> piece(pieces.front());
pieces.pop_front();
- // Throw away pieces with invalid locations.
+ // Throw away pieces with invalid locations. Note that we can't throw away
+ // calls just yet because they might have something interesting inside them.
+ // If so, their locations will be adjusted as necessary later.
if (piece->getKind() != PathDiagnosticPiece::Call &&
piece->getLocation().asLocation().isInvalid())
continue;
@@ -217,25 +227,16 @@ bool BugReporter::RemoveUneededCalls(PathPieces &pieces, BugReport *R,
containsSomethingInteresting = true;
break;
}
- // Recursively clean out the subclass. Keep this call around if
- // it contains any informative diagnostics.
- PathDiagnosticCallPiece *NewCallWithLoc =
- call->getLocation().asLocation().isValid()
- ? call : CallWithLoc;
-
- if (!RemoveUneededCalls(call->path, R, NewCallWithLoc))
- continue;
- if (NewCallWithLoc == CallWithLoc && CallWithLoc) {
- call->callEnter = CallWithLoc->callEnter;
- }
+ if (!RemoveUnneededCalls(call->path, R))
+ continue;
containsSomethingInteresting = true;
break;
}
case PathDiagnosticPiece::Macro: {
PathDiagnosticMacroPiece *macro = cast<PathDiagnosticMacroPiece>(piece);
- if (!RemoveUneededCalls(macro->subPieces, R))
+ if (!RemoveUnneededCalls(macro->subPieces, R))
continue;
containsSomethingInteresting = true;
break;
@@ -258,36 +259,66 @@ bool BugReporter::RemoveUneededCalls(PathPieces &pieces, BugReport *R,
return containsSomethingInteresting;
}
+/// Recursively scan through a path and make sure that all call pieces have
+/// valid locations. Note that all other pieces with invalid locations should
+/// have already been pruned out.
+static void adjustCallLocations(PathPieces &Pieces,
+ PathDiagnosticLocation *LastCallLocation = 0) {
+ for (PathPieces::iterator I = Pieces.begin(), E = Pieces.end(); I != E; ++I) {
+ PathDiagnosticCallPiece *Call = dyn_cast<PathDiagnosticCallPiece>(*I);
+
+ if (!Call) {
+ assert((*I)->getLocation().asLocation().isValid());
+ continue;
+ }
+
+ if (LastCallLocation) {
+ if (!Call->callEnter.asLocation().isValid() ||
+ Call->getCaller()->isImplicit())
+ Call->callEnter = *LastCallLocation;
+ if (!Call->callReturn.asLocation().isValid() ||
+ Call->getCaller()->isImplicit())
+ Call->callReturn = *LastCallLocation;
+ }
+
+ // Recursively clean out the subclass. Keep this call around if
+ // it contains any informative diagnostics.
+ PathDiagnosticLocation *ThisCallLocation;
+ if (Call->callEnterWithin.asLocation().isValid() &&
+ !Call->getCallee()->isImplicit())
+ ThisCallLocation = &Call->callEnterWithin;
+ else
+ ThisCallLocation = &Call->callEnter;
+
+ assert(ThisCallLocation && "Outermost call has an invalid location");
+ adjustCallLocations(Call->path, ThisCallLocation);
+ }
+}
+
//===----------------------------------------------------------------------===//
// PathDiagnosticBuilder and its associated routines and helper objects.
//===----------------------------------------------------------------------===//
-typedef llvm::DenseMap<const ExplodedNode*,
-const ExplodedNode*> NodeBackMap;
-
namespace {
class NodeMapClosure : public BugReport::NodeResolver {
- NodeBackMap& M;
+ InterExplodedGraphMap &M;
public:
- NodeMapClosure(NodeBackMap *m) : M(*m) {}
- ~NodeMapClosure() {}
+ NodeMapClosure(InterExplodedGraphMap &m) : M(m) {}
const ExplodedNode *getOriginalNode(const ExplodedNode *N) {
- NodeBackMap::iterator I = M.find(N);
- return I == M.end() ? 0 : I->second;
+ return M.lookup(N);
}
};
class PathDiagnosticBuilder : public BugReporterContext {
BugReport *R;
PathDiagnosticConsumer *PDC;
- OwningPtr<ParentMap> PM;
NodeMapClosure NMC;
public:
const LocationContext *LC;
PathDiagnosticBuilder(GRBugReporter &br,
- BugReport *r, NodeBackMap *Backmap,
+ BugReport *r, InterExplodedGraphMap &Backmap,
PathDiagnosticConsumer *pdc)
: BugReporterContext(br),
R(r), PDC(pdc), NMC(Backmap), LC(r->getErrorNode()->getLocationContext())
@@ -552,7 +583,7 @@ static bool GenerateMinimalPathDiagnostic(PathDiagnostic& PD,
ProgramPoint P = N->getLocation();
do {
- if (const CallExitEnd *CE = dyn_cast<CallExitEnd>(&P)) {
+ if (Optional<CallExitEnd> CE = P.getAs<CallExitEnd>()) {
PathDiagnosticCallPiece *C =
PathDiagnosticCallPiece::construct(N, *CE, SMgr);
GRBugReporter& BR = PDB.getBugReporter();
@@ -563,7 +594,7 @@ static bool GenerateMinimalPathDiagnostic(PathDiagnostic& PD,
break;
}
- if (const CallEnter *CE = dyn_cast<CallEnter>(&P)) {
+ if (Optional<CallEnter> CE = P.getAs<CallEnter>()) {
// Flush all locations, and pop the active path.
bool VisitedEntireCall = PD.isWithinCall();
PD.popActivePath();
@@ -591,7 +622,7 @@ static bool GenerateMinimalPathDiagnostic(PathDiagnostic& PD,
break;
}
- if (const BlockEdge *BE = dyn_cast<BlockEdge>(&P)) {
+ if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) {
const CFGBlock *Src = BE->getSrc();
const CFGBlock *Dst = BE->getDst();
const Stmt *T = Src->getTerminator();
@@ -1267,7 +1298,81 @@ static void reversePropagateInterestingSymbols(BugReport &R,
}
}
}
-
+
+//===----------------------------------------------------------------------===//
+// Functions for determining if a loop was executed 0 times.
+//===----------------------------------------------------------------------===//
+
+/// Return true if the terminator is a loop and the destination is the
+/// false branch.
+static bool isLoopJumpPastBody(const Stmt *Term, const BlockEdge *BE) {
+ switch (Term->getStmtClass()) {
+ case Stmt::ForStmtClass:
+ case Stmt::WhileStmtClass:
+ case Stmt::ObjCForCollectionStmtClass:
+ break;
+ default:
+ // Note that we intentionally do not include do..while here.
+ return false;
+ }
+
+ // Did we take the false branch?
+ const CFGBlock *Src = BE->getSrc();
+ assert(Src->succ_size() == 2);
+ return (*(Src->succ_begin()+1) == BE->getDst());
+}
+
+static bool isContainedByStmt(ParentMap &PM, const Stmt *S, const Stmt *SubS) {
+ while (SubS) {
+ if (SubS == S)
+ return true;
+ SubS = PM.getParent(SubS);
+ }
+ return false;
+}
+
+static const Stmt *getStmtBeforeCond(ParentMap &PM, const Stmt *Term,
+ const ExplodedNode *N) {
+ while (N) {
+ Optional<StmtPoint> SP = N->getLocation().getAs<StmtPoint>();
+ if (SP) {
+ const Stmt *S = SP->getStmt();
+ if (!isContainedByStmt(PM, Term, S))
+ return S;
+ }
+ N = GetPredecessorNode(N);
+ }
+ return 0;
+}
+
+static bool isInLoopBody(ParentMap &PM, const Stmt *S, const Stmt *Term) {
+ const Stmt *LoopBody = 0;
+ switch (Term->getStmtClass()) {
+ case Stmt::ForStmtClass: {
+ const ForStmt *FS = cast<ForStmt>(Term);
+ if (isContainedByStmt(PM, FS->getInc(), S))
+ return true;
+ LoopBody = FS->getBody();
+ break;
+ }
+ case Stmt::ObjCForCollectionStmtClass: {
+ const ObjCForCollectionStmt *FC = cast<ObjCForCollectionStmt>(Term);
+ LoopBody = FC->getBody();
+ break;
+ }
+ case Stmt::WhileStmtClass:
+ LoopBody = cast<WhileStmt>(Term)->getBody();
+ break;
+ default:
+ return false;
+ }
+ return isContainedByStmt(PM, LoopBody, S);
+}
+
+//===----------------------------------------------------------------------===//
+// Top-level logic for generating extensive path diagnostics.
+//===----------------------------------------------------------------------===//
+
static bool GenerateExtensivePathDiagnostic(PathDiagnostic& PD,
PathDiagnosticBuilder &PDB,
const ExplodedNode *N,
@@ -1284,14 +1389,14 @@ static bool GenerateExtensivePathDiagnostic(PathDiagnostic& PD,
ProgramPoint P = N->getLocation();
do {
- if (const PostStmt *PS = dyn_cast<PostStmt>(&P)) {
+ if (Optional<PostStmt> PS = P.getAs<PostStmt>()) {
if (const Expr *Ex = PS->getStmtAs<Expr>())
reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE,
N->getState().getPtr(), Ex,
N->getLocationContext());
}
- if (const CallExitEnd *CE = dyn_cast<CallExitEnd>(&P)) {
+ if (Optional<CallExitEnd> CE = P.getAs<CallExitEnd>()) {
const Stmt *S = CE->getCalleeContext()->getCallSite();
if (const Expr *Ex = dyn_cast_or_null<Expr>(S)) {
reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE,
@@ -1315,7 +1420,7 @@ static bool GenerateExtensivePathDiagnostic(PathDiagnostic& PD,
// Pop the call hierarchy if we are done walking the contents
// of a function call.
- if (const CallEnter *CE = dyn_cast<CallEnter>(&P)) {
+ if (Optional<CallEnter> CE = P.getAs<CallEnter>()) {
// Add an edge to the start of the function.
const Decl *D = CE->getCalleeContext()->getDecl();
PathDiagnosticLocation pos =
@@ -1360,7 +1465,7 @@ static bool GenerateExtensivePathDiagnostic(PathDiagnostic& PD,
PDB.LC = N->getLocationContext();
// Block edges.
- if (const BlockEdge *BE = dyn_cast<BlockEdge>(&P)) {
+ if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) {
// Does this represent entering a call? If so, look at propagating
// interesting symbols across call boundaries.
if (NextNode) {
@@ -1397,16 +1502,39 @@ static bool GenerateExtensivePathDiagnostic(PathDiagnostic& PD,
EB.addEdge(BL);
}
}
-
- if (const Stmt *Term = BE->getSrc()->getTerminator())
+
+ const CFGBlock *BSrc = BE->getSrc();
+ ParentMap &PM = PDB.getParentMap();
+
+ if (const Stmt *Term = BSrc->getTerminator()) {
+ // Are we jumping past the loop body without ever executing the
+ // loop (because the condition was false)?
+ if (isLoopJumpPastBody(Term, &*BE) &&
+ !isInLoopBody(PM,
+ getStmtBeforeCond(PM,
+ BSrc->getTerminatorCondition(),
+ N),
+ Term)) {
+ PathDiagnosticLocation L(Term, SM, PDB.LC);
+ PathDiagnosticEventPiece *PE =
+ new PathDiagnosticEventPiece(L, "Loop body executed 0 times");
+ PE->setPrunable(true);
+
+ EB.addEdge(PE->getLocation(), true);
+ PD.getActivePath().push_front(PE);
+ }
+
+ // In any case, add the terminator as the current statement
+ // context for control edges.
EB.addContext(Term);
+ }
break;
}
- if (const BlockEntrance *BE = dyn_cast<BlockEntrance>(&P)) {
- CFGElement First = BE->getFirstElement();
- if (const CFGStmt *S = First.getAs<CFGStmt>()) {
+ if (Optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) {
+ Optional<CFGElement> First = BE->getFirstElement();
+ if (Optional<CFGStmt> S = First ? First->getAs<CFGStmt>() : None) {
const Stmt *stmt = S->getStmt();
if (IsControlFlowExpr(stmt)) {
// Add the proper context for '&&', '||', and '?'.
@@ -1502,8 +1630,9 @@ const Decl *BugReport::getDeclWithIssue() const {
void BugReport::Profile(llvm::FoldingSetNodeID& hash) const {
hash.AddPointer(&BT);
hash.AddString(Description);
- if (UniqueingLocation.isValid()) {
- UniqueingLocation.Profile(hash);
+ PathDiagnosticLocation UL = getUniqueingLocation();
+ if (UL.isValid()) {
+ UL.Profile(hash);
} else if (Location.isValid()) {
Location.Profile(hash);
} else {
@@ -1623,7 +1752,7 @@ const Stmt *BugReport::getStmt() const {
ProgramPoint ProgP = ErrorNode->getLocation();
const Stmt *S = NULL;
- if (BlockEntrance *BE = dyn_cast<BlockEntrance>(&ProgP)) {
+ if (Optional<BlockEntrance> BE = ProgP.getAs<BlockEntrance>()) {
CFGBlock &Exit = ProgP.getLocationContext()->getCFG()->getExit();
if (BE->getBlock() == &Exit)
S = GetPreviousStmt(ErrorNode);
@@ -1667,6 +1796,9 @@ PathDiagnosticLocation BugReport::getLocation(const SourceManager &SM) const {
if (const BinaryOperator *B = dyn_cast<BinaryOperator>(S))
return PathDiagnosticLocation::createOperatorLoc(B, SM);
+ if (ErrorNode->getLocation().getAs<PostStmtPurgeDeadSymbols>())
+ return PathDiagnosticLocation::createEnd(S, SM, LC);
+
return PathDiagnosticLocation::createBegin(S, SM, LC);
}
} else {
@@ -1741,141 +1873,174 @@ void BugReporter::FlushReports() {
// PathDiagnostics generation.
//===----------------------------------------------------------------------===//
-static std::pair<std::pair<ExplodedGraph*, NodeBackMap*>,
- std::pair<ExplodedNode*, unsigned> >
-MakeReportGraph(const ExplodedGraph* G,
- SmallVectorImpl<const ExplodedNode*> &nodes) {
+namespace {
+/// A wrapper around a report graph, which contains only a single path, and its
+/// node maps.
+class ReportGraph {
+public:
+ InterExplodedGraphMap BackMap;
+ OwningPtr<ExplodedGraph> Graph;
+ const ExplodedNode *ErrorNode;
+ size_t Index;
+};
+
+/// A wrapper around a trimmed graph and its node maps.
+class TrimmedGraph {
+ InterExplodedGraphMap InverseMap;
+
+ typedef llvm::DenseMap<const ExplodedNode *, unsigned> PriorityMapTy;
+ PriorityMapTy PriorityMap;
+
+ typedef std::pair<const ExplodedNode *, size_t> NodeIndexPair;
+ SmallVector<NodeIndexPair, 32> ReportNodes;
- // Create the trimmed graph. It will contain the shortest paths from the
- // error nodes to the root. In the new graph we should only have one
- // error node unless there are two or more error nodes with the same minimum
- // path length.
- ExplodedGraph* GTrim;
- InterExplodedGraphMap* NMap;
+ OwningPtr<ExplodedGraph> G;
- llvm::DenseMap<const void*, const void*> InverseMap;
- llvm::tie(GTrim, NMap) = G->Trim(nodes.data(), nodes.data() + nodes.size(),
- &InverseMap);
+ /// A helper class for sorting ExplodedNodes by priority.
+ template <bool Descending>
+ class PriorityCompare {
+ const PriorityMapTy &PriorityMap;
- // Create owning pointers for GTrim and NMap just to ensure that they are
- // released when this function exists.
- OwningPtr<ExplodedGraph> AutoReleaseGTrim(GTrim);
- OwningPtr<InterExplodedGraphMap> AutoReleaseNMap(NMap);
+ public:
+ PriorityCompare(const PriorityMapTy &M) : PriorityMap(M) {}
+
+ bool operator()(const ExplodedNode *LHS, const ExplodedNode *RHS) const {
+ PriorityMapTy::const_iterator LI = PriorityMap.find(LHS);
+ PriorityMapTy::const_iterator RI = PriorityMap.find(RHS);
+ PriorityMapTy::const_iterator E = PriorityMap.end();
+
+ if (LI == E)
+ return Descending;
+ if (RI == E)
+ return !Descending;
+
+ return Descending ? LI->second > RI->second
+ : LI->second < RI->second;
+ }
+
+ bool operator()(const NodeIndexPair &LHS, const NodeIndexPair &RHS) const {
+ return (*this)(LHS.first, RHS.first);
+ }
+ };
+
+public:
+ TrimmedGraph(const ExplodedGraph *OriginalGraph,
+ ArrayRef<const ExplodedNode *> Nodes);
+
+ bool popNextReportGraph(ReportGraph &GraphWrapper);
+};
+}
+
+TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph,
+ ArrayRef<const ExplodedNode *> Nodes) {
+ // The trimmed graph is created in the body of the constructor to ensure
+ // that the DenseMaps have been initialized already.
+ InterExplodedGraphMap ForwardMap;
+ G.reset(OriginalGraph->trim(Nodes, &ForwardMap, &InverseMap));
// Find the (first) error node in the trimmed graph. We just need to consult
- // the node map (NMap) which maps from nodes in the original graph to nodes
+ // the node map which maps from nodes in the original graph to nodes
// in the new graph.
+ llvm::SmallPtrSet<const ExplodedNode *, 32> RemainingNodes;
- std::queue<const ExplodedNode*> WS;
- typedef llvm::DenseMap<const ExplodedNode*, unsigned> IndexMapTy;
- IndexMapTy IndexMap;
-
- for (unsigned nodeIndex = 0 ; nodeIndex < nodes.size(); ++nodeIndex) {
- const ExplodedNode *originalNode = nodes[nodeIndex];
- if (const ExplodedNode *N = NMap->getMappedNode(originalNode)) {
- WS.push(N);
- IndexMap[originalNode] = nodeIndex;
+ for (unsigned i = 0, count = Nodes.size(); i < count; ++i) {
+ if (const ExplodedNode *NewNode = ForwardMap.lookup(Nodes[i])) {
+ ReportNodes.push_back(std::make_pair(NewNode, i));
+ RemainingNodes.insert(NewNode);
}
}
- assert(!WS.empty() && "No error node found in the trimmed graph.");
-
- // Create a new (third!) graph with a single path. This is the graph
- // that will be returned to the caller.
- ExplodedGraph *GNew = new ExplodedGraph();
+ assert(!RemainingNodes.empty() && "No error node found in the trimmed graph");
- // Sometimes the trimmed graph can contain a cycle. Perform a reverse BFS
- // to the root node, and then construct a new graph that contains only
- // a single path.
- llvm::DenseMap<const void*,unsigned> Visited;
+ // Perform a forward BFS to find all the shortest paths.
+ std::queue<const ExplodedNode *> WS;
- unsigned cnt = 0;
- const ExplodedNode *Root = 0;
+ assert(G->num_roots() == 1);
+ WS.push(*G->roots_begin());
+ unsigned Priority = 0;
while (!WS.empty()) {
const ExplodedNode *Node = WS.front();
WS.pop();
- if (Visited.find(Node) != Visited.end())
- continue;
+ PriorityMapTy::iterator PriorityEntry;
+ bool IsNew;
+ llvm::tie(PriorityEntry, IsNew) =
+ PriorityMap.insert(std::make_pair(Node, Priority));
+ ++Priority;
- Visited[Node] = cnt++;
-
- if (Node->pred_empty()) {
- Root = Node;
- break;
+ if (!IsNew) {
+ assert(PriorityEntry->second <= Priority);
+ continue;
}
- for (ExplodedNode::const_pred_iterator I=Node->pred_begin(),
- E=Node->pred_end(); I!=E; ++I)
+ if (RemainingNodes.erase(Node))
+ if (RemainingNodes.empty())
+ break;
+
+ for (ExplodedNode::const_pred_iterator I = Node->succ_begin(),
+ E = Node->succ_end();
+ I != E; ++I)
WS.push(*I);
}
- assert(Root);
+ // Sort the error paths from longest to shortest.
+ std::sort(ReportNodes.begin(), ReportNodes.end(),
+ PriorityCompare<true>(PriorityMap));
+}
- // Now walk from the root down the BFS path, always taking the successor
- // with the lowest number.
- ExplodedNode *Last = 0, *First = 0;
- NodeBackMap *BM = new NodeBackMap();
- unsigned NodeIndex = 0;
+bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) {
+ if (ReportNodes.empty())
+ return false;
- for ( const ExplodedNode *N = Root ;;) {
- // Lookup the number associated with the current node.
- llvm::DenseMap<const void*,unsigned>::iterator I = Visited.find(N);
- assert(I != Visited.end());
+ const ExplodedNode *OrigN;
+ llvm::tie(OrigN, GraphWrapper.Index) = ReportNodes.pop_back_val();
+ assert(PriorityMap.find(OrigN) != PriorityMap.end() &&
+ "error node not accessible from root");
+ // Create a new graph with a single path. This is the graph
+ // that will be returned to the caller.
+ ExplodedGraph *GNew = new ExplodedGraph();
+ GraphWrapper.Graph.reset(GNew);
+ GraphWrapper.BackMap.clear();
+
+ // Now walk from the error node up the BFS path, always taking the
+ // predeccessor with the lowest number.
+ ExplodedNode *Succ = 0;
+ while (true) {
// Create the equivalent node in the new graph with the same state
// and location.
- ExplodedNode *NewN = GNew->getNode(N->getLocation(), N->getState());
+ ExplodedNode *NewN = GNew->getNode(OrigN->getLocation(), OrigN->getState());
// Store the mapping to the original node.
- llvm::DenseMap<const void*, const void*>::iterator IMitr=InverseMap.find(N);
+ InterExplodedGraphMap::const_iterator IMitr = InverseMap.find(OrigN);
assert(IMitr != InverseMap.end() && "No mapping to original node.");
- (*BM)[NewN] = (const ExplodedNode*) IMitr->second;
+ GraphWrapper.BackMap[NewN] = IMitr->second;
// Link up the new node with the previous node.
- if (Last)
- NewN->addPredecessor(Last, *GNew);
+ if (Succ)
+ Succ->addPredecessor(NewN, *GNew);
+ else
+ GraphWrapper.ErrorNode = NewN;
- Last = NewN;
+ Succ = NewN;
// Are we at the final node?
- IndexMapTy::iterator IMI =
- IndexMap.find((const ExplodedNode*)(IMitr->second));
- if (IMI != IndexMap.end()) {
- First = NewN;
- NodeIndex = IMI->second;
+ if (OrigN->pred_empty()) {
+ GNew->addRoot(NewN);
break;
}
- // Find the next successor node. We choose the node that is marked
- // with the lowest DFS number.
- ExplodedNode::const_succ_iterator SI = N->succ_begin();
- ExplodedNode::const_succ_iterator SE = N->succ_end();
- N = 0;
-
- for (unsigned MinVal = 0; SI != SE; ++SI) {
-
- I = Visited.find(*SI);
-
- if (I == Visited.end())
- continue;
-
- if (!N || I->second < MinVal) {
- N = *SI;
- MinVal = I->second;
- }
- }
-
- assert(N);
+ // Find the next predeccessor node. We choose the node that is marked
+ // with the lowest BFS number.
+ OrigN = *std::min_element(OrigN->pred_begin(), OrigN->pred_end(),
+ PriorityCompare<false>(PriorityMap));
}
- assert(First);
-
- return std::make_pair(std::make_pair(GNew, BM),
- std::make_pair(First, NodeIndex));
+ return true;
}
+
/// CompactPathDiagnostic - This function postprocesses a PathDiagnostic object
/// and collapses PathDiagosticPieces that are expanded by macros.
static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM) {
@@ -1978,128 +2143,128 @@ bool GRBugReporter::generatePathDiagnostic(PathDiagnostic& PD,
assert(!bugReports.empty());
bool HasValid = false;
- SmallVector<const ExplodedNode *, 10> errorNodes;
+ bool HasInvalid = false;
+ SmallVector<const ExplodedNode *, 32> errorNodes;
for (ArrayRef<BugReport*>::iterator I = bugReports.begin(),
E = bugReports.end(); I != E; ++I) {
if ((*I)->isValid()) {
HasValid = true;
errorNodes.push_back((*I)->getErrorNode());
} else {
+ // Keep the errorNodes list in sync with the bugReports list.
+ HasInvalid = true;
errorNodes.push_back(0);
}
}
- // If all the reports have been marked invalid, we're done.
+ // If all the reports have been marked invalid by a previous path generation,
+ // we're done.
if (!HasValid)
return false;
- // Construct a new graph that contains only a single path from the error
- // node to a root.
- const std::pair<std::pair<ExplodedGraph*, NodeBackMap*>,
- std::pair<ExplodedNode*, unsigned> >&
- GPair = MakeReportGraph(&getGraph(), errorNodes);
-
- // Find the BugReport with the original location.
- assert(GPair.second.second < bugReports.size());
- BugReport *R = bugReports[GPair.second.second];
- assert(R && "No original report found for sliced graph.");
- assert(R->isValid() && "Report selected from trimmed graph marked invalid.");
-
- OwningPtr<ExplodedGraph> ReportGraph(GPair.first.first);
- OwningPtr<NodeBackMap> BackMap(GPair.first.second);
- const ExplodedNode *N = GPair.second.first;
-
- // Start building the path diagnostic...
- PathDiagnosticBuilder PDB(*this, R, BackMap.get(), &PC);
-
- // Register additional node visitors.
- R->addVisitor(new NilReceiverBRVisitor());
- R->addVisitor(new ConditionBRVisitor());
-
- BugReport::VisitorList visitors;
- unsigned originalReportConfigToken, finalReportConfigToken;
-
- // While generating diagnostics, it's possible the visitors will decide
- // new symbols and regions are interesting, or add other visitors based on
- // the information they find. If they do, we need to regenerate the path
- // based on our new report configuration.
- do {
- // Get a clean copy of all the visitors.
- for (BugReport::visitor_iterator I = R->visitor_begin(),
- E = R->visitor_end(); I != E; ++I)
- visitors.push_back((*I)->clone());
-
- // Clear out the active path from any previous work.
- PD.resetPath();
- originalReportConfigToken = R->getConfigurationChangeToken();
-
- // Generate the very last diagnostic piece - the piece is visible before
- // the trace is expanded.
- if (PDB.getGenerationScheme() != PathDiagnosticConsumer::None) {
+ typedef PathDiagnosticConsumer::PathGenerationScheme PathGenerationScheme;
+ PathGenerationScheme ActiveScheme = PC.getGenerationScheme();
+
+ TrimmedGraph TrimG(&getGraph(), errorNodes);
+ ReportGraph ErrorGraph;
+
+ while (TrimG.popNextReportGraph(ErrorGraph)) {
+ // Find the BugReport with the original location.
+ assert(ErrorGraph.Index < bugReports.size());
+ BugReport *R = bugReports[ErrorGraph.Index];
+ assert(R && "No original report found for sliced graph.");
+ assert(R->isValid() && "Report selected by trimmed graph marked invalid.");
+
+ // Start building the path diagnostic...
+ PathDiagnosticBuilder PDB(*this, R, ErrorGraph.BackMap, &PC);
+ const ExplodedNode *N = ErrorGraph.ErrorNode;
+
+ // Register additional node visitors.
+ R->addVisitor(new NilReceiverBRVisitor());
+ R->addVisitor(new ConditionBRVisitor());
+ R->addVisitor(new LikelyFalsePositiveSuppressionBRVisitor());
+
+ BugReport::VisitorList visitors;
+ unsigned origReportConfigToken, finalReportConfigToken;
+
+ // While generating diagnostics, it's possible the visitors will decide
+ // new symbols and regions are interesting, or add other visitors based on
+ // the information they find. If they do, we need to regenerate the path
+ // based on our new report configuration.
+ do {
+ // Get a clean copy of all the visitors.
+ for (BugReport::visitor_iterator I = R->visitor_begin(),
+ E = R->visitor_end(); I != E; ++I)
+ visitors.push_back((*I)->clone());
+
+ // Clear out the active path from any previous work.
+ PD.resetPath();
+ origReportConfigToken = R->getConfigurationChangeToken();
+
+ // Generate the very last diagnostic piece - the piece is visible before
+ // the trace is expanded.
PathDiagnosticPiece *LastPiece = 0;
for (BugReport::visitor_iterator I = visitors.begin(), E = visitors.end();
- I != E; ++I) {
+ I != E; ++I) {
if (PathDiagnosticPiece *Piece = (*I)->getEndPath(PDB, N, *R)) {
assert (!LastPiece &&
- "There can only be one final piece in a diagnostic.");
+ "There can only be one final piece in a diagnostic.");
LastPiece = Piece;
}
}
- if (!LastPiece)
- LastPiece = BugReporterVisitor::getDefaultEndPath(PDB, N, *R);
- if (LastPiece)
- PD.setEndOfPath(LastPiece);
- else
- return false;
- }
- switch (PDB.getGenerationScheme()) {
- case PathDiagnosticConsumer::Extensive:
- if (!GenerateExtensivePathDiagnostic(PD, PDB, N, visitors)) {
- assert(!R->isValid() && "Failed on valid report");
- // Try again. We'll filter out the bad report when we trim the graph.
- // FIXME: It would be more efficient to use the same intermediate
- // trimmed graph, and just repeat the shortest-path search.
- return generatePathDiagnostic(PD, PC, bugReports);
- }
- break;
- case PathDiagnosticConsumer::Minimal:
- if (!GenerateMinimalPathDiagnostic(PD, PDB, N, visitors)) {
- assert(!R->isValid() && "Failed on valid report");
- // Try again. We'll filter out the bad report when we trim the graph.
- return generatePathDiagnostic(PD, PC, bugReports);
+ if (ActiveScheme != PathDiagnosticConsumer::None) {
+ if (!LastPiece)
+ LastPiece = BugReporterVisitor::getDefaultEndPath(PDB, N, *R);
+ assert(LastPiece);
+ PD.setEndOfPath(LastPiece);
}
- break;
- case PathDiagnosticConsumer::None:
- if (!GenerateVisitorsOnlyPathDiagnostic(PD, PDB, N, visitors)) {
- assert(!R->isValid() && "Failed on valid report");
- // Try again. We'll filter out the bad report when we trim the graph.
- return generatePathDiagnostic(PD, PC, bugReports);
+
+ switch (ActiveScheme) {
+ case PathDiagnosticConsumer::Extensive:
+ GenerateExtensivePathDiagnostic(PD, PDB, N, visitors);
+ break;
+ case PathDiagnosticConsumer::Minimal:
+ GenerateMinimalPathDiagnostic(PD, PDB, N, visitors);
+ break;
+ case PathDiagnosticConsumer::None:
+ GenerateVisitorsOnlyPathDiagnostic(PD, PDB, N, visitors);
+ break;
}
- break;
- }
- // Clean up the visitors we used.
- llvm::DeleteContainerPointers(visitors);
+ // Clean up the visitors we used.
+ llvm::DeleteContainerPointers(visitors);
- // Did anything change while generating this path?
- finalReportConfigToken = R->getConfigurationChangeToken();
- } while(finalReportConfigToken != originalReportConfigToken);
+ // Did anything change while generating this path?
+ finalReportConfigToken = R->getConfigurationChangeToken();
+ } while (finalReportConfigToken != origReportConfigToken);
- // Finally, prune the diagnostic path of uninteresting stuff.
- if (!PD.path.empty()) {
- // Remove messages that are basically the same.
- removeRedundantMsgs(PD.getMutablePieces());
+ if (!R->isValid())
+ continue;
- if (R->shouldPrunePath()) {
- bool hasSomethingInteresting = RemoveUneededCalls(PD.getMutablePieces(),
- R);
- assert(hasSomethingInteresting);
- (void) hasSomethingInteresting;
+ // Finally, prune the diagnostic path of uninteresting stuff.
+ if (!PD.path.empty()) {
+ // Remove messages that are basically the same.
+ removeRedundantMsgs(PD.getMutablePieces());
+
+ if (R->shouldPrunePath() &&
+ getEngine().getAnalysisManager().options.shouldPrunePaths()) {
+ bool stillHasNotes = RemoveUnneededCalls(PD.getMutablePieces(), R);
+ assert(stillHasNotes);
+ (void)stillHasNotes;
+ }
+
+ adjustCallLocations(PD.getMutablePieces());
}
+
+ // We found a report and didn't suppress it.
+ return true;
}
- return true;
+ // We suppressed all the reports in this equivalence class.
+ assert(!HasInvalid && "Inconsistent suppression");
+ (void)HasInvalid;
+ return false;
}
void BugReporter::Register(BugType *BT) {
@@ -2265,7 +2430,12 @@ void BugReporter::FlushReport(BugReport *exampleReport,
exampleReport->getBugType().getName(),
exampleReport->getDescription(),
exampleReport->getShortDescription(/*Fallback=*/false),
- BT.getCategory()));
+ BT.getCategory(),
+ exampleReport->getUniqueingLocation(),
+ exampleReport->getUniqueingDecl()));
+
+ MaxBugClassSize = std::max(bugReports.size(),
+ static_cast<size_t>(MaxBugClassSize));
// Generate the full path diagnostic, using the generation scheme
// specified by the PathDiagnosticConsumer. Note that we have to generate
@@ -2275,6 +2445,9 @@ void BugReporter::FlushReport(BugReport *exampleReport,
if (!generatePathDiagnostic(*D.get(), PD, bugReports))
return;
+ MaxValidBugClassSize = std::max(bugReports.size(),
+ static_cast<size_t>(MaxValidBugClassSize));
+
// If the path is empty, generate a single step path with the location
// of the issue.
if (D->path.empty()) {