xref: /llvm-project/clang/test/Analysis/loop-assumptions.c (revision bb27d5e5c6b194a1440b8ac4e5ace68d0ee2a849)
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   // Previously this was mishandled in `eagerly-assume` mode (which is enabled
69   // by default), because the code remembered that there was a bifurcation on
70   // the first iteration of the loop and didn't realize that this is obsolete.
71 
72   // NOTE: The variable `i` is introduced to ensure that the iterations of the
73   // loop change the state -- otherwise the analyzer stops iterating because it
74   // returns to the same `ExplodedNode`.
75   int i = 0;
76   while (arg > 3) {
77     clang_analyzer_numTimesReached(); // expected-warning {{4}}
78     i++;
79   }
80 
81   clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
82 }
83 
84 void dontAssumeFourthIterartion(int arg) {
85   if (arg == 2)
86     return;
87 
88   // In this function the analyzer cannot leave the loop after exactly two
89   // iterations (because it knows that `arg != 2` at that point), so it
90   // performs a third iteration, but it does not assume that a fourth iteration
91   // is also possible.
92   for (int i = 0; i < arg; i++)
93     clang_analyzer_numTimesReached(); // expected-warning {{3}}
94 
95   clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
96 }
97 
98 #define TRUE 1
99 void shortCircuitInLoopCondition(int arg) {
100   // When the loop condition expression contains short-circuiting operators, it
101   // performs "inner" bifurcations for those operators and only considers the
102   // last (rightmost) operand as the branch condition that is associated with
103   // the loop itself (as its loop condition).
104   // This means that assumptions taken in the left-hand side of a short-circuiting
105   // operator are not recognized as "opaque" loop condition, so the loop in
106   // this test case is allowed to finish four iterations.
107   // FIXME: This corner case is responsible for at least one out-of-bounds
108   // false positive on the ffmpeg codebase. Eventually we should properly
109   // recognize the full syntactical loop condition expression as "the loop
110   // condition", but this will be complicated to implement.
111   for (int i = 0; i < arg && TRUE; i++) {
112     clang_analyzer_numTimesReached(); // expected-warning {{4}}
113   }
114   clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
115 }
116 
117 void shortCircuitInLoopConditionRHS(int arg) {
118   // Unlike `shortCircuitInLoopCondition()`, this case is handled properly
119   // because the analyzer thinks that the right hand side of the `&&` is the
120   // loop condition.
121   for (int i = 0; TRUE && i < arg; i++) {
122     clang_analyzer_numTimesReached(); // expected-warning {{2}}
123   }
124   clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
125 }
126 
127 void eagerlyAssumeInSubexpression(int arg) {
128   // The `EagerlyAssume` logic is another complication that can "split the
129   // state" within the loop condition, but before the `processBranch()` call
130   // which is (in theory) responsible for evaluating the loop condition.
131   // The current implementation partially compensates this by noticing the
132   // cases where the loop condition is targeted by `EagerlyAssume`, but does
133   // not handle the (fortunately rare) case when `EagerlyAssume` hits a
134   // sub-expression of the loop condition (as in this contrived test case).
135   // FIXME: I don't know a real-world example for this inconsistency, but it
136   // would be good to eliminate it eventually.
137   for (int i = 0; (i >= arg) - 1; i++) {
138     clang_analyzer_numTimesReached(); // eagerlyassume-warning {{4}} noeagerlyassume-warning {{2}}
139   }
140   clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
141 }
142 
143 void calledTwice(int arg, int isFirstCall) {
144   // This function is called twice (with two different unknown 'arg' values) to
145   // check the iteration count handling in this situation.
146   for (int i = 0; i < arg; i++) {
147     if (isFirstCall) {
148       clang_analyzer_numTimesReached(); // expected-warning {{2}}
149     } else {
150       clang_analyzer_numTimesReached(); // expected-warning {{2}}
151     }
152   }
153 }
154 
155 void caller(int arg, int arg2) {
156   // Entry point for `calledTwice()`.
157   calledTwice(arg, 1);
158   calledTwice(arg2, 0);
159 }
160 
161 void innerLoopClearCondition(void) {
162   // A "control group" test case for the behavior of an inner loop. Notice that
163   // although the (default) value of `-analyzer-max-loop` is 4, we only see 3 iterations
164   // of the inner loop, because `-analyzer-max-loop` limits the number of
165   // evaluations of _the loop condition of the inner loop_ and in addition to
166   // the 3 evaluations before the 3 iterations, there is also a step where it
167   // evaluates to false (in the first iteration of the outer loop).
168   for (int outer = 0; outer < 2; outer++) {
169     int limit = 0;
170     if (outer)
171       limit = 10;
172     clang_analyzer_dump(limit); // expected-warning {{0}} expected-warning {{10}}
173     for (int i = 0; i < limit; i++) {
174       clang_analyzer_numTimesReached(); // expected-warning {{3}}
175     }
176   }
177 }
178 
179 void innerLoopOpaqueCondition(int arg) {
180   // In this test case the engine doesn't assume a second iteration within the
181   // inner loop (in the second iteration of the outer loop, when the limit is
182   // opaque) because `CoreEngine::getCompletedIterationCount()` is based on the
183   // `BlockCount` values queried from the `BlockCounter` which count _all_
184   // evaluations of a given `CFGBlock` (in our case, the loop condition) and
185   // not just the evaluations within the current iteration of the outer loop.
186   // FIXME: This inaccurate iteration count could in theory cause some false
187   // negatives, although I think this would be unusual in practice, as the
188   // small default value of `-analyzer-max-loop` means that this is only
189   // relevant if the analyzer can deduce that the inner loop performs 0 or 1
190   // iterations within the first iteration of the outer loop (and then the
191   // condition of the inner loop is opaque within the second iteration of the
192   // outer loop).
193   for (int outer = 0; outer < 2; outer++) {
194     int limit = 0;
195     if (outer)
196       limit = arg;
197     clang_analyzer_dump(limit); // expected-warning {{0}} expected-warning {{reg_$}}
198     for (int i = 0; i < limit; i++) {
199       clang_analyzer_numTimesReached(); // expected-warning {{1}}
200     }
201   }
202 }
203 
204 void onlyLoopConditions(int arg) {
205   // This "don't assume third iteration" logic only examines the conditions of
206   // loop statements and does not affect the analysis of code that implements
207   // similar behavior with different language features like if + break, goto,
208   // recursive functions, ...
209   int i = 0;
210   while (1) {
211     clang_analyzer_numTimesReached(); // expected-warning {{4}}
212 
213     // This is not a loop condition.
214     if (i++ > arg)
215       break;
216   }
217 
218   clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
219 }
220