[llvm] [libc++] Fix the behavior of throwing `operator new` under -fno-exceptions (PR #69498)

Louis Dionne via llvm-commits llvm-commits at lists.llvm.org
Tue Oct 24 12:58:59 PDT 2023


ldionne wrote:

> I'm tempted to just do the non-conforming thing here (because `-fno-exceptions` is non-conforming), and just assume that when you're building with `-fno-exceptions`, you're not trying to replace the throwing operator new, and you'll be fine if we have the throwing kind call the non-throwing kind.

So IIUC you are suggesting the following behavior under `-fno-exceptions`?

```
operator new(size_t) // We assume it is never overridden, we call new(size_t, nothrow_t) and we abort if that fails
operator new(size_t, nothrow_t) // Users may override it. Default implementation tries to allocate memory using malloc
```

Under `-fexceptions`, the behavior would stay as it is right now, i.e.

```
operator new(size_t) // Can be overridden, by default tries to allocate with malloc and throws if that fails
operator new(size_t, nothrow_t) // Users may override, by default it uses new(size_t) and uses try-catch to return nullptr on failure
```

Is that your suggestion? The problem I see with this is that we end up with inverted dependencies between `new(size_t)` and `new(size_t, nothrow_t)` depending on whether exceptions are enabled. With `-fexceptions`, we get the usual dependency `new(size_t, nothrow_t) calls new(size_t)`, but with `-fno-exceptions` we have `new(size_t) calls new(size_t, nothrow_t)`. IMO this change in semantics is an issue, especially considering that users can use a different exception mode than what the dylib is built with. That seems like a recipe for confusion.

Another issue is that concretely, 99% of people replace `new(size_t)` and probably never bother to replace (or even use) `new(size_t, nothrow_t)`. So I feel like expecting users to override `new(size_t, nothrow_t)` instead of `new(size_t)` under `-fno-exceptions` is probably unrealistic.

The currently-proposed approach kinda sucks too (because we don't know how to implement `new(size_t, nothrow_t)` if you replace `new(size_t)`), but at least it's consistent in every other regard (AFAICT) and it is as Standards conforming as possible.

If we could design this from scratch, what would we do? I think we would likely specify that `new(size_t)` is implemented as calling `new(size_t, nothrow_t)` and doing `if (result == nullptr) throw std::bad_alloc();`. Then the common way to override `new` amongst users would be to override `new(size_t, nothrow_t)` instead of `new(size_t)` and they would immediately get the right behavior. Under `-fno-exceptions`, we would simply `abort()` inside `new(size_t)` in case the `nothrow_t` version returned `nullptr`. Do we think there is even a slim chance of ever changing that in the Standard?

https://github.com/llvm/llvm-project/pull/69498


More information about the llvm-commits mailing list