debugger: Fix phantom JavaScript frames (#32469)

JavaScript debugger is using a phantom stack frame to delineate await
points; that frame reuses a frame ID of 0, which collides with other
frames returned from that adapter.

934075df8c/src/adapter/stackTrace.ts (L287)

The bug has since been fixed in
https://github.com/microsoft/vscode-js-debug/issues/2234, but we'll need
to wait for a new release of node debugger for that to make a
difference. Until then..

Release Notes:

- Fixed a bug with JavaScript debugging which led to stack trace list
containing excessive amount of `await` entries.

---------

Co-authored-by: Conrad Irwin <conrad@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Piotr Osiewicz 2025-06-10 22:48:07 +02:00 committed by GitHub
parent 71d5c57119
commit 295db79c47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -13,7 +13,7 @@ use super::dap_command::{
}; };
use super::dap_store::DapStore; use super::dap_store::DapStore;
use anyhow::{Context as _, Result, anyhow}; use anyhow::{Context as _, Result, anyhow};
use collections::{HashMap, HashSet, IndexMap, IndexSet}; use collections::{HashMap, HashSet, IndexMap};
use dap::adapters::{DebugAdapterBinary, DebugAdapterName}; use dap::adapters::{DebugAdapterBinary, DebugAdapterName};
use dap::messages::Response; use dap::messages::Response;
use dap::requests::{Request, RunInTerminal, StartDebugging}; use dap::requests::{Request, RunInTerminal, StartDebugging};
@ -25,7 +25,7 @@ use dap::{
}; };
use dap::{ use dap::{
ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEvent, OutputEventCategory, ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEvent, OutputEventCategory,
RunInTerminalRequestArguments, StartDebuggingRequestArguments, RunInTerminalRequestArguments, StackFramePresentationHint, StartDebuggingRequestArguments,
}; };
use futures::SinkExt; use futures::SinkExt;
use futures::channel::{mpsc, oneshot}; use futures::channel::{mpsc, oneshot};
@ -106,7 +106,7 @@ impl ThreadStatus {
#[derive(Debug)] #[derive(Debug)]
pub struct Thread { pub struct Thread {
dap: dap::Thread, dap: dap::Thread,
stack_frame_ids: IndexSet<StackFrameId>, stack_frames: Vec<StackFrame>,
_has_stopped: bool, _has_stopped: bool,
} }
@ -114,7 +114,7 @@ impl From<dap::Thread> for Thread {
fn from(dap: dap::Thread) -> Self { fn from(dap: dap::Thread) -> Self {
Self { Self {
dap, dap,
stack_frame_ids: Default::default(), stack_frames: Default::default(),
_has_stopped: false, _has_stopped: false,
} }
} }
@ -1948,8 +1948,8 @@ impl Session {
let stack_frames = stack_frames.log_err()?; let stack_frames = stack_frames.log_err()?;
let entry = this.threads.entry(thread_id).and_modify(|thread| { let entry = this.threads.entry(thread_id).and_modify(|thread| {
thread.stack_frame_ids = thread.stack_frames =
stack_frames.iter().map(|frame| frame.id).collect(); stack_frames.iter().cloned().map(StackFrame::from).collect();
}); });
debug_assert!( debug_assert!(
matches!(entry, indexmap::map::Entry::Occupied(_)), matches!(entry, indexmap::map::Entry::Occupied(_)),
@ -1959,6 +1959,15 @@ impl Session {
this.stack_frames.extend( this.stack_frames.extend(
stack_frames stack_frames
.iter() .iter()
.filter(|frame| {
// Workaround for JavaScript debug adapter sending out "fake" stack frames for delineating await points. This is fine,
// except that they always use an id of 0 for it, which collides with other (valid) stack frames.
!(frame.id == 0
&& frame.line == 0
&& frame.column == 0
&& frame.presentation_hint
== Some(StackFramePresentationHint::Label))
})
.cloned() .cloned()
.map(|frame| (frame.id, StackFrame::from(frame))), .map(|frame| (frame.id, StackFrame::from(frame))),
); );
@ -1976,14 +1985,7 @@ impl Session {
self.threads self.threads
.get(&thread_id) .get(&thread_id)
.map(|thread| { .map(|thread| thread.stack_frames.clone())
thread
.stack_frame_ids
.iter()
.filter_map(|id| self.stack_frames.get(id))
.cloned()
.collect()
})
.unwrap_or_default() .unwrap_or_default()
} }