Skip to content

Commit 8791294

Browse files
committed
Add dedicated tests
1 parent db45119 commit 8791294

File tree

1 file changed

+205
-0
lines changed

1 file changed

+205
-0
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection \
2+
// RUN: -verify=expected,eagerlyassume %s
3+
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection \
4+
// RUN: -analyzer-config eagerly-assume=false \
5+
// RUN: -verify=expected,noeagerlyassume %s
6+
7+
// These tests validate the logic within `ExprEngine::processBranch` which
8+
// ensures that in loops with opaque conditions we don't assume execution paths
9+
// if the code does not imply that they are possible.
10+
11+
void clang_analyzer_numTimesReached(void);
12+
void clang_analyzer_warnIfReached(void);
13+
void clang_analyzer_dump(int);
14+
15+
void clearCondition(void) {
16+
// If the analyzer can definitely determine the value of the loop condition,
17+
// then this corrective logic doesn't activate and the engine executes
18+
// `-analyzer-max-loop` iterations (by default, 4).
19+
for (int i = 0; i < 10; i++)
20+
clang_analyzer_numTimesReached(); // expected-warning {{4}}
21+
22+
clang_analyzer_warnIfReached(); // unreachable
23+
}
24+
25+
void opaqueCondition(int arg) {
26+
// If the loop condition is opaque, don't assume more than two iterations,
27+
// because the presence of a loop does not imply that the programmer thought
28+
// that more than two iterations are possible. (It _does_ imply that two
29+
// iterations may be possible at least in some cases, because otherwise an
30+
// `if` would've been enough.)
31+
for (int i = 0; i < arg; i++)
32+
clang_analyzer_numTimesReached(); // expected-warning {{2}}
33+
34+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
35+
}
36+
37+
int check(void);
38+
39+
void opaqueConditionCall(int arg) {
40+
// Same situation as `opaqueCondition()` but with a `while ()` loop. This
41+
// is also an example for a situation where the programmer cannot easily
42+
// insert an assertion to guide the analyzer and rule out more than two
43+
// iterations (so the analyzer needs to proactively avoid those unjustified
44+
// branches).
45+
while (check())
46+
clang_analyzer_numTimesReached(); // expected-warning {{2}}
47+
48+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
49+
}
50+
51+
void opaqueConditionDoWhile(int arg) {
52+
// Same situation as `opaqueCondition()` but with a `do {} while ()` loop.
53+
// This is tested separately because this loop type is a special case in the
54+
// iteration count calculation.
55+
int i = 0;
56+
do {
57+
clang_analyzer_numTimesReached(); // expected-warning {{2}}
58+
} while (i++ < arg);
59+
60+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
61+
}
62+
63+
void dontRememberOldBifurcation(int arg) {
64+
// In this (slightly contrived) test case the analyzer performs an assumption
65+
// at the first iteration of the loop, but does not make any new assumptions
66+
// in the subsequent iterations, so the analyzer should continue evaluating
67+
// the loop.
68+
// FIXME: This is mishandled in `eagerly-assume` mode (which is enabled by
69+
// default), because `didEagerlyAssumeBifurcateAt()` returns true for the
70+
// loop condition -- referring to the bifurcation which happened on an early
71+
// iteration.
72+
73+
// NOTE: The variable `i` is introduced to ensure that the iterations of the
74+
// loop change the state -- otherwise the analyzer stops iterating because it
75+
// returns to the same `ExplodedNode`.
76+
int i = 0;
77+
while (arg > 3) {
78+
clang_analyzer_numTimesReached(); // noeagerlyassume-warning {{4}} eagerlyassume-warning {{2}}
79+
i++;
80+
}
81+
82+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
83+
}
84+
85+
void dontAssumeFourthIterartion(int arg) {
86+
if (arg == 2)
87+
return;
88+
89+
// In this function the analyzer cannot leave the loop after exactly two
90+
// iterations (because it knows that `arg != 2` at that point), so it
91+
// performs a third iteration, but it does not assume that a fourth iteration
92+
// is also possible.
93+
// FIXME: This test case is also affected by the bug described in
94+
// `dontRememberOldBifurcation()`.
95+
for (int i = 0; i < arg; i++)
96+
clang_analyzer_numTimesReached(); // noeagerlyassume-warning {{3}} eagerlyassume-warning {{2}}
97+
98+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
99+
}
100+
101+
#define TRUE 1
102+
void shortCircuitInLoopCondition(int arg) {
103+
// When the loop condition expression contains short-circuiting operators, it
104+
// performs "inner" bifurcations for those operators and only considers the
105+
// last (rightmost) operand as the branch condition that is associated with
106+
// the loop itself (as its loop condition).
107+
// This means that assumptions taken in the left-hand side of a short-circuiting
108+
// operator are not recognized as "opaque" loop condition, so the loop in
109+
// this test case is allowed to finish four iterations.
110+
// FIXME: This corner case is responsible for at least one out-of-bounds
111+
// false positive on the ffmpeg codebase. Eventually we should properly
112+
// recognize the full syntactical loop condition expression as "the loop
113+
// condition", but this will be complicated to implement.
114+
for (int i = 0; i < arg && TRUE; i++) {
115+
clang_analyzer_numTimesReached(); // expected-warning {{4}}
116+
}
117+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
118+
}
119+
120+
void shortCircuitInLoopConditionRHS(int arg) {
121+
// Unlike `shortCircuitInLoopCondition()`, this case is handled properly
122+
// because the analyzer thinks that the right hand side of the `&&` is the
123+
// loop condition.
124+
for (int i = 0; TRUE && i < arg; i++) {
125+
clang_analyzer_numTimesReached(); // expected-warning {{2}}
126+
}
127+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
128+
}
129+
130+
void eagerlyAssumeInSubexpression(int arg) {
131+
// The `EagerlyAssume` logic is another complication that can "split the
132+
// state" within the loop condition, but before the `processBranch()` call
133+
// which is (in theory) responsible for evaluating the loop condition.
134+
// The current implementation partially compensates this by noticing the
135+
// cases where the loop condition is targeted by `EagerlyAssume`, but does
136+
// not handle the (fortunately rare) case when `EagerlyAssume` hits a
137+
// sub-expression of the loop condition (as in this contrived test case).
138+
// FIXME: I don't know a real-world example for this inconsistency, but it
139+
// would be good to eliminate it eventually.
140+
for (int i = 0; (i >= arg) - 1; i++) {
141+
clang_analyzer_numTimesReached(); // eagerlyassume-warning {{4}} noeagerlyassume-warning {{2}}
142+
}
143+
clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
144+
}
145+
146+
void calledTwice(int arg, int isFirstCall) {
147+
// This function is called twice (with two different unknown 'arg' values) to
148+
// check the iteration count handling in this situation.
149+
for (int i = 0; i < arg; i++) {
150+
if (isFirstCall) {
151+
clang_analyzer_numTimesReached(); // expected-warning {{2}}
152+
} else {
153+
clang_analyzer_numTimesReached(); // expected-warning {{2}}
154+
}
155+
}
156+
}
157+
158+
void caller(int arg, int arg2) {
159+
// Entry point for `calledTwice()`.
160+
calledTwice(arg, 1);
161+
calledTwice(arg2, 0);
162+
}
163+
164+
void innerLoopClearCondition(void) {
165+
// A "control group" test case for the behavior of an inner loop. Notice that
166+
// although the (default) value of `-analyzer-max-loop` is 4, we only see 3 iterations
167+
// of the inner loop, because `-analyzer-max-loop` limits the number of
168+
// evaluations of _the loop condition of the inner loop_ and in addition to
169+
// the 3 evaluations before the 3 iterations, there is also a step where it
170+
// evaluates to false (in the first iteration of the outer loop).
171+
for (int outer = 0; outer < 2; outer++) {
172+
int limit = 0;
173+
if (outer)
174+
limit = 10;
175+
clang_analyzer_dump(limit); // expected-warning {{0}} expected-warning {{10}}
176+
for (int i = 0; i < limit; i++) {
177+
clang_analyzer_numTimesReached(); // expected-warning {{3}}
178+
}
179+
}
180+
}
181+
182+
void innerLoopOpaqueCondition(int arg) {
183+
// In this test case the engine doesn't assume a second iteration within the
184+
// inner loop (in the second iteration of the outer loop, when the limit is
185+
// opaque) because `CoreEngine::getCompletedIterationCount()` is based on the
186+
// `BlockCount` values queried from the `BlockCounter` which count _all_
187+
// evaluations of a given `CFGBlock` (in our case, the loop condition) and
188+
// not just the evaluations within the current iteration of the outer loop.
189+
// FIXME: This inaccurate iteration count could in theory cause some false
190+
// negatives, although I think this would be unusual in practice, as the
191+
// small default value of `-analyzer-max-loop` means that this is only
192+
// relevant if the analyzer can deduce that the inner loop performs 0 or 1
193+
// iterations within the first iteration of the outer loop (and then the
194+
// condition of the inner loop is opaque within the second iteration of the
195+
// outer loop).
196+
for (int outer = 0; outer < 2; outer++) {
197+
int limit = 0;
198+
if (outer)
199+
limit = arg;
200+
clang_analyzer_dump(limit); // expected-warning {{0}} expected-warning {{reg_$}}
201+
for (int i = 0; i < limit; i++) {
202+
clang_analyzer_numTimesReached(); // expected-warning {{1}}
203+
}
204+
}
205+
}

0 commit comments

Comments
 (0)