https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106477
Bug ID: 106477
Summary: With -fno-exception operator new(nothrow) aborts
instead of returning null
Product: gcc
Version: 11.2.1
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: libstdc++
Assignee: unassigned at gcc dot gnu.org
Reporter: matthijs at stdin dot nl
Target Milestone: ---
The nothrow version of operator new is intended to return null on allocation
failure. However, when libstdc++ is compiled with -fno-exceptions, it aborts
instead.
The cause of this failure is that the nothrow operators work by calling the
regular operators, catching any allocation failure exception and turning that
into a null return. However, with -fno-exceptions, the regular operator aborts
instead of throwing, so the nothrow operator never gets a chance to return
null.
Originally, this *did* work as expected, because the nothrow operators would
just call malloc directly. However, as reported in
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68210 this violates the C++11
requirement that the nothrow versions must call the regular versions (so
applications can replace the regular version and get the nothrow for free), so
this was changed in
https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=b66e5a95c0065fda3569a1bfd3766963a848a00d
Note this comment by Jonathan Wakely in the linked report, which actually
already warns against introducing the behavior I am describing (but the comment
was apparently not considered when applying the fix):
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68210#c2
In any case, we have two conflicting requirements:
1. nothrow operators should return null on failure
2. nothrow operators should call regular operators
I can see no way to satisfy both. Since -fno-exceptions is already violating
the spec, it would make sense to me to, when -fno-exceptions is specified, only
satisfy 1 and allow 2 to be violated (which is more of a fringe case anyway,
and applications can always replace the nothrow versions too to get the
behavior they need).
Essentially this would mean that with -fno-exceptions, the nothrow versions
would have to call malloc again directly like before (either duplicating code
like before, or maybe introducing a null-returning helper function?).
To reproduce, I made a small testcase. I was originally seeing this in the
Arduino environment on an Atmel samd chip, but I made a self-contained testcase
and tested that using gcc from https://developer.arm.com (using the linker
script from Atmel/Arduino), which is compiled with -fno-exceptions.
The main testcase is simple: An _sbrk() implementation that always fails to
force allocation failure (overriding the default libnosys implementation that
always succeeds), and a single call to operator new that should return null,
but aborts:
$ cat test.cpp
#include <new>
volatile void* foo;
extern "C"
void *_sbrk(int n) {
// Just always fail allocation
return (void*)-1;
}
int main() {
// This should return nullptr, but actually aborts (with -fno-exceptions)
foo = new (std::nothrow) char[65000];
return 0;
}
In addition, I added a minimal startup.c for memory initialization and reset
vector and a linker script taken verbatim from
https://github.com/arduino/ArduinoCore-samd/raw/master/variants/arduino_zero/linker_scripts/gcc/flash_without_bootloader.ld
(I will attach both files next).
Compiled using:
$ ~/Downloads/gcc-arm-11.2-2022.02-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc
-mcpu=cortex-m0plus -mthumb -g -fno-exceptions --specs=nosys.specs
--specs=nano.specs -Tflash_without_bootloader.ld -nostartfiles test.cpp
startup.c -lstdc++
Running this on the Arduino zero (using openocd and gdb to upload the code
through the EDBG port) shows it aborts:
Program received signal SIGINT, Interrupt.
_exit (rc=rc@entry=1) at
/data/jenkins/workspace/GNU-toolchain/arm-11/src/newlib-cygwin/libgloss/libnosys/_exit.c:16
16
/data/jenkins/workspace/GNU-toolchain/arm-11/src/newlib-cygwin/libgloss/libnosys/_exit.c:
No such file or directory.
(gdb) bt
#0 _exit (rc=rc@entry=1) at
/data/jenkins/workspace/GNU-toolchain/arm-11/src/newlib-cygwin/libgloss/libnosys/_exit.c:16
#1 0x0000013a in abort () at
/data/jenkins/workspace/GNU-toolchain/arm-11/src/newlib-cygwin/newlib/libc/stdlib/abort.c:59
#2 0x00000128 in operator new (sz=65000) at
/data/jenkins/workspace/GNU-toolchain/arm-11/src/gcc/libstdc++-v3/libsupc++/new_op.cc:54
#3 0x00000106 in operator new[] (sz=<optimized out>) at
/data/jenkins/workspace/GNU-toolchain/arm-11/src/gcc/libstdc++-v3/libsupc++/new_opv.cc:32
#4 0x000000fe in operator new[] (sz=<optimized out>) at
/data/jenkins/workspace/GNU-toolchain/arm-11/src/gcc/libstdc++-v3/libsupc++/new_opvnt.cc:38
#5 0x00000034 in main () at test.cpp:17