CallEnterKind,
CallExitKind,
MinPostStmtKind = PostStmtKind,
- MaxPostStmtKind = CallExitKind };
+ MaxPostStmtKind = CallExitKind,
+ EpsilonKind};
private:
std::pair<const void *, const void *> Data;
}
};
+/// This is a meta program point, which should be skipped by all the diagnostic
+/// reasoning etc.
+class EpsilonPoint : public ProgramPoint {
+public:
+ EpsilonPoint(const LocationContext *L, const void *Data1,
+ const void *Data2 = 0, const ProgramPointTag *tag = 0)
+ : ProgramPoint(Data1, Data2, EpsilonKind, L, tag) {}
+
+ const void *getData() const { return getData1(); }
+
+ static bool classof(const ProgramPoint* Location) {
+ return Location->getKind() == EpsilonKind;
+ }
+};
+
/// ProgramPoints can be "tagged" as representing points specific to a given
/// analysis entity. Tags are abstract annotations, with an associated
/// description and potentially other information.
def analyzer_inlining_mode : Separate<"-analyzer-inlining-mode">,
HelpText<"Specify the function selection heuristic used during inlining">;
def analyzer_inlining_mode_EQ : Joined<"-analyzer-inlining-mode=">, Alias<analyzer_inlining_mode>;
+
+def analyzer_retry_exhausted : Flag<"-analyzer-retry-exhausted">,
+ HelpText<"Re-analyze paths leading to exhausted nodes with a different strategy for better code coverage">;
def analyzer_max_nodes : Separate<"-analyzer-max-nodes">,
HelpText<"The maximum number of nodes the analyzer can generate (150000 default, 0 = no limit)">;
unsigned CFGAddInitializers : 1;
unsigned EagerlyTrimEGraph : 1;
unsigned PrintStats : 1;
+ unsigned RetryExhausted : 1;
unsigned InlineMaxStackDepth;
unsigned InlineMaxFunctionSize;
AnalysisInliningMode InliningMode;
CFGAddInitializers = 0;
EagerlyTrimEGraph = 0;
PrintStats = 0;
+ RetryExhausted = 0;
// Cap the stack depth at 4 calls (5 stack frames, base + 4 calls).
InlineMaxStackDepth = 5;
InlineMaxFunctionSize = 200;
/// \brief The mode of function selection used during inlining.
AnalysisInliningMode InliningMode;
+ /// \brief Re-analyze paths leading to exhausted nodes with a different
+ /// strategy for better code coverage.
+ bool RetryExhausted;
+
public:
AnalysisManager(ASTContext &ctx, DiagnosticsEngine &diags,
const LangOptions &lang, PathDiagnosticConsumer *pd,
AnalysisIPAMode ipa,
unsigned inlineMaxStack,
unsigned inlineMaxFunctionSize,
- AnalysisInliningMode inliningMode);
+ AnalysisInliningMode inliningMode,
+ bool retry);
/// Construct a clone of the given AnalysisManager with the given ASTContext
/// and DiagnosticsEngine.
CoreEngine(const CoreEngine&); // Do not implement.
CoreEngine& operator=(const CoreEngine&);
- void enqueueStmtNode(ExplodedNode *N,
- const CFGBlock *Block, unsigned Idx);
-
ExplodedNode *generateCallExitNode(ExplodedNode *N);
public:
ProgramStateRef InitState,
ExplodedNodeSet &Dst);
+ /// Dispatch the work list item based on the given location information.
+ /// Use Pred parameter as the predecessor state.
+ void dispatchWorkItem(ExplodedNode* Pred, ProgramPoint Loc,
+ const WorkListUnit& WU);
+
// Functions for external checking of whether we have unfinished work
bool wasBlockAborted() const { return !blocksAborted.empty(); }
bool wasBlocksExhausted() const { return !blocksExhausted.empty(); }
/// \brief enqueue the nodes corresponding to the end of function onto the
/// end of path / work list.
void enqueueEndOfFunction(ExplodedNodeSet &Set);
+
+ /// \brief Enqueue a single node created as a result of statement processing.
+ void enqueueStmtNode(ExplodedNode *N, const CFGBlock *Block, unsigned Idx);
};
// TODO: Turn into a calss.
bool isSink() const { return Succs.getFlag(); }
+ bool hasSinglePred() const {
+ return (pred_size() == 1);
+ }
+
ExplodedNode *getFirstPred() {
return pred_empty() ? NULL : *(pred_begin());
}
#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CoreEngine.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Type.h"
ProgramStateRef St, SVal location,
const ProgramPointTag *tag, bool isLoad);
+ bool shouldInlineDecl(const FunctionDecl *FD, ExplodedNode *Pred);
bool InlineCall(ExplodedNodeSet &Dst, const CallExpr *CE, ExplodedNode *Pred);
+
+ bool replayWithoutInlining(ExplodedNode *P, const LocationContext *CalleeLC);
+};
+
+/// Traits for storing the call processing policy inside GDM.
+/// The GDM stores the corresponding CallExpr pointer.
+struct ReplayWithoutInlining{};
+template <>
+struct ProgramStateTrait<ReplayWithoutInlining> :
+ public ProgramStatePartialTrait<void*> {
+ static void *GDMIndex() { static int index = 0; return &index; }
};
} // end ento namespace
return (void*) (uintptr_t) d;
}
};
-
+
+ // Partial specialization for void*.
+ template <> struct ProgramStatePartialTrait<void*> {
+ typedef void *data_type;
+
+ static inline data_type MakeData(void *const* p) {
+ return p ? *p
+ : data_type();
+ }
+ static inline void *MakeVoidPtr(data_type d) {
+ return d;
+ }
+ };
+
} // end GR namespace
} // end clang namespace
Res.push_back("-analyzer-viz-egraph-graphviz");
if (Opts.VisualizeEGUbi)
Res.push_back("-analyzer-viz-egraph-ubigraph");
+ if (Opts.RetryExhausted)
+ Res.push_back("-analyzer-retry-exhausted");
for (unsigned i = 0, e = Opts.CheckersControlList.size(); i != e; ++i) {
const std::pair<std::string, bool> &opt = Opts.CheckersControlList[i];
Opts.ShowCheckerHelp = Args.hasArg(OPT_analyzer_checker_help);
Opts.VisualizeEGDot = Args.hasArg(OPT_analyzer_viz_egraph_graphviz);
Opts.VisualizeEGUbi = Args.hasArg(OPT_analyzer_viz_egraph_ubigraph);
+ Opts.RetryExhausted = Args.hasArg(OPT_analyzer_retry_exhausted);
Opts.AnalyzeAll = Args.hasArg(OPT_analyzer_opt_analyze_headers);
Opts.AnalyzerDisplayProgress = Args.hasArg(OPT_analyzer_display_progress);
Opts.AnalyzeNestedBlocks =
AnalysisIPAMode ipa,
unsigned inlineMaxStack,
unsigned inlineMaxFunctionSize,
- AnalysisInliningMode IMode)
+ AnalysisInliningMode IMode,
+ bool retry)
: AnaCtxMgr(useUnoptimizedCFG, addImplicitDtors, addInitializers),
Ctx(ctx), Diags(diags), LangOpts(lang), PD(pd),
CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr),
IPAMode(ipa),
InlineMaxStackDepth(inlineMaxStack),
InlineMaxFunctionSize(inlineMaxFunctionSize),
- InliningMode(IMode)
+ InliningMode(IMode),
+ RetryExhausted(retry)
{
AnaCtxMgr.getCFGBuildOptions().setAllAlwaysAdd();
}
IPAMode(ParentAM.IPAMode),
InlineMaxStackDepth(ParentAM.InlineMaxStackDepth),
InlineMaxFunctionSize(ParentAM.InlineMaxFunctionSize),
- InliningMode(ParentAM.InliningMode)
+ InliningMode(ParentAM.InliningMode),
+ RetryExhausted(ParentAM.RetryExhausted)
{
AnaCtxMgr.getCFGBuildOptions().setAllAlwaysAdd();
}
// Retrieve the node.
ExplodedNode *Node = WU.getNode();
- // Dispatch on the location type.
- switch (Node->getLocation().getKind()) {
- case ProgramPoint::BlockEdgeKind:
- HandleBlockEdge(cast<BlockEdge>(Node->getLocation()), Node);
- break;
-
- case ProgramPoint::BlockEntranceKind:
- HandleBlockEntrance(cast<BlockEntrance>(Node->getLocation()), Node);
- break;
-
- case ProgramPoint::BlockExitKind:
- assert (false && "BlockExit location never occur in forward analysis.");
- break;
+ dispatchWorkItem(Node, Node->getLocation(), WU);
+ }
+ SubEng.processEndWorklist(hasWorkRemaining());
+ return WList->hasWork();
+}
- case ProgramPoint::CallEnterKind: {
- CallEnter CEnter = cast<CallEnter>(Node->getLocation());
- if (AnalyzedCallees)
- if (const CallExpr* CE =
- dyn_cast_or_null<CallExpr>(CEnter.getCallExpr()))
- if (const Decl *CD = CE->getCalleeDecl())
- AnalyzedCallees->insert(CD);
- SubEng.processCallEnter(CEnter, Node);
- break;
- }
+void CoreEngine::dispatchWorkItem(ExplodedNode* Pred, ProgramPoint Loc,
+ const WorkListUnit& WU) {
+ // Dispatch on the location type.
+ switch (Loc.getKind()) {
+ case ProgramPoint::BlockEdgeKind:
+ HandleBlockEdge(cast<BlockEdge>(Loc), Pred);
+ break;
+
+ case ProgramPoint::BlockEntranceKind:
+ HandleBlockEntrance(cast<BlockEntrance>(Loc), Pred);
+ break;
+
+ case ProgramPoint::BlockExitKind:
+ assert (false && "BlockExit location never occur in forward analysis.");
+ break;
+
+ case ProgramPoint::CallEnterKind: {
+ CallEnter CEnter = cast<CallEnter>(Loc);
+ if (AnalyzedCallees)
+ if (const CallExpr* CE =
+ dyn_cast_or_null<CallExpr>(CEnter.getCallExpr()))
+ if (const Decl *CD = CE->getCalleeDecl())
+ AnalyzedCallees->insert(CD);
+ SubEng.processCallEnter(CEnter, Pred);
+ break;
+ }
- case ProgramPoint::CallExitKind:
- SubEng.processCallExit(Node);
- break;
+ case ProgramPoint::CallExitKind:
+ SubEng.processCallExit(Pred);
+ break;
- default:
- assert(isa<PostStmt>(Node->getLocation()) ||
- isa<PostInitializer>(Node->getLocation()));
- HandlePostStmt(WU.getBlock(), WU.getIndex(), Node);
- break;
+ case ProgramPoint::EpsilonKind: {
+ assert(Pred->hasSinglePred() &&
+ "Assume epsilon has exactly one predecessor by construction");
+ ExplodedNode *PNode = Pred->getFirstPred();
+ dispatchWorkItem(Pred, PNode->getLocation(), WU);
+ break;
}
+ default:
+ assert(isa<PostStmt>(Loc) ||
+ isa<PostInitializer>(Loc));
+ HandlePostStmt(WU.getBlock(), WU.getIndex(), Pred);
+ break;
}
-
- SubEng.processEndWorklist(hasWorkRemaining());
- return WList->hasWork();
}
void CoreEngine::ExecuteWorkListWithInitialState(const LocationContext *L,
return;
}
+ if (isa<EpsilonPoint>(N->getLocation())) {
+ WList->enqueue(N, Block, Idx);
+ return;
+ }
+
const CFGStmt *CS = (*Block)[Idx].getAs<CFGStmt>();
const Stmt *St = CS ? CS->getStmt() : 0;
PostStmt Loc(St, N->getLocationContext());
STATISTIC(NumMaxBlockCountReachedInInlined,
"The # of aborted paths due to reaching the maximum block count in "
"an inlined function");
+STATISTIC(NumTimesRetriedWithoutInlining,
+ "The # of times we re-evaluated a call without inlining");
+
+STATISTIC(NumNotNew,
+ "Cached out");
+STATISTIC(NumNull,
+ "Null node");
//===----------------------------------------------------------------------===//
// Utility functions.
}
}
+bool ExprEngine::replayWithoutInlining(ExplodedNode *N,
+ const LocationContext *CalleeLC) {
+ const StackFrameContext *CalleeSF = CalleeLC->getCurrentStackFrame();
+ const StackFrameContext *CallerSF = CalleeSF->getParent()->getCurrentStackFrame();
+ assert(CalleeSF && CallerSF);
+ ExplodedNode *BeforeProcessingCall = 0;
+
+ // Find the first node before we started processing the call expression.
+ while (N) {
+ ProgramPoint L = N->getLocation();
+ BeforeProcessingCall = N;
+ N = N->pred_empty() ? NULL : *(N->pred_begin());
+
+ // Skip the nodes corresponding to the inlined code.
+ if (L.getLocationContext()->getCurrentStackFrame() != CallerSF)
+ continue;
+ // We reached the caller. Find the node right before we started
+ // processing the CallExpr.
+ if (isa<PostPurgeDeadSymbols>(L))
+ continue;
+ if (const StmtPoint *SP = dyn_cast<StmtPoint>(&L))
+ if (SP->getStmt() == CalleeSF->getCallSite())
+ continue;
+ break;
+ }
+
+ if (!BeforeProcessingCall) {
+ NumNull++;
+ return false;
+ }
+
+ // TODO: Clean up the unneeded nodes.
+
+ // Build an Epsilon node from which we will restart the analyzes.
+ const Stmt *CE = CalleeSF->getCallSite();
+ ProgramPoint NewNodeLoc =
+ EpsilonPoint(BeforeProcessingCall->getLocationContext(), CE);
+ // Add the special flag to GDM to signal retrying with no inlining.
+ // Note, changing the state ensures that we are not going to cache out.
+ ProgramStateRef NewNodeState = BeforeProcessingCall->getState();
+ NewNodeState = NewNodeState->set<ReplayWithoutInlining>((void*)CE);
+
+ // Make the new node a successor of BeforeProcessingCall.
+ bool IsNew = false;
+ ExplodedNode *NewNode = G.getNode(NewNodeLoc, NewNodeState, false, &IsNew);
+ if (!IsNew) {
+ NumNotNew++;
+ return false;
+ }
+ NewNode->addPredecessor(BeforeProcessingCall, G);
+
+ // Add the new node to the work list.
+ Engine.enqueueStmtNode(NewNode, CalleeSF->getCallSiteBlock(),
+ CalleeSF->getIndex());
+ NumTimesRetriedWithoutInlining++;
+ return true;
+}
+
/// Block entrance. (Update counters).
void ExprEngine::processCFGBlockEntrance(NodeBuilderWithSinks &nodeBuilder) {
// Check if we stopped at the top level function or not.
// Root node should have the location context of the top most function.
- if ((*G.roots_begin())->getLocation().getLocationContext() !=
- pred->getLocation().getLocationContext())
- NumMaxBlockCountReachedInInlined++;
- else
+ const LocationContext *CalleeLC = pred->getLocation().getLocationContext();
+ const LocationContext *RootLC =
+ (*G.roots_begin())->getLocation().getLocationContext();
+ if (RootLC->getCurrentStackFrame() != CalleeLC->getCurrentStackFrame()) {
+ // Re-run the call evaluation without inlining it, by storing the
+ // no-inlining policy in the state and enqueuing the new work item on
+ // the list. Replay should almost never fail. Use the stats to catch it
+ // if it does.
+ if (!(AMgr.RetryExhausted && replayWithoutInlining(pred, CalleeLC)))
+ NumMaxBlockCountReachedInInlined++;
+ } else
NumMaxBlockCountReached++;
}
}
Out << "CallExit";
break;
+ case ProgramPoint::EpsilonKind:
+ Out << "Epsilon Point";
+ break;
+
default: {
if (StmtPoint *L = dyn_cast<StmtPoint>(&Loc)) {
const Stmt *S = L->getStmt();
}
// Determine if we should inline the call.
-static bool shouldInline(const FunctionDecl *FD, ExplodedNode *Pred,
- AnalysisManager &AMgr) {
+bool ExprEngine::shouldInlineDecl(const FunctionDecl *FD, ExplodedNode *Pred) {
AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(FD);
const CFG *CalleeCFG = CalleeADC->getCFG();
return true;
}
+// For now, skip inlining variadic functions.
+// We also don't inline blocks.
+static bool shouldInlineCallExpr(const CallExpr *CE, ExprEngine *E) {
+ if (!E->getAnalysisManager().shouldInlineCall())
+ return false;
+ QualType callee = CE->getCallee()->getType();
+ const FunctionProtoType *FT = 0;
+ if (const PointerType *PT = callee->getAs<PointerType>())
+ FT = dyn_cast<FunctionProtoType>(PT->getPointeeType());
+ else if (const BlockPointerType *BT = callee->getAs<BlockPointerType>()) {
+ // FIXME: inline blocks.
+ // FT = dyn_cast<FunctionProtoType>(BT->getPointeeType());
+ (void) BT;
+ return false;
+ }
+ // If we have no prototype, assume the function is okay.
+ if (!FT)
+ return true;
+
+ // Skip inlining of variadic functions.
+ return !FT->isVariadic();
+}
+
bool ExprEngine::InlineCall(ExplodedNodeSet &Dst,
const CallExpr *CE,
ExplodedNode *Pred) {
+ if (!shouldInlineCallExpr(CE, this))
+ return false;
+
ProgramStateRef state = Pred->getState();
const Expr *Callee = CE->getCallee();
const FunctionDecl *FD =
// FIXME: Handle C++.
break;
case Stmt::CallExprClass: {
- if (!shouldInline(FD, Pred, AMgr))
+ if (!shouldInlineDecl(FD, Pred))
return false;
// Construct a new stack frame for the callee.
}
-// For now, skip inlining variadic functions.
-// We also don't inline blocks.
-static bool shouldInlineCall(const CallExpr *CE, ExprEngine &Eng) {
- if (!Eng.getAnalysisManager().shouldInlineCall())
- return false;
- QualType callee = CE->getCallee()->getType();
- const FunctionProtoType *FT = 0;
- if (const PointerType *PT = callee->getAs<PointerType>())
- FT = dyn_cast<FunctionProtoType>(PT->getPointeeType());
- else if (const BlockPointerType *BT = callee->getAs<BlockPointerType>()) {
- // FIXME: inline blocks.
- // FT = dyn_cast<FunctionProtoType>(BT->getPointeeType());
- (void) BT;
- return false;
+static ProgramStateRef getReplayWithoutInliningState(ExplodedNode *&N,
+ const CallExpr *CE) {
+ void *ReplayState = N->getState()->get<ReplayWithoutInlining>();
+ if (!ReplayState)
+ return 0;
+ const CallExpr *ReplayCE = reinterpret_cast<const CallExpr*>(ReplayState);
+ if (CE == ReplayCE) {
+ return N->getState()->remove<ReplayWithoutInlining>();
}
-
- // If we have no prototype, assume the function is okay.
- if (!FT)
- return true;
-
- // Skip inlining of variadic functions.
- return !FT->isVariadic();
+ return 0;
}
void ExprEngine::VisitCallExpr(const CallExpr *CE, ExplodedNode *Pred,
DefaultEval(ExprEngine &eng, const CallExpr *ce)
: Eng(eng), CE(ce) {}
virtual void expandGraph(ExplodedNodeSet &Dst, ExplodedNode *Pred) {
- // Should we inline the call?
- if (shouldInlineCall(CE, Eng) &&
- Eng.InlineCall(Dst, CE, Pred)) {
+
+ ProgramStateRef state = getReplayWithoutInliningState(Pred, CE);
+
+ // First, try to inline the call.
+ if (state == 0 && Eng.InlineCall(Dst, CE, Pred))
return;
- }
// First handle the return value.
StmtNodeBuilder Bldr(Pred, Dst, *Eng.currentBuilderContext);
// Get the callee.
const Expr *Callee = CE->getCallee()->IgnoreParens();
- ProgramStateRef state = Pred->getState();
+ if (state == 0)
+ state = Pred->getState();
SVal L = state->getSVal(Callee, Pred->getLocationContext());
// Figure out the result type. We do this dance to handle references.
Opts.IPAMode,
Opts.InlineMaxStackDepth,
Opts.InlineMaxFunctionSize,
- Opts.InliningMode));
+ Opts.InliningMode,
+ Opts.RetryExhausted));
}
virtual void HandleTranslationUnit(ASTContext &C);
--- /dev/null
+// RUN: %clang_cc1 -analyze -analyzer-checker=core,unix.Malloc -analyzer-store=region -analyzer-max-loop 4 -analyzer-retry-exhausted -verify %s
+#include "system-header-simulator.h"
+
+typedef __typeof(sizeof(int)) size_t;
+void *malloc(size_t);
+
+static int another_function(int *y) {
+ if (*y > 0)
+ return *y;
+ return 0;
+}
+
+static void function_which_doesnt_give_up(int **x) {
+ *x = 0;
+}
+
+static void function_which_gives_up(int *x) {
+ for (int i = 0; i < 5; ++i)
+ (*x)++;
+}
+
+static void function_which_gives_up_nested(int *x) {
+ function_which_gives_up(x);
+ for (int i = 0; i < 5; ++i)
+ (*x)++;
+}
+
+static void function_which_doesnt_give_up_nested(int *x, int *y) {
+ *y = another_function(x);
+ function_which_gives_up(x);
+}
+
+void coverage1(int *x) {
+ function_which_gives_up(x);
+ char *m = (char*)malloc(12); // expected-warning {{potential leak}}
+}
+
+void coverage2(int *x) {
+ if (x) {
+ function_which_gives_up(x);
+ char *m = (char*)malloc(12);// expected-warning {{potential leak}}
+ }
+}
+
+void coverage3(int *x) {
+ x++;
+ function_which_gives_up(x);
+ char *m = (char*)malloc(12);// expected-warning {{potential leak}}
+}
+
+void coverage4(int *x) {
+ *x += another_function(x);
+ function_which_gives_up(x);
+ char *m = (char*)malloc(12);// expected-warning {{potential leak}}
+}
+
+void coverage5(int *x) {
+ for (int i = 0; i<7; ++i)
+ function_which_gives_up(x);
+ // The root function gives up here.
+ char *m = (char*)malloc(12); // no-warning
+}
+
+void coverage6(int *x) {
+ for (int i = 0; i<3; ++i) {
+ function_which_gives_up(x);
+ }
+ char *m = (char*)malloc(12); // expected-warning {{potential leak}}
+}
+
+int coverage7_inline(int *i) {
+ function_which_doesnt_give_up(&i);
+ return *i; // expected-warning {{Dereference}}
+}
+
+void coverage8(int *x) {
+ int y;
+ function_which_doesnt_give_up_nested(x, &y);
+ y = (*x)/y; // expected-warning {{Division by zero}}
+ char *m = (char*)malloc(12); // expected-warning {{potential leak}}
+}
+
+void function_which_gives_up_settonull(int **x) {
+ *x = 0;
+ int y = 0;
+ for (int i = 0; i < 5; ++i)
+ y++;
+}
+
+void coverage9(int *x) {
+ int y = 5;
+ function_which_gives_up_settonull(&x);
+ y = (*x); // no warning
+}