"Gary E. Miller" <[email protected]> writes:

> On Sun, 01 Nov 2026 07:54:46 -0500
> Greg Troxel <[email protected]> wrote:
>
>> So if a is of type "char", and on that platform/compiler char is
>> signed (quite common), then if a holds for example é (value 233
>> decimal, 0xe9), then when it is promoted to int, the results is -105.
>>   That is not "representable as an unsigned char" and it is not EOF.
>> Therefore the behavior is undefined, and code that leads to UB is
>> incorrect.
>
> I had checked the musl, and other, source code and found not issues
> with passing any int to isXXXXX().

It's perfectly ok for musl to choose some helpful behavior when the
standard says it is UB.  But code that ends up UB is still wrong.

>  But the GNU libc code does
> in fact break:
>
> /usr/include/ctype.h:
>
>    These point into arrays of 384, so they can be indexed by any
> `unsigned char' value [0,255]; by EOF (-1); or by any `signed char'
> value [-128,-1).  ISO C requires that the ctype functions work for
> `unsigned char' values and for EOF; we also support negative `signed
> char' values for broken old programs. 

Interesting.  Not only code written to have "what the programmer
expected" as the outcome, but a comment that says using signed char
that's negative is "broken".

> So in their mind passing a signed char to isXXXX() is also bad, nut
> tolerated.  Ugh.  The GNU libc man page should go beyond saying
> "undefined" and mention OOB read.

I think in GNU libc, if I follow above, it's not OOB; it's probably "c +
128" as the array index.

> That makes current gpsd code like this tolerated by GNU libc, but wrong:
>
> ./libgps/gpsutils.c:    while (isspace((int)*p)) {
>
> I'll do an audit of this.  I wonder why coveriry never mantioned
> this?  Is there a gcc flag to error on this?

That sounds good.  Indeed, all casts should be to unsigned char.

I have long found it funny that the only related warning I've ever heard
is due to gcc and the NetBSD ctype implementation.  It's a macro (as is
allowed):

  #define isspace(c)      ((int)((_ctype_tab_ + 1)[(c)] & _CTYPE_S))

which is very efficient when the input is EOF or 0..255.  Well, it's
efficient regardless, reading random memory.  But it takes advantage of
the UB leeway in the spec to avoid extra operations.

It looks like glibc tests the argument's range, but it's hard to follow
all the conditionals.


I think it's unfortunate glibc is accomodating.  It's the gorilla, and I
think it would be better for the world to abort on bad values, where
they would get fixed.  NetBSD I think tried to do that but that just led
to massive pain as code debugged on linux often has this problem    And
accomodating is the GNU way; it's similar to bash accepting 'test =='
which is wrong, but people sometimes type it.   By accepting it instead
of giving a helpful error, non-portable (buggy) scripts enter the world
and then fail when encountering a non-bash POSIX-conforming sh.

Reply via email to