Skip to content

[Bug]: Memory crash (SIGBUS/SIGSEGV) when using swc-loader (npm) instead of builtin:swc-loader #12899

@T9-Forever

Description

@T9-Forever

System Info

  • Rspack/Rsbuild version: 1.6.6
  • swc-loader version: 0.2.7
  • @swc/core version: 1.14.0
  • Node.js version: 18.20.8
  • OS: macOS (Apple Silicon / arm64)

Details

Summary

When using swc-loader (npm package) instead of builtin:swc-loader with Rspack, a race condition causes memory crashes (SIGBUS/SIGSEGV) due to a use-after-free issue in BuildInfo::with_ref.

Steps to reproduce

  1. Replace builtin:swc-loader with swc-loader (npm package) in bundlerChain:
// rsbuild.config.ts
bundlerChain: (chain, { CHAIN_ID }) => {
  const jsRule = chain.module.rule(CHAIN_ID.RULE.JS)
  jsRule
    .use('swc')
    .loader('swc-loader')  // npm package instead of builtin:swc-loader
    .options({
      jsc: {
        target: 'es2016',
        parser: { tsx: true, syntax: 'typescript' },
        experimental: {
          plugins: [
            ['@swc/plugin-styled-components', { /* ... */ }],
          ]
        },
      }
    })
}
  1. Run rsbuild dev with a large project (many files)
  2. The build crashes with ~90% probability

Error message

Panic occurred at runtime. Please file an issue on GitHub with the backtrace below:
https://github.com/web-infra-dev/rspack/issues:
panicked at /rustc/.../library/alloc/src/raw_vec/mod.rs:558:17:
capacity overflow

Or:

memory allocation of 25457679912665952 bytes failed

Workaround

Adding a passthrough loader with an empty pitch function before swc-loader prevents the crash:

// pitch-workaround.js
module.exports = function passthroughLoader(source) {
  return source
}

module.exports.pitch = function () {
  // Empty - just needs to exist
}
jsRule
  .use('pitch-workaround')
  .loader('./pitch-workaround.js')
  .end()
  .use('swc')
  .loader('swc-loader')
  .options(swcOptions)

Root Cause Analysis

Debug stack trace (using @rspack-debug/core@1.6.6)

Crash 1: SIGBUS

Process 6938 stopped
* thread #1, name = 'rsbuild-node', queue = 'com.apple.main-thread', stop reason = signal SIGBUS
    frame #0: 0x00000001130c584c rspack.darwin-arm64.node`<rspack_binding_api::build_info::BuildInfo>::with_ref::<(), <rspack_binding_api::build_info::BuildInfo as napi::bindgen_runtime::js_values::ToNapiValue>::to_napi_value::{closure#0}::{closure#1}> + 120
