{"record":{"id":"281491e82ec545b8","repo":"firecrawl/pdf-inspector","slug":"ctx-rust-panic-msg","errorCode":null,"errorMessage":"{ctx}: Rust panic: {msg}","messagePattern":"(.+?): Rust panic: (.+?)","errorType":"exception","errorClass":"napi::Error (Status::GenericFailure)","httpStatus":null,"severity":"error","filePath":"napi/src/lib.rs","lineNumber":436,"sourceCode":"}\n\n/// Run a closure, catching any Rust panic and converting it to a NAPI error.\n/// Prevents process abort from unwind panics in the native module.\nfn catch_panic<F, T>(ctx: &str, f: F) -> Result<T>\nwhere\n    F: FnOnce() -> Result<T> + panic::UnwindSafe,\n{\n    match panic::catch_unwind(f) {\n        Ok(result) => result,\n        Err(payload) => {\n            let msg = if let Some(s) = payload.downcast_ref::<&str>() {\n                s.to_string()\n            } else if let Some(s) = payload.downcast_ref::<String>() {\n                s.clone()\n            } else {\n                \"unknown panic\".to_string()\n            };\n            Err(Error::new(\n                Status::GenericFailure,\n                format!(\"{ctx}: Rust panic: {msg}\"),\n            ))\n        }\n    }\n}\n\n// ---------------------------------------------------------------------------\n// Shared implementations (single body behind sync and async entry points)\n// ---------------------------------------------------------------------------\n\nfn process_pdf_impl(bytes: &[u8], pages: Option<Vec<u32>>) -> Result<PdfResult> {\n    let mut opts = pdf_inspector::PdfOptions::new();\n    if let Some(p) = pages {\n        opts = opts.pages(p);\n    }\n    let result = pdf_inspector::process_pdf_mem_with_options(bytes, opts)\n        .map_err(|e| to_napi_err(e, \"process_pdf\"))?;","sourceCodeStart":418,"sourceCodeEnd":454,"githubUrl":"https://github.com/firecrawl/pdf-inspector/blob/636ca1a58bdc1af4cd3fc20b8c1f549a1121cca7/napi/src/lib.rs#L418-L454","documentation":"catch_panic in napi/src/lib.rs wraps Rust closures so that a panic (unwind) inside the native module is converted into a NAPI Error with message \"{ctx}: Rust panic: {msg}\" instead of aborting the Node process. Rust panics normally abort or unwind across FFI, which is UB/abort in native modules; this converts them into a catchable JS Error. The payload is the panic message (&str, String, or 'unknown panic').","triggerScenarios":"Any pdf2md NAPI binding call whose Rust internals hit a panic: unreachable!()/unwrap()/expect failure, index-out-of-bounds, integer overflow in debug, or assertion inside extraction/table/layout code.","commonSituations":"Processing an unusual or malformed PDF that triggers an unhandled edge case (e.g. corrupt content stream, extreme coordinates); running a release binary compiled with panic=abort bypassing this guard; hitting a library bug on a specific document.","solutions":["Capture the message after 'Rust panic:' and reduce the input to a minimal PDF that reproduces it, then file a bug with that file.","Wrap the binding call in try/catch in Node.js so one bad document doesn't take down the process — this error IS the catchable form of the panic.","Isolate risky documents in a worker thread/child process so even an abort cannot kill the main process.","Upgrade the package — panics on valid PDFs are bugs that get fixed; check the changelog for the panic site mentioned in the message."],"exampleFix":"// before\nconst md = pdf2md.processFileSync(pdfPath);\n// after\nlet md;\ntry {\n  md = pdf2md.processFileSync(pdfPath);\n} catch (e) {\n  if (String(e.message).includes('Rust panic:')) {\n    console.error('native panic on', pdfPath, e.message);\n    md = null; // skip / quarantine this document\n  } else { throw e; }\n}","handlingStrategy":"try-catch","validationCode":"function looksLikePdf(buf) {\n  const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);\n  return b.length > 5 && b.subarray(0, 5).toString('latin1').startsWith('%PDF-');\n}","typeGuard":"function isPanicError(e) {\n  return e instanceof Error && e.message.includes(': Rust panic: ');\n}","tryCatchPattern":"try {\n  return pdf2md.processFileSync(pdfPath);\n} catch (e) {\n  if (isPanicError(e)) {\n    quarantine(pdfPath, e.message.split(': Rust panic: ')[1]);\n    return null; // isolate: don't let one doc break the batch\n  }\n  throw e;\n}","preventionTips":["Wrap every native-module call in try/catch — this error is the catchable form of a Rust panic.","Run batch processing in worker threads or child processes so a true abort cannot kill the main process.","Quarantine and report any PDF that triggers a panic; panics on valid input are library bugs.","Keep the native module updated; check the changelog when a panic message mentions a known site."],"tags":["panic","napi","native-module","rust"],"backgroundTag":"rust-panic-ffi","analyzedSha":"636ca1a58bdc1af4cd3fc20b8c1f549a1121cca7","analyzedAt":"2026-09-05T08:40:31.256Z","contentChangedAt":"2026-09-05T08:40:31.256Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}