aboutsummaryrefslogtreecommitdiff
path: root/clang/test/Analysis/loop-assumptions.c
diff options
context:
space:
mode:
Diffstat (limited to 'clang/test/Analysis/loop-assumptions.c')
-rw-r--r--clang/test/Analysis/loop-assumptions.c219
1 files changed, 219 insertions, 0 deletions
diff --git a/clang/test/Analysis/loop-assumptions.c b/clang/test/Analysis/loop-assumptions.c
new file mode 100644
index 0000000..eb0ffdc
--- /dev/null
+++ b/clang/test/Analysis/loop-assumptions.c
@@ -0,0 +1,219 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection \
+// RUN: -verify=expected,eagerlyassume %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection \
+// RUN: -analyzer-config eagerly-assume=false \
+// RUN: -verify=expected,noeagerlyassume %s
+
+// These tests validate the logic within `ExprEngine::processBranch` which
+// ensures that in loops with opaque conditions we don't assume execution paths
+// if the code does not imply that they are possible.
+
+void clang_analyzer_numTimesReached(void);
+void clang_analyzer_warnIfReached(void);
+void clang_analyzer_dump(int);
+
+void clearCondition(void) {
+ // If the analyzer can definitely determine the value of the loop condition,
+ // then this corrective logic doesn't activate and the engine executes
+ // `-analyzer-max-loop` iterations (by default, 4).
+ for (int i = 0; i < 10; i++)
+ clang_analyzer_numTimesReached(); // expected-warning {{4}}
+
+ clang_analyzer_warnIfReached(); // unreachable
+}
+
+void opaqueCondition(int arg) {
+ // If the loop condition is opaque, don't assume more than two iterations,
+ // because the presence of a loop does not imply that the programmer thought
+ // that more than two iterations are possible. (It _does_ imply that two
+ // iterations may be possible at least in some cases, because otherwise an
+ // `if` would've been enough.)
+ for (int i = 0; i < arg; i++)
+ clang_analyzer_numTimesReached(); // expected-warning {{2}}
+
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+}
+
+int check(void);
+
+void opaqueConditionCall(int arg) {
+ // Same situation as `opaqueCondition()` but with a `while ()` loop. This
+ // is also an example for a situation where the programmer cannot easily
+ // insert an assertion to guide the analyzer and rule out more than two
+ // iterations (so the analyzer needs to proactively avoid those unjustified
+ // branches).
+ while (check())
+ clang_analyzer_numTimesReached(); // expected-warning {{2}}
+
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+}
+
+void opaqueConditionDoWhile(int arg) {
+ // Same situation as `opaqueCondition()` but with a `do {} while ()` loop.
+ // This is tested separately because this loop type is a special case in the
+ // iteration count calculation.
+ int i = 0;
+ do {
+ clang_analyzer_numTimesReached(); // expected-warning {{2}}
+ } while (i++ < arg);
+
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+}
+
+void dontRememberOldBifurcation(int arg) {
+ // In this (slightly contrived) test case the analyzer performs an assumption
+ // at the first iteration of the loop, but does not make any new assumptions
+ // in the subsequent iterations, so the analyzer should continue evaluating
+ // the loop.
+ // Previously this was mishandled in `eagerly-assume` mode (which is enabled
+ // by default), because the code remembered that there was a bifurcation on
+ // the first iteration of the loop and didn't realize that this is obsolete.
+
+ // NOTE: The variable `i` is introduced to ensure that the iterations of the
+ // loop change the state -- otherwise the analyzer stops iterating because it
+ // returns to the same `ExplodedNode`.
+ int i = 0;
+ while (arg > 3) {
+ clang_analyzer_numTimesReached(); // expected-warning {{4}}
+ i++;
+ }
+
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+}
+
+void dontAssumeFourthIterartion(int arg) {
+ if (arg == 2)
+ return;
+
+ // In this function the analyzer cannot leave the loop after exactly two
+ // iterations (because it knows that `arg != 2` at that point), so it
+ // performs a third iteration, but it does not assume that a fourth iteration
+ // is also possible.
+ for (int i = 0; i < arg; i++)
+ clang_analyzer_numTimesReached(); // expected-warning {{3}}
+
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+}
+
+#define TRUE 1
+void shortCircuitInLoopCondition(int arg) {
+ // When the loop condition expression contains short-circuiting operators, it
+ // performs "inner" bifurcations for those operators and only considers the
+ // last (rightmost) operand as the branch condition that is associated with
+ // the loop itself (as its loop condition).
+ // This means that assumptions taken in the left-hand side of a short-circuiting
+ // operator are not recognized as "opaque" loop condition, so the loop in
+ // this test case is allowed to finish four iterations.
+ // FIXME: This corner case is responsible for at least one out-of-bounds
+ // false positive on the ffmpeg codebase. Eventually we should properly
+ // recognize the full syntactical loop condition expression as "the loop
+ // condition", but this will be complicated to implement.
+ for (int i = 0; i < arg && TRUE; i++) {
+ clang_analyzer_numTimesReached(); // expected-warning {{4}}
+ }
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+}
+
+void shortCircuitInLoopConditionRHS(int arg) {
+ // Unlike `shortCircuitInLoopCondition()`, this case is handled properly
+ // because the analyzer thinks that the right hand side of the `&&` is the
+ // loop condition.
+ for (int i = 0; TRUE && i < arg; i++) {
+ clang_analyzer_numTimesReached(); // expected-warning {{2}}
+ }
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+}
+
+void eagerlyAssumeInSubexpression(int arg) {
+ // The `EagerlyAssume` logic is another complication that can "split the
+ // state" within the loop condition, but before the `processBranch()` call
+ // which is (in theory) responsible for evaluating the loop condition.
+ // The current implementation partially compensates this by noticing the
+ // cases where the loop condition is targeted by `EagerlyAssume`, but does
+ // not handle the (fortunately rare) case when `EagerlyAssume` hits a
+ // sub-expression of the loop condition (as in this contrived test case).
+ // FIXME: I don't know a real-world example for this inconsistency, but it
+ // would be good to eliminate it eventually.
+ for (int i = 0; (i >= arg) - 1; i++) {
+ clang_analyzer_numTimesReached(); // eagerlyassume-warning {{4}} noeagerlyassume-warning {{2}}
+ }
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+}
+
+void calledTwice(int arg, int isFirstCall) {
+ // This function is called twice (with two different unknown 'arg' values) to
+ // check the iteration count handling in this situation.
+ for (int i = 0; i < arg; i++) {
+ if (isFirstCall) {
+ clang_analyzer_numTimesReached(); // expected-warning {{2}}
+ } else {
+ clang_analyzer_numTimesReached(); // expected-warning {{2}}
+ }
+ }
+}
+
+void caller(int arg, int arg2) {
+ // Entry point for `calledTwice()`.
+ calledTwice(arg, 1);
+ calledTwice(arg2, 0);
+}
+
+void innerLoopClearCondition(void) {
+ // A "control group" test case for the behavior of an inner loop. Notice that
+ // although the (default) value of `-analyzer-max-loop` is 4, we only see 3 iterations
+ // of the inner loop, because `-analyzer-max-loop` limits the number of
+ // evaluations of _the loop condition of the inner loop_ and in addition to
+ // the 3 evaluations before the 3 iterations, there is also a step where it
+ // evaluates to false (in the first iteration of the outer loop).
+ for (int outer = 0; outer < 2; outer++) {
+ int limit = 0;
+ if (outer)
+ limit = 10;
+ clang_analyzer_dump(limit); // expected-warning {{0}} expected-warning {{10}}
+ for (int i = 0; i < limit; i++) {
+ clang_analyzer_numTimesReached(); // expected-warning {{3}}
+ }
+ }
+}
+
+void innerLoopOpaqueCondition(int arg) {
+ // In this test case the engine doesn't assume a second iteration within the
+ // inner loop (in the second iteration of the outer loop, when the limit is
+ // opaque) because `CoreEngine::getCompletedIterationCount()` is based on the
+ // `BlockCount` values queried from the `BlockCounter` which count _all_
+ // evaluations of a given `CFGBlock` (in our case, the loop condition) and
+ // not just the evaluations within the current iteration of the outer loop.
+ // FIXME: This inaccurate iteration count could in theory cause some false
+ // negatives, although I think this would be unusual in practice, as the
+ // small default value of `-analyzer-max-loop` means that this is only
+ // relevant if the analyzer can deduce that the inner loop performs 0 or 1
+ // iterations within the first iteration of the outer loop (and then the
+ // condition of the inner loop is opaque within the second iteration of the
+ // outer loop).
+ for (int outer = 0; outer < 2; outer++) {
+ int limit = 0;
+ if (outer)
+ limit = arg;
+ clang_analyzer_dump(limit); // expected-warning {{0}} expected-warning {{reg_$}}
+ for (int i = 0; i < limit; i++) {
+ clang_analyzer_numTimesReached(); // expected-warning {{1}}
+ }
+ }
+}
+
+void onlyLoopConditions(int arg) {
+ // This "don't assume third iteration" logic only examines the conditions of
+ // loop statements and does not affect the analysis of code that implements
+ // similar behavior with different language features like if + break, goto,
+ // recursive functions, ...
+ int i = 0;
+ while (1) {
+ clang_analyzer_numTimesReached(); // expected-warning {{4}}
+
+ // This is not a loop condition.
+ if (i++ > arg)
+ break;
+ }
+
+ clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}}
+}