diff --git a/src/engine/devtools/DevTool.cpp b/src/engine/devtools/DevTool.cpp index f41add73..8b9f0448 100644 --- a/src/engine/devtools/DevTool.cpp +++ b/src/engine/devtools/DevTool.cpp @@ -135,6 +135,10 @@ void DevTool::attach(ssq::VM& vm, bool sampleLocals) { attach(vm.getHandle(), sa void DevTool::attach(eve::Runtime& runtime, bool sampleLocals) { attach(runtime.vm(), sampleLocals); runtime_ = &runtime; + // Capture throw-site stacks for try/catch so reportError / lastScriptError + // can surface the original site (load.nut pattern). Safe here because the + // Runtime-bound error hook only stashes context and does not notify/pause. + sq_notifyallexceptions(runtime.handle(), SQTrue); // Route Runtime-boundary errors into the slicer/report. The VM error hook // only captures the stack when this Runtime sink is bound; this handler // reports compile / reflect / unload failures and uncaught execute errors @@ -182,7 +186,11 @@ void DevTool::attach(HSQUIRRELVM vm, bool sampleLocals) { Debugger::instance().setPump([this]() { pumpWhilePaused(); }); sq_enabledebuginfo(vm_, SQTrue); - sq_notifyallexceptions(vm_, SQTrue); + // Do not enable sq_notifyallexceptions here. With notify-all + break-on-error, + // this hook would pause on every caught throw before script catch/reportError + // runs (double pause → hung script thread → flaky teardown SEGFAULT in + // devtools.dap.caughtErrorPausesAtReportSite). Runtime::installErrorHandler + // already turns notify-all on for production VMs; attach(Runtime&) keeps it. sq_setnativedebughook(vm_, nativeDebugHook); // Route uncaught script errors into the debugger (break-on-error aware). sq_newclosure(vm_, runtimeErrorHook, 0); diff --git a/test/dap.cpp b/test/dap.cpp index a86db9c3..55ea8ee5 100644 --- a/test/dap.cpp +++ b/test/dap.cpp @@ -869,6 +869,13 @@ TEST_CASE("devtools.dap.caughtErrorPausesAtReportSite") { std::this_thread::sleep_for(std::chrono::milliseconds(10)); dap.poll(); } + // If the script thread is still blocked in waitWhilePaused, force-resume + // before teardown so dap.stop()/detach cannot race a paused VM (SEGFAULT). + if (!scriptDone.load() && dbg.isPaused()) dbg.resume(); + for (int i = 0; i < 100 && !scriptDone.load(); ++i) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + dap.poll(); + } if (scriptThread.joinable()) { if (scriptDone.load()) scriptThread.join();