diff --git a/Source/JavaScriptCore/debugger/Debugger.cpp b/Source/JavaScriptCore/debugger/Debugger.cpp index 1aa6438cedc5..f3b8fecb1896 100644 --- a/Source/JavaScriptCore/debugger/Debugger.cpp +++ b/Source/JavaScriptCore/debugger/Debugger.cpp @@ -163,34 +163,51 @@ Debugger::~Debugger() globalObject->setDebugger(nullptr); } -void Debugger::attach(JSGlobalObject* globalObject) +void Debugger::notifySourceParsedForExistingCode(JSGlobalObject* globalObject) { - ASSERT(!globalObject->debugger()); - globalObject->setDebugger(this); - m_globalObjects.add(globalObject); - - m_vm.setShouldBuildPCToCodeOriginMapping(); + // Avoid heap walk when no observers are registered — sourceParsed will + // early-return for every entry anyway. + if (!canDispatchFunctionToObservers()) + return; - // Call `sourceParsed` after iterating because it will execute JavaScript in Web Inspector. + // Enumerate all CodeBlocks to find source providers for scripts that were + // already loaded before the debugger attached. Fires sourceParsed for each + // one, notifying observers (e.g., InspectorDebuggerAgent sends scriptParsed). UncheckedKeyHashSet> sourceProviders; { JSLockHolder locker(m_vm); - HeapIterationScope iterationScope(m_vm.heap); - m_vm.heap.objectSpace().forEachLiveCell(iterationScope, [&] (HeapCell* heapCell, HeapCell::Kind kind) { - if (isJSCellKind(kind)) { - auto* cell = static_cast(heapCell); - if (auto* function = jsDynamicCast(cell)) { - if (function->scope()->globalObject() == globalObject && function->executable()->isFunctionExecutable() && !function->isHostOrBuiltinFunction()) - sourceProviders.add(jsCast(function->executable())->source().provider()); - } + m_vm.heap.forEachCodeBlock([&](CodeBlock* codeBlock) { + if (codeBlock->globalObject() == globalObject) { + if (auto* provider = codeBlock->ownerExecutable()->source().provider()) + sourceProviders.add(provider); } - return IterationStatus::Continue; }); } for (auto& sourceProvider : sourceProviders) sourceParsed(globalObject, sourceProvider.get(), -1, nullString()); } +void Debugger::attach(JSGlobalObject* globalObject) +{ + if (globalObject->debugger() == this) { +#if USE(BUN_JSC_ADDITIONS) + // Debugger was pre-attached before observers were registered. + // Replay sourceParsed events now that observers exist. + if (canDispatchFunctionToObservers()) + notifySourceParsedForExistingCode(globalObject); +#endif + return; + } + ASSERT(!globalObject->debugger()); + globalObject->setDebugger(this); + m_globalObjects.add(globalObject); + + m_vm.setShouldBuildPCToCodeOriginMapping(); + + // Call `sourceParsed` after iterating because it will execute JavaScript in Web Inspector. + notifySourceParsedForExistingCode(globalObject); +} + void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason) { // If we're detaching from the currently executing global object, manually tear down our diff --git a/Source/JavaScriptCore/debugger/Debugger.h b/Source/JavaScriptCore/debugger/Debugger.h index 2f664e697202..0ad627c4097f 100644 --- a/Source/JavaScriptCore/debugger/Debugger.h +++ b/Source/JavaScriptCore/debugger/Debugger.h @@ -142,6 +142,8 @@ class Debugger : public DoublyLinkedListNode { JS_EXPORT_PRIVATE virtual void sourceParsed(JSGlobalObject*, SourceProvider*, int errorLineNumber, const WTF::String& errorMessage); + void notifySourceParsedForExistingCode(JSGlobalObject*); + void exception(JSGlobalObject*, CallFrame*, JSValue exceptionValue, bool hasCatchHandler); void atStatement(CallFrame*); void atExpression(CallFrame*); @@ -161,6 +163,10 @@ class Debugger : public DoublyLinkedListNode { void registerCodeBlock(CodeBlock*); void forEachRegisteredCodeBlock(NOESCAPE const Function&); +#if USE(BUN_JSC_ADDITIONS) + bool isPauseAtNextOpportunitySet() const { return m_pauseAtNextOpportunity; } +#endif + void didCreateNativeExecutable(NativeExecutable&); void willCallNativeExecutable(CallFrame*); diff --git a/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp b/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp index 10be94d63a3f..5d3176d4a9de 100644 --- a/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp +++ b/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp @@ -152,7 +152,14 @@ DebuggerScope* DebuggerCallFrame::scope(VM& vm) CodeBlock* codeBlock = m_validMachineFrame->isNativeCalleeFrame() ? nullptr : m_validMachineFrame->codeBlock(); if (isTailDeleted()) scope = m_shadowChickenFrame.scope; - else if (codeBlock && codeBlock->scopeRegister().isValid()) + // Code compiled without CodeGenerationMode::Debugger may have its scope + // register repurposed by the DFG (DFGStackLayoutPhase invalidates it when + // needsScopeRegister() is false). Reading the scope register from such a + // frame crashes because the slot contains stale data (e.g., a VirtualRegister + // offset instead of a JSScope pointer). ShadowChicken has the same guard. + // Falls through to callee->scope() which is always valid. + else if (codeBlock && codeBlock->scopeRegister().isValid() + && codeBlock->wasCompiledWithDebuggingOpcodes()) scope = m_validMachineFrame->scope(codeBlock->scopeRegister().offset()); else if (JSCallee* callee = jsDynamicCast(m_validMachineFrame->jsCallee())) scope = callee->scope(); diff --git a/Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp b/Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp index 803c4e3acbbc..1ce34b3f736a 100644 --- a/Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp +++ b/Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp @@ -149,9 +149,6 @@ void JSGlobalObjectInspectorController::connectFrontend(FrontendChannel& fronten void JSGlobalObjectInspectorController::disconnectFrontend(FrontendChannel& frontendChannel) { - // FIXME: change this to notify agents which frontend has disconnected (by id). - m_agents.willDestroyFrontendAndBackend(DisconnectReason::InspectorDestroyed); - m_frontendRouter->disconnectFrontend(frontendChannel); m_isAutomaticInspection = false; @@ -161,6 +158,8 @@ void JSGlobalObjectInspectorController::disconnectFrontend(FrontendChannel& fron if (!disconnectedLastFrontend) return; + m_agents.willDestroyFrontendAndBackend(DisconnectReason::InspectorDestroyed); + #if ENABLE(INSPECTOR_ALTERNATE_DISPATCHERS) if (m_augmentingClient) m_augmentingClient->inspectorDisconnected(); diff --git a/Source/JavaScriptCore/runtime/VMTraps.cpp b/Source/JavaScriptCore/runtime/VMTraps.cpp index 13a32c3df2b8..394934bd5e49 100644 --- a/Source/JavaScriptCore/runtime/VMTraps.cpp +++ b/Source/JavaScriptCore/runtime/VMTraps.cpp @@ -29,6 +29,7 @@ #include "CallFrameInlines.h" #include "CodeBlock.h" #include "CodeBlockSet.h" +#include "Debugger.h" #include "DFGCommonData.h" #include "ExceptionHelpers.h" #include "HeapInlines.h" @@ -46,6 +47,11 @@ #include #include +#if USE(BUN_JSC_ADDITIONS) +extern "C" __attribute__((weak)) void Bun__drainQueuedCDPMessages(JSC::VM&); +extern "C" __attribute__((weak)) bool Bun__shouldBreakAfterMessageDrain(JSC::VM&); +#endif + namespace JSC { #if ENABLE(SIGNAL_BASED_VM_TRAPS) @@ -474,9 +480,28 @@ bool VMTraps::handleTraps(VMTraps::BitField mask) bool didHandleTrap = false; while (needHandling(mask)) { auto event = takeTopPriorityTrap(mask); + if (event == NoEvent) + break; switch (event) { case NeedDebuggerBreak: invalidateCodeBlocksOnStack(vm.topCallFrame); +#if USE(BUN_JSC_ADDITIONS) + // Drain queued CDP messages. If a command like Debugger.pause + // is dispatched, it sets m_javaScriptPauseScheduled on the agent. + if (Bun__drainQueuedCDPMessages) + Bun__drainQueuedCDPMessages(vm); + // Only enter breakProgram() if a pause was actually requested + // (bootstrap, Debugger.pause command, breakpoint). For plain + // message delivery, the drain above is sufficient. + if (!Bun__shouldBreakAfterMessageDrain || Bun__shouldBreakAfterMessageDrain(vm)) { + if (vm.topCallFrame) { + if (auto* globalObject = vm.topCallFrame->lexicalGlobalObject(vm)) { + if (auto* debugger = globalObject->debugger()) + debugger->breakProgram(); + } + } + } +#endif didHandleTrap = true; break; diff --git a/Source/cmake/WebKitCompilerFlags.cmake b/Source/cmake/WebKitCompilerFlags.cmake index c94cb1924ac5..0d071d3bd30f 100644 --- a/Source/cmake/WebKitCompilerFlags.cmake +++ b/Source/cmake/WebKitCompilerFlags.cmake @@ -265,7 +265,9 @@ if (COMPILER_IS_GCC_OR_CLANG) -Wl,-U,_WTFTimer__secondsUntilTimer -Wl,-U,_WTFTimer__cancel -Wl,-U,_Bun__errorInstance__finalize - -Wl,-U,_Bun__reportUnhandledError) + -Wl,-U,_Bun__reportUnhandledError + -Wl,-U,_Bun__drainQueuedCDPMessages + -Wl,-U,_Bun__shouldBreakAfterMessageDrain) else() WEBKIT_PREPEND_GLOBAL_COMPILER_FLAGS(-Wl,-u,_WTFTimer__create -Wl,-u,_WTFTimer__update @@ -274,7 +276,9 @@ if (COMPILER_IS_GCC_OR_CLANG) -Wl,-u,_WTFTimer__secondsUntilTimer -Wl,-u,_WTFTimer__cancel -Wl,-u,_Bun__errorInstance__finalize - -Wl,-u,_Bun__reportUnhandledError) + -Wl,-u,_Bun__reportUnhandledError + -Wl,-u,_Bun__drainQueuedCDPMessages + -Wl,-u,_Bun__shouldBreakAfterMessageDrain) endif() endif ()