From: Rasmus Villemoes > Sent: 02 February 2022 20:44 > > On 02/02/2022 17.19, David Laight wrote: > > From: Kees Cook > >> Sent: 31 January 2022 20:44 > >> > >> The __is_constexpr() macro is dark magic. Shed some light on it with > >> a comment to explain how and why it works. > >> > > ... > >> diff --git a/include/linux/const.h b/include/linux/const.h > >> index 435ddd72d2c4..7122d6a1f8ce 100644 > >> --- a/include/linux/const.h > >> +++ b/include/linux/const.h > >> @@ -7,6 +7,30 @@ > >> * This returns a constant expression while determining if an argument is > >> * a constant expression, most importantly without evaluating the argument. > >> * Glory to Martin Uecker <Martin.Uecker@xxxxxxxxxxxxxxxxxxxxx> > >> + * > >> + * Details: > >> + * - sizeof() is an integer constant expression, and does not evaluate the > >> + * value of its operand; it only examines the type of its operand. > >> + * - The results of comparing two integer constant expressions is also > >> + * an integer constant expression. > >> + * - The use of literal "8" is to avoid warnings about unaligned pointers; > >> + * these could otherwise just be "1"s. > >> + * - (long)(x) is used to avoid warnings about 64-bit types on 32-bit > >> + * architectures. > >> + * - The C standard defines an "integer constant expression" as different > >> + * from a "null pointer constant" (an integer constant 0 pointer). > >> + * - The conditional operator ("... ? ... : ...") returns the type of the > >> + * operand that isn't a null pointer constant. This behavior is the > >> + * central mechanism of the macro. > >> + * - If (x) is an integer constant expression, then the "* 0l" resolves it > >> + * into a null pointer constant, which forces the conditional operator > >> + * to return the type of the last operand: "(int *)". > >> + * - If (x) is not an integer constant expression, then the type of the > >> + * conditional operator is from the first operand: "(void *)". > >> + * - sizeof(int) == 4 and sizeof(void) == 1. > >> + * - The ultimate comparison to "sizeof(int)" chooses between either: > >> + * sizeof(*((int *) (8)) == sizeof(int) (x was a constant expression) > >> + * sizeof(*((void *)(8)) == sizeof(void) (x was not a constant expression) > >> */ > >> #define __is_constexpr(x) \ > >> (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8))) > > > > This has been making my head hurt all day. > > The above isn't really a true description - ?: doesn't work that way. > > Try the following for size: > > > > - The conditional operator (?:) requires that both expressions have the > > the same type (after numeric promotions). > > No. Please read 6.5.15.3 for the preconditions, and 6.5.15.5 and > 6.5.15.6 for the rules governing the type of the whole expression. > > > The type of the result is a compile time constant and doesn't depend on any > > variables. > > Yes, the type of any expression in C is known at compile time, and is > determined via the rules in the C standard. I wouldn't call it a > "compile time constant" though. > > > - If the expressions have distinct non-NULL pointer types then they are both > > cast to (void *) and the result has type 'void *'. > > Wrong. gah server me right for using godbolt to test it. > > > - A NULL pointer can be made from any integer constant expression that > > evaluates to 0, not just a literal 0. > > - So the type of (0 ? (void *)(x) : (int *)8) is 'int *' if (x) is zero > > (because of the NULL) and (void *) otherwise because the pointer types > > don't match. > > That's basically how this macro works, but "So" is not warranted as it > does not follow from any of the previous, wrong, statements. > > > You can test this by evaluating: > > sizeof *(0 ? (float *)4 : (int *)4) > > That's an ill-formed conditional operator, and gcc says as much even > without any -Wall in effect. > > warning: pointer type mismatch in conditional expression > 8 | return sizeof(*(0 ? (float *)4 : (int *)4)); > > > > This is 1 because of the implicit (void *) cast. > > There is no such thing. Ok let's try again... The compiler needs to find a 'compatible type' either for: (void *)x and (int *)8 or for: (void *)0 and (int *)8 In the former it is 'void *' and the latter 'int *' because the (void *)0 is NULL and thus a valid 'int *' pointer. In any case suggesting that it is based on the value before the ? is bogus. That is probably a reasonable description. David - Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK Registration No: 1397386 (Wales)