Clang: _Pragma() Macro Issue After Upgrade?
Have you encountered a perplexing issue with Clang after an upgrade? Specifically, are your macros behaving unexpectedly, particularly those involving _Pragma()? You're not alone. This article delves into a peculiar problem where _Pragma() within a macro seems to interfere with the macro's return type, leading to compilation errors. We'll explore a minimal example that demonstrates this issue, discuss the potential causes, and highlight a workaround.
The Curious Case of _Pragma() and Clang
The _Pragma() directive in C and C++ is a powerful tool that allows you to insert compiler-specific directives into your code. It's often used to control warnings, pragmas, and other compiler behaviors within a specific scope. However, a recent upgrade to Clang-22 seems to have introduced an anomaly where the use of _Pragma() within a macro can disrupt the macro's return type, resulting in compilation errors that didn't exist in previous versions. This issue has been observed across a range of Clang versions (13-21) and GCC versions (10-15), indicating a potential regression in Clang-22's handling of _Pragma() within macros.
Decoding the Problem: A Minimal Example
To illustrate this issue, consider the following minimal example written in C:
#include <stdio.h>
#include <stdint.h>
size_t
_cexds__arr_len(const void* arr)
{
// This function is a mock for returning length of dynamic arrays
return (arr) ? 6969 : 0;
}
# define arr$len(arr) \
({ \
_Pragma("GCC diagnostic push"); \
/* NOTE: temporary disable syntax error to support both static array length and \
* arr$(T) */ \
_Pragma("GCC diagnostic ignored \"-Wsizeof-pointer-div\""); \
/* NOLINTBEGIN */ \
__builtin_types_compatible_p( \
typeof(arr), \
typeof(&(arr)[0]) \
) /* check if array or ptr */ \
? _cexds__arr_len(arr) /* some pointer or arr$ */ \
: ( \
sizeof(arr) / sizeof((arr)[0]) /* static array[] */ \
); \
/* NOLINTEND */ \
_Pragma("GCC diagnostic pop"); \
})
int main(int argc, char** argv) {
char* a[] = {"a", "b"};
printf("a is static, arr$len=%zu\n", arr$len(a));
char* a1 = (void*)0xbadcaffe;
printf("a is kinda dynamic, arr$len=%zu\n", arr$len(a1));
return 0;
}
This code defines a macro arr$len(arr) that attempts to determine the length of an array, whether it's a static array or a dynamically allocated one. The macro uses _Pragma() directives to temporarily disable a specific GCC diagnostic warning (-Wsizeof-pointer-div) and then restore the previous diagnostic settings. This is a common practice to suppress warnings that might be irrelevant in the context of the macro's logic.
Now, let's analyze what's happening within the arr$len macro. The core logic revolves around determining whether the input arr is a static array or a pointer. It achieves this using the __builtin_types_compatible_p built-in function, which checks if two types are compatible. If arr is a static array, the macro calculates the length using sizeof(arr) / sizeof((arr)[0]). If it's a pointer (or a special arr$ type, which is not elaborated in this example but hinted at in the comments), it calls the _cexds__arr_len function, a mock function that returns a predefined length (6969 in this case).
The _Pragma directives are used to manage compiler warnings. `_Pragma(