Skip to content

[see #168] Changes to debugger for runtime inspector#161

Closed
alii wants to merge 8 commits intomainfrom
ali/stw3
Closed

[see #168] Changes to debugger for runtime inspector#161
alii wants to merge 8 commits intomainfrom
ali/stw3

Conversation

@alii
Copy link
Member

@alii alii commented Feb 13, 2026

#168

Summary

Fixes for JSC's Debugger to support Bun's SIGUSR1 runtime inspector activation, where the debugger attaches to a VM that is already executing code (e.g., while(true){}).

1. Idempotent Debugger::attach() with source replay

When attach() is called on a GlobalObject that already has this debugger (pre-attach case), replay sourceParsed events for all existing code blocks. Handles the case where the debugger was set on the GlobalObject before observers were registered — the initial sourceParsed calls were no-ops because canDispatchFunctionToObservers() returned false.

2. notifySourceParsedForExistingCode() helper

Uses Heap::forEachCodeBlock() to find all SourceProviders belonging to a GlobalObject via ownerExecutable()->source().provider(). Covers both function and module sources. Replaces the previous heap cell iteration approach.

3. wasCompiledWithDebuggingOpcodes() guard in DebuggerCallFrame::scope()

Code compiled without CodeGenerationMode::Debugger may have its scope register repurposed by the DFG. Reading it crashes because the slot contains stale data (e.g., a VirtualRegister offset instead of a JSScope pointer). Added the same guard that ShadowChicken already uses — falls through to callee->scope() which is always valid.

4. isPauseAtNextOpportunitySet() accessor

Public accessor for m_pauseAtNextOpportunity, used by Bun to check if a Debugger.pause CDP command was dispatched during a message drain.

5. VMTraps NeedDebuggerBreak handler improvements

  • NoEvent TOCTOU race fix: needHandling() returns true but takeTopPriorityTrap() returns NoEvent due to concurrent bit clearing. Previously hit RELEASE_ASSERT_NOT_REACHED.
  • CDP message drain before pause: Bun__drainQueuedCDPMessages (weak symbol) dispatches queued CDP messages before breakProgram() so commands like Debugger.pause set the correct pause reason on the agent.
  • Conditional breakProgram(): Bun__shouldBreakAfterMessageDrain (weak symbol) checks if a pause was actually requested. Plain message delivery skips the pause loop.

6. Only tear down agents on last frontend disconnect

Previously, disconnectFrontend unconditionally called willDestroyFrontendAndBackend which detached the debugger and disabled all agents — even when other frontends were still connected. This caused CDP responses to be lost on client reconnection. Moved the agent teardown after the hasFrontends() check so it only runs when the last frontend disconnects.

7. Linker flags

Added -Wl,-U / -Wl,-u flags for Bun__drainQueuedCDPMessages and Bun__shouldBreakAfterMessageDrain for the standalone JSC build.

@alii alii changed the title Ali/stw3 fix: improve Debugger for runtime inspector activation Feb 13, 2026
@alii alii marked this pull request as ready for review February 13, 2026 02:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@Source/JavaScriptCore/debugger/Debugger.cpp`:
- Around line 166-183: Add an early-return guard at the start of
Debugger::notifySourceParsedForExistingCode to skip allocating sourceProviders
and walking the heap when there are no registered observers for sourceParsed;
check the debugger's observer registry (e.g., a
hasObservers()/m_observers.isEmpty() style check) and return immediately if
empty, otherwise proceed to create sourceProviders, call
m_vm.heap.forEachCodeBlock to collect providers, and then call sourceParsed as
before.

@github-actions
Copy link

github-actions bot commented Feb 13, 2026

Preview Builds

Commit Release Date
802b48b3 autobuild-preview-pr-161-802b48b3 2026-02-25 07:50:21 UTC
0596ebdb autobuild-preview-pr-161-0596ebdb 2026-02-17 03:18:02 UTC
211cf848 autobuild-preview-pr-161-211cf848 2026-02-14 03:43:12 UTC
1d5717a3 autobuild-preview-pr-161-1d5717a3 2026-02-13 23:53:25 UTC
d796f228 autobuild-preview-pr-161-d796f228 2026-02-13 22:09:48 UTC
aa4d1424 autobuild-preview-pr-161-aa4d1424 2026-02-13 20:18:21 UTC
c7bcfafe autobuild-preview-pr-161-c7bcfafe 2026-02-13 02:46:53 UTC

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@Source/JavaScriptCore/debugger/Debugger.h`:
- Around line 166-167: The accessor isPauseAtNextOpportunitySet() is a
Bun-specific API and must be guarded with the build flag; wrap its declaration
and corresponding implementation (the method in class Debugger and any inline
definition) with `#if` USE(BUN_JSC_ADDITIONS) ... `#endif` so it is only compiled
when BUN_JSC_ADDITIONS is enabled, similar to other Bun-specific members in
Debugger.h/Debugger.cpp.

In `@Source/JavaScriptCore/runtime/VMTraps.cpp`:
- Around line 497-513: The block using Bun event-loop hooks should be compiled
only when the Bun event loop is enabled: wrap the calls and checks to
Bun__drainQueuedCDPMessages and Bun__shouldBreakAfterMessageDrain inside a
USE(BUN_EVENT_LOOP) guard (in addition to the existing USE(BUN_JSC_ADDITIONS])
around the VMTraps.cpp snippet) so those symbols are only referenced when the
event loop is enabled; also add/mirror the same USE(BUN_EVENT_LOOP) guard around
the weak extern declarations for Bun__drainQueuedCDPMessages and
Bun__shouldBreakAfterMessageDrain near the top of the file so the declarations
and uses are consistently guarded.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Source/JavaScriptCore/runtime/VMTraps.cpp`:
- Around line 50-53: The extern "C" weak declarations
Bun__drainQueuedCDPMessages and Bun__shouldBreakAfterMessageDrain are only
guarded by USE(BUN_JSC_ADDITIONS) but the linker flags are added under
USE(BUN_EVENT_LOOP); update both the declarations (the block around
Bun__drainQueuedCDPMessages and Bun__shouldBreakAfterMessageDrain) and their
usage sites (the section handling message drain logic) to be guarded by both
USE(BUN_JSC_ADDITIONS) and USE(BUN_EVENT_LOOP) so the declarations and calls are
compiled only when the corresponding link-time symbols are available.

@alii alii changed the title fix: improve Debugger for runtime inspector activation Changes to debugger for runtime inspector Feb 17, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Source/JavaScriptCore/runtime/VMTraps.cpp`:
- Around line 488-504: The CDP drain and break logic currently wrapped only by
USE(BUN_JSC_ADDITIONS) must also be guarded by USE(BUN_EVENT_LOOP); update the
preprocessor condition around the block that calls
Bun__drainQueuedCDPMessages(vm) and checks Bun__shouldBreakAfterMessageDrain(vm)
(the section that inspects vm.topCallFrame, calls lexicalGlobalObject(vm) and
invokes debugger->breakProgram()) so it is enclosed by both
USE(BUN_JSC_ADDITIONS) and USE(BUN_EVENT_LOOP) (e.g., combine the macros in the
`#if/`#endif) to match the extern declarations and ensure event-loop hooks are
only compiled when enabled.
- Around line 50-53: The weak extern declarations for
Bun__drainQueuedCDPMessages and Bun__shouldBreakAfterMessageDrain are event-loop
hooks and must be compiled only when both Bun JSC additions and the Bun event
loop are enabled; update the preprocessor guard around the declarations of
Bun__drainQueuedCDPMessages and Bun__shouldBreakAfterMessageDrain to require
USE(BUN_JSC_ADDITIONS) && USE(BUN_EVENT_LOOP) (i.e., change the existing `#if`
USE(BUN_JSC_ADDITIONS) to check both macros) so these symbols are only declared
when both features are present.

alii added 8 commits February 25, 2026 06:38
Previously, disconnectFrontend unconditionally called
willDestroyFrontendAndBackend which detached the debugger and
disabled all agents — even when other frontends were still connected.
This caused CDP responses to be lost on client reconnection.

Move the agent teardown after the hasFrontends() check so it only
runs when the last frontend disconnects.
@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Walkthrough

The changes restructure JavaScriptCore's debugger attachment flow, refactoring the public attach method and introducing notifySourceParsedForExistingCode for pre-loaded code providers. Updates include scope guard validation in debugger call frames, deferred agent notification on inspector frontend disconnect, CDP message draining integration in VM trap handling, and expanded linker symbol flags.

Changes

Cohort / File(s) Summary
Debugger Attachment Refactoring
Source/JavaScriptCore/debugger/Debugger.cpp, Source/JavaScriptCore/debugger/Debugger.h
Renamed previous public attach method to internal notifySourceParsedForExistingCode, added new public attach method with replay behavior for pre-attached debuggers, implemented sourceParsed event emission via forEachCodeBlock, and added conditional isPauseAtNextOpportunitySet accessor.
Debugger Call Frame Guard
Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp
Added guard condition to scope method to prevent stale scope pointer access when frame was not compiled with debugging opcodes; falls back to callee\->scope() or lexical global environment when guard condition not met.
Inspector Frontend Lifecycle
Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp
Deferred agent destruction notification in disconnectFrontend to only trigger when final frontend disconnects rather than on every disconnect.
VM Trap CDP Integration
Source/JavaScriptCore/runtime/VMTraps.cpp
Added weak external symbols Bun\__drainQueuedCDPMessages and Bun\__shouldBreakAfterMessageDrain for CDP message handling; integrated message draining and conditional debugger break in NeedDebuggerBreak trap path; added early exit on NoEvent.
Build Configuration
Source/cmake/WebKitCompilerFlags.cmake
Extended linker undefined symbol flags to include Bun\__drainQueuedCDPMessages and Bun\__shouldBreakAfterMessageDrain alongside existing Bun\__reportUnhandledError for dynamic runtime resolution.
🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is vague and overly generic; it refers broadly to "debugger" and "runtime inspector" changes without specifying the primary improvements or fixes being made. Revise the title to highlight the main change, such as 'Support runtime debugger attachment to already-executing code' or 'Make Debugger::attach() idempotent with source replay'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all seven key changes with clear explanations of the rationale and implementation details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
Source/JavaScriptCore/runtime/VMTraps.cpp (1)

50-53: Guard Bun event-loop hooks with USE(BUN_EVENT_LOOP) in addition to USE(BUN_JSC_ADDITIONS).

The linker flags for these two symbols are gated behind USE_BUN_EVENT_LOOP in WebKitCompilerFlags.cmake (Lines 259-282). The declarations (Lines 50-53) and usage (Lines 488-504) should match that guard to stay consistent, otherwise the symbols are referenced in builds where the linker doesn't expect them. As per coding guidelines, "Guard Bun-specific features with USE(BUN_JSC_ADDITIONS) and event-loop integration with USE(BUN_EVENT_LOOP)".

Also applies to: 488-504

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Source/JavaScriptCore/runtime/VMTraps.cpp` around lines 50 - 53, The
declarations for Bun__drainQueuedCDPMessages and
Bun__shouldBreakAfterMessageDrain and their usage must be wrapped with both
feature guards; update the existing `#if` USE(BUN_JSC_ADDITIONS) blocks to `#if`
USE(BUN_JSC_ADDITIONS) && USE(BUN_EVENT_LOOP) for the extern "C" weak
declarations (Bun__drainQueuedCDPMessages, Bun__shouldBreakAfterMessageDrain)
and mirror the same combined guard around the code that calls or references
those symbols (the VM trap handling block that invokes
Bun__drainQueuedCDPMessages/Bun__shouldBreakAfterMessageDrain) so declarations
and usages match the linker flags.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp`:
- Around line 155-163: The condition order in DebuggerCallFrame.cpp should
prioritize the semantic guard wasCompiledWithDebuggingOpcodes() for readability:
change the if-expression so codeBlock->wasCompiledWithDebuggingOpcodes() is
evaluated first, then codeBlock->scopeRegister().isValid(), and finally use that
to call m_validMachineFrame->scope(codeBlock->scopeRegister().offset()); keep
the existing fallthrough to callee->scope() unchanged; this only reorders checks
(accept the negligible redundant virtual call when the scope register is
invalid) and retains the same symbols and behavior.

In `@Source/JavaScriptCore/runtime/VMTraps.cpp`:
- Around line 486-506: The NeedDebuggerBreak trap currently only calls
debugger->breakProgram() inside the `#if` USE(BUN_JSC_ADDITIONS) block, causing
debugger pauses to be dropped when that macro is disabled; update VMTraps.cpp so
the core breakProgram() invocation runs unconditionally: keep the Bun-specific
CDP drain and Bun__shouldBreakAfterMessageDrain check inside the `#if`, but add an
`#else` path that, when a pause is required, walks vm.topCallFrame ->
lexicalGlobalObject(vm) and invokes debugger->breakProgram() (or otherwise call
breakProgram() directly) so that invalidateCodeBlocksOnStack(vm.topCallFrame) is
followed by the standard unconditional breakProgram() behavior even when
USE(BUN_JSC_ADDITIONS) is false, referencing NeedDebuggerBreak,
invalidateCodeBlocksOnStack, vm.topCallFrame, lexicalGlobalObject, and
debugger->breakProgram() to locate where to add the `#else` branch.

---

Duplicate comments:
In `@Source/JavaScriptCore/runtime/VMTraps.cpp`:
- Around line 50-53: The declarations for Bun__drainQueuedCDPMessages and
Bun__shouldBreakAfterMessageDrain and their usage must be wrapped with both
feature guards; update the existing `#if` USE(BUN_JSC_ADDITIONS) blocks to `#if`
USE(BUN_JSC_ADDITIONS) && USE(BUN_EVENT_LOOP) for the extern "C" weak
declarations (Bun__drainQueuedCDPMessages, Bun__shouldBreakAfterMessageDrain)
and mirror the same combined guard around the code that calls or references
those symbols (the VM trap handling block that invokes
Bun__drainQueuedCDPMessages/Bun__shouldBreakAfterMessageDrain) so declarations
and usages match the linker flags.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5197a6a and 802b48b.

📒 Files selected for processing (6)
  • Source/JavaScriptCore/debugger/Debugger.cpp
  • Source/JavaScriptCore/debugger/Debugger.h
  • Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp
  • Source/JavaScriptCore/inspector/JSGlobalObjectInspectorController.cpp
  • Source/JavaScriptCore/runtime/VMTraps.cpp
  • Source/cmake/WebKitCompilerFlags.cmake

Comment on lines +155 to 163
// 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());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify wasCompiledWithDebuggingOpcodes is declared on CodeBlock and check its semantics
rg "wasCompiledWithDebuggingOpcodes" -C 3 --type cpp --type h

Repository: oven-sh/WebKit

Length of output: 6185


🏁 Script executed:

#!/bin/bash
# Check needsScopeRegister relationship with debugging opcodes
rg "needsScopeRegister" -C 3 --type cpp --type h | head -100

Repository: oven-sh/WebKit

Length of output: 4204


🏁 Script executed:

#!/bin/bash
# Locate CodeBlock.h to inspect the method declaration
fd "CodeBlock.h" --type f

Repository: oven-sh/WebKit

Length of output: 697


Core guard is correct — minor readability nit on condition ordering.

The fix is sound: guarding the scope-register read with wasCompiledWithDebuggingOpcodes() is the right predicate. DFGGraph::m_hasDebuggerEnabled is directly set based on wasCompiledWithDebuggingOpcodes(), and DFGStackLayoutPhase invalidates the scope register when debugger is not enabled. The fallback to callee->scope() is safe for the attach-to-running-code scenario even though it may miss any active with-scope layer.

One readability nit: the semantic primary guard is wasCompiledWithDebuggingOpcodes(), but it is checked last. Putting it first makes the intent immediately clear at the cost of one redundant virtual call if the scope register is invalid — not a correctness issue.

♻️ Optional reordering for clarity
-        else if (codeBlock && codeBlock->scopeRegister().isValid()
-                 && codeBlock->wasCompiledWithDebuggingOpcodes())
+        else if (codeBlock && codeBlock->wasCompiledWithDebuggingOpcodes()
+                 && codeBlock->scopeRegister().isValid())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 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());
// 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->wasCompiledWithDebuggingOpcodes()
&& codeBlock->scopeRegister().isValid())
scope = m_validMachineFrame->scope(codeBlock->scopeRegister().offset());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp` around lines 155 - 163,
The condition order in DebuggerCallFrame.cpp should prioritize the semantic
guard wasCompiledWithDebuggingOpcodes() for readability: change the
if-expression so codeBlock->wasCompiledWithDebuggingOpcodes() is evaluated
first, then codeBlock->scopeRegister().isValid(), and finally use that to call
m_validMachineFrame->scope(codeBlock->scopeRegister().offset()); keep the
existing fallthrough to callee->scope() unchanged; this only reorders checks
(accept the negligible redundant virtual call when the scope register is
invalid) and retains the same symbols and behavior.

Comment on lines 486 to 506
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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check git history for VMTraps.cpp
cd Source/JavaScriptCore/runtime
git log --oneline -n 30 VMTraps.cpp

Repository: oven-sh/WebKit

Length of output: 123


🏁 Script executed:

#!/bin/bash
# Get the detailed diff for the NeedDebuggerBreak case to see what changed
git show HEAD -- Source/JavaScriptCore/runtime/VMTraps.cpp | grep -A 40 -B 5 "NeedDebuggerBreak"

Repository: oven-sh/WebKit

Length of output: 1974


🏁 Script executed:

#!/bin/bash
# Check the previous version to see the original implementation
git show HEAD~1:Source/JavaScriptCore/runtime/VMTraps.cpp 2>/dev/null | sed -n '480,510p' || echo "Previous version not available or file path differs"

Repository: oven-sh/WebKit

Length of output: 40


🏁 Script executed:

#!/bin/bash
# Check the parent commit to see original code
git show HEAD~1:Source/JavaScriptCore/runtime/VMTraps.cpp | grep -A 20 "NeedDebuggerBreak" | head -30

Repository: oven-sh/WebKit

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Also check if NeedDebuggerBreak existed at all before
git log --all -S "NeedDebuggerBreak" --oneline -- Source/JavaScriptCore/runtime/VMTraps.cpp | head -10

Repository: oven-sh/WebKit

Length of output: 424


🏁 Script executed:

#!/bin/bash
# Get more context - what does the current file look like in full context?
git show HEAD:Source/JavaScriptCore/runtime/VMTraps.cpp | sed -n '480,520p'

Repository: oven-sh/WebKit

Length of output: 1771


🏁 Script executed:

#!/bin/bash
# Find all references to NeedDebuggerBreak
rg "NeedDebuggerBreak" --type cpp --type h -B 3 -A 3

Repository: oven-sh/WebKit

Length of output: 6042


🏁 Script executed:

#!/bin/bash
# Check the enum definition for trap events
rg "enum.*Trap|NeedDebuggerBreak" Source/JavaScriptCore/runtime/VMTraps.h -B 5 -A 5

Repository: oven-sh/WebKit

Length of output: 1453


🏁 Script executed:

#!/bin/bash
# Search for any unconditional breakProgram calls in the trap handler
rg "breakProgram" Source/JavaScriptCore/runtime/VMTraps.cpp -B 2 -A 2

Repository: oven-sh/WebKit

Length of output: 621


🏁 Script executed:

#!/bin/bash
# Check how USE(BUN_JSC_ADDITIONS) is defined
rg "define.*USE.*BUN_JSC_ADDITIONS|#if.*USE.*BUN_JSC_ADDITIONS" -B 2 -A 2 | head -40

Repository: oven-sh/WebKit

Length of output: 2516


🏁 Script executed:

#!/bin/bash
# Check if this code is meant to work without Bun additions
rg "NeedDebuggerBreak" Source/WebKit -A 5 -B 5 | grep -A 10 -B 10 "notifyNeedDebuggerBreak"

Repository: oven-sh/WebKit

Length of output: 3260


🏁 Script executed:

#!/bin/bash
# Look for similar patterns - how do other trap handlers handle conditional code?
rg "case Need" Source/JavaScriptCore/runtime/VMTraps.cpp -A 15 | head -80

Repository: oven-sh/WebKit

Length of output: 2202


The entire breakProgram() handler path is conditionally compiled on USE(BUN_JSC_ADDITIONS), breaking debugger functionality when disabled.

WebKit's WebInspectorInterruptDispatcher unconditionally calls vm->notifyNeedDebuggerBreak() to pause execution during debugging. However, the handler only calls breakProgram() inside the #if USE(BUN_JSC_ADDITIONS) guard. When this macro is disabled, the NeedDebuggerBreak trap becomes a silent no-op—only invalidateCodeBlocksOnStack() runs, and all debugger pause requests are silently dropped.

Unlike other trap handlers (NeedShellTimeoutCheck, NeedWatchdogCheck, NeedTermination, NeedStopTheWorld, NeedExceptionHandling), which all execute unconditionally, this is the only handler with core functionality guarded. The Bun-specific parts (CDP message draining, the shouldBreakAfterMessageDrain check) can remain guarded, but the core breakProgram() invocation must execute unconditionally to preserve standard WebKit debugger behavior.

Add an #else clause that preserves the unconditional breakProgram() call:

Proposed fix
             }
         }
 `#endif`
+#else
+            if (vm.topCallFrame) {
+                if (auto* globalObject = vm.topCallFrame->lexicalGlobalObject(vm)) {
+                    if (auto* debugger = globalObject->debugger())
+                        debugger->breakProgram();
+                }
+            }
+#endif
             didHandleTrap = true;
             break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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;
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();
}
}
}
`#else`
if (vm.topCallFrame) {
if (auto* globalObject = vm.topCallFrame->lexicalGlobalObject(vm)) {
if (auto* debugger = globalObject->debugger())
debugger->breakProgram();
}
}
`#endif`
didHandleTrap = true;
break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Source/JavaScriptCore/runtime/VMTraps.cpp` around lines 486 - 506, The
NeedDebuggerBreak trap currently only calls debugger->breakProgram() inside the
`#if` USE(BUN_JSC_ADDITIONS) block, causing debugger pauses to be dropped when
that macro is disabled; update VMTraps.cpp so the core breakProgram() invocation
runs unconditionally: keep the Bun-specific CDP drain and
Bun__shouldBreakAfterMessageDrain check inside the `#if`, but add an `#else` path
that, when a pause is required, walks vm.topCallFrame -> lexicalGlobalObject(vm)
and invokes debugger->breakProgram() (or otherwise call breakProgram() directly)
so that invalidateCodeBlocksOnStack(vm.topCallFrame) is followed by the standard
unconditional breakProgram() behavior even when USE(BUN_JSC_ADDITIONS) is false,
referencing NeedDebuggerBreak, invalidateCodeBlocksOnStack, vm.topCallFrame,
lexicalGlobalObject, and debugger->breakProgram() to locate where to add the
`#else` branch.

@alii alii closed this Feb 25, 2026
@alii alii changed the title Changes to debugger for runtime inspector [see #168] Changes to debugger for runtime inspector Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant