Skip to content

Comments

Add nothrow operator new/delete to src/coreclr/jit/alloc.cpp#124715

Open
gwr wants to merge 1 commit intodotnet:mainfrom
gwr:new-nothrow
Open

Add nothrow operator new/delete to src/coreclr/jit/alloc.cpp#124715
gwr wants to merge 1 commit intodotnet:mainfrom
gwr:new-nothrow

Conversation

@gwr
Copy link
Contributor

@gwr gwr commented Feb 22, 2026

The JIT's standalone build overrides the throwing operator new but not the nothrow variants. When libstdc++'s nothrow implementation calls the throwing version internally, it gets the JIT's assert-enabled override instead of the standard library's throwing version.

Solution needed: Add nothrow operator new/delete overrides to src/coreclr/jit/alloc.cpp to prevent libstdc++ from calling through to the asserting throwing version.

Without these additions, dotnet crashes with an assertion.

Copilot AI review requested due to automatic review settings February 22, 2026 01:12
@github-actions github-actions bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Feb 22, 2026
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Feb 22, 2026
@gwr
Copy link
Contributor Author

gwr commented Feb 22, 2026

Here's the analysis from Copilot helping track down this problem.
Problem-new-nothrow.txt

Call chain causing the crash:

  1. AllocTHREAD() calls new(std::nothrow) CPalThread()
  2. Links to libstdc++'s operator new(size_t, std::nothrow_t const&)
  3. libstdc++'s nothrow version internally calls operator new(size_t) (the
    throwing version)
  4. Symbol resolution picks JIT's overridden operator new(size_t) from
    alloc.cpp
  5. Assertion fires: "Global new called; use HostAllocator..."

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds nothrow operator new/delete overrides to the JIT's standalone build to prevent libstdc++'s nothrow implementation from calling through to the JIT's assert-enabled throwing operator new. The issue arises because the JIT overrides the throwing operator new to add assertions, but libstdc++'s nothrow variants internally delegate to the throwing version, triggering unwanted assertions.

Changes:

  • Added nothrow variants of operator new and operator new[] that directly call malloc without assertions
  • Added temporary #if 1 preprocessor directive with XXX comment indicating the code needs proper conditional guards

Comment on lines +371 to +389
void* __cdecl operator new(std::size_t size, const std::nothrow_t&) noexcept
{
if (size == 0)
{
size++;
}

return malloc(size);
}

void* __cdecl operator new[](std::size_t size, const std::nothrow_t&) noexcept
{
if (size == 0)
{
size++;
}

return malloc(size);
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nothrow operator new implementations are missing corresponding nothrow operator delete overloads. According to C++ standard, when providing placement new operators (including nothrow variants), matching placement delete operators should be provided to handle cleanup if construction throws. Add void operator delete(void* ptr, const std::nothrow_t&) noexcept and void operator delete[](void* ptr, const std::nothrow_t&) noexcept that call free(ptr) to match the pattern of the regular delete operators.

Copilot uses AI. Check for mistakes.
The JIT's standalone build overrides the throwing operator new but not the
nothrow variants. When libstdc++'s nothrow implementation calls the throwing
version internally, it gets the JIT's assert-enabled override instead of the
standard library's throwing version.

Solution needed: Add nothrow operator new/delete overrides to
src/coreclr/jit/alloc.cpp to prevent libstdc++ from calling through to the
asserting throwing version.
@jakobbotsch
Copy link
Member

When libstdc++'s nothrow implementation calls the throwing version internally, it gets the JIT's assert-enabled override instead of the standard library's throwing version.

How do we end up calling these from the JIT? We should not be allocating heap memory in the JIT, so it sounds like a bug and the same assert should be added in these new versions.

@gwr
Copy link
Contributor Author

gwr commented Feb 22, 2026

When libstdc++'s nothrow implementation calls the throwing version internally, it gets the JIT's assert-enabled override instead of the standard library's throwing version.

How do we end up calling these from the JIT? We should not be allocating heap memory in the JIT, so it sounds like a bug and the same assert should be added in these new versions.

It's a bit complicated. See the doc I attached in #124715 (comment)

@jkotas
Copy link
Member

jkotas commented Feb 22, 2026

It's a bit complicated. See the doc I attached in #124715 (comment)

Makes sense, overwriting global operator new always comes with issues like that. The change needs a better comment than just "Also need to override the "nothrow" variants".

Alternative fixes that I can think of:

  • Ifdef out the local implementation of operator new for SunOS. It is debug-only diagnostics, not strictly required.
    or
  • Get rid of the JIT dependency on Win32 PAL. I am not sure how many dependencies are still there.

@jakobbotsch
Copy link
Member

  • Ifdef out the local implementation of operator new for SunOS. It is debug-only diagnostics, not strictly required.

Yes, maybe that is best.
Do the same kinds of issues arise on Windows? I am not sure I understand how the symbol resolution here works to make this problem happen in practice and why we only see it on SunOS. But if it can happen, does anything prevent the same thing from happening directly with the throwing variants and on other platforms?
If not perhaps we should entirely disable this diagnostic in all non-Windows builds.

@jakobbotsch
Copy link
Member

Would marking the operators with __attribute__((visibility("hidden"))) help?

@jkotas
Copy link
Member

jkotas commented Feb 22, 2026

I am not sure I understand how the symbol resolution here works to make this problem happen in practice

Blocking of operator new we have in the JIT assumes that no code linked into the JIT .dll/.so depends on operator new working.

This invariant does not hold for combination of Win32 PAL and SunOS C++ runtime library: Win32 PAL depends on non-throwing operator new, SunOS C++ runtime library implements the non-throwing operator new by forwarding to throwing operator new, and all this gets linked into the JIT .dll/.so.

This invariant can be broken by changes in implementation details of any code linked into the JIT. For example, if utilcode (that the JIT depends on currently) was refactored in modern C++ using standard C++ library, we would likely end up with the same problem, even on Windows.

Would marking the operators with attribute((visibility("hidden"))) help?

I doubt it would help. We mark all symbols as visibility hidden, except the explicitly exported ones.

@jakobbotsch
Copy link
Member

Ah, ok, I thought that some other part of the system outside the JIT was somehow getting the JIT's overridden operator new.
Out of curiosity -- how did we end up in AllocTHREAD called by the JIT @gwr?

@gwr
Copy link
Contributor Author

gwr commented Feb 22, 2026

How do we end up calling these from the JIT? We should not be allocating heap memory in the JIT, so it sounds like a bug and the same assert should be added in these new versions.

Here are the gory details from gdb:
Crash-nothrow-gdb.txt

@gwr
Copy link
Contributor Author

gwr commented Feb 22, 2026

I do wonder why this seems to be exposed only on SunOS. I wonder if it's simply because I built everything with configuration=Debug?

In any case, it seems like the fix might be technically correct on all platforms, even though the bug does not appear to be exposed elsewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants