// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core %s \
// RUN: -analyzer-output=plist -o %t.plist \
// RUN: -analyzer-config expand-macros=true
//
// Check the actual plist output.
// RUN: %normalize_plist <%t.plist | diff -ub \
// RUN: %S/Inputs/expected-plists/plist-macros-with-expansion.cpp.plist -
//
// Check the macro expansions from the plist output here, to make the test more
// understandable.
// RUN: FileCheck --input-file=%t.plist %s
void print(const void*);
//===----------------------------------------------------------------------===//
// Tests for non-function-like macro expansions.
//===----------------------------------------------------------------------===//
#define SET_PTR_VAR_TO_NULL \
ptr = 0
void nonFunctionLikeMacroTest() {
int *ptr;
SET_PTR_VAR_TO_NULL;
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameSET_PTR_VAR_TO_NULL
// CHECK-NEXT: expansionptr = 0
#define NULL 0
#define SET_PTR_VAR_TO_NULL_WITH_NESTED_MACRO \
ptr = NULL
void nonFunctionLikeNestedMacroTest() {
int *ptr;
SET_PTR_VAR_TO_NULL_WITH_NESTED_MACRO;
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameSET_PTR_VAR_TO_NULL_WITH_NESTED_MACRO
// CHECK-NEXT: expansionptr =0
//===----------------------------------------------------------------------===//
// Tests for function-like macro expansions.
//===----------------------------------------------------------------------===//
void setToNull(int **vptr) {
*vptr = nullptr;
}
#define TO_NULL(x) \
setToNull(x)
void functionLikeMacroTest() {
int *ptr;
TO_NULL(&ptr);
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameTO_NULL
// CHECK-NEXT: expansionsetToNull(&ptr)
#define DOES_NOTHING(x) \
{ \
int b; \
b = 5; \
} \
print(x)
#define DEREF(x) \
DOES_NOTHING(x); \
*x
void functionLikeNestedMacroTest() {
int *a;
TO_NULL(&a);
DEREF(a) = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameTO_NULL
// CHECK-NEXT: expansionsetToNull(&a)
// CHECK: nameDEREF
// CHECK-NEXT: expansion{ int b; b = 5; } print(a); *a
//===----------------------------------------------------------------------===//
// Tests for undefining and/or redifining macros.
//===----------------------------------------------------------------------===//
#define WILL_UNDEF_SET_NULL_TO_PTR(ptr) \
ptr = nullptr;
void undefinedMacroByTheEndOfParsingTest() {
int *ptr;
WILL_UNDEF_SET_NULL_TO_PTR(ptr);
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
#undef WILL_UNDEF_SET_NULL_TO_PTR
// CHECK: nameWILL_UNDEF_SET_NULL_TO_PTR
// CHECK-NEXT: expansionptr = nullptr;
#define WILL_REDIFINE_MULTIPLE_TIMES_SET_TO_NULL(ptr) \
/* Nothing */
#undef WILL_REDIFINE_MULTIPLE_TIMES_SET_TO_NULL
#define WILL_REDIFINE_MULTIPLE_TIMES_SET_TO_NULL(ptr) \
ptr = nullptr;
void macroRedefinedMultipleTimesTest() {
int *ptr;
WILL_REDIFINE_MULTIPLE_TIMES_SET_TO_NULL(ptr)
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
#undef WILL_REDIFINE_MULTIPLE_TIMES_SET_TO_NULL
#define WILL_REDIFINE_MULTIPLE_TIMES_SET_TO_NULL(ptr) \
print("This string shouldn't be in the plist file at all. Or anywhere, " \
"but here.");
// CHECK: nameWILL_REDIFINE_MULTIPLE_TIMES_SET_TO_NULL
// CHECK-NEXT: expansionptr = nullptr;
#define WILL_UNDEF_SET_NULL_TO_PTR_2(ptr) \
ptr = nullptr;
#define PASS_PTR_TO_MACRO_THAT_WILL_BE_UNDEFD(ptr) \
WILL_UNDEF_SET_NULL_TO_PTR_2(ptr)
void undefinedMacroInsideAnotherMacroTest() {
int *ptr;
PASS_PTR_TO_MACRO_THAT_WILL_BE_UNDEFD(ptr);
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// TODO: Expand arguments.
// CHECK: namePASS_PTR_TO_MACRO_THAT_WILL_BE_UNDEFD
// CHECK-NEXT: expansionptr = nullptr;
#undef WILL_UNDEF_SET_NULL_TO_PTR_2
//===----------------------------------------------------------------------===//
// Tests for macro arguments containing commas and parantheses.
//
// As of writing these tests, the algorithm expands macro arguments by lexing
// the macro's expansion location, and relies on finding tok::comma and
// tok::l_paren/tok::r_paren.
//===----------------------------------------------------------------------===//
// Note that this commas, parantheses in strings aren't parsed as tok::comma or
// tok::l_paren/tok::r_paren, but why not test them.
#define TO_NULL_AND_PRINT(x, str) \
x = 0; \
print(str)
void macroArgContainsCommaInStringTest() {
int *a;
TO_NULL_AND_PRINT(a, "Will this , cause a crash?");
*a = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameTO_NULL_AND_PRINT
// CHECK-NEXT: expansiona = 0; print( "Will this , cause a crash?")
void macroArgContainsLParenInStringTest() {
int *a;
TO_NULL_AND_PRINT(a, "Will this ( cause a crash?");
*a = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameTO_NULL_AND_PRINT
// CHECK-NEXT: expansiona = 0; print( "Will this ( cause a crash?")
void macroArgContainsRParenInStringTest() {
int *a;
TO_NULL_AND_PRINT(a, "Will this ) cause a crash?");
*a = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameTO_NULL_AND_PRINT
// CHECK-NEXT: expansiona = 0; print( "Will this ) cause a crash?")
#define CALL_FUNCTION(funcCall) \
funcCall
// Function calls do contain both tok::comma and tok::l_paren/tok::r_paren.
void macroArgContainsLParenRParenTest() {
int *a;
CALL_FUNCTION(setToNull(&a));
*a = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameCALL_FUNCTION
// CHECK-NEXT: expansionsetToNull(&a)
void setToNullAndPrint(int **vptr, const char *str) {
setToNull(vptr);
print(str);
}
void macroArgContainsCommaLParenRParenTest() {
int *a;
CALL_FUNCTION(setToNullAndPrint(&a, "Hello!"));
*a = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameCALL_FUNCTION
// CHECK-NEXT: expansionsetToNullAndPrint(&a, "Hello!")
#define CALL_FUNCTION_WITH_TWO_PARAMS(funcCall, param1, param2) \
funcCall(param1, param2)
void macroArgContainsCommaLParenRParenTest2() {
int *a;
CALL_FUNCTION_WITH_TWO_PARAMS(setToNullAndPrint, &a, "Hello!");
*a = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameCALL_FUNCTION_WITH_TWO_PARAMS
// CHECK-NEXT: expansionsetToNullAndPrint( &a, "Hello!")
#define CALL_LAMBDA(l) \
l()
void commaInBracketsTest() {
int *ptr;
const char str[] = "Hello!";
// You need to add parantheses around a lambda expression to compile this,
// else the comma in the capture will be parsed as divider of macro args.
CALL_LAMBDA(([&ptr, str] () mutable { TO_NULL(&ptr); }));
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameCALL_LAMBDA
// CHECK-NEXT: expansion([&ptr, str] () mutable { setToNull(&ptr); })()
#define PASTE_CODE(code) \
code
void commaInBracesTest() {
PASTE_CODE({ // expected-warning{{Dereference of null pointer}}
// NOTE: If we were to add a new variable here after a comma, we'd get a
// compilation error, so this test is mainly here to show that this was also
// investigated.
// int *ptr = nullptr, a;
int *ptr = nullptr;
*ptr = 5;
})
}
// CHECK: namePASTE_CODE
// CHECK-NEXT: expansion{ int *ptr = nullptr; *ptr = 5; }
// Example taken from
// https://gcc.gnu.org/onlinedocs/cpp/Macro-Arguments.html#Macro-Arguments.
#define POTENTIALLY_EMPTY_PARAM(x, y) \
x; \
y = nullptr
void emptyParamTest() {
int *ptr;
POTENTIALLY_EMPTY_PARAM(,ptr);
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: namePOTENTIALLY_EMPTY_PARAM
// CHECK-NEXT: expansion;ptr = nullptr
#define NESTED_EMPTY_PARAM(a, b) \
POTENTIALLY_EMPTY_PARAM(a, b);
void nestedEmptyParamTest() {
int *ptr;
NESTED_EMPTY_PARAM(, ptr);
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameNESTED_EMPTY_PARAM
// CHECK-NEXT: expansion; ptr = nullptr;
#define CALL_FUNCTION_WITH_ONE_PARAM_THROUGH_MACRO(func, param) \
CALL_FUNCTION(func(param))
void lParenRParenInNestedMacro() {
int *ptr;
CALL_FUNCTION_WITH_ONE_PARAM_THROUGH_MACRO(setToNull, &ptr);
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameCALL_FUNCTION_WITH_ONE_PARAM_THROUGH_MACRO
// CHECK-NEXT: expansionsetToNull( &ptr)
//===----------------------------------------------------------------------===//
// Tests for variadic macro arguments.
//===----------------------------------------------------------------------===//
template
void variadicFunc(Args ...args);
#define VARIADIC_SET_TO_NULL(ptr, ...) \
ptr = nullptr; \
variadicFunc(__VA_ARGS__)
void variadicMacroArgumentTest() {
int *ptr;
VARIADIC_SET_TO_NULL(ptr, 1, 5, "haha!");
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameVARIADIC_SET_TO_NULL
// CHECK-NEXT: expansionptr = nullptr; variadicFunc( 1, 5, "haha!")
void variadicMacroArgumentWithoutAnyArgumentTest() {
int *ptr;
// Not adding a single parameter to ... is silly (and also causes a
// preprocessor warning), but is not an excuse to crash on it.
VARIADIC_SET_TO_NULL(ptr);
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameVARIADIC_SET_TO_NULL
// CHECK-NEXT: expansionptr = nullptr; variadicFunc()
//===----------------------------------------------------------------------===//
// Tests for # and ##.
//===----------------------------------------------------------------------===//
#define DECLARE_FUNC_AND_SET_TO_NULL(funcName, ptr) \
void generated_##funcName(); \
ptr = nullptr;
void hashHashOperatorTest() {
int *ptr;
DECLARE_FUNC_AND_SET_TO_NULL(whatever, ptr);
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameDECLARE_FUNC_AND_SET_TO_NULL
// CHECK-NEXT: expansionvoid generated_whatever(); ptr = nullptr;
void macroArgContainsHashHashInStringTest() {
int *a;
TO_NULL_AND_PRINT(a, "Will this ## cause a crash?");
*a = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameTO_NULL_AND_PRINT
// CHECK-NEXT: expansiona = 0; print( "Will this ## cause a crash?")
#define PRINT_STR(str, ptr) \
print(#str); \
ptr = nullptr
void hashOperatorTest() {
int *ptr;
PRINT_STR(Hello, ptr);
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: namePRINT_STR
// CHECK-NEXT: expansionprint("Hello"); ptr = nullptr
void macroArgContainsHashInStringTest() {
int *a;
TO_NULL_AND_PRINT(a, "Will this # cause a crash?");
*a = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameTO_NULL_AND_PRINT
// CHECK-NEXT: expansiona = 0; print( "Will this # cause a crash?")
//===----------------------------------------------------------------------===//
// Tests for more complex macro expansions.
//
// We won't cover anything that wasn't covered up to this point, but rather
// show more complex, macros with deeper nesting, more arguments (some unused)
// and so on.
//===----------------------------------------------------------------------===//
#define IF(Condition) \
if ( Condition )
#define L_BRACE {
#define R_BRACE }
#define LESS <
#define GREATER >
#define EQUALS =
#define SEMICOLON ;
#define NEGATIVE -
#define RETURN return
#define ZERO 0
#define EUCLIDEAN_ALGORITHM(A, B) \
IF(A LESS ZERO) L_BRACE \
A EQUALS NEGATIVE A SEMICOLON \
R_BRACE \
IF(B LESS ZERO) L_BRACE \
B EQUALS NEGATIVE B SEMICOLON \
R_BRACE \
\
/* This is where a while loop would be, but that seems to be too complex */ \
/* for the analyzer just yet. Let's just pretend that this algorithm */ \
/* works. */ \
\
RETURN B / (B - B) SEMICOLON
int getLowestCommonDenominator(int A, int B) {
EUCLIDEAN_ALGORITHM(A, B) // expected-warning{{Division by zero}}
}
void testVeryComplexAlgorithm() {
int tmp = 8 / (getLowestCommonDenominator(5, 7) - 1);
print(&tmp);
}
// CHECK: nameEUCLIDEAN_ALGORITHM
// CHECK-NEXT: expansionif (A<0 ){A=-A;} if ( B<0 ){ B=- B;}return B / ( B - B);
#define YET_ANOTHER_SET_TO_NULL(x, y, z) \
print((void *) x); \
print((void *) y); \
z = nullptr;
#define DO_NOTHING(str) str
#define DO_NOTHING2(str2) DO_NOTHING(str2)
void test() {
int *ptr;
YET_ANOTHER_SET_TO_NULL(5, DO_NOTHING2("Remember the Vasa"), ptr);
*ptr = 5; // expected-warning{{Dereference of null pointer}}
}
// CHECK: nameYET_ANOTHER_SET_TO_NULL
// CHECK-NEXT: expansionprint((void *)5); print((void *)"Remember the Vasa"); ptr = nullptr;
int garbage_value;
#define REC_MACRO_FUNC(REC_MACRO_PARAM) garbage_##REC_MACRO_PARAM
#define value REC_MACRO_FUNC(value)
void recursiveMacroUser() {
if (value == 0)
1 / value; // expected-warning{{Division by zero}}
// expected-warning@-1{{expression result unused}}
}
// CHECK: namevalue
// CHECK-NEXT: expansiongarbage_
#define FOO(x) int foo() { return x; }
#define APPLY_ZERO1(function) function(0)
APPLY_ZERO1(FOO)
void useZeroApplier1() { (void)(1 / foo()); } // expected-warning{{Division by zero}}
// CHECK: nameAPPLY_ZERO1
// CHECK-NEXT: expansionint foo() { return x; }(0)
#define BAR(x) int bar() { return x; }
#define APPLY_ZERO2 BAR(0)
APPLY_ZERO2
void useZeroApplier2() { (void)(1 / bar()); } // expected-warning{{Division by zero}}
// CHECK: nameAPPLY_ZERO2
// CHECK-NEXT: expansionint bar() { return 0; }
void foo(int &x, const char *str);
#define PARAMS_RESOLVE_TO_VA_ARGS(i, fmt) foo(i, fmt); \
i = 0;
#define DISPATCH(...) PARAMS_RESOLVE_TO_VA_ARGS(__VA_ARGS__);
void mulitpleParamsResolveToVA_ARGS(void) {
int x = 1;
DISPATCH(x, "LF1M healer");
(void)(10 / x); // expected-warning{{Division by zero}}
}
// CHECK: nameDISPATCH
// CHECK-NEXT: expansionfoo(x, "LF1M healer");x = 0;;
void variadicCFunction(int &x, const char *str, ...);
#define CONCAT_VA_ARGS(i, fmt, ...) variadicCFunction(i, fmt, ##__VA_ARGS__); \
i = 0;
void concatVA_ARGS(void) {
int x = 1;
CONCAT_VA_ARGS(x, "You need to construct additional pylons.", 'c', 9);
(void)(10 / x); // expected-warning{{Division by zero}}
}
// CHECK: nameCONCAT_VA_ARGS
// CHECK-NEXT: expansionvariadicCFunction(x, "You need to construct additional pylons.",'c', 9);x = 0;
void concatVA_ARGSEmpty(void) {
int x = 1;
CONCAT_VA_ARGS(x, "You need to construct");
(void)(10 / x); // expected-warning{{Division by zero}}
}
// FIXME: The comma shouldn't be present after the last argument.
// CHECK: nameCONCAT_VA_ARGS
// CHECK-NEXT: expansionvariadicCFunction(x, "You need to construct",);x = 0;
#define STRINGIFIED_VA_ARGS(i, fmt, ...) variadicCFunction(i, fmt, #__VA_ARGS__); \
i = 0;
void stringifyVA_ARGS(void) {
int x = 1;
STRINGIFIED_VA_ARGS(x, "Additional supply depots required.", 'a', 10);
(void)(10 / x); // expected-warning{{Division by zero}}
}
// FIXME: Stringify and escape __VA_ARGS__ correctly.
// CHECK: nameSTRINGIFIED_VA_ARGS
// CHECK-NEXT: expansionvariadicCFunction(x, "Additional supply depots required.", "'a'", 10);x = 0;
void stringifyVA_ARGSEmpty(void) {
int x = 1;
STRINGIFIED_VA_ARGS(x, "Additional supply depots required.");
(void)(10 / x); // expected-warning{{Division by zero}}
}
// FIXME: Stringify and escape __VA_ARGS__ correctly.
// CHECK: nameSTRINGIFIED_VA_ARGS
// CHECK-NEXT: expansionvariadicCFunction(x, "Additional supply depots required.", ")";x = 0;
// bz44493: Support GNU-style named variadic arguments in plister
#define BZ44493_GNUVA(i, args...) --(i);
int bz44493(void) {
int a = 2;
BZ44493_GNUVA(a);
BZ44493_GNUVA(a, "arg2");
(void)(10 / a); // expected-warning{{Division by zero}}
return 0;
}
// CHECK: nameBZ44493_GNUVA
// CHECK-NEXT: expansion--(a);