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

Commit a98dc4a

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 (cherry picked from commit c208eda)
1 parent a44ea13 commit a98dc4a

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
@@ -1173,6 +1173,12 @@ class CFG {
11731173
/// implementation needs such an interface.
11741174
unsigned size() const { return NumBlockIDs; }
11751175

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

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,25 @@ class ExprEngine : public SubEngine {
714714
AnalyzerOptions &Opts,
715715
const EvalCallOptions &CallOpts);
716716

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

lib/Analysis/CFG.cpp

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

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

lib/StaticAnalyzer/Core/BugReporterVisitors.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -538,15 +538,14 @@ class NoStoreFuncVisitor final : public BugReporterVisitor {
538538
// initialize its out-parameter, and additionally check that such
539539
// precondition can actually be fulfilled on the current path.
540540
if (Call.isInSystemHeader()) {
541-
// We make an exception for system header functions that have no branches,
542-
// i.e. have exactly 3 CFG blocks: begin, all its code, end. Such
543-
// functions unconditionally fail to initialize the variable.
541+
// We make an exception for system header functions that have no branches.
542+
// Such functions unconditionally fail to initialize the variable.
544543
// If they call other functions that have more paths within them,
545544
// this suppression would still apply when we visit these inner functions.
546545
// One common example of a standard function that doesn't ever initialize
547546
// its out parameter is operator placement new; it's up to the follow-up
548547
// constructor (if any) to initialize the memory.
549-
if (N->getStackFrame()->getCFG()->size() > 3)
548+
if (!N->getStackFrame()->getCFG()->isLinear())
550549
R.markInvalid(getTag(), nullptr);
551550
return nullptr;
552551
}

lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp

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

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

386406
// Do not count the small functions when determining the stack depth.
387407
AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(DI);
388-
const CFG *CalleeCFG = CalleeADC->getCFG();
389-
if (CalleeCFG->getNumBlockIDs() > AMgr.options.AlwaysInlineSize)
408+
if (!isSmall(CalleeADC))
390409
++StackDepth;
391410
}
392411
LCtx = LCtx->getParent();
@@ -833,8 +852,7 @@ static bool isCXXSharedPtrDtor(const FunctionDecl *FD) {
833852
/// This checks static properties of the function, such as its signature and
834853
/// CFG, to determine whether the analyzer should ever consider inlining it,
835854
/// in any context.
836-
static bool mayInlineDecl(AnalysisManager &AMgr,
837-
AnalysisDeclContext *CalleeADC) {
855+
bool ExprEngine::mayInlineDecl(AnalysisDeclContext *CalleeADC) const {
838856
AnalyzerOptions &Opts = AMgr.getAnalyzerOptions();
839857
// FIXME: Do not inline variadic calls.
840858
if (CallEvent::isVariadic(CalleeADC->getDecl()))
@@ -879,7 +897,7 @@ static bool mayInlineDecl(AnalysisManager &AMgr,
879897
return false;
880898

881899
// Do not inline large functions.
882-
if (CalleeCFG->getNumBlockIDs() > Opts.MaxInlinableSize)
900+
if (isHuge(CalleeADC))
883901
return false;
884902

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

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

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

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

968980
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
@@ -18,27 +18,41 @@ namespace clang {
1818
namespace analysis {
1919
namespace {
2020

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

2841
class CFGCallback : public ast_matchers::MatchFinder::MatchCallback {
2942
public:
30-
BuildResult TheBuildResult = ToolRan;
43+
BuildResult TheBuildResult = BuildResult::ToolRan;
3144

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

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

5872
// Constructing a CFG for a range-based for over a dependent type fails (but
@@ -64,7 +78,7 @@ TEST(CFG, RangeBasedForOverDependentType) {
6478
" for (const Foo *TheFoo : Range) {\n"
6579
" }\n"
6680
"}\n";
67-
EXPECT_EQ(SawFunctionBody, BuildCFG(Code));
81+
EXPECT_EQ(BuildResult::SawFunctionBody, BuildCFG(Code).getStatus());
6882
}
6983

7084
// Constructing a CFG containing a delete expression on a dependent type should
@@ -74,7 +88,7 @@ TEST(CFG, DeleteExpressionOnDependentType) {
7488
"void f(T t) {\n"
7589
" delete t;\n"
7690
"}\n";
77-
EXPECT_EQ(BuiltCFG, BuildCFG(Code));
91+
EXPECT_EQ(BuildResult::BuiltCFG, BuildCFG(Code).getStatus());
7892
}
7993

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

90121
} // namespace

0 commit comments

Comments
 (0)