
Recently some users reported that they observed large increases of runtime (up to +600% on some translation units) when they upgraded to a more recent (slightly patched, internal) clang version. Bisection revealed that the bulk of this increase was probably caused by my earlier commit bb27d5e5c6b194a1440b8ac4e5ace68d0ee2a849 ("Don't assume third iteration in loops"). As I evaluated that earlier commit on several open source project, it turns out that on average it's runtime-neutral (or slightly helpful: it reduced the total analysis time by 1.5%) but it can cause runtime spikes on some code: in particular it more than doubled the time to analyze `tmux` (one of the smaller test projects). Further profiling and investigation proved that these spikes were caused by an _increase of analysis scope_ because there was an heuristic that placed functions on a "don't inline this" blacklist if they reached the `-analyzer-max-loop` limit (anywhere, on any one execution path) -- which became significantly rarer when my commit ensured the analyzer no longer "just assumes" four iterations. (With more inlining significantly more entry points use up their allocated budgets, which leads to the increased runtime.) I feel that this heuristic for the "don't inline" blacklist is unjustified and arbitrary, because reaching the "retry without inlining" limit on one path does not imply that inlining the function won't be valuable on other paths -- so I hope that we can eventually replace it with more "natural" limits of the analysis scope. However, the runtime increases are annoying for the users whose project is affected, so I created this quick workaround commit that approximates the "don't inline" blacklist effects of ambiguous loops (where the analyzer doesn't understand the loop condition) without fully reverting the "Don't assume third iteration" commit (to avoid reintroducing the false positives that were eliminated by it). Investigating this issue was a team effort: I'm grateful to Endre Fülöp (gamesh411) who did the bisection and shared his time measurement setup, and Gábor Tóthvári (tigbr) who helped me in profiling.
583 lines
14 KiB
C++
583 lines
14 KiB
C++
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config unroll-loops=true,cfg-loopexit=true -verify=expected,default -std=c++14 -analyzer-config exploration_strategy=unexplored_first_queue %s
|
|
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config unroll-loops=true,cfg-loopexit=true,exploration_strategy=dfs -verify=expected,dfs -std=c++14 %s
|
|
|
|
void clang_analyzer_numTimesReached();
|
|
void clang_analyzer_warnIfReached();
|
|
|
|
int getNum();
|
|
void foo(int &);
|
|
|
|
int simple_unroll1() {
|
|
int a[9];
|
|
int k = 42;
|
|
for (int i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{9}}
|
|
a[i] = 42;
|
|
}
|
|
int b = 22 / (k - 42); // expected-warning {{Division by zero}}
|
|
return 0;
|
|
}
|
|
|
|
int simple_unroll2() {
|
|
int a[9];
|
|
int k = 42;
|
|
int i;
|
|
for (i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{9}}
|
|
a[i] = 42;
|
|
}
|
|
|
|
for (int j = 0; j <= 9; ++j) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{10}}
|
|
a[j] = 42;
|
|
}
|
|
|
|
int b = 22 / (k - 42); // expected-warning {{Division by zero}}
|
|
return 0;
|
|
}
|
|
|
|
int simple_unroll3_unsigned() {
|
|
int a[9];
|
|
int k = 42;
|
|
for (unsigned i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{9}}
|
|
a[i] = 42;
|
|
}
|
|
int b = 22 / (k - 42); // expected-warning {{Division by zero}}
|
|
return 0;
|
|
}
|
|
|
|
int simple_unroll4_unsigned() {
|
|
int a[9];
|
|
int k = 42;
|
|
unsigned i;
|
|
for (i = (0); i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{9}}
|
|
a[i] = 42;
|
|
}
|
|
int b = 22 / (k - 42); // expected-warning {{Division by zero}}
|
|
return 0;
|
|
}
|
|
|
|
int simple_no_unroll1() {
|
|
int a[9];
|
|
int k = 42;
|
|
for (int i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{2}}
|
|
a[i] = 42;
|
|
foo(i);
|
|
}
|
|
int b = 22 / (k - 42); // expected-warning {{Division by zero}}
|
|
return 0;
|
|
}
|
|
|
|
int simple_no_unroll2() {
|
|
int a[9];
|
|
int k = 42;
|
|
int i;
|
|
for (i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{2}}
|
|
a[i] = 42;
|
|
i += getNum();
|
|
}
|
|
int b = 22 / (k - 42); // expected-warning {{Division by zero}}
|
|
return 0;
|
|
}
|
|
|
|
int simple_no_unroll3() {
|
|
int a[9];
|
|
int k = 42;
|
|
for (int i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
a[i] = 42;
|
|
(void)&i;
|
|
}
|
|
int b = 22 / (k - 42); // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int simple_no_unroll4() {
|
|
int a[9];
|
|
int k = 42;
|
|
int i;
|
|
for (i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
a[i] = 42;
|
|
int &j = i;
|
|
}
|
|
int b = 22 / (k - 42); // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int simple_no_unroll5() {
|
|
int a[9];
|
|
int k = 42;
|
|
int i;
|
|
for (i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
a[i] = 42;
|
|
int &j{i};
|
|
}
|
|
int b = 22 / (k - 42); // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment() {
|
|
for (int i = 0; i < 9; i++) {
|
|
i = i + 1;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment2() {
|
|
for (int i = 0; i < 9; i++) {
|
|
i *= 2;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment3() {
|
|
for (int i = 128; i > 0; i--) {
|
|
i /= 2;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment4() {
|
|
for (int i = 0; i < 9; i++) {
|
|
i -= 2;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment5() {
|
|
for (int i = 0; i < 9; i++) {
|
|
i += 1;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment6() {
|
|
for (int i = 128; i > 0; i--) {
|
|
i >>= 1;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment7() {
|
|
for (int i = 0; i < 512; i++) {
|
|
i <<= 1;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment8() {
|
|
for (int i = 0; i < 9; i++) {
|
|
i %= 8;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment9() {
|
|
for (int i = 0; i < 9; i++) {
|
|
i &= 31;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment10() {
|
|
for (int i = 0; i < 9; i++) {
|
|
i |= 2;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int no_unroll_assignment11() {
|
|
for (int i = 0; i < 9; i++) {
|
|
i ^= 2;
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int make_new_branches_loop_cached() {
|
|
for (int i = 0; i < 8; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
if (getNum()) {
|
|
(void)i; // Since this Stmt does not change the State the analyzer
|
|
// won't make a new execution path but reuse the earlier nodes.
|
|
}
|
|
}
|
|
clang_analyzer_warnIfReached(); // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int make_new_branches_loop_uncached() {
|
|
int l = 2;
|
|
for (int i = 0; i < 8; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{10}}
|
|
if (getNum()) {
|
|
++l;
|
|
}
|
|
}
|
|
clang_analyzer_warnIfReached(); // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int make_new_branches_loop_uncached2() {
|
|
int l = 2;
|
|
for (int i = 0; i < 8; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{10}}
|
|
if (getNum()) {
|
|
++l;
|
|
}
|
|
(void)&i; // This ensures that the loop won't be unrolled.
|
|
}
|
|
clang_analyzer_warnIfReached(); // no-warning
|
|
return 0;
|
|
}
|
|
|
|
|
|
int escape_before_loop_no_unroll1() {
|
|
int a[9];
|
|
int k = 42;
|
|
int i;
|
|
int &j = i;
|
|
for (i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
a[i] = 42;
|
|
}
|
|
int b = 22 / (k - 42); // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int escape_before_loop_no_unroll2() {
|
|
int a[9];
|
|
int k = 42;
|
|
int i;
|
|
int *p = &i;
|
|
for (i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
a[i] = 42;
|
|
}
|
|
int b = 22 / (k - 42); // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int escape_before_loop_no_unroll3() {
|
|
int a[9];
|
|
int k = 42;
|
|
int i;
|
|
foo(i);
|
|
for (i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
a[i] = 42;
|
|
}
|
|
int b = 22 / (k - 42); // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int nested_outer_unrolled() {
|
|
int a[9];
|
|
int k = 42;
|
|
int j = 0;
|
|
for (int i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{1}}
|
|
for (j = 0; j < 9; ++j) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
a[j] = 22;
|
|
(void)&j; // ensures that the inner loop won't be unrolled
|
|
}
|
|
a[i] = 42;
|
|
}
|
|
int b = 22 / (k - 42); // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int nested_inner_unrolled() {
|
|
int a[9];
|
|
int k = 42;
|
|
int j = 0;
|
|
for (int i = 0; i < getNum(); i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{2}}
|
|
for (j = 0; j < 8; ++j) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{16}}
|
|
a[j] = 22;
|
|
}
|
|
a[i] = 42;
|
|
}
|
|
int b = 22 / (k - 42); // expected-warning {{Division by zero}}
|
|
return 0;
|
|
}
|
|
|
|
int nested_both_unrolled() {
|
|
int a[9];
|
|
int k = 42;
|
|
int j = 0;
|
|
for (int i = 0; i < 7; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{7}}
|
|
for (j = 0; j < 6; ++j) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{42}}
|
|
a[j] = 22;
|
|
}
|
|
a[i] = 42;
|
|
}
|
|
int b = 22 / (k - 42); // expected-warning {{Division by zero}}
|
|
return 0;
|
|
}
|
|
|
|
int simple_known_bound_loop() {
|
|
// Iteration count visible: can be unrolled and fully executed.
|
|
for (int i = 2; i < 12; i++) {
|
|
// This function is inlined in nested_inlined_unroll1()
|
|
clang_analyzer_numTimesReached(); // expected-warning {{90}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int simple_unknown_bound_loop() {
|
|
// Iteration count unknown: unrolling won't happen and the execution will be
|
|
// split two times:
|
|
// (1) split between skipped loop (immediate exit) and entering the loop
|
|
// (2) split between exit after 1 iteration and entering the second iteration
|
|
// After these there is no third state split because the "don't assume third
|
|
// iteration" logic in `ExprEngine::processBranch` prevents it; but the
|
|
// `legacy-inlining-prevention` logic will put this function onto the list of
|
|
// functions that may not be inlined in the future.
|
|
// The exploration strategy apparently influences the number of times this
|
|
// function can be inlined before it's placed on the "don't inline" list.
|
|
for (int i = 2; i < getNum(); i++) {
|
|
clang_analyzer_numTimesReached(); // default-warning {{4}} dfs-warning {{8}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int nested_inlined_unroll1() {
|
|
// Here the analyzer can unroll and fully execute both the outer loop and the
|
|
// inner loop within simple_known_bound_loop().
|
|
int k;
|
|
for (int i = 0; i < 9; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{9}}
|
|
k = simple_known_bound_loop();
|
|
}
|
|
int a = 22 / k; // expected-warning {{Division by zero}}
|
|
return 0;
|
|
}
|
|
|
|
int nested_inlined_no_unroll1() {
|
|
// Here no unrolling happens and we only run `analyzer-max-loop` (= 4)
|
|
// iterations of the loop within this function, but some state splits happen
|
|
// in `simple_unknown_bound_loop()` calls.
|
|
int k;
|
|
for (int i = 0; i < 40; i++) {
|
|
clang_analyzer_numTimesReached(); // default-warning {{9}} dfs-warning {{12}}
|
|
k = simple_unknown_bound_loop();
|
|
}
|
|
int a = 22 / k; // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int recursion_unroll1(bool b) {
|
|
int k = 2;
|
|
for (int i = 0; i < 5; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{13}}
|
|
if (i == 0 && b) // Splits the state in the first iteration but the recursion
|
|
// call will be unrolled anyway since the condition is known there.
|
|
recursion_unroll1(false);
|
|
clang_analyzer_numTimesReached(); // expected-warning {{14}}
|
|
}
|
|
int a = 22 / k; // no-warning
|
|
return 0;
|
|
}
|
|
|
|
int recursion_unroll2(bool b) {
|
|
int k = 0;
|
|
for (int i = 0; i < 5; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{9}}
|
|
if (i == 0 && b)
|
|
recursion_unroll2(false);
|
|
clang_analyzer_numTimesReached(); // expected-warning {{9}}
|
|
}
|
|
int a = 22 / k; // expected-warning {{Division by zero}}
|
|
return 0;
|
|
}
|
|
|
|
int recursion_unroll3(bool b) {
|
|
int k = 2;
|
|
for (int i = 0; i < 5; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{10}}
|
|
if (i == 4 && b) {
|
|
recursion_unroll3(false);
|
|
break;
|
|
}
|
|
clang_analyzer_numTimesReached(); // expected-warning {{10}}
|
|
}
|
|
int a = 22 / k;
|
|
return 0;
|
|
}
|
|
|
|
int recursion_unroll4(bool b) {
|
|
int k = 2;
|
|
for (int i = 0; i < 5; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{13}}
|
|
if (i == 0 && b) {
|
|
recursion_unroll4(false);
|
|
continue;
|
|
}
|
|
clang_analyzer_numTimesReached(); // expected-warning {{13}}
|
|
}
|
|
int a = 22 / k;
|
|
return 0;
|
|
}
|
|
|
|
int loop_exit_while_empty_loop_stack() {
|
|
if (getNum())
|
|
for (int i = 1; i < 8; i++)
|
|
;
|
|
return 0;
|
|
}
|
|
|
|
int num_steps_on_limit() {
|
|
for (int i = 0; i < 128; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{128}}
|
|
}
|
|
clang_analyzer_numTimesReached(); // expected-warning {{1}}
|
|
return 0;
|
|
}
|
|
|
|
int num_steps_over_limit1() {
|
|
for (int i = 0; i < 129; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int num_steps_on_limit2() {
|
|
for (int i = 0; i < 2; i++) {
|
|
for (int j = 0; j < 64; j++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{128}}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int num_steps_over_limit2() {
|
|
for (int i = 0; i < 2; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{1}}
|
|
for (int j = 0; j <= 64; j++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int num_steps_on_limit3() {
|
|
for (int i = 0; i < getNum(); i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{2}}
|
|
for (int j = 0; j < 32; j++) {
|
|
// Here the loop unrollig logic calculates with four potential iterations
|
|
// in the outer loop where it cannot determine the iteration count in
|
|
// advance; but after two loops the analyzer conservatively assumes that
|
|
// the (still opaque) loop condition is false.
|
|
clang_analyzer_numTimesReached(); // expected-warning {{64}}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int num_steps_over_limit3() {
|
|
for (int i = 0; i < getNum(); i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{1}}
|
|
for (int j = 0; j < 33; j++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int num_steps_on_limit4() {
|
|
for (int i = 0; i < 4; i++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
for (int j = 0; j < 32; j++) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{128}}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void pr34943() {
|
|
for (int i = 0; i < 6L; ++i) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{6}}
|
|
}
|
|
}
|
|
|
|
void parm_by_value_as_loop_counter(int i) {
|
|
for (i = 0; i < 10; ++i) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{10}}
|
|
}
|
|
}
|
|
|
|
void parm_by_ref_as_loop_counter(int &i) {
|
|
for (i = 0; i < 10; ++i) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
}
|
|
|
|
void capture_by_value_as_loop_counter() {
|
|
int out = 0;
|
|
auto l = [i = out]() mutable {
|
|
for (i = 0; i < 10; ++i) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{10}}
|
|
}
|
|
};
|
|
}
|
|
|
|
void capture_by_ref_as_loop_counter() {
|
|
int out = 0;
|
|
auto l = [&i = out]() {
|
|
for (i = 0; i < 10; ++i) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
};
|
|
}
|
|
|
|
void capture_implicitly_by_value_as_loop_counter() {
|
|
int i = 0;
|
|
auto l = [=]() mutable {
|
|
for (i = 0; i < 10; ++i) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{10}}
|
|
}
|
|
};
|
|
}
|
|
|
|
void capture_implicitly_by_ref_as_loop_counter() {
|
|
int i = 0;
|
|
auto l = [&]() mutable {
|
|
for (i = 0; i < 10; ++i) {
|
|
clang_analyzer_numTimesReached(); // expected-warning {{4}}
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
void test_escaping_on_var_before_switch_case_no_crash(int c) {
|
|
// https://github.com/llvm/llvm-project/issues/68819
|
|
switch (c) {
|
|
int i; // no-crash: The declaration of `i` is found here.
|
|
case 0: {
|
|
for (i = 0; i < 16; i++) {}
|
|
break;
|
|
}
|
|
}
|
|
}
|