rusty_v8 icon indicating copy to clipboard operation
rusty_v8 copied to clipboard

Unreachable code reached in `ScopeData::get_current_mut(isolate)` called from external interrupt handler

Open aapoalas opened this issue 3 years ago • 2 comments

If an external interrupt handler is used to call back into rusty_v8 using CallbackScope to initialise a call stack, there is a chance that the creation of the CallbackScope will panic on unreachable!() macro in the ScopeData::get_current_mut(isolate) call.

The unreachable code is the non ScopeStatus::Current { .. } => self_mut match arm in the method, with the actual state of the scope being ScopeStatus::Shadowed { zombie: false }.

A Linux-only reproduction code is (using the deno crate) as follows:

# test_ffi/Cargo.toml, added
[dependencies]
nix = "0.24.1"
// test_ffi/src/lib.rs, added at the end
use nix::sys::signal::*;
use nix::unistd::alarm;

extern "C" fn signal_handler(_: nix::libc::c_int) {
  call_stored_function();
}

#[no_mangle]
extern "C" fn call_stored_function_after_delay() {
  let sa = SigAction::new(
    SigHandler::Handler(signal_handler),
    SaFlags::empty(),
    SigSet::empty(),
  );
  unsafe {
    sigaction(Signal::SIGALRM, &sa).unwrap();
  }
  alarm::set(1); // Increase the timeout to verify that isolate is being kept alive until this fires and that it's not just your computer being slow to run the code
}
// interrupt.js
const libPath = ""; // Get a path to target/debug/libtest_ffi.so somehow
const dylib = Deno.dlopen(libPath, {
  store_function: {
    parameters: ["function"],
    result: "void",
  },
  call_stored_function_after_delay: {
    parameters: [],
    result: "void",
  },
});

const cb = () => {
  // console.log("Sync"); // uncomment this to get random panics on double-borrow of OpState which is not exactly related to this issue
  Promise.resolve().then(() => {
    console.log("Async");
    callback.unref();
  });
};

const callback = new Deno.UnsafeCallback({
  parameters: [],
  result: "void",
}, cb);
callback.ref(); // Keep the isolate from exiting until callback is called
dylib.symbols.store_function(callback.pointer);
dylib.symbols.call_stored_function_after_delay();

I don't exactly understand what the different ScopeStates are, but it might be useful to make it possible to build up a stack from nothing.

aapoalas avatar Jul 07 '22 17:07 aapoalas

@piscisaureus can you take a look?

bartlomieju avatar Jul 07 '22 17:07 bartlomieju

Oh yeah, I nearly forgot to mention: The panic only occurs about 1 in 5 to 1 in 10 calls, at least on my machine.

aapoalas avatar Jul 07 '22 19:07 aapoalas

  • You are not supposed to re-enter v8 inside an interrupt callback.
  • CallbackScope doesn't create a scope; it merely asserts that one exists (that's why creating one is unsafe). Apparently a scope does not always exist when an interrupt callback gets called.
  • Inside a signal handler you can only call async signal safe functions. V8's API doesn't have async signal safe functions (maybe some are by accident).

piscisaureus avatar Dec 21 '22 07:12 piscisaureus