NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: toolchain/60289 (NetBSD 11 gcc mis-optimises some NULL checks)



The following reply was made to PR toolchain/60289; it has been noted by GNATS.

From: Taylor R Campbell <riastradh%NetBSD.org@localhost>
To: RVP <rvp%SDF.ORG@localhost>
Cc: gnats-bugs%netbsd.org@localhost, toolchain-manager%netbsd.org@localhost,
	gnats-admin%netbsd.org@localhost,
	netbsd-bugs%netbsd.org@localhost, Manuel.Bouyer%lip6.fr@localhost
Subject: Re: toolchain/60289 (NetBSD 11 gcc mis-optimises some NULL checks)
Date: Sun, 24 May 2026 14:43:31 +0000

 > Date: Sun, 24 May 2026 09:10:03 +0000 (UTC)
 > From: RVP <rvp%SDF.ORG@localhost>
 >=20
 > On Sun, 24 May 2026, Manuel Bouyer wrote:
 >=20
 > > A quick search showed other projects (bind, openjdk) running into this
 >=20
 > This is an old issue, and the gcc devs' response to all the howling on the
 > Linux side was "Will not fix".
 >=20
 > https://lwn.net/Articles/342330/
 > https://lwn.net/Articles/342420/
 
 The case in https://lwn.net/Articles/342330/ is different for two
 reasons:
 
 1. The Linux code quoted there actually dereferences the null pointer
    _before_ the check, rather than simply computing an address
    indirection.
 
 	struct sock *sk =3D tun->sk;
 	unsigned int mask =3D 0;
 
 	if (!tun)
 	    return POLLERR;
 
    Actually dereferencing a null pointer is unambiguously UB without
    any involvement of language lawyers.  C99, Sec. 6.5.3.2 `Address
    and indirection operators', clause 4:
 
 	If an invalid value has been assigned to the pointer, the
 	behavior of the unary * operator is undefined.^87)
 
 	87) ... Among the invalid values for dereferencing a pointer by
 	    the unary * operator are a null pointer, ...
 
    In contrast, the code bouyer@ was discussing involves _address of_
    a member operator.  It would be as if the code were instead:
 
 	struct sock **sk =3D &tun->sk;
 	unsigned int mask =3D 0;
 
 	if (!tun)
 	    return POLLERR;
 
    Whether this is UB takes more language-lawyering to argue (and,
    e.g., if it is UB, that breaks the traditional definition of
    offsetof(T, F) as (size_t)((char *)&((T *)0)->F - (char *)(T *)0)).
 
 2. We are using -fno-delete-null-pointer-checks here.  GCC is supposed
    to assume _even if_ the pointer is dereferenced, downstream logic
    _still_ cannot assume the pointer is nonnull:
 
 	-fdelete-null-pointer-checks
 
 		Assume that programs cannot safely dereference null
 		pointers, and that no code or data element resides at
 		address zero.  This option enables simple constant
 		folding optimizations at all optimization levels.  In
 		addition, other optimization passes in GCC use this
 		flag to control global dataflow analyses that
 		eliminate useless checks for null pointers; these
 		assume that a memory access to address zero always
 		results in a trap, so that if a pointer is checked
 		after it has already been dereferenced, it cannot be
 		null.
 
 		Note however that in some environments this assumption
 		is not true.  Use -fno-delete-null-pointer-checks to
 		disable this optimization for programs that depend on
 		that behavior.
 
 	https://gcc.gnu.org/onlinedocs/gcc-14.3.0/gcc/Optimize-Options.html#index-=
 fdelete-null-pointer-checks
 
    If dereferencing the pointer as barp->foo weren't enough to justify
    pruning downstream null checks, surely taking the address
    &barp->foo shouldn't justify pruning downstream null checks.
 



Home | Main Index | Thread Index | Old Index