|
| 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