// RUN: %clang_analyze_cc1 -triple hexagon-unknown-linux -verify %s \ // RUN: -analyzer-checker=core,optin.core.UnconditionalVAArg \ // RUN: -analyzer-output=text // // RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu -verify %s \ // RUN: -analyzer-checker=core,optin.core.UnconditionalVAArg \ // RUN: -analyzer-output=text #include "Inputs/system-header-simulator-for-valist.h" int printf(const char *format, ...); void abort(void); void log_message(const char *msg, ...) { // This artifical but plausible example function expects that the list of // variadic arguments is terminated by a NULL pointer. Although careful use // of this function is well-defined, passing no variadic arguments would // trigger undefined behavior, so it may be better to choose a different way // to mark the count of variadic arguments. va_list va; const char *arg; printf("%s\n", msg); va_start(va, msg); while ((arg = va_arg(va, const char *))) { // expected-warning@-1 {{Unconditional use of va_arg()}} // expected-note@-2 {{Calls to 'log_message' always reach this va_arg() expression, so calling 'log_message' with no variadic arguments would be undefined behavior}} printf(" * %s\n", arg); } va_end(va); } void never_called(int fst, ...) { // This simple test function is not called in this test file. va_list va; va_start(va, fst); (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}} // expected-note@-1 {{Calls to 'never_called' always reach this va_arg() expression, so calling 'never_called' with no variadic arguments would be undefined behavior}} va_end(va); } void called_with_varargs(int fst, ...) { // This function is identical to never_called(), but it is called by another // function (which will serve as the entrypoint) and the call passes several // variadic arguments. The diagnostic is still raised. va_list va; va_start(va, fst); (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}} // expected-note@-1 {{Calls to 'called_with_varargs' always reach this va_arg() expression, so calling 'called_with_varargs' with no variadic arguments would be undefined behavior}} va_end(va); } void caller1(void) { called_with_varargs(1, 2, 3, 4); } void called_without_varargs(int fst, ...) { // This function is identical to never_called(), but it is called by another // function (which will serve as the entrypoint) and the call passes no // variadic arguments. The diagnostic is still raised. va_list va; va_start(va, fst); (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}} // expected-note@-1 {{Calls to 'called_without_varargs' always reach this va_arg() expression, so calling 'called_without_varargs' with no variadic arguments would be undefined behavior}} // FIXME: Here the checker should mention that this function is _actually_ // called with no variadic arguments. va_end(va); } void caller2(void) { called_without_varargs(1); } void multiple_va_arg_calls(int fst, ...) { // This function function unconditionally calls va_arg() multiple times. To // avoid spamming the user, only the first use is reported. va_list va; va_start(va, fst); (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}} // expected-note@-1 {{Calls to 'multiple_va_arg_calls' always reach this va_arg() expression, so calling 'multiple_va_arg_calls' with no variadic arguments would be undefined behavior}} (void)va_arg(va, int); // no-warning (void)va_arg(va, int); // no-warning va_end(va); } void has_conditional_return(int fst, ...) { // The va_arg call is not always executed, so don't report this function. // Actually understanding the conditional logic is infeasible, so the checker // should accept any conditional logic that sometimes avoids the va_arg(). va_list va; if (fst < 0) return; va_start(va, fst); (void)va_arg(va, int); // no-warning va_end(va); } void has_conditional_logic(int fst, ...) { // As static analyzer only follows one execution path, it cannot see that // va_arg() is always executed in this function. Theoretically it would be // better to produce a report here, but implementing this would be a very // ineffective use of development effort. va_list va; if (fst < 0) fst = -fst; va_start(va, fst); (void)va_arg(va, int); // no-warning va_end(va); } #define IS_FEATURE_ENABLED 1 void has_constant_condition(int fst, ...) { // This checker doesn't have special logic for recognizing and ignoring // conditionals that can be resolved at compile time. This feature could be // added in the future. va_list va; if (IS_FEATURE_ENABLED) { va_start(va, fst); (void)va_arg(va, int); // no-warning va_end(va); } } void caller_has_conditional_logic(int fst, ...) { // This function is identical to never_called(), but it is used by a function // that performs a state split before the call. // This test validates that state splits _before_ the variadic call (which // are irrelevant) do not influence the report creation. This is basically a // workaround for the fact that not all functions are entrypoints and before // reaching "our" variadic function the analyzer may go through arbitrary // irrelevant code. va_list va; va_start(va, fst); (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}} // expected-note@-1 {{Calls to 'caller_has_conditional_logic' always reach this va_arg() expression, so calling 'caller_has_conditional_logic' with no variadic arguments would be undefined behavior}} va_end(va); } void caller_with_conditional_logic(int flag) { if (flag) caller_has_conditional_logic(1, 2, 3); } int has_shortcircuiting_operator(int fst, ...) { // In addition to 'if' and similar statements, ternary operators and // shortcircuiting logical operators can also ensure that the use of va_arg() // is not unconditional. (These all behave identically in the BranchCondition // callback, so this simple testcase is mostly for documentation purposes.) va_list va; int x; va_start(va, fst); x = fst || va_arg(va, int); // no-warning va_end(va); return x; } void validate_argument(int fst) { if (fst <= 0) { printf("Negative first argument is illegal: %d!\n", fst); abort(); } } void noreturn_in_function(int fst, ...) { // This function calls a function which may call the noreturn function // abort() before reaching the va_arg() call, so the checker doesn't produce // a warning. This behavior is important e.g. for cases when a function uses // assertions to mark preconditions. va_list va; validate_argument(fst); va_start(va, fst); (void)va_arg(va, int); // no-warning va_end(va); } void print_negative(int fst) { if (fst <= 0) { printf("First argument is negative: %d!\n", fst); } } void conditional_in_function(int fst, ...) { // This function is very similar to noreturn_in_function(), but the called // function always returns. However, the analyzer doesn't see this difference // (because it follows a _single_ execution path and cannot see the // alternative) so the checker behaves as in noreturn_in_function. va_list va; print_negative(fst); va_start(va, fst); (void)va_arg(va, int); // no-warning va_end(va); } void defined_in_different_tu(int); void unknown_call(int fst, ...) { // This function is very similar to noreturn_in_function() or // conditional_in_function() but the definition of the called function is not // visible for the analyzer. In theis situation the analyzer ignores the call // and reports the va_arg() expression that unconditionally appears after it. // This would be incorrect behavior only in a situation when the called // function can return _and_ can be noreturn which should be fairly rare. va_list va; defined_in_different_tu(fst); va_start(va, fst); (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}} // expected-note@-1 {{Calls to 'unknown_call' always reach this va_arg() expression, so calling 'unknown_call' with no variadic arguments would be undefined behavior}} va_end(va); } void trivial_variadic(int fst, ...) { // Do nothing. } void calls_other_variadic(int fst, ...) { // As the 'HasUnconditionalPath' can "remember" only one variadic function, // I would expect that the presence of 'trivial_variadic' would prevent the // checker from reporting the unconditional use of va_arg() in this function. // However, for some unclear reason the checker is still able to produce this // (true positive) report. va_list va; trivial_variadic(fst, 2); va_start(va, fst); (void)va_arg(va, int); // expected-warning{{Unconditional use of va_arg()}} // expected-note@-1 {{Calls to 'calls_other_variadic' always reach this va_arg() expression, so calling 'calls_other_variadic' with no variadic arguments would be undefined behavior}} va_end(va); } int get_int_from_va_list(va_list *va) { // This checker can report va_arg() expressions that are unconditionally // reached from a variadic function, even if they are located in a different // function. In this case the originating variadic function is marked with a // note (to make it easier to find). return va_arg(*va, int); // expected-warning{{Unconditional use of va_arg()}} // expected-note@-1 {{Calls to 'uses_wrapped_va_arg' always reach this va_arg() expression, so calling 'uses_wrapped_va_arg' with no variadic arguments would be undefined behavior}} } void uses_wrapped_va_arg(int fst, ...) { // expected-note@-1 {{Variadic function 'uses_wrapped_va_arg' is defined here}} va_list va; va_start(va, fst); (void)get_int_from_va_list(&va); va_end(va); } int standalone_non_variadic(va_list *va) { // A va_arg() expression is only reported if it is unconditionally reached // from the beginning of a _variadic_ function. Functions that use va_arg() // on a va_list obtained from other sources (e.g. as an argument) are // presumed to be small helper subroutines of a complex variadic function, so // we do not report cases where the va_arg() is unconditionally reached from // the beginning of a _non-variadic_ function. return va_arg(*va, int); //no-warning } int variadic_but_also_takes_va_list(va_list *va, ...) { // The checker just looks for va_arg() calls without checking the origin of // their argument, so in this (very artifical) example it produces a // result that is arguably a false positive. return va_arg(*va, int); // expected-warning{{Unconditional use of va_arg()}} // expected-note@-1 {{Calls to 'variadic_but_also_takes_va_list' always reach this va_arg() expression, so calling 'variadic_but_also_takes_va_list' with no variadic arguments would be undefined behavior}} }