diff --git a/scripts/build/deps/webkit.ts b/scripts/build/deps/webkit.ts index e50f02454d23..fbb60710f023 100644 --- a/scripts/build/deps/webkit.ts +++ b/scripts/build/deps/webkit.ts @@ -6,9 +6,11 @@ // oven-sh/WebKit main: macOS + Windows artifacts cross-compiled on Linux, // -lto variants built with ThinLTO (per-module summaries for cross-language // importing), every x64 at the nehalem floor (no separate -baseline variant), -// typed-array constructor ClassInfo kept address-unique under LTO, and the -// Windows ICU data table filtered + per-item zstd compressed. -export const WEBKIT_VERSION = "c9296e353e365ecf0de82f273bb0a88a3df465be"; +// typed-array constructor ClassInfo kept address-unique under LTO, the +// Windows ICU data table filtered + per-item zstd compressed, and Windows +// unwind info (RtlAddGrowableFunctionTable) registered for the fixed JIT +// pool (LLInt pending offlineasm .seh_* emission). +export const WEBKIT_VERSION = "a40d462206e1caf8388062120acde61e37a4ae7d"; /** * WebKit (JavaScriptCore) — the JS engine. diff --git a/src/bun_core/Global.rs b/src/bun_core/Global.rs index 997b459eea0a..0d1ea3aee8bd 100644 --- a/src/bun_core/Global.rs +++ b/src/bun_core/Global.rs @@ -718,8 +718,12 @@ pub fn raise_ignoring_panic_handler_raw(sig: c_int) -> ! { // preconditions, so `safe fn` discharges the link-time proof. unsafe extern "system" { safe fn RemoveVectoredExceptionHandler(Handle: *mut core::ffi::c_void) -> u32; + safe fn SetUnhandledExceptionFilter( + f: Option i32>, + ) -> Option i32>; } let _ = RemoveVectoredExceptionHandler(handle); + let _ = SetUnhandledExceptionFilter(None); } } diff --git a/src/crash_handler/lib.rs b/src/crash_handler/lib.rs index 0b999f57c9ca..2c0470e8508e 100644 --- a/src/crash_handler/lib.rs +++ b/src/crash_handler/lib.rs @@ -1765,6 +1765,10 @@ mod draft { } #[cfg(windows)] { + let range = bun_sys::windows::exe_image_range(); + WINDOWS_EXE_IMAGE_BASE.store(range.start, Ordering::Relaxed); + WINDOWS_EXE_IMAGE_END.store(range.end, Ordering::Relaxed); + // SAFETY: AddVectoredExceptionHandler is a valid Win32 call unsafe { // SAFETY: ABI-identical `extern "system" fn(*mut _) -> i32` — @@ -1781,6 +1785,19 @@ mod draft { // `reset_on_posix`). `HANDLE` is `*mut c_void`; cast is identity. bun_core::WINDOWS_SEGFAULT_HANDLE .store(handle as *mut core::ffi::c_void, Ordering::Relaxed); + + // Backstop for exceptions the VEH passed on: runs only after + // every frame-based (SEH) handler has declined, so an + // exception a foreign module handles itself never reaches it. + // The handler JSC registers for JIT frames + // (ZigGlobalObject.cpp -> setJITExceptionHandlerWin) catches + // the under-JIT case before this. + bun_sys::windows::kernel32::SetUnhandledExceptionFilter(Some( + bun_ptr::cast_fn_ptr::< + extern "system" fn(*mut bun_sys::windows::EXCEPTION_POINTERS) -> c_long, + unsafe extern "system" fn(*mut core::ffi::c_void) -> i32, + >(handle_unhandled_exception_windows), + )); } } #[cfg(any( @@ -2020,6 +2037,11 @@ mod draft { unsafe { bun_sys::windows::kernel32::RemoveVectoredExceptionHandler(handle) }; debug_assert!(rc != 0); } + // SAFETY: no memory-safety preconditions; clears the top-level + // filter back to the OS default. + unsafe { + bun_sys::windows::kernel32::SetUnhandledExceptionFilter(None); + } return; } @@ -2038,41 +2060,73 @@ mod draft { } #[cfg(windows)] - pub(crate) extern "system" fn handle_segfault_windows( - info: *mut bun_sys::windows::EXCEPTION_POINTERS, - ) -> c_long { - // SAFETY: kernel provides a valid EXCEPTION_POINTERS - let info = unsafe { &*info }; - let reason = match unsafe { (*info.ExceptionRecord).ExceptionCode } { + static WINDOWS_EXE_IMAGE_BASE: AtomicUsize = AtomicUsize::new(0); + #[cfg(windows)] + static WINDOWS_EXE_IMAGE_END: AtomicUsize = AtomicUsize::new(0); + + #[cfg(windows)] + fn classify_exception_windows( + record: &bun_sys::windows::EXCEPTION_RECORD, + ) -> Option { + Some(match record.ExceptionCode { bun_sys::windows::EXCEPTION_DATATYPE_MISALIGNMENT => CrashReason::DatatypeMisalignment, bun_sys::windows::EXCEPTION_ACCESS_VIOLATION => { - CrashReason::SegmentationFault(unsafe { - (*info.ExceptionRecord).ExceptionInformation[1] - }) + CrashReason::SegmentationFault(record.ExceptionInformation[1]) } bun_sys::windows::EXCEPTION_ILLEGAL_INSTRUCTION => { // `ExceptionAddress` is the faulting RIP for `STATUS_ILLEGAL_ // INSTRUCTION` (winnt.h); avoids depending on the arch-specific // `CONTEXT` layout. - CrashReason::IllegalInstruction( - unsafe { (*info.ExceptionRecord).ExceptionAddress } as usize - ) + CrashReason::IllegalInstruction(record.ExceptionAddress as usize) } bun_sys::windows::EXCEPTION_STACK_OVERFLOW => CrashReason::StackOverflow, + _ => return None, + }) + } - // exception used for thread naming - // https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2017/debugger/how-to-set-a-thread-name-in-native-code?view=vs-2017#set-a-thread-name-by-throwing-an-exception - // related commit - // https://github.com/go-delve/delve/pull/1384 - bun_sys::windows::MS_VC_EXCEPTION => { - return bun_sys::windows::EXCEPTION_CONTINUE_EXECUTION; - } + #[cfg(windows)] + pub(crate) extern "system" fn handle_segfault_windows( + info: *mut bun_sys::windows::EXCEPTION_POINTERS, + ) -> c_long { + // SAFETY: kernel provides a valid EXCEPTION_POINTERS / EXCEPTION_RECORD. + let info = unsafe { &*info }; + let record = unsafe { &*info.ExceptionRecord }; + + // exception used for thread naming + // https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2017/debugger/how-to-set-a-thread-name-in-native-code?view=vs-2017#set-a-thread-name-by-throwing-an-exception + // related commit + // https://github.com/go-delve/delve/pull/1384 + if record.ExceptionCode == bun_sys::windows::MS_VC_EXCEPTION { + return bun_sys::windows::EXCEPTION_CONTINUE_EXECUTION; + } - _ => return bun_sys::windows::EXCEPTION_CONTINUE_SEARCH, + let Some(reason) = classify_exception_windows(record) else { + return bun_sys::windows::EXCEPTION_CONTINUE_SEARCH; }; - // SAFETY: kernel provides a valid EXCEPTION_RECORD; ExceptionAddress is - // the faulting instruction. - let pc = unsafe { (*info.ExceptionRecord).ExceptionAddress } as usize; + + // VEH runs before any frame-based (SEH) handler. Windows system code + // deliberately uses SEH to probe unchecked handles: CRYPTSP.dll, for + // example, validates an HCRYPTPROV by reading `[rcx+0E8h]` inside a + // `__try`/`__except` that turns the access violation into + // `ERROR_INVALID_PARAMETER`. Treating that first-chance exception as + // fatal kills the process for what the callee was about to recover + // from. So only take over here when the faulting instruction is inside + // Bun's own image; for foreign code, let SEH dispatch proceed. JSC + // registers unwind info for its JIT pool with a language-specific + // handler (LLInt is pending build-time offlineasm .seh_* emission) + // that routes back to `Bun__crashHandlerFromJSCFrame`, and + // `handle_unhandled_exception_windows` reports anything that still + // goes unhandled. Stack overflow is always claimed here: no foreign + // `__except` recovers from it in practice, and SEH dispatch itself + // costs stack the guard reserve may not have. + let pc = record.ExceptionAddress as usize; + let base = WINDOWS_EXE_IMAGE_BASE.load(Ordering::Relaxed); + let end = WINDOWS_EXE_IMAGE_END.load(Ordering::Relaxed); + if !matches!(reason, CrashReason::StackOverflow) && base != 0 && !(base..end).contains(&pc) + { + return bun_sys::windows::EXCEPTION_CONTINUE_SEARCH; + } + // Windows: capture_from_context walks via RtlVirtualUnwind seeded from // the fault CONTEXT, so the handler's own frames are never captured. crash_handler( @@ -2084,6 +2138,67 @@ mod draft { ); } + /// Called from JSC's `jscJITSEHHandler` when SEH dispatch reaches a JIT + /// frame with an unhandled exception. Reports the crash if the reason is + /// one we classify; otherwise continues the search so an outer handler + /// (or UEF) can claim it. + #[cfg(windows)] + #[unsafe(no_mangle)] + pub(crate) extern "C" fn Bun__crashHandlerFromJSCFrame( + record: *mut bun_sys::windows::EXCEPTION_RECORD, + _establisher_frame: *mut core::ffi::c_void, + context: *mut core::ffi::c_void, + _dispatcher: *mut core::ffi::c_void, + ) -> c_long { + use bun_sys::windows::disposition::ExceptionContinueSearch; + // SAFETY: kernel provides a valid EXCEPTION_RECORD. + let record = unsafe { &*record }; + // A PEXCEPTION_ROUTINE can also be invoked during the unwind phase if + // the frame's UNWIND_INFO carries UNW_FLAG_UHANDLER (the WebKit side + // currently sets EHANDLER only; this matches SpiderMonkey's guard). + // Also decline once `reset_segfault_handler` has torn down the VEH so + // a re-fault during teardown reaches the OS default instead of + // re-entering `crash_handler`. + if record.ExceptionFlags & bun_sys::windows::EXCEPTION_UNWIND != 0 + || bun_core::WINDOWS_SEGFAULT_HANDLE + .load(Ordering::Relaxed) + .is_null() + { + return ExceptionContinueSearch; + } + let Some(reason) = classify_exception_windows(record) else { + return ExceptionContinueSearch; + }; + let pc = record.ExceptionAddress as usize; + crash_handler( + reason, + TraceSeed::Fault { + pc, + fp: context as usize, + }, + ); + } + + #[cfg(windows)] + pub(crate) extern "system" fn handle_unhandled_exception_windows( + info: *mut bun_sys::windows::EXCEPTION_POINTERS, + ) -> c_long { + // SAFETY: kernel provides a valid EXCEPTION_POINTERS / EXCEPTION_RECORD. + let info = unsafe { &*info }; + let record = unsafe { &*info.ExceptionRecord }; + let Some(reason) = classify_exception_windows(record) else { + return bun_sys::windows::EXCEPTION_CONTINUE_SEARCH; + }; + let pc = record.ExceptionAddress as usize; + crash_handler( + reason, + TraceSeed::Fault { + pc, + fp: info.ContextRecord as usize, + }, + ); + } + #[cfg(all(target_os = "linux", target_env = "gnu"))] unsafe extern "C" { fn gnu_get_libc_version() -> *const c_char; diff --git a/src/jsc/bindings/JSCTestingHelpers.cpp b/src/jsc/bindings/JSCTestingHelpers.cpp index 6807ad2b2ee8..caca7766c736 100644 --- a/src/jsc/bindings/JSCTestingHelpers.cpp +++ b/src/jsc/bindings/JSCTestingHelpers.cpp @@ -6,6 +6,11 @@ #include #include "ZigGlobalObject.h" +#if OS(WINDOWS) +#include +#include +#endif + namespace Bun { using namespace JSC; @@ -49,6 +54,16 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionIsLatin1String, return {}; } +#if OS(WINDOWS) +JSC_DEFINE_HOST_FUNCTION(jsFunctionStartOfFixedExecutableMemoryPool, + (JSGlobalObject * globalObject, CallFrame*)) +{ + auto& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + RELEASE_AND_RETURN(scope, JSValue::encode(JSBigInt::makeHeapBigIntOrBigInt32(globalObject, static_cast(JSC::startOfFixedExecutableMemoryPool())))); +} +#endif + JSC::JSValue createJSCTestingHelpers(Zig::GlobalObject* globalObject) { auto& vm = JSC::getVM(globalObject); @@ -65,6 +80,13 @@ JSC::JSValue createJSCTestingHelpers(Zig::GlobalObject* globalObject) jsFunctionIsLatin1String, ImplementationVisibility::Public, NoIntrinsic, JSC::PropertyAttribute::DontDelete | 0); +#if OS(WINDOWS) + object->putDirectNativeFunction( + vm, globalObject, JSC::Identifier::fromString(vm, "startOfFixedExecutableMemoryPool"_s), 0, + jsFunctionStartOfFixedExecutableMemoryPool, ImplementationVisibility::Public, NoIntrinsic, + JSC::PropertyAttribute::DontDelete | 0); +#endif + return object; } diff --git a/src/jsc/bindings/ZigGlobalObject.cpp b/src/jsc/bindings/ZigGlobalObject.cpp index b2b5535f071b..eebbb5cfedab 100644 --- a/src/jsc/bindings/ZigGlobalObject.cpp +++ b/src/jsc/bindings/ZigGlobalObject.cpp @@ -278,6 +278,11 @@ extern "C" unsigned getJSCBytecodeCacheVersion() extern "C" void Bun__REPRL__registerFuzzilliFunctions(Zig::GlobalObject*); #endif +#if OS(WINDOWS) && (CPU(X86_64) || CPU(ARM64)) +#include +extern "C" long Bun__crashHandlerFromJSCFrame(void*, void*, void*, void*); +#endif + extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(const char* ptr, size_t length), bool evalMode, bool oneShotStartup) { static std::once_flag jsc_init_flag; @@ -357,6 +362,15 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c } JSC::Options::assertOptionsAreCoherent(); }); // end JSC::initialize lambda + +#if OS(WINDOWS) && (CPU(X86_64) || CPU(ARM64)) + // JSC::initialize() registered unwind info + a language-specific SEH + // handler for the JIT pool. Route that handler to the crash reporter + // so a hardware fault under a JIT frame is reported deterministically + // at the JSC boundary. LLInt is not yet covered (needs build-time + // offlineasm .seh_* emission). + JSC::setJITExceptionHandlerWin(&Bun__crashHandlerFromJSCFrame); +#endif }); // end std::call_once lambda // NOLINTEND diff --git a/src/sys/windows/mod.rs b/src/sys/windows/mod.rs index 89dec6edd33c..60827a60ba7e 100644 --- a/src/sys/windows/mod.rs +++ b/src/sys/windows/mod.rs @@ -1210,6 +1210,42 @@ pub const EXCEPTION_CONTINUE_EXECUTION: i32 = -1; pub const EXCEPTION_CONTINUE_SEARCH: i32 = 0; pub const MS_VC_EXCEPTION: u32 = 0x406d1388; +/// `EXCEPTION_DISPOSITION` (excpt.h): return type of a `PEXCEPTION_ROUTINE` +/// language-specific handler. A different enum from the filter/VEH constants +/// above (`EXCEPTION_CONTINUE_SEARCH == 0` there, `ExceptionContinueSearch == +/// 1` here); mixing the two turns "continue search" into "resume at fault". +#[allow(nonstandard_style)] +pub mod disposition { + use core::ffi::c_long; + pub const ExceptionContinueExecution: c_long = 0; + pub const ExceptionContinueSearch: c_long = 1; +} + +/// `EXCEPTION_UNWIND` (winnt.h): mask of `ExceptionFlags` bits set during the +/// unwind (not search) phase of frame-based dispatch. +pub const EXCEPTION_UNWIND: u32 = 0x66; + +/// `[base, base + SizeOfImage)` of the process executable, read once from the +/// mapped PE header. The crash handler uses this to tell first-chance +/// exceptions raised inside Bun's own code from those raised inside foreign +/// modules. +pub fn exe_image_range() -> core::ops::Range { + // SAFETY: null module name returns the exe's HMODULE, which on Windows is + // its mapped base address. The IMAGE_DOS_HEADER at `base` and + // IMAGE_NT_HEADERS at `base + e_lfanew` are part of the loader-mapped + // image and remain valid for the process lifetime. + unsafe { + let base = bun_windows_sys::kernel32::GetModuleHandleW(ptr::null()) as usize; + let e_lfanew = *(base as *const u8).add(0x3C).cast::() as usize; + // IMAGE_NT_HEADERS64: Signature(4) + IMAGE_FILE_HEADER(20) + + // IMAGE_OPTIONAL_HEADER64.SizeOfImage at offset 56. + let size_of_image = *(base as *const u8) + .add(e_lfanew + 4 + 20 + 56) + .cast::() as usize; + base..base + size_of_image + } +} + // `STATUS_*` values surfaced as `ExceptionCode` (winnt.h). pub const EXCEPTION_ACCESS_VIOLATION: u32 = 0xC0000005; pub const EXCEPTION_DATATYPE_MISALIGNMENT: u32 = 0x80000002; diff --git a/src/windows_sys/externs.rs b/src/windows_sys/externs.rs index f7c0918642d8..f2eb397a1155 100644 --- a/src/windows_sys/externs.rs +++ b/src/windows_sys/externs.rs @@ -972,6 +972,14 @@ pub mod kernel32 { First: u32, Handler: unsafe extern "system" fn(*mut c_void) -> i32, ) -> *mut c_void; + /// `SetUnhandledExceptionFilter` (`errhandlingapi.h`). Runs after all + /// frame-based (SEH) handlers have declined. + pub fn SetUnhandledExceptionFilter( + lpTopLevelExceptionFilter: Option i32>, + ) -> Option i32>; + /// `GetModuleHandleW` (`libloaderapi.h`). `lpModuleName == null` returns + /// the base address of the calling process's executable image. + pub fn GetModuleHandleW(lpModuleName: LPCWSTR) -> HMODULE; /// `RemoveVectoredExceptionHandler` (`errhandlingapi.h`). pub fn RemoveVectoredExceptionHandler(Handle: *mut c_void) -> u32; } diff --git a/test/cli/run/run-crash-handler.test.ts b/test/cli/run/run-crash-handler.test.ts index c6b207237e13..681404266535 100644 --- a/test/cli/run/run-crash-handler.test.ts +++ b/test/cli/run/run-crash-handler.test.ts @@ -164,6 +164,147 @@ test.if(isWindows && isDebug)("Windows: segfault inside a system DLL captures th expect(span).toBeLessThan(2n ** 31n); }); +// The Windows crash handler is a Vectored Exception Handler, which sees every +// first-chance exception process-wide before frame-based SEH does. Third-party +// DLLs injected into the process (AV/EDR agents such as BeyondTrust's +// PGHook.dll, virtualization guest tools, shell extensions) routinely raise +// and then handle access violations under SEH as part of normal operation. +// The VEH must let those through rather than treating them as a fatal crash. +// `IsBadReadPtr` is the canonical example: it probes its argument inside a +// `__try`/`__except` in kernel32, so the AV it raises is inside a system DLL +// and is immediately swallowed by that DLL's own SEH. +// +// See https://github.com/oven-sh/bun/issues/10056 (Carbon Black), +// https://github.com/oven-sh/bun/issues/11898 (Trend Micro). +describe.if(isWindows)("Windows VEH handler and first-chance faults in external DLLs", () => { + test("SEH-guarded probe survives", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + `const { dlopen } = require("bun:ffi"); + const lib = dlopen("kernel32.dll", { + IsBadReadPtr: { args: ["usize", "usize"], returns: "i32" }, + }); + const rc = lib.symbols.IsBadReadPtr(0xE8, 8); + console.log("SURVIVED rc=" + rc);`, + ], + env: noReportEnv, + stdio: ["ignore", "pipe", "pipe"], + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).not.toContain("Segmentation fault"); + // rc=1: kernel32's SEH caught the AV and reported the pointer as bad. + expect(stdout.trim()).toBe("SURVIVED rc=1"); + expect(exitCode).toBe(0); + }); + + // `RtlFillMemory` has no `__try`/`__except` around its store. With the VEH + // now returning CONTINUE_SEARCH for out-of-image PCs, the catch point is + // JSC's jscJITSEHHandler (registered for JIT frames), which routes to + // Bun__crashHandlerFromJSCFrame, or UEF. This exercises that the crash is + // still reported and the report carries the fault address. + test("unguarded fault still crash-reports", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "--debug-crash-handler-use-trace-string", + "-e", + `const { dlopen } = require("bun:ffi"); + const lib = dlopen("ntdll.dll", { + RtlFillMemory: { args: ["usize", "usize", "i32"], returns: "void" }, + }); + lib.symbols.RtlFillMemory(0xE8, 8, 0); + console.log("SHOULD NOT REACH");`, + ], + env: noReportEnv, + stdio: ["ignore", "pipe", "pipe"], + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).toContain("Segmentation fault at address 0xE8"); + expect(stdout).not.toContain("SHOULD NOT REACH"); + expect(exitCode).not.toBe(0); + }); + + // Validate WebKit's registerJITUnwindInfo against the actual unwinder: + // RtlLookupFunctionEntry must return a RUNTIME_FUNCTION for a JIT pool PC. + // This is the smoke test for the hand-encoded UNWIND_INFO / .xdata bytes. + // LLInt PCs are not covered here: LLInt lives in image .text and Windows + // only consults static .pdata for in-module PCs; that needs build-time + // .seh_* emission in offlineasm (follow-up). + test("RtlLookupFunctionEntry resolves JSC JIT pool PCs", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "-e", + `const { dlopen, FFIType, ptr } = require("bun:ffi"); + const { symbols } = dlopen("ntdll.dll", { + RtlLookupFunctionEntry: { + args: [FFIType.u64, FFIType.pointer, FFIType.pointer], + returns: FFIType.pointer, + }, + }); + const { jscInternals } = require("bun:internal-for-testing"); + const pool = jscInternals.startOfFixedExecutableMemoryPool(); + const imageBase = new BigUint64Array(1); + const jitEntry = symbols.RtlLookupFunctionEntry(pool + 0x100n, ptr(imageBase), null); + console.log(JSON.stringify({ + pool: pool.toString(16), + jitEntry: jitEntry === null ? "null" : "ok", + }));`, + ], + env: noReportEnv, + stdio: ["ignore", "pipe", "pipe"], + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).toBe(""); + const out = JSON.parse(stdout.trim()); + expect(out.jitEntry).toBe("ok"); + expect(exitCode).toBe(0); + }); + + // End-to-end: warm a JS function into the JIT, then fault from inside it + // via FFI. The crash report must fire via jscJITSEHHandler at the JIT + // boundary. Clears the UEF backstop first so the assertion isolates the JSC + // handler (deleting setJITExceptionHandlerWin would break this test, not + // just fall through to UEF). Disables the concurrent JIT so warm-up is + // deterministic. + test("unguarded fault from inside a JIT-compiled frame still crash-reports via the JSC handler", async () => { + await using proc = Bun.spawn({ + cmd: [ + bunExe(), + "--debug-crash-handler-use-trace-string", + "-e", + `const { dlopen } = require("bun:ffi"); + const ntdll = dlopen("ntdll.dll", { + RtlFillMemory: { args: ["usize", "usize", "i32"], returns: "void" }, + }); + const k32 = dlopen("kernel32.dll", { + SetUnhandledExceptionFilter: { args: ["usize"], returns: "usize" }, + }); + function hot(i) { + if (i === 10000) ntdll.symbols.RtlFillMemory(0xE8, 8, 0); + return i; + } + for (let i = 0; i < 10000; i++) hot(i); + k32.symbols.SetUnhandledExceptionFilter(0); + hot(10000); + console.log("SHOULD NOT REACH");`, + ], + env: { ...noReportEnv, BUN_JSC_jitPolicyScale: "0", BUN_JSC_useConcurrentJIT: "0" }, + stdio: ["ignore", "pipe", "pipe"], + }); + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + expect(stderr).toContain("Segmentation fault at address 0xE8"); + expect(stdout).not.toContain("SHOULD NOT REACH"); + expect(exitCode).not.toBe(0); + }); +}); + test.if(process.platform === "darwin")("macOS has the assumed image offset", () => { // If this fails, then https://bun.report will be incorrect and the stack // trace remappings will stop working.