rspack.darwin-arm64.node`<rspack_binding_api::build_info::BuildInfo>::with_ref::<(), <rspack_binding_api::build_info::BuildInfo as napi::bindgen_runtime::js_values::ToNapiValue>::to_napi_value::{closure#0}::{closure#1}>:
->  0x1130c584c <+120>: ldr    x8, [x0, #0x48]
    0x1130c5850 <+124>: cmn    x8, #0x1
    0x1130c5854 <+128>: b.eq   0x1130c5c24    ; <+1104>
    0x1130c5858 <+132>: mov    x22, x0
Target 0: (node) stopped.
(lldb) bt
* thread #1, name = 'rsbuild-node', queue = 'com.apple.main-thread', stop reason = signal SIGBUS
  * frame #0: 0x00000001130c584c rspack.darwin-arm64.node`<rspack_binding_api::build_info::BuildInfo>::with_ref::<(), <rspack_binding_api::build_info::BuildInfo as napi::bindgen_runtime::js_values::ToNapiValue>::to_napi_value::{closure#0}::{closure#1}> + 120
    frame #1: 0x0000000112f9b144 rspack.darwin-arm64.node`<std::thread::local::LocalKey<core::cell::RefCell<alloc::vec::Vec<napi::js_values::object_property::Property>>>>::with::<<rspack_binding_api::build_info::BuildInfo as napi::bindgen_runtime::js_values::ToNapiValue>::to_napi_value::{closure#0}, core::result::Result<(), napi::error::Error>> + 240
    frame #2: 0x0000000113111f54 rspack.darwin-arm64.node`<rspack_binding_api::build_info::BuildInfo as napi::bindgen_runtime::js_values::ToNapiValue>::to_napi_value + 200
    frame #3: 0x0000000112fbca94 rspack.darwin-arm64.node`<rspack_binding_api::modules::normal_module::NormalModule>::new_inherited::build_info_getter + 436
    frame #4: 0x000000010008a108 node`v8impl::(anonymous namespace)::FunctionCallbackWrapper::Invoke(v8::FunctionCallbackInfo<v8::Value> const&) + 104
    frame #5: 0x00000001002ca49c node`v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) + 192
    frame #6: 0x00000001002c9ffc node`v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) + 448
    frame #7: 0x00000001002c9aa8 node`v8::internal::Builtins::InvokeApiFunction(v8::internal::Isolate*, bool, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*, v8::internal::Handle<v8::internal::HeapObject>) + 460
    frame #8: 0x00000001003a1550 node`v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) + 428
    frame #9: 0x00000001003a138c node`v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) + 196
    frame #10: 0x00000001006b7a7c node`v8::internal::Object::GetPropertyWithAccessor(v8::internal::LookupIterator*) + 860
    frame #11: 0x00000001006b7044 node`v8::internal::Object::GetProperty(v8::internal::LookupIterator*, bool) + 332
    frame #12: 0x00000001007c47f8 node`v8::internal::Runtime::GetObjectProperty(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, bool*) + 192
    frame #13: 0x00000001007c6b44 node`v8::internal::Runtime_GetProperty(int, unsigned long*, v8::internal::Isolate*) + 932
    frame #14: 0x0000000100b3510c node`Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit + 108
    frame #15: 0x0000000105318050
    frame #16: 0x0000000100af1ef4 node`Builtins_AsyncFunctionAwaitResolveClosure + 84
    frame #17: 0x0000000100b80838 node`Builtins_PromiseFulfillReactionJob + 56
    frame #18: 0x0000000100ae3c4c node`Builtins_RunMicrotasks + 588
    frame #19: 0x0000000100abe3a4 node`Builtins_JSRunMicrotasksEntry + 164
    frame #20: 0x00000001003a1d74 node`v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) + 2512
    frame #21: 0x00000001003a2368 node`v8::internal::(anonymous namespace)::InvokeWithTryCatch(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) + 88
    frame #22: 0x00000001003a2554 node`v8::internal::Execution::TryRunMicrotasks(v8::internal::Isolate*, v8::internal::MicrotaskQueue*, v8::internal::MaybeHandle<v8::internal::Object>*) + 64
    frame #23: 0x00000001003cc3f4 node`v8::internal::MicrotaskQueue::RunMicrotasks(v8::internal::Isolate*) + 336
    frame #24: 0x00000001003ccb80 node`v8::internal::MicrotaskQueue::PerformCheckpoint(v8::Isolate*) + 124
    frame #25: 0x0000000100004c84 node`node::InternalCallbackScope::Close() + 256
    frame #26: 0x000000010000482c node`node::CallbackScope::~CallbackScope() + 64
    frame #27: 0x000000010009b3c0 node`(anonymous namespace)::uvimpl::Work::AfterThreadPoolWork(int) + 412
    frame #28: 0x000000010009b7ec node`node::ThreadPoolWork::ScheduleWork()::'lambda'(uv_work_s*, int)::operator()(uv_work_s*, int) const + 320
    frame #29: 0x000000010009b6a0 node`node::ThreadPoolWork::ScheduleWork()::'lambda'(uv_work_s*, int)::__invoke(uv_work_s*, int) + 28
    frame #30: 0x0000000100a98f84 node`uv__work_done + 192
    frame #31: 0x0000000100a9c6e8 node`uv__async_io + 320
    frame #32: 0x0000000100aaf8dc node`uv__io_poll + 1044
    frame #33: 0x0000000100a9cba0 node`uv_run + 388
    frame #34: 0x00000001000057e0 node`node::SpinEventLoop(node::Environment*) + 248
    frame #35: 0x000000010011097c node`node::NodeMainInstance::Run() + 200
    frame #36: 0x0000000100095938 node`node::LoadSnapshotDataAndRun(node::SnapshotData const**, node::InitializationResult const*) + 456
    frame #37: 0x0000000100095b60 node`node::Start(int, char**) + 468
    frame #38: 0x0000000185936b98 dyld`start + 6076

Crash 2: SIGABRT (unsafe precondition violated)


context: /Users/edson.lin/Desktop/SourceCode/editor

Panic occurred at runtime. Please file an issue on GitHub with the backtrace below: https://github.com/web-infra-dev/rspack/issues: panicked at /Users/runner/.rustup/toolchains/nightly-2025-11-13-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:1639:18:
unsafe precondition(s) violated: slice::from_raw_parts requires the pointer to be aligned and non-null, and the total size of the slice not to exceed `isize::MAX`

This indicates a bug in the program. This Undefined Behavior check is optional, and cannot be relied on for safety.
thread caused non-unwinding panic. aborting.
Process 15455 launched: '/Users/edson.lin/.nvm/versions/node/v18.20.8/bin/node' (arm64)
Process 15455 stopped
* thread #1, name = 'rsbuild-node', queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x0000000185c9e388 libsystem_kernel.dylib`__pthread_kill + 8
libsystem_kernel.dylib`__pthread_kill:
->  0x185c9e388 <+8>:  b.lo   0x185c9e3a8    ; <+40>
    0x185c9e38c <+12>: pacibsp
    0x185c9e390 <+16>: stp    x29, x30, [sp, #-0x10]!
    0x185c9e394 <+20>: mov    x29, sp
Target 0: (node) stopped.
(lldb) dt
error: 'dt' is not a valid command.
(lldb) bt
* thread #1, name = 'rsbuild-node', queue = 'com.apple.main-thread', stop reason = signal SIGABRT
  * frame #0: 0x0000000185c9e388 libsystem_kernel.dylib`__pthread_kill + 8
    frame #1: 0x0000000185cd7848 libsystem_pthread.dylib`pthread_kill + 296
    frame #2: 0x0000000185be09e4 libsystem_c.dylib`abort + 124
    frame #3: 0x0000000135168344 rspack.darwin-arm64.node`std::sys::pal::unix::abort_internal + 12
    frame #4: 0x0000000135167d38 rspack.darwin-arm64.node`std::process::abort + 12
    frame #5: 0x0000000134fa2b18 rspack.darwin-arm64.node`std::panicking::panic_with_hook + 872
    frame #6: 0x0000000134fa18bc rspack.darwin-arm64.node`std::panicking::panic_handler::{closure#0} + 100
    frame #7: 0x0000000134fa1490 rspack.darwin-arm64.node`std::sys::backtrace::__rust_end_short_backtrace::<std::panicking::panic_handler::{closure#0}, !> + 12
    frame #8: 0x0000000134fa1b14 rspack.darwin-arm64.node`__rustc::rust_begin_unwind + 32
    frame #9: 0x000000013516a348 rspack.darwin-arm64.node`core::panicking::panic_nounwind_fmt + 52
    frame #10: 0x0000000134f3ed3c rspack.darwin-arm64.node`<indexmap::map::IndexMap<alloc::string::String, serde_json::value::Value>>::iter + 152
    frame #11: 0x0000000134f262b0 rspack.darwin-arm64.node`<&serde_json::map::Map<alloc::string::String, serde_json::value::Value> as napi::bindgen_runtime::js_values::ToNapiValue>::to_napi_value + 104
    frame #12: 0x00000001321c98a0 rspack.darwin-arm64.node`<rspack_binding_api::build_info::BuildInfo>::with_ref::<(), <rspack_binding_api::build_info::BuildInfo as napi::bindgen_runtime::js_values::ToNapiValue>::to_napi_value::{closure#0}::{closure#1}> + 204
    frame #13: 0x000000013209f144 rspack.darwin-arm64.node`<std::thread::local::LocalKey<core::cell::RefCell<alloc::vec::Vec<napi::js_values::object_property::Property>>>>::with::<<rspack_binding_api::build_info::BuildInfo as napi::bindgen_runtime::js_values::ToNapiValue>::to_napi_value::{closure#0}, core::result::Result<(), napi::error::Error>> + 240
    frame #14: 0x0000000132215f54 rspack.darwin-arm64.node`<rspack_binding_api::build_info::BuildInfo as napi::bindgen_runtime::js_values::ToNapiValue>::to_napi_value + 200
    frame #15: 0x00000001320c0a94 rspack.darwin-arm64.node`<rspack_binding_api::modules::normal_module::NormalModule>::new_inherited::build_info_getter + 436
    frame #16: 0x000000010008a108 node`v8impl::(anonymous namespace)::FunctionCallbackWrapper::Invoke(v8::FunctionCallbackInfo<v8::Value> const&) + 104
    frame #17: 0x00000001002ca49c node`v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) + 192
    frame #18: 0x00000001002c9ffc node`v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) + 448
    frame #19: 0x00000001002c9aa8 node`v8::internal::Builtins::InvokeApiFunction(v8::internal::Isolate*, bool, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*, v8::internal::Handle<v8::internal::HeapObject>) + 460
    frame #20: 0x00000001003a1550 node`v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) + 428
    frame #21: 0x00000001003a138c node`v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) + 196
    frame #22: 0x00000001006b7a7c node`v8::internal::Object::GetPropertyWithAccessor(v8::internal::LookupIterator*) + 860
    frame #23: 0x00000001006b7044 node`v8::internal::Object::GetProperty(v8::internal::LookupIterator*, bool) + 332
    frame #24: 0x00000001007c47f8 node`v8::internal::Runtime::GetObjectProperty(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, bool*) + 192
    frame #25: 0x00000001007c6b44 node`v8::internal::Runtime_GetProperty(int, unsigned long*, v8::internal::Isolate*) + 932
    frame #26: 0x0000000100b3510c node`Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit + 108
    frame #27: 0x00000001058264f8
    frame #28: 0x0000000100af1ef4 node`Builtins_AsyncFunctionAwaitResolveClosure + 84
    frame #29: 0x0000000100b80838 node`Builtins_PromiseFulfillReactionJob + 56
    frame #30: 0x0000000100ae3c4c node`Builtins_RunMicrotasks + 588
    frame #31: 0x0000000100abe3a4 node`Builtins_JSRunMicrotasksEntry + 164
    frame #32: 0x00000001003a1d74 node`v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) + 2512
    frame #33: 0x00000001003a2368 node`v8::internal::(anonymous namespace)::InvokeWithTryCatch(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) + 88
    frame #34: 0x00000001003a2554 node`v8::internal::Execution::TryRunMicrotasks(v8::internal::Isolate*, v8::internal::MicrotaskQueue*, v8::internal::MaybeHandle<v8::internal::Object>*) + 64
    frame #35: 0x00000001003cc3f4 node`v8::internal::MicrotaskQueue::RunMicrotasks(v8::internal::Isolate*) + 336
    frame #36: 0x00000001003ccb80 node`v8::internal::MicrotaskQueue::PerformCheckpoint(v8::Isolate*) + 124
    frame #37: 0x0000000100004c84 node`node::InternalCallbackScope::Close() + 256
    frame #38: 0x000000010000482c node`node::CallbackScope::~CallbackScope() + 64
    frame #39: 0x000000010009b3c0 node`(anonymous namespace)::uvimpl::Work::AfterThreadPoolWork(int) + 412
    frame #40: 0x000000010009b7ec node`node::ThreadPoolWork::ScheduleWork()::'lambda'(uv_work_s*, int)::operator()(uv_work_s*, int) const + 320
    frame #41: 0x000000010009b6a0 node`node::ThreadPoolWork::ScheduleWork()::'lambda'(uv_work_s*, int)::__invoke(uv_work_s*, int) + 28
    frame #42: 0x0000000100a98f84 node`uv__work_done + 192
    frame #43: 0x0000000100a9c6e8 node`uv__async_io + 320
    frame #44: 0x0000000100aaf8dc node`uv__io_poll + 1044
    frame #45: 0x0000000100a9cba0 node`uv_run + 388
    frame #46: 0x00000001000057e0 node`node::SpinEventLoop(node::Environment*) + 248
    frame #47: 0x000000010011097c node`node::NodeMainInstance::Run() + 200
    frame #48: 0x0000000100095938 node`node::LoadSnapshotDataAndRun(node::SnapshotData const**, node::InitializationResult const*) + 456
    frame #49: 0x0000000100095b60 node`node::Start(int, char**) + 468
    frame #50: 0x0000000185936b98 dyld`start + 6076

Analysis

The crash occurs in rspack_binding_api/src/build_info.rs:

// line 136-150
fn with_ref<T>(
    &mut self,
    f: impl FnOnce(&dyn rspack_core::Module) -> napi::Result<T>,
) -> napi::Result<T> {
    match self.module_reference.get_mut() {
      Some(reference) => {
        let (_, module) = reference.as_ref()?;  // 🔥 CRASH HERE
        f(module)
      }
      None => Err(...)
    }
}

The issue: BuildInfo holds a WeakReference<Module> (line 122). The with_ref method checks if the JavaScript object is still alive via get_mut(), but this doesn't protect against the underlying Rust data being invalidated by another thread.

Race condition diagram

┌─────────────────────────────────────────────────────────────────┐
│  Thread A (Loader Runner)                                       │
│    - Processing/rebuilding module                               │
│    - Module's Rust data is dropped/moved/reallocated           │
│                                                                 │
│  Thread B (JS Callback / Promise resolution)                    │
│    - WeakReference.get_mut() → Some(reference) ✅               │
│      (JS wrapper object still exists)                           │
│    - reference.as_ref() → 💥 SIGBUS                            │
│      (Rust data pointer is now invalid!)                        │
└─────────────────────────────────────────────────────────────────┘

Reproduce link

No response

Reproduce Steps

npm run dev

Metadata

Metadata

Assignees

Labels

pending triageThe issue/PR is currently untouched.

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions