[tz] [PROPOSED 1/7] Avoid undefined behavior if no Link lines

Paul Eggert eggert at cs.ucla.edu
Wed Oct 26 17:23:44 UTC 2022


On 2022-10-26 07:09, Steve Summit via tz wrote:
>> The core dump occurred because GCC translates this:
>>     qsort(links, nlinks, sizeof *links, qsort_linkcmp);
>> as if it were this:
>>
>>     if (nlinks == 0)
>>       __builtin_trap();
>>     qsort(links, nlinks, sizeof *links, qsort_linkcmp);
> This is an astonishing result.  It's hard to imagine it's
> worthwhile for gcc to perform this "optimization".

Oh, sorry, it's astonishing because I misread the assembly language that 
GCC generates. The actual translation looks at links, not nlinks. That 
is, it's translated as if it were:

    if (links == NULL)
      __builtin_trap();
    qsort(links, nlinks, sizeof *links, qsort_linkcmp);

Sorry about the confusion.


> Paul, which version of gcc is this, and what is
> $(GCC_DEBUG_FLAGS) expanding to?

It's GCC 12.2.1 20220819 (Red Hat 12.2.1-2). $(GCC_DEBUG_FLAGS) is the 
default in the Makefile, which expands to the long string at the end of 
this email. The key option here is -fsanitize=undefined, which causes 
GCC to insert extra checking code to trap in many (but not all) places 
when behavior is undefined; this explains why GCC pessimized the code here.

GCC generates the ud2 instruction because $(GCC_DEBUG_FLAGS) also 
contains -fsanitize-undefined-trap-on-error. I use this option, despite 
the hassle it causes when debugging, because it means I don't have to 
worry about libubsan which can be a configuration problem.

I use these debugging options partly to check tzcode's portability. 
Although qsort(NULL, 0, ...) works fine on most practical platforms, as 
Clive noted there is (or was) an oddball platform or two that is (or 
was) verrrry picky about pointers, and for a program like zic where 
portability is more important than performance, it's nice if zic runs 
even on oddballs. Clive, do you happen to know what these platforms are 
(or were), and whether they're still supported?

As a practical matter, even though the C standard says that expressions 
like qsort(NULL, 0, ...) and (char *)NULL + 0 have undefined behavior, 
any new platform would be verrrry wise to define them to work the same 
way that most practical platforms do; this is true regardless of what 
the C standard says. And to some extent this means the C standard is not 
at the sweetest spot it could be in, as a contract between implementers 
and programmers. I write and see a lot of code where adding 0 to NULL is 
expected to yield NULL, and nobody thinks twice about it (nor should they).

Here's what $(GCC_DEBUG_FLAGS) expands to in the default build:

   -DTZDIR='"/usr/share/zoneinfo"' -DGCC_LINT -g3 -O3 -fno-common 
-fsanitize=undefined -fsanitize-address-use-after-scope 
-fsanitize-undefined-trap-on-error -fstack-protector -Wall -Wextra 
-Walloc-size-larger-than=100000 -Warray-bounds=2 -Wbad-function-cast 
-Wbidi-chars=any,ucn -Wcast-align=strict -Wdate-time 
-Wdeclaration-after-statement -Wdouble-promotion -Wduplicated-branches 
-Wduplicated-cond -Wformat=2 -Wformat-overflow=2 -Wformat-signedness 
-Wformat-truncation -Winit-self -Wlogical-op -Wmissing-declarations 
-Wmissing-prototypes -Wnested-externs -Wnull-dereference 
-Wold-style-definition -Woverlength-strings -Wpointer-arith -Wshadow 
-Wshift-overflow=2 -Wstrict-overflow -Wstrict-prototypes 
-Wstringop-overflow=4 -Wstringop-truncation -Wsuggest-attribute=cold 
-Wsuggest-attribute=const -Wsuggest-attribute=format 
-Wsuggest-attribute=malloc -Wsuggest-attribute=noreturn 
-Wsuggest-attribute=pure -Wtrampolines -Wundef -Wuninitialized 
-Wunused-macros -Wuse-after-free=3 -Wvariadic-macros -Wvla 
-Wwrite-strings -Wno-address -Wno-format-nonliteral -Wno-sign-compare 
-Wno-type-limits -Wno-unused-parameter


More information about the tz mailing list