blob: 73627112e2d327724f2f23a5ad92bdbaae15e3ba [file] [log] [blame] [edit]
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify=expected,default %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -analyzer-config inline-functions-with-ambiguous-loops=true -verify=expected,enabled %s
// This file tests some heuristics in the engine that put functions on a
// "do not inline" list if their analyisis reaches the `analyzer-max-loop`
// limit (by default 4 iterations) in a loop. This was almost surely intended
// as memoization optimization for the "retry without inlining" fallback (if we
// had to retry once, next time don't even try inlining), but aggressively
// oversteps the "natural" scope: reaching 4 iterations on _one particular_
// execution path does not imply that each path would need "retry without
// inlining" especially if a different call receives different arguments.
//
// This heuristic significantly affects the scope/depth of the analysis (and
// therefore the execution time) because without this limitation on the
// inlining significantly more entry points would be able to exhaust their
// `max-nodes` quota. (Trivial thin wrappers around big complex functions are
// common in many projects.)
//
// Unfortunately, this arbitrary heuristic strongly relies on the current loop
// handling model and its many limitations, so improvements in loop handling
// can cause surprising slowdowns by reducing the "do not inline" blacklist.
// In the tests "FIXME-BUT-NEEDED" comments mark "problematic" (aka buggy)
// analyzer behavior which cannot be fixed without also improving the
// heuristics for (not) inlining large functions.
int getNum(void); // Get an unknown symbolic number.
void clang_analyzer_dump(int arg);
//-----------------------------------------------------------------------------
// Simple case: inlined function never reaches `analyzer-max-loop`, so it is
// always inlined.
int inner_simple(int callIdx) {
clang_analyzer_dump(callIdx); // expected-warning {{1 S32}}
// expected-warning@-1 {{2 S32}}
return 42;
}
int outer_simple(void) {
int x = inner_simple(1);
int y = inner_simple(2);
return 53 / (x - y); // expected-warning {{Division by zero}}
}
//-----------------------------------------------------------------------------
// Inlined function always reaches `analyzer-max-loop`, which stops the
// analysis on that path and puts the function on the "do not inline" list.
int inner_fixed_loop_1(int callIdx) {
int i;
clang_analyzer_dump(callIdx); // expected-warning {{1 S32}}
for (i = 0; i < 10; i++); // FIXME-BUT-NEEDED: This stops the analysis.
clang_analyzer_dump(callIdx); // no-warning
return 42;
}
int outer_fixed_loop_1(void) {
int x = inner_fixed_loop_1(1);
int y = inner_fixed_loop_1(2);
// FIXME-BUT-NEEDED: The analysis doesn't reach this zero division.
return 53 / (x - y); // no-warning
}
//-----------------------------------------------------------------------------
// Inlined function always reaches `analyzer-max-loop`; inlining is prevented
// even for different entry points.
// NOTE: the analyzer happens to analyze the entry points in a reversed order,
// so `outer_2_fixed_loop_2` is analyzed first and it will be the one which is
// able to inline the inner function.
int inner_fixed_loop_2(int callIdx) {
// Identical copy of inner_fixed_loop_1.
int i;
clang_analyzer_dump(callIdx); // expected-warning {{2 S32}}
for (i = 0; i < 10; i++); // FIXME-BUT-NEEDED: This stops the analysis.
clang_analyzer_dump(callIdx); // no-warning
return 42;
}
int outer_1_fixed_loop_2(void) {
return inner_fixed_loop_2(1);
}
int outer_2_fixed_loop_2(void) {
return inner_fixed_loop_2(2);
}
//-----------------------------------------------------------------------------
// Inlined function reaches `analyzer-max-loop` only in its second call. The
// function is inlined twice but the second call doesn't finish and ends up
// being conservatively evaluated.
int inner_parametrized_loop_1(int count) {
int i;
clang_analyzer_dump(count); // expected-warning {{2 S32}}
// expected-warning@-1 {{10 S32}}
for (i = 0; i < count; i++);
// FIXME-BUT-NEEDED: This loop stops the analysis when count >=4.
clang_analyzer_dump(count); // expected-warning {{2 S32}}
return 42;
}
int outer_parametrized_loop_1(void) {
int x = inner_parametrized_loop_1(2);
int y = inner_parametrized_loop_1(10);
// FIXME-BUT-NEEDED: The analysis doesn't reach this zero division.
return 53 / (x - y); // no-warning
}
//-----------------------------------------------------------------------------
// Inlined function reaches `analyzer-max-loop` on its first call, so the
// second call isn't inlined (although it could be fully evaluated).
int inner_parametrized_loop_2(int count) {
// Identical copy of inner_parametrized_loop_1.
int i;
clang_analyzer_dump(count); // expected-warning {{10 S32}}
for (i = 0; i < count; i++);
// FIXME-BUT-NEEDED: This loop stops the analysis when count >=4.
clang_analyzer_dump(count); // no-warning
return 42;
}
int outer_parametrized_loop_2(void) {
int y = inner_parametrized_loop_2(10);
int x = inner_parametrized_loop_2(2);
// FIXME-BUT-NEEDED: The analysis doesn't reach this zero division.
return 53 / (x - y); // no-warning
}
//-----------------------------------------------------------------------------
// Inlined function may or may not reach `analyzer-max-loop` depending on an
// ambiguous check before the loop. This is very similar to the "fixed loop"
// cases: the function is placed on the "don't inline" list when any execution
// path reaches `analyzer-max-loop` (even if other execution paths reach the
// end of the function).
// NOTE: This is tested with two separate entry points to ensure that one
// inlined call is fully evaluated before we try to inline the other call.
// NOTE: the analyzer happens to analyze the entry points in a reversed order,
// so `outer_2_conditional_loop` is analyzed first and it will be the one which
// is able to inline the inner function.
int inner_conditional_loop(int callIdx) {
int i;
clang_analyzer_dump(callIdx); // expected-warning {{2 S32}}
if (getNum() == 777) {
for (i = 0; i < 10; i++);
}
clang_analyzer_dump(callIdx); // expected-warning {{2 S32}}
return 42;
}
int outer_1_conditional_loop(void) {
return inner_conditional_loop(1);
}
int outer_2_conditional_loop(void) {
return inner_conditional_loop(2);
}
//-----------------------------------------------------------------------------
// Inlined function executes an ambiguous loop that may or may not reach
// `analyzer-max-loop`. Historically, before the "don't assume third iteration"
// commit (bb27d5e5c6b194a1440b8ac4e5ace68d0ee2a849) this worked like the
// `conditional_loop` cases: the analyzer was able to find a path reaching
// `analyzer-max-loop` so inlining was disabled. After that commit the analyzer
// does not _assume_ a third (or later) iteration (i.e. does not enter those
// iterations if the loop condition is an unknown value), so e.g. this test
// function does not reach `analyzer-max-loop` iterations and the inlining is
// not disabled.
// Unfortunately this change significantly increased the workload and
// runtime of the analyzer (more entry points used up their budget), so the
// option `inline-functions-with-ambiguous-loops` was introduced and disabled
// by default to suppress the inlining in situations where the "don't assume
// third iteration" logic activates.
// NOTE: This is tested with two separate entry points to ensure that one
// inlined call is fully evaluated before we try to inline the other call.
// NOTE: the analyzer happens to analyze the entry points in a reversed order,
// so `outer_2_ambiguous_loop` is analyzed first and it will be the one which
// is able to inline the inner function.
int inner_ambiguous_loop(int callIdx) {
int i;
clang_analyzer_dump(callIdx); // default-warning {{2 S32}}
// enabled-warning@-1 {{1 S32}}
// enabled-warning@-2 {{2 S32}}
for (i = 0; i < getNum(); i++);
return i;
}
int outer_1_ambiguous_loop(void) {
return inner_ambiguous_loop(1);
}
int outer_2_ambiguous_loop(void) {
return inner_ambiguous_loop(2);
}