{"record":{"id":"4f6f5591b9b3cf4a","repo":"swc-project/swc","slug":"should-able-to-read-memory-from-given-ptr","errorCode":null,"errorMessage":"Should able to read memory from given ptr","messagePattern":"Should able to read memory from given ptr","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/swc_plugin_runner/src/memory_interop.rs","lineNumber":16,"sourceCode":"use swc_common::plugin::serialized::PluginSerializedBytes;\n\nuse crate::runtime;\n\n#[cfg_attr(debug_assertions, tracing::instrument(level = \"info\", skip_all))]\npub fn copy_bytes_into_host(\n    caller: &dyn runtime::Caller<'_>,\n    bytes_ptr: i32,\n    bytes_ptr_len: i32,\n    buf: &mut Vec<u8>,\n) {\n    let len: usize = bytes_ptr_len.try_into().unwrap();\n    buf.resize(len, 0);\n    caller\n        .read_buf(bytes_ptr as u32, buf)\n        .expect(\"Should able to read memory from given ptr\");\n}\n\n/// Locate a view from given memory, write serialized bytes into.\n#[cfg_attr(debug_assertions, tracing::instrument(level = \"info\", skip_all))]\npub fn write_into_memory_view<F>(\n    view: &mut dyn runtime::Caller<'_>,\n    serialized_bytes: &PluginSerializedBytes,\n    get_allocated_ptr: F,\n) -> (u32, u32)\nwhere\n    F: Fn(&mut dyn runtime::Caller<'_>, usize) -> u32,\n{\n    let serialized_len = serialized_bytes.as_slice().len();\n\n    let ptr_start = get_allocated_ptr(view, serialized_len);\n    let serialized_size = serialized_len\n        .try_into()\n        .expect(\"Should be able to convert to i32\");","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/swc-project/swc/blob/5176682b65416c6b5de6b47379ae1588ea3ecb3f/crates/swc_plugin_runner/src/memory_interop.rs#L1-L34","documentation":"Thrown by swc_plugin_runner when the host side of the SWC wasm-plugin bridge copies bytes out of the plugin's linear memory: caller.read_buf(bytes_ptr, buf) failed for the (ptr, len) pair the plugin passed to a host-imported function. It means the pointer range is outside the plugin guest's current memory bounds, or points at stale/freed memory. This is a Rust panic (expect), so it aborts the embedding process unless caught.","triggerScenarios":"A plugin calls a host import that takes (ptr, len) arguments (transform result retrieval, comments proxy, source-map proxy, diagnostics) with a pointer that was freed, was computed before a memory.grow, or exceeds the wasm memory size; also when the plugin binary's ABI does not match the host runtime.","commonSituations":"Plugin compiled with a swc_core version that does not match the host swc version (schema/ABI drift); plugin built for the wrong wasm target; plugin panicking or corrupting its own allocator mid-transform; extremely large serialized payloads overflowing the guest heap.","solutions":["Rebuild the plugin against the exact swc_core version required by the swc/@swc/core version you run (check the plugin's compatibility table) and target wasm32-wasi.","Upgrade or pin the host (swc crate or @swc/core npm package) to the version matching the plugin.","Reproduce with debug_assertions enabled to get the tracing spans (copy_bytes_into_host is instrumented) showing which host import received the bad pointer.","Isolate plugin transforms with std::panic::catch_unwind or a subprocess/worker so a buggy plugin cannot abort the whole build."],"exampleFix":"// before: plugin panic kills the whole compiler process\nlet out = compiler.process_js_file(&fm, &handler, &config)?;\n\n// after: isolate plugin-bearing transforms and degrade gracefully\nlet out = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {\n    compiler.process_js_file(&fm, &handler, &config)\n})) {\n    Ok(Ok(res)) => res,\n    Ok(Err(e)) => return Err(e),\n    Err(payload) => {\n        tracing::error!(?payload, \"swc plugin passed a bad ptr; retrying without plugins\");\n        let cfg_no_plugins = strip_plugins(config);\n        compiler.process_js_file(&fm, &handler, &cfg_no_plugins)?\n    }\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// The failure is a host-side panic, so the only catch site is catch_unwind\n// (or a subprocess). Isolate every plugin-bearing transform.\nuse std::panic::{catch_unwind, AssertUnwindSafe};\n\nfn run_with_plugins<T>(f: impl FnOnce() -> anyhow::Result<T>) -> anyhow::Result<T> {\n    match catch_unwind(AssertUnwindSafe(f)) {\n        Ok(res) => res,\n        Err(payload) => {\n            let msg = payload\n                .downcast_ref::<&str>()\n                .map(|s| s.to_string())\n                .or_else(|| payload.downcast_ref::<String>().cloned())\n                .unwrap_or_else(|| \"unknown plugin panic\".into());\n            anyhow::bail!(\"swc plugin panicked: {msg}\")\n        }\n    }\n}","preventionTips":["Keep the plugin's swc_core version identical to the swc/@swc/core version executing it; check the plugin README compatibility table on every upgrade.","Build plugins with the official swc_plugin macro targeting wasm32-wasi in release mode; never mix debug/release or custom allocators.","Do not build with panic=abort if you rely on catch_unwind isolation; prefer a worker subprocess for full isolation.","Smoke-test each plugin on small inputs in CI before large production files hit it."],"tags":["wasm","plugin","memory","panic","swc-plugin-runner"],"backgroundTag":"wasm-memory-out-of-bounds","analyzedSha":"5176682b65416c6b5de6b47379ae1588ea3ecb3f","analyzedAt":"2026-08-17T16:16:52.067Z","contentChangedAt":"2026-08-17T16:16:52.067Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}