// 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); }