Skip to content
This repository was archived by the owner on Oct 24, 2019. It is now read-only.

Commit c208eda

Browse files
committed
[analyzer] Treat functions without run-time branches as "small".
Currently we always inline functions that have no branches, i.e. have exactly three CFG blocks: ENTRY, some code, EXIT. This makes sense because when there are no branches, it means that there's no exponential complexity introduced by inlining such function. Such functions also don't trigger various fundamental problems with our inlining mechanism, such as the problem of inlined defensive checks. Sometimes the CFG may contain more blocks, but in practice it still has linear structure because all directions (except, at most, one) of all branches turned out to be unreachable. When this happens, still treat the function as "small". This is useful, in particular, for dealing with C++17 if constexpr. Differential Revision: https://reviews.llvm.org/D61051 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@359531 91177308-0d34-0410-b5e6-96231b3b80d8
1 parent 4ecb1e1 commit c208eda

File tree

7 files changed

+163
-33
lines changed

7 files changed

+163
-33
lines changed

include/clang/Analysis/CFG.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,6 +1172,12 @@ class CFG {
11721172
/// implementation needs such an interface.
11731173
unsigned size() const { return NumBlockIDs; }
11741174

1175+
/// Returns true if the CFG has no branches. Usually it boils down to the CFG
1176+
/// having exactly three blocks (entry, the actual code, exit), but sometimes
1177+
/// more blocks appear due to having control flow that can be fully
1178+
/// resolved in compile time.
1179+
bool isLinear() const;
1180+
11751181
//===--------------------------------------------------------------------===//
11761182
// CFG Debugging: Pretty-Printing and Visualization.
11771183
//===--------------------------------------------------------------------===//

include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,25 @@ class ExprEngine : public SubEngine {
718718
AnalyzerOptions &Opts,
719719
const EvalCallOptions &CallOpts);
720720

721+
/// See if the given AnalysisDeclContext is built for a function that we
722+
/// should always inline simply because it's small enough.
723+
/// Apart from "small" functions, we also have "large" functions
724+
/// (cf. isLarge()), some of which are huge (cf. isHuge()), and we classify
725+
/// the remaining functions as "medium".
726+
bool isSmall(AnalysisDeclContext *ADC) const;
727+
728+
/// See if the given AnalysisDeclContext is built for a function that we
729+
/// should inline carefully because it looks pretty large.
730+
bool isLarge(AnalysisDeclContext *ADC) const;
731+
732+
/// See if the given AnalysisDeclContext is built for a function that we
733+
/// should never inline because it's legit gigantic.
734+
bool isHuge(AnalysisDeclContext *ADC) const;
735+
736+
/// See if the given AnalysisDeclContext is built for a function that we
737+
/// should inline, just by looking at the declaration of the function.
738+
bool mayInlineDecl(AnalysisDeclContext *ADC) const;
739+
721740
/// Checks our policies and decides weither the given call should be inlined.
722741
bool shouldInlineCall(const CallEvent &Call, const Decl *D,
723742
const ExplodedNode *Pred,

lib/Analysis/CFG.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4677,6 +4677,51 @@ std::unique_ptr<CFG> CFG::buildCFG(const Decl *D, Stmt *Statement,
46774677
return Builder.buildCFG(D, Statement);
46784678
}
46794679

4680+
bool CFG::isLinear() const {
4681+
// Quick path: if we only have the ENTRY block, the EXIT block, and some code
4682+
// in between, then we have no room for control flow.
4683+
if (size() <= 3)
4684+
return true;
4685+
4686+
// Traverse the CFG until we find a branch.
4687+
// TODO: While this should still be very fast,
4688+
// maybe we should cache the answer.
4689+
llvm::SmallPtrSet<const CFGBlock *, 4> Visited;
4690+
const CFGBlock *B = Entry;
4691+
while (B != Exit) {
4692+
auto IteratorAndFlag = Visited.insert(B);
4693+
if (!IteratorAndFlag.second) {
4694+
// We looped back to a block that we've already visited. Not linear.
4695+
return false;
4696+
}
4697+
4698+
// Iterate over reachable successors.
4699+
const CFGBlock *FirstReachableB = nullptr;
4700+
for (const CFGBlock::AdjacentBlock &AB : B->succs()) {
4701+
if (!AB.isReachable())
4702+
continue;
4703+
4704+
if (FirstReachableB == nullptr) {
4705+
FirstReachableB = &*AB;
4706+
} else {
4707+
// We've encountered a branch. It's not a linear CFG.
4708+
return false;
4709+
}
4710+
}
4711+
4712+
if (!FirstReachableB) {
4713+
// We reached a dead end. EXIT is unreachable. This is linear enough.
4714+
return true;
4715+
}
4716+
4717+
// There's only one way to move forward. Proceed.
4718+
B = FirstReachableB;
4719+
}
4720+
4721+
// We reached EXIT and found no branches.
4722+
return true;
4723+
}
4724+
46804725
const CXXDestructorDecl *
46814726
CFGImplicitDtor::getDestructorDecl(ASTContext &astContext) const {
46824727
switch (getKind()) {

lib/StaticAnalyzer/Core/BugReporterVisitors.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -563,15 +563,14 @@ class NoStoreFuncVisitor final : public BugReporterVisitor {
563563
// initialize its out-parameter, and additionally check that such
564564
// precondition can actually be fulfilled on the current path.
565565
if (Call.isInSystemHeader()) {
566-
// We make an exception for system header functions that have no branches,
567-
// i.e. have exactly 3 CFG blocks: begin, all its code, end. Such
568-
// functions unconditionally fail to initialize the variable.
566+
// We make an exception for system header functions that have no branches.
567+
// Such functions unconditionally fail to initialize the variable.
569568
// If they call other functions that have more paths within them,
570569
// this suppression would still apply when we visit these inner functions.
571570
// One common example of a standard function that doesn't ever initialize
572571
// its out parameter is operator placement new; it's up to the follow-up
573572
// constructor (if any) to initialize the memory.
574-
if (N->getStackFrame()->getCFG()->size() > 3)
573+
if (!N->getStackFrame()->getCFG()->isLinear())
575574
R.markInvalid(getTag(), nullptr);
576575
return nullptr;
577576
}

lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,26 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {
364364
}
365365
}
366366

367+
bool ExprEngine::isSmall(AnalysisDeclContext *ADC) const {
368+
// When there are no branches in the function, it means that there's no
369+
// exponential complexity introduced by inlining such function.
370+
// Such functions also don't trigger various fundamental problems
371+
// with our inlining mechanism, such as the problem of
372+
// inlined defensive checks. Hence isLinear().
373+
const CFG *Cfg = ADC->getCFG();
374+
return Cfg->isLinear() || Cfg->size() <= AMgr.options.AlwaysInlineSize;
375+
}
376+
377+
bool ExprEngine::isLarge(AnalysisDeclContext *ADC) const {
378+
const CFG *Cfg = ADC->getCFG();
379+
return Cfg->size() >= AMgr.options.MinCFGSizeTreatFunctionsAsLarge;
380+
}
381+
382+
bool ExprEngine::isHuge(AnalysisDeclContext *ADC) const {
383+
const CFG *Cfg = ADC->getCFG();
384+
return Cfg->getNumBlockIDs() > AMgr.options.MaxInlinableSize;
385+
}
386+
367387
void ExprEngine::examineStackFrames(const Decl *D, const LocationContext *LCtx,
368388
bool &IsRecursive, unsigned &StackDepth) {
369389
IsRecursive = false;
@@ -384,8 +404,7 @@ void ExprEngine::examineStackFrames(const Decl *D, const LocationContext *LCtx,
384404

385405
// Do not count the small functions when determining the stack depth.
386406
AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(DI);
387-
const CFG *CalleeCFG = CalleeADC->getCFG();
388-
if (CalleeCFG->getNumBlockIDs() > AMgr.options.AlwaysInlineSize)
407+
if (!isSmall(CalleeADC))
389408
++StackDepth;
390409
}
391410
LCtx = LCtx->getParent();
@@ -832,8 +851,7 @@ static bool isCXXSharedPtrDtor(const FunctionDecl *FD) {
832851
/// This checks static properties of the function, such as its signature and
833852
/// CFG, to determine whether the analyzer should ever consider inlining it,
834853
/// in any context.
835-
static bool mayInlineDecl(AnalysisManager &AMgr,
836-
AnalysisDeclContext *CalleeADC) {
854+
bool ExprEngine::mayInlineDecl(AnalysisDeclContext *CalleeADC) const {
837855
AnalyzerOptions &Opts = AMgr.getAnalyzerOptions();
838856
// FIXME: Do not inline variadic calls.
839857
if (CallEvent::isVariadic(CalleeADC->getDecl()))
@@ -878,7 +896,7 @@ static bool mayInlineDecl(AnalysisManager &AMgr,
878896
return false;
879897

880898
// Do not inline large functions.
881-
if (CalleeCFG->getNumBlockIDs() > Opts.MaxInlinableSize)
899+
if (isHuge(CalleeADC))
882900
return false;
883901

884902
// It is possible that the live variables analysis cannot be
@@ -918,7 +936,7 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D,
918936
} else {
919937
// We haven't actually checked the static properties of this function yet.
920938
// Do that now, and record our decision in the function summaries.
921-
if (mayInlineDecl(getAnalysisManager(), CalleeADC)) {
939+
if (mayInlineDecl(CalleeADC)) {
922940
Engine.FunctionSummaries->markMayInline(D);
923941
} else {
924942
Engine.FunctionSummaries->markShouldNotInline(D);
@@ -939,29 +957,23 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D,
939957
return false;
940958
}
941959

942-
const CFG *CalleeCFG = CalleeADC->getCFG();
943-
944960
// Do not inline if recursive or we've reached max stack frame count.
945961
bool IsRecursive = false;
946962
unsigned StackDepth = 0;
947963
examineStackFrames(D, Pred->getLocationContext(), IsRecursive, StackDepth);
948964
if ((StackDepth >= Opts.InlineMaxStackDepth) &&
949-
((CalleeCFG->getNumBlockIDs() > Opts.AlwaysInlineSize)
950-
|| IsRecursive))
965+
(!isSmall(CalleeADC) || IsRecursive))
951966
return false;
952967

953968
// Do not inline large functions too many times.
954969
if ((Engine.FunctionSummaries->getNumTimesInlined(D) >
955970
Opts.MaxTimesInlineLarge) &&
956-
CalleeCFG->getNumBlockIDs() >=
957-
Opts.MinCFGSizeTreatFunctionsAsLarge) {
971+
isLarge(CalleeADC)) {
958972
NumReachedInlineCountMax++;
959973
return false;
960974
}
961975

962-
if (HowToInline == Inline_Minimal &&
963-
(CalleeCFG->getNumBlockIDs() > Opts.AlwaysInlineSize
964-
|| IsRecursive))
976+
if (HowToInline == Inline_Minimal && (!isSmall(CalleeADC) || IsRecursive))
965977
return false;
966978

967979
return true;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \
2+
// RUN: -analyzer-inline-max-stack-depth=5 -w -std=c++17 -verify %s
3+
4+
void clang_analyzer_eval(bool);
5+
6+
namespace inline_large_functions_with_if_constexpr {
7+
bool f0() { if constexpr (true); return true; }
8+
bool f1() { if constexpr (true); return f0(); }
9+
bool f2() { if constexpr (true); return f1(); }
10+
bool f3() { if constexpr (true); return f2(); }
11+
bool f4() { if constexpr (true); return f3(); }
12+
bool f5() { if constexpr (true); return f4(); }
13+
bool f6() { if constexpr (true); return f5(); }
14+
bool f7() { if constexpr (true); return f6(); }
15+
void bar() {
16+
clang_analyzer_eval(f7()); // expected-warning{{TRUE}}
17+
}
18+
} // namespace inline_large_functions_with_if_constexpr

unittests/Analysis/CFGTest.cpp

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,41 @@ namespace clang {
1717
namespace analysis {
1818
namespace {
1919

20-
enum BuildResult {
21-
ToolFailed,
22-
ToolRan,
23-
SawFunctionBody,
24-
BuiltCFG,
20+
class BuildResult {
21+
public:
22+
enum Status {
23+
ToolFailed,
24+
ToolRan,
25+
SawFunctionBody,
26+
BuiltCFG,
27+
};
28+
29+
BuildResult(Status S, std::unique_ptr<CFG> Cfg = nullptr)
30+
: S(S), Cfg(std::move(Cfg)) {}
31+
32+
Status getStatus() const { return S; }
33+
CFG *getCFG() const { return Cfg.get(); }
34+
35+
private:
36+
Status S;
37+
std::unique_ptr<CFG> Cfg;
2538
};
2639

2740
class CFGCallback : public ast_matchers::MatchFinder::MatchCallback {
2841
public:
29-
BuildResult TheBuildResult = ToolRan;
42+
BuildResult TheBuildResult = BuildResult::ToolRan;
3043

3144
void run(const ast_matchers::MatchFinder::MatchResult &Result) override {
3245
const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
3346
Stmt *Body = Func->getBody();
3447
if (!Body)
3548
return;
36-
TheBuildResult = SawFunctionBody;
49+
TheBuildResult = BuildResult::SawFunctionBody;
3750
CFG::BuildOptions Options;
3851
Options.AddImplicitDtors = true;
39-
if (CFG::buildCFG(nullptr, Body, Result.Context, Options))
40-
TheBuildResult = BuiltCFG;
52+
if (std::unique_ptr<CFG> Cfg =
53+
CFG::buildCFG(nullptr, Body, Result.Context, Options))
54+
TheBuildResult = {BuildResult::BuiltCFG, std::move(Cfg)};
4155
}
4256
};
4357

@@ -50,8 +64,8 @@ BuildResult BuildCFG(const char *Code) {
5064
tooling::newFrontendActionFactory(&Finder));
5165
std::vector<std::string> Args = {"-std=c++11", "-fno-delayed-template-parsing"};
5266
if (!tooling::runToolOnCodeWithArgs(Factory->create(), Code, Args))
53-
return ToolFailed;
54-
return Callback.TheBuildResult;
67+
return BuildResult::ToolFailed;
68+
return std::move(Callback.TheBuildResult);
5569
}
5670

5771
// Constructing a CFG for a range-based for over a dependent type fails (but
@@ -63,7 +77,7 @@ TEST(CFG, RangeBasedForOverDependentType) {
6377
" for (const Foo *TheFoo : Range) {\n"
6478
" }\n"
6579
"}\n";
66-
EXPECT_EQ(SawFunctionBody, BuildCFG(Code));
80+
EXPECT_EQ(BuildResult::SawFunctionBody, BuildCFG(Code).getStatus());
6781
}
6882

6983
// Constructing a CFG containing a delete expression on a dependent type should
@@ -73,7 +87,7 @@ TEST(CFG, DeleteExpressionOnDependentType) {
7387
"void f(T t) {\n"
7488
" delete t;\n"
7589
"}\n";
76-
EXPECT_EQ(BuiltCFG, BuildCFG(Code));
90+
EXPECT_EQ(BuildResult::BuiltCFG, BuildCFG(Code).getStatus());
7791
}
7892

7993
// Constructing a CFG on a function template with a variable of incomplete type
@@ -83,7 +97,24 @@ TEST(CFG, VariableOfIncompleteType) {
8397
" class Undefined;\n"
8498
" Undefined u;\n"
8599
"}\n";
86-
EXPECT_EQ(BuiltCFG, BuildCFG(Code));
100+
EXPECT_EQ(BuildResult::BuiltCFG, BuildCFG(Code).getStatus());
101+
}
102+
103+
TEST(CFG, IsLinear) {
104+
auto expectLinear = [](bool IsLinear, const char *Code) {
105+
BuildResult B = BuildCFG(Code);
106+
EXPECT_EQ(BuildResult::BuiltCFG, B.getStatus());
107+
EXPECT_EQ(IsLinear, B.getCFG()->isLinear());
108+
};
109+
110+
expectLinear(true, "void foo() {}");
111+
expectLinear(true, "void foo() { if (true) return; }");
112+
expectLinear(true, "void foo() { if constexpr (false); }");
113+
expectLinear(false, "void foo(bool coin) { if (coin) return; }");
114+
expectLinear(false, "void foo() { for(;;); }");
115+
expectLinear(false, "void foo() { do {} while (true); }");
116+
expectLinear(true, "void foo() { do {} while (false); }");
117+
expectLinear(true, "void foo() { foo(); }"); // Recursion is not our problem.
87118
}
88119

89120
} // namespace

0 commit comments

Comments
 (0)