Suppose I am maintaining a library function that takes two arguments, both pointers. The second argument exists only for backward compatibility; callers should always pass a NULL. I would like to put something into my header file that makes the compiler issue warnings if the second argument is not a compile-time constant NULL. I thought I would be able to do this using GCC's __builtin_constant_p and __attribute__((warning)) extensions:
extern void thefun_called_with_nonnull_arg (void)
__attribute__((__warning__(
"'thefun' called with second argument not NULL")));
extern int real_thefun (void *, void *);
static inline int
thefun (void *a, void *b)
{
if (!__builtin_constant_p(b) || b != 0)
thefun_called_with_nonnull_arg();
return real_thefun(a, b);
}
int warning_expected (void *a, void *b)
{
return thefun(a, b);
}
int warning_not_expected (void *a)
{
return thefun(a, 0);
}
But this doesn't work with any version of GCC I have tested. I get warnings for both calls to thefun. (Compiler Explorer demo.)
Can anyone suggest an alternative construct that will produce a warning for warning_expected, and not for warning_not_expected ?
Notes:
b is an int.attribute((warning)) and I haven't had any luck finding an alternative.)b is an int and thefun is marked always-inline.)thefun as a macro would be preferable to one that does.-Werror or equivalent is active.EDIT: Based on Kamil Cuk's discovery that the unwanted warning can be suppressed by casting the pointer to an integer of a different size, I have determined that this is an oversight in the implementation of __builtin_constant_p and filed GCC bug report #91554. I'd still be interested in answers that provide ways to do this with clang, icc, or any other compiler that's commonly used together with GNU libc.
I finally managed to get it to work:
if (!__builtin_constant_p((int)(uintptr_t)b) || b != 0) {
With this you get only one warning.
It seems that gcc can't do __builtin_constant_p on a pointer type. The __builtin_constant_p(b) always returns 0, so the warn function is always linked. Casting b to int strangely works. Although it looses precision in the pointer value, we don't care about it, cause we only check if it's a constant.
There is no way to do what you describe without GNU extensions.
This portable approach gives a hard error (because _Static_assert requires a constant expression):
#define thefun(a, b) \
({ \
_Static_assert(b == 0, \
"'thefun' called with second argument not NULL"); \
real_thefun(a, b); \
})
However, there is one fortified-style approach that works on both GCC and Clang:
extern void thefun_called_with_nonnull_arg (void)
__attribute__((__deprecated__(
"'thefun' called with second argument not NULL")));
extern int real_thefun (void *, void *);
static inline int
thefun (void *a, void *b)
{
if (!__builtin_constant_p((unsigned short)(unsigned long)b) || b != 0)
thefun_called_with_nonnull_arg();
return real_thefun(a, b);
}
int warning_expected (void *a, void *b)
{
return thefun(a, b);
}
int warning_not_expected (void *a)
{
return thefun(a, 0);
}
Tested with GCC 8.3.0 and Clang 8.0.0.
See GCC bug report #91554 for more information about the need for the casts.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With