CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
263 lines
11 KiB
C
263 lines
11 KiB
C
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection \
|
|
// RUN: -verify=expected,noassumeone,eagerlyassume,combo %s
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection \
|
|
// RUN: -analyzer-config eagerly-assume=false \
|
|
// RUN: -verify=expected,noassumeone,noeagerlyassume,combo %s
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection \
|
|
// RUN: -analyzer-config assume-at-least-one-iteration=true \
|
|
// RUN: -verify=expected,eagerlyassume,combo %s
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection \
|
|
// RUN: -analyzer-config assume-at-least-one-iteration=true,eagerly-assume=false \
|
|
// RUN: -verify=expected,noeagerlyassume %s
|
|
|
|
// The verify tag "combo" is used for one unique warning which is produced in three
|
|
// of the four RUN combinations.
|
|
|
|
// 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.
|
|
// In particular, if two (or more) iterations are already completed in a loop,
|
|
// we don't assume that there can be another iteration. Moreover, if the
|
|
// analyzer option `assume-at-least-one-iteration` is enabled, then we don't
|
|
// assume that a loop can be skipped completely.
|
|
|
|
void clang_analyzer_numTimesReached(void);
|
|
void clang_analyzer_dump(int);
|
|
|
|
void clearTrueCondition(void) {
|
|
// If the analyzer can definitely determine that the loop condition is true,
|
|
// then this corrective logic doesn't activate and the engine executes
|
|
// `-analyzer-max-loop` iterations (by default, 4).
|
|
int i;
|
|
for (i = 0; i < 10; i++)
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
|
|
clang_analyzer_dump(i); // Unreachable, no reports.
|
|
}
|
|
|
|
void clearFalseCondition(void) {
|
|
// If the analyzer can definitely determine that the loop condition is false,
|
|
// then the loop is skipped, even in `assume-at-least-one-iteration` mode.
|
|
int i;
|
|
for (i = 0; i > 10; i++)
|
|
clang_analyzer_numTimesReached(); // Unreachable, no report.
|
|
|
|
clang_analyzer_dump(i); // expected-warning {{0}}
|
|
}
|
|
|
|
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.)
|
|
// Moreover, if `assume-at-least-one-iteration` is enabled, then assume at
|
|
// least one iteration.
|
|
int i;
|
|
for (i = 0; i < arg; i++)
|
|
clang_analyzer_numTimesReached(); // expected-warning {{2}}
|
|
|
|
clang_analyzer_dump(i); // noassumeone-warning {{0}} expected-warning {{1}} expected-warning {{2}}
|
|
}
|
|
|
|
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).
|
|
int i = 0; // Helper to distinguish the the branches after the loop.
|
|
while (check()) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{2}}
|
|
i++;
|
|
}
|
|
|
|
clang_analyzer_dump(i); // noassumeone-warning {{0}} expected-warning {{1}} expected-warning {{2}}
|
|
}
|
|
|
|
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.
|
|
// Obviously, this loop guarantees that at least one iteration will happen.
|
|
int i = 0;
|
|
do {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{2}}
|
|
} while (i++ < arg);
|
|
|
|
clang_analyzer_dump(i); // expected-warning {{1}} expected-warning {{2}}
|
|
}
|
|
|
|
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 significant 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_dump(i); // noassumeone-warning {{0}}
|
|
}
|
|
|
|
void dontAssumeFourthIterartion(int arg) {
|
|
int i;
|
|
|
|
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 (i = 0; i < arg; i++)
|
|
clang_analyzer_numTimesReached(); // expected-warning {{3}}
|
|
|
|
clang_analyzer_dump(i); // noassumeone-warning {{0}} expected-warning {{1}} expected-warning {{3}}
|
|
}
|
|
|
|
#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.
|
|
int i;
|
|
for (i = 0; i < arg && TRUE; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
|
|
clang_analyzer_dump(i); // expected-warning {{0}} expected-warning {{1}} expected-warning {{2}} expected-warning {{3}}
|
|
}
|
|
|
|
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.
|
|
int i;
|
|
for (i = 0; TRUE && i < arg; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{2}}
|
|
}
|
|
|
|
clang_analyzer_dump(i); // noassumeone-warning {{0}} expected-warning {{1}} expected-warning {{2}}
|
|
}
|
|
|
|
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 would be "naturally" responsible for evaluating the loop condition.
|
|
// The current implementation tries to handle 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: It would be good to eventually eliminate this inconsistency, but
|
|
// I don't know a realistic example that could appear in real-world code, so
|
|
// this seems to be a low-priority goal.
|
|
int i;
|
|
for (i = 0; (i >= arg) - 1; i++) {
|
|
clang_analyzer_numTimesReached(); // eagerlyassume-warning {{4}} noeagerlyassume-warning {{2}}
|
|
}
|
|
|
|
// The 'combo' note intentionally appears if `assume-at-least-one-iteration`
|
|
// is disabled, but also appears as a bug when `eagerly-assume` and
|
|
// `assume-at-least-one-iteration` are both enabled.
|
|
clang_analyzer_dump(i); // combo-warning {{0}} expected-warning {{1}} expected-warning {{2}} eagerlyassume-warning {{3}}
|
|
}
|
|
|
|
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.
|
|
int i;
|
|
for (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_dump(i); // expected-warning {{1}} expected-warning {{2}} expected-warning {{3}} expected-warning {{4}}
|
|
}
|