BAssert.h: use __builtin_trap for BCRASH on clang-cl - #316
Conversation
clang-cl defines __clang__ but not __GNUC__, so the existing defined(__GNUC__) gate sent it into the fallback BCRASH() that calls ((void(*)())0)(). Clang treats a call through a null function pointer as unconditional UB and uses it to prove the containing branch unreachable, so at -O2 every RELEASE_BASSERT(x) in bmalloc compiled away entirely on Windows clang-cl. The most visible effect was in the USE_MIMALLOC path of bmalloc::api::malloc, where the RELEASE_BASSERT(memory) guarding the mi_malloc result was deleted and WTF::fastMalloc/fastCompactMalloc compiled to a bare `jmp mi_malloc`. On OOM mi_malloc returned nullptr and callers placement-newed into 0x0 (BUN-2Z94: std::_Atomic_storage ctor at address 0x0 inside StringImpl::createUninitializedInternalNonEmpty via JSON.parse). Extend the __builtin_trap gate to defined(__GNUC__) || defined(__clang__) so clang-cl takes the well-defined trap path. Verified: with this change clang-cl -O2 keeps the null check and emits `test rax,rax; je ...; mov [0xbbadbeef],0; ud2` for fastCompactMalloc.
|
Warning Review limit reached
Next review available in: 4 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
Comment |
There was a problem hiding this comment.
LGTM — one-token preprocessor guard widening, well-diagnosed and verified.
What was reviewed:
- Confirmed the only functional change is
defined(__GNUC__)→defined(__GNUC__) || defined(__clang__); GCC and non-Windows clang already define__GNUC__, so behavior changes only for clang-cl. - Checked that
__builtin_trap()andBIGNORE_CLANG_STATIC_ANALYZER_WARNINGS_ATTRIBUTE(→[[clang::suppress]]) are both valid under clang-cl. - The
#elsefallback for real MSVC is untouched;#endifcomment updated to match.
Extended reasoning...
Overview
Single-file change to Source/bmalloc/bmalloc/BAssert.h. The functional diff is one token: the BCRASH() definition guard changes from #if defined(__GNUC__) to #if defined(__GNUC__) || defined(__clang__), plus an explanatory comment and a matching #endif comment update. This routes clang-cl (which defines __clang__ but not __GNUC__) into the __builtin_trap() branch instead of the ((void(*)())0)() fallback, which clang was treating as pure UB and using to delete the surrounding RELEASE_BASSERT checks at -O2.
Security risks
None introduced. The change strictly restores intended crash-on-assert behavior on Windows/clang-cl; it cannot weaken any check on other platforms since GCC and clang (non-MSVC-driver) already define __GNUC__ and were taking this branch. If anything it improves safety by reinstating ~60 elided RELEASE_BASSERT sites in bmalloc on Windows.
Level of scrutiny
Low. This is a mechanical compiler-detection fix in a preprocessor conditional. The macro body being selected (*(int*)0xbbadbeef = 0; __builtin_trap();) is identical to what every other clang build already uses. __builtin_trap() is supported by clang-cl, and BIGNORE_CLANG_STATIC_ANALYZER_WARNINGS_ATTRIBUTE expands to [[clang::suppress]] under clang, which clang-cl also accepts. The PR description includes disassembly before/after and a runtime repro confirming the fix.
Other factors
No prior reviewer comments to address (only a CodeRabbit rate-limit notice). The bug hunting system found no issues. The change is self-contained, has clear intent tied to a Sentry issue, and follows the same __GNUC__ || __clang__ pattern used elsewhere for clang-cl compatibility.
Preview Builds
|
|
Another instance of this, from bun 1.4.0 (WebKit 0f966e8) on Windows x64: Sentry BUN-4RGJ. Disassembly of the shipped #512 makes |
Problem
On Windows,
WTF::fastMallocandWTF::fastCompactMalloccompile to a barejmp mi_mallocwith no null check, so on OOM they returnnullptr(violating theirRETURNS_NONNULLcontract) and callers crash downstream with a confusing null dereference. The most frequent symptom is BUN-2Z94:std::_Atomic_storage<unsigned int,4>segfault at0x0insideStringImpl::createUninitializedInternalNonEmpty, reached fromJSON.parse.Disassembly of
FastMalloc.cpp.objfrom the current Windows prebuilt (autobuild-c9296e353e):The
RELEASE_BASSERT(memory)that is supposed to crash on null is gone.Cause
BCRASH()is gated ondefined(__GNUC__)(BAssert.h:63). clang-cl defines__clang__and_MSC_VERbut not__GNUC__, so it falls into the#else:Calling a null function pointer is UB. clang uses it to prove the containing branch unreachable, which backward-propagates to delete the
if (!memory)check entirely. Minimal reproducer:This affects every
RELEASE_BASSERT/BASSERTin bmalloc on Windows (about 60 call sites), not just the malloc wrappers.Fix
Extend the gate to
defined(__GNUC__) || defined(__clang__)so clang-cl takes the__builtin_trap()path. The#elsefallback is retained for real MSVC (cl.exe), which does not perform this UB-based elimination.Verification
With this patch,
clang-cl --target=x86_64-pc-windows-msvc /O2 /DNDEBUGcompilesfastCompactMallocas:Null check preserved, crash site is the allocator instead of an arbitrary downstream write.
Runtime repro (Bun canary, Windows x64 baseline): loop
JSON.parseover 1MB string values until commit limit is hit. Before:Segmentation fault at address 0x0with a JSONAtomStringCache stack. After: clean crash at0xBBADBEEFinsidefastCompactMalloc.