forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			472 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			472 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
| // RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core -verify %s
 | |
| //
 | |
| // 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: <key>name</key><string>SET_PTR_VAR_TO_NULL</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>ptr = 0</string>
 | |
| 
 | |
| #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: <key>name</key><string>SET_PTR_VAR_TO_NULL_WITH_NESTED_MACRO</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>ptr =0</string>
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // 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: <key>name</key><string>TO_NULL</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>setToNull(&ptr)</string>
 | |
| 
 | |
| #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: <key>name</key><string>TO_NULL</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>setToNull(&a)</string>
 | |
| 
 | |
| // CHECK: <key>name</key><string>DEREF</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>{ int b; b = 5; } print(a); *a</string>
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // 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: <key>name</key><string>WILL_UNDEF_SET_NULL_TO_PTR</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>ptr = nullptr;</string>
 | |
| 
 | |
| #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: <key>name</key><string>WILL_REDIFINE_MULTIPLE_TIMES_SET_TO_NULL</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>ptr = nullptr;</string>
 | |
| 
 | |
| #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: <key>name</key><string>PASS_PTR_TO_MACRO_THAT_WILL_BE_UNDEFD</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>ptr = nullptr;</string>
 | |
| 
 | |
| #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: <key>name</key><string>TO_NULL_AND_PRINT</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>a = 0; print( "Will this , cause a crash?")</string>
 | |
| 
 | |
| void macroArgContainsLParenInStringTest() {
 | |
|   int *a;
 | |
|   TO_NULL_AND_PRINT(a, "Will this ( cause a crash?");
 | |
|   *a = 5; // expected-warning{{Dereference of null pointer}}
 | |
| }
 | |
| 
 | |
| // CHECK: <key>name</key><string>TO_NULL_AND_PRINT</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>a = 0; print( "Will this ( cause a crash?")</string>
 | |
| 
 | |
| void macroArgContainsRParenInStringTest() {
 | |
|   int *a;
 | |
|   TO_NULL_AND_PRINT(a, "Will this ) cause a crash?");
 | |
|   *a = 5; // expected-warning{{Dereference of null pointer}}
 | |
| }
 | |
| 
 | |
| // CHECK: <key>name</key><string>TO_NULL_AND_PRINT</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>a = 0; print( "Will this ) cause a crash?")</string>
 | |
| 
 | |
| #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: <key>name</key><string>CALL_FUNCTION</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>setToNull(&a)</string>
 | |
| 
 | |
| 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: <key>name</key><string>CALL_FUNCTION</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>setToNullAndPrint(&a, "Hello!")</string>
 | |
| 
 | |
| #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: <key>name</key><string>CALL_FUNCTION_WITH_TWO_PARAMS</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>setToNullAndPrint( &a, "Hello!")</string>
 | |
| 
 | |
| #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: <key>name</key><string>CALL_LAMBDA</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>([&ptr, str] () mutable { setToNull(&ptr); })()</string>
 | |
| 
 | |
| #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: <key>name</key><string>PASTE_CODE</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>{ int *ptr = nullptr; *ptr = 5; }</string>
 | |
| 
 | |
| // 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: <key>name</key><string>POTENTIALLY_EMPTY_PARAM</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>;ptr = nullptr</string>
 | |
| 
 | |
| #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: <key>name</key><string>NESTED_EMPTY_PARAM</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>; ptr = nullptr;</string>
 | |
| 
 | |
| #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: <key>name</key><string>CALL_FUNCTION_WITH_ONE_PARAM_THROUGH_MACRO</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>setToNull( &ptr)</string>
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // Tests for variadic macro arguments.
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| template <typename ...Args>
 | |
| 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: <key>name</key><string>VARIADIC_SET_TO_NULL</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>ptr = nullptr; variadicFunc( 1, 5, "haha!")</string>
 | |
| 
 | |
| 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: <key>name</key><string>VARIADIC_SET_TO_NULL</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>ptr = nullptr; variadicFunc()</string>
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // 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: <key>name</key><string>DECLARE_FUNC_AND_SET_TO_NULL</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>void generated_whatever(); ptr = nullptr;</string>
 | |
| 
 | |
| void macroArgContainsHashHashInStringTest() {
 | |
|   int *a;
 | |
|   TO_NULL_AND_PRINT(a, "Will this ## cause a crash?");
 | |
|   *a = 5; // expected-warning{{Dereference of null pointer}}
 | |
| }
 | |
| 
 | |
| // CHECK: <key>name</key><string>TO_NULL_AND_PRINT</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>a = 0; print( "Will this ## cause a crash?")</string>
 | |
| 
 | |
| #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: <key>name</key><string>PRINT_STR</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>print("Hello"); ptr = nullptr</string>
 | |
| 
 | |
| void macroArgContainsHashInStringTest() {
 | |
|   int *a;
 | |
|   TO_NULL_AND_PRINT(a, "Will this # cause a crash?");
 | |
|   *a = 5; // expected-warning{{Dereference of null pointer}}
 | |
| }
 | |
| 
 | |
| // CHECK: <key>name</key><string>TO_NULL_AND_PRINT</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>a = 0; print( "Will this # cause a crash?")</string>
 | |
| 
 | |
| //===----------------------------------------------------------------------===//
 | |
| // 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: <key>name</key><string>EUCLIDEAN_ALGORITHM</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>if (A<0 ){A=-A;} if ( B<0 ){ B=- B;}return B / ( B - B);</string>
 | |
| 
 | |
| #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: <key>name</key><string>YET_ANOTHER_SET_TO_NULL</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>print((void *)5); print((void *)"Remember the Vasa"); ptr = nullptr;</string>
 | |
| 
 | |
| 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}}
 | |
| }
 | |
| 
 | |
| #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: <key>name</key><string>APPLY_ZERO1</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>int foo() { return x; }(0)</string>
 | |
| 
 | |
| #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: <key>name</key><string>APPLY_ZERO2</string>
 | |
| // CHECK-NEXT: <key>expansion</key><string>int bar() { return 0; }</string>
 |