Hi Ben! On Sun, Jul 20, 2025 at 12:48:07PM -0400, Ben Kallus wrote: > The C standard specifies that it's undefined behavior to dereference > NULL (even if you use & right after). The hand-rolled offsetof idiom > &(((s*)NULL)->f) is thus technically undefined.
I'm having a strong doubt about the second part of that sentence, do you have a link ? For me this is not a dereference, there's no memory access nor no following of a pointer. It's how everyone defines offsetof() in a portable way (i.e. without __builtin_offsetof()), even gcc uses it, e.g: https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=include/objalloc.h;h=231485a8;hb=HEAD#l61 There are even still ~200 of them in the linux kernel that I can find with a quick grep. That's why I'm asking for the text reference in the spec that considers &((ptr)->member) as a dereference, as I wouldn't be surprised if it was an excessive check in the sanitizer. Note that I'm not against modifying the code but touching such low levels also have impacts on included libs and/or older compilers support, so each time we need to be extremely careful, and more importantly it's so easy to reintroduce such idioms that we need the exact reference to the claimed problem if we want to be certain to avoid them in the future. I've checked https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf Annex J.2 (UB) for anything like this and can't find anything mentioning the address calculation of a member based on nullptr. All of them mention accesses, like this one: (148) Arbitrarily copying or changing the bytes of or copying from a non-null pointer into a nullptr_t object and then reading that object > This clutters the > output of UBSan and is simple to fix: just use the real offsetof when > it's available. > > This patch also changes two instances of pointer arithmetic on void * > to use char * instead, again to avoid UB. With this one I agree. > After this patch, HAProxy can run without crashing after building w/ > clang-19 -fsanitize=undefined -fno-sanitize=function,alignment One strange thing here is that your patch makes use of: typeof(*(t)NULL) which according to your own definition above should be seen as a NULL deref by the sanitizer. Are you sure that the sanitizer didn't just complain for the void* calculation ? Thanks, Willy