Clang: _Pragma() Macro Issue After Upgrade?

by Alex Johnson 44 views

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(