Skip to content

Commit 17b19c5

Browse files
authored
[analyzer] Unroll loops of compile-time upper-bounded loops (#169400)
Previously, only literal upper-bounded loops were recognized. This patch relaxes this matching to accept any compile-time deducible constant expression. It would be better to rely on the SVals (values from the symbolic domain), as those could potentially have more accurate answers, but this one is much simpler. Note that at the time we calculate this value, we have not evaluated the sub-exprs of the condition, consequently, we can't just query the Environment for the folded SVal. Because of this, the next best tool in our toolbox is comp-time evaluating the Expr. rdar://165363923
1 parent d748c81 commit 17b19c5

File tree

2 files changed

+52
-26
lines changed

2 files changed

+52
-26
lines changed

clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ using namespace clang;
2323
using namespace ento;
2424
using namespace clang::ast_matchers;
2525

26+
using ast_matchers::internal::Matcher;
27+
2628
static const int MAXIMUM_STEP_UNROLLED = 128;
2729

2830
namespace {
@@ -69,6 +71,11 @@ struct LoopState {
6971
REGISTER_LIST_WITH_PROGRAMSTATE(LoopStack, LoopState)
7072

7173
namespace clang {
74+
namespace {
75+
AST_MATCHER(QualType, isIntegralOrEnumerationType) {
76+
return Node->isIntegralOrEnumerationType();
77+
}
78+
} // namespace
7279
namespace ento {
7380

7481
static bool isLoopStmt(const Stmt *S) {
@@ -82,22 +89,23 @@ ProgramStateRef processLoopEnd(const Stmt *LoopStmt, ProgramStateRef State) {
8289
return State;
8390
}
8491

85-
static internal::Matcher<Stmt> simpleCondition(StringRef BindName,
86-
StringRef RefName) {
92+
static Matcher<Stmt> simpleCondition(StringRef BindName, StringRef RefName) {
93+
auto LoopVariable = ignoringParenImpCasts(
94+
declRefExpr(to(varDecl(hasType(isInteger())).bind(BindName)))
95+
.bind(RefName));
96+
auto UpperBound = ignoringParenImpCasts(
97+
expr(hasType(isIntegralOrEnumerationType())).bind("boundNum"));
98+
8799
return binaryOperator(
88100
anyOf(hasOperatorName("<"), hasOperatorName(">"),
89101
hasOperatorName("<="), hasOperatorName(">="),
90102
hasOperatorName("!=")),
91-
hasEitherOperand(ignoringParenImpCasts(
92-
declRefExpr(to(varDecl(hasType(isInteger())).bind(BindName)))
93-
.bind(RefName))),
94-
hasEitherOperand(
95-
ignoringParenImpCasts(integerLiteral().bind("boundNum"))))
103+
anyOf(binaryOperator(hasLHS(LoopVariable), hasRHS(UpperBound)),
104+
binaryOperator(hasRHS(LoopVariable), hasLHS(UpperBound))))
96105
.bind("conditionOperator");
97106
}
98107

99-
static internal::Matcher<Stmt>
100-
changeIntBoundNode(internal::Matcher<Decl> VarNodeMatcher) {
108+
static Matcher<Stmt> changeIntBoundNode(Matcher<Decl> VarNodeMatcher) {
101109
return anyOf(
102110
unaryOperator(anyOf(hasOperatorName("--"), hasOperatorName("++")),
103111
hasUnaryOperand(ignoringParenImpCasts(
@@ -107,30 +115,27 @@ changeIntBoundNode(internal::Matcher<Decl> VarNodeMatcher) {
107115
declRefExpr(to(varDecl(VarNodeMatcher)))))));
108116
}
109117

110-
static internal::Matcher<Stmt>
111-
callByRef(internal::Matcher<Decl> VarNodeMatcher) {
118+
static Matcher<Stmt> callByRef(Matcher<Decl> VarNodeMatcher) {
112119
return callExpr(forEachArgumentWithParam(
113120
declRefExpr(to(varDecl(VarNodeMatcher))),
114121
parmVarDecl(hasType(references(qualType(unless(isConstQualified())))))));
115122
}
116123

117-
static internal::Matcher<Stmt>
118-
assignedToRef(internal::Matcher<Decl> VarNodeMatcher) {
124+
static Matcher<Stmt> assignedToRef(Matcher<Decl> VarNodeMatcher) {
119125
return declStmt(hasDescendant(varDecl(
120126
allOf(hasType(referenceType()),
121127
hasInitializer(anyOf(
122128
initListExpr(has(declRefExpr(to(varDecl(VarNodeMatcher))))),
123129
declRefExpr(to(varDecl(VarNodeMatcher)))))))));
124130
}
125131

126-
static internal::Matcher<Stmt>
127-
getAddrTo(internal::Matcher<Decl> VarNodeMatcher) {
132+
static Matcher<Stmt> getAddrTo(Matcher<Decl> VarNodeMatcher) {
128133
return unaryOperator(
129134
hasOperatorName("&"),
130135
hasUnaryOperand(declRefExpr(hasDeclaration(VarNodeMatcher))));
131136
}
132137

133-
static internal::Matcher<Stmt> hasSuspiciousStmt(StringRef NodeName) {
138+
static Matcher<Stmt> hasSuspiciousStmt(StringRef NodeName) {
134139
return hasDescendant(stmt(
135140
anyOf(gotoStmt(), switchStmt(), returnStmt(),
136141
// Escaping and not known mutation of the loop counter is handled
@@ -142,7 +147,7 @@ static internal::Matcher<Stmt> hasSuspiciousStmt(StringRef NodeName) {
142147
assignedToRef(equalsBoundNode(std::string(NodeName))))));
143148
}
144149

145-
static internal::Matcher<Stmt> forLoopMatcher() {
150+
static Matcher<Stmt> forLoopMatcher() {
146151
return forStmt(
147152
hasCondition(simpleCondition("initVarName", "initVarRef")),
148153
// Initialization should match the form: 'int i = 6' or 'i = 42'.
@@ -271,23 +276,26 @@ static bool shouldCompletelyUnroll(const Stmt *LoopStmt, ASTContext &ASTCtx,
271276
if (!isLoopStmt(LoopStmt))
272277
return false;
273278

274-
// TODO: Match the cases where the bound is not a concrete literal but an
275-
// integer with known value
276279
auto Matches = match(forLoopMatcher(), *LoopStmt, ASTCtx);
277280
if (Matches.empty())
278281
return false;
279282

280283
const auto *CounterVarRef = Matches[0].getNodeAs<DeclRefExpr>("initVarRef");
281-
llvm::APInt BoundNum =
282-
Matches[0].getNodeAs<IntegerLiteral>("boundNum")->getValue();
284+
const Expr *BoundNumExpr = Matches[0].getNodeAs<Expr>("boundNum");
285+
286+
Expr::EvalResult BoundNumResult;
287+
if (!BoundNumExpr || !BoundNumExpr->EvaluateAsInt(BoundNumResult, ASTCtx,
288+
Expr::SE_NoSideEffects)) {
289+
return false;
290+
}
283291
llvm::APInt InitNum =
284292
Matches[0].getNodeAs<IntegerLiteral>("initNum")->getValue();
285293
auto CondOp = Matches[0].getNodeAs<BinaryOperator>("conditionOperator");
286-
unsigned MaxWidth = std::max(InitNum.getBitWidth(), BoundNum.getBitWidth());
294+
unsigned MaxWidth = std::max(InitNum.getBitWidth(),
295+
BoundNumResult.Val.getInt().getBitWidth());
287296

288297
InitNum = InitNum.zext(MaxWidth);
289-
BoundNum = BoundNum.zext(MaxWidth);
290-
298+
llvm::APInt BoundNum = BoundNumResult.Val.getInt().zext(MaxWidth);
291299
if (CondOp->getOpcode() == BO_GE || CondOp->getOpcode() == BO_LE)
292300
maxStep = (BoundNum - InitNum + 1).abs().getZExtValue();
293301
else

clang/test/Analysis/loop-unrolling.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config unroll-loops=true,cfg-loopexit=true -verify=expected,default -std=c++14 -analyzer-config exploration_strategy=unexplored_first_queue %s
2-
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config unroll-loops=true,cfg-loopexit=true,exploration_strategy=dfs -verify=expected,dfs -std=c++14 %s
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config unroll-loops=true,cfg-loopexit=true -verify=expected,default -std=c++17 -analyzer-config exploration_strategy=unexplored_first_queue %s
2+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config unroll-loops=true,cfg-loopexit=true,exploration_strategy=dfs -verify=expected,dfs -std=c++17 %s
33

44
void clang_analyzer_numTimesReached();
55
void clang_analyzer_warnIfReached();
@@ -580,3 +580,21 @@ void test_escaping_on_var_before_switch_case_no_crash(int c) {
580580
}
581581
}
582582
}
583+
584+
template <int Val> struct Integer {
585+
static constexpr int value = Val;
586+
};
587+
588+
void complicated_compile_time_upper_bound() {
589+
static_assert((sizeof(char) * Integer<4>::value + 3) == 7);
590+
for (int i = 0; i < (sizeof(char) * Integer<4>::value + (((3)))); ++i) {
591+
clang_analyzer_numTimesReached(); // expected-warning {{7}}
592+
}
593+
}
594+
595+
void complicated_compile_time_upper_bound_indirect() {
596+
using Seven = Integer<(sizeof(char) * Integer<4>::value + 3)>;
597+
for (int i = 0; i < ((Seven::value)); ++i) {
598+
clang_analyzer_numTimesReached(); // expected-warning {{7}}
599+
}
600+
}

0 commit comments

Comments
 (0)