{"record":{"id":"465dcda50427c753","repo":"denoland/deno","slug":"op-was-not-marked-as-op2-reentrant-but-re","errorCode":null,"errorMessage":"op {} was not marked as #[op2(reentrant)], but re-entrantly invoked op {}","messagePattern":"op (.+?) was not marked as #\\[op2\\(reentrant\\)\\], but re-entrantly invoked op (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"libs/core/ops.rs","lineNumber":54,"sourceCode":"\n#[cfg(debug_assertions)]\nimpl Drop for ReentrancyGuard {\n  fn drop(&mut self) {\n    CURRENT_OP.with(|f| f.set(None));\n  }\n}\n\n/// Creates an op re-entrancy check for the given [`OpDecl`].\n#[cfg(debug_assertions)]\n#[doc(hidden)]\npub fn reentrancy_check(decl: &'static OpDecl) -> Option<ReentrancyGuard> {\n  if decl.is_reentrant {\n    return None;\n  }\n\n  let current = CURRENT_OP.with(|f| f.get());\n  if let Some(current) = current {\n    panic!(\n      \"op {} was not marked as #[op2(reentrant)], but re-entrantly invoked op {}\",\n      current.name, decl.name\n    );\n  }\n  CURRENT_OP.with(|f| f.set(Some(decl)));\n  Some(ReentrancyGuard {})\n}\n\n#[derive(Clone, Copy)]\npub struct OpMetadata {\n  /// A description of the op for use in sanitizer output.\n  pub sanitizer_details: Option<&'static str>,\n  /// The fix for the issue described in `sanitizer_details`.\n  pub sanitizer_fix: Option<&'static str>,\n}\n\nimpl OpMetadata {\n  pub const fn default() -> Self {","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/denoland/deno/blob/336da420f4343cbb1dcbd5eed9d075ff555ed6ee/libs/core/ops.rs#L36-L72","documentation":"In debug builds, deno_core wraps every op with a reentrancy guard: if an op is invoked while another op is still on the stack and the outer op is not marked #[op2(reentrant)], the runtime panics naming both ops. The guard exists because re-entering ops can re-borrow OpState and deadlock or corrupt state. It compiles away in release builds, so it only bites debug builds of Deno or of an embedder using deno_core.","triggerScenarios":"Running a debug build where a non-reentrant op synchronously executes JS (execute_script, calling a v8 function, console callbacks) and that JS invokes another op — the classic in-tree example is op_destructure_error re-entering op_apply_source_map, which is explicitly marked reentrant.","commonSituations":"Contributors and embedders running debug builds; custom ops that synchronously call a JS callback inside the op body without the reentrant marker; deno_core upgrades where a newly added synchronous JS call trips the previously-unnoticed guard.","solutions":["Mark the outer op #[op2(reentrant)] after auditing that it holds no OpState borrow across the JS call","Refactor the op to defer JS execution to the event loop instead of re-entering synchronously","If the named ops are Deno's own, update — the pairing may already be fixed","Do not 'fix' it by shipping only release builds; treat the panic as a real state-safety warning"],"exampleFix":"// before — op re-enters JS (which calls another op) without the marker\n#[op2]\nfn op_run_hook(scope: &mut v8::HandleScope, code: v8::Local<v8::String>) {\n  scope.execute_script(v8::String::new(scope, \"\").unwrap(), code);\n}\n\n// after — marked reentrant after verifying no OpState borrow spans the call\n#[op2(reentrant)]\nfn op_run_hook(scope: &mut v8::HandleScope, code: v8::Local<v8::String>) {\n  scope.execute_script(v8::String::new(scope, \"\").unwrap(), code);\n}","handlingStrategy":"validation","validationCode":"// audit rule: any op that synchronously executes JS must opt in\n#[op2(reentrant)] // required when this op can call back into JS\nfn op_with_js_callback(scope: &mut v8::HandleScope, cb: v8::Local<v8::Function>) {\n  // scope-based call executes JS which may invoke other ops\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Mark ops #[op2(reentrant)] only after verifying no OpState borrow spans the JS call","Prefer deferring JS callbacks to the event loop over synchronous re-entry","Run debug builds of custom extensions in CI so the reentrancy guard actually executes"],"tags":["deno-core","ops","reentrant","debug-build","embedder"],"backgroundTag":"op-reentrancy-violation","analyzedSha":"336da420f4343cbb1dcbd5eed9d075ff555ed6ee","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}