debugger: A support for data breakpoint's on variables (#34391)
Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com> Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
parent
8b6b039b63
commit
fd5650d4ed
6 changed files with 588 additions and 87 deletions
|
@ -11,6 +11,7 @@ use dap::{
|
|||
proto_conversions::ProtoConversion,
|
||||
requests::{Continue, Next},
|
||||
};
|
||||
|
||||
use rpc::proto;
|
||||
use serde_json::Value;
|
||||
use util::ResultExt;
|
||||
|
@ -813,7 +814,7 @@ impl DapCommand for RestartCommand {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct VariablesCommand {
|
||||
pub variables_reference: u64,
|
||||
pub filter: Option<VariablesArgumentsFilter>,
|
||||
|
@ -1667,6 +1668,130 @@ impl LocalDapCommand for SetBreakpoints {
|
|||
Ok(message.breakpoints)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum DataBreakpointContext {
|
||||
Variable {
|
||||
variables_reference: u64,
|
||||
name: String,
|
||||
bytes: Option<u64>,
|
||||
},
|
||||
Expression {
|
||||
expression: String,
|
||||
frame_id: Option<u64>,
|
||||
},
|
||||
Address {
|
||||
address: String,
|
||||
bytes: Option<u64>,
|
||||
},
|
||||
}
|
||||
|
||||
impl DataBreakpointContext {
|
||||
pub fn human_readable_label(&self) -> String {
|
||||
match self {
|
||||
DataBreakpointContext::Variable { name, .. } => format!("Variable: {}", name),
|
||||
DataBreakpointContext::Expression { expression, .. } => {
|
||||
format!("Expression: {}", expression)
|
||||
}
|
||||
DataBreakpointContext::Address { address, bytes } => {
|
||||
let mut label = format!("Address: {}", address);
|
||||
if let Some(bytes) = bytes {
|
||||
label.push_str(&format!(
|
||||
" ({} byte{})",
|
||||
bytes,
|
||||
if *bytes == 1 { "" } else { "s" }
|
||||
));
|
||||
}
|
||||
label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct DataBreakpointInfoCommand {
|
||||
pub context: Arc<DataBreakpointContext>,
|
||||
pub mode: Option<String>,
|
||||
}
|
||||
|
||||
impl LocalDapCommand for DataBreakpointInfoCommand {
|
||||
type Response = dap::DataBreakpointInfoResponse;
|
||||
type DapRequest = dap::requests::DataBreakpointInfo;
|
||||
const CACHEABLE: bool = true;
|
||||
|
||||
// todo(debugger): We should expand this trait in the future to take a &self
|
||||
// Depending on this command is_supported could be differentb
|
||||
fn is_supported(capabilities: &Capabilities) -> bool {
|
||||
capabilities.supports_data_breakpoints.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
|
||||
let (variables_reference, name, frame_id, as_address, bytes) = match &*self.context {
|
||||
DataBreakpointContext::Variable {
|
||||
variables_reference,
|
||||
name,
|
||||
bytes,
|
||||
} => (
|
||||
Some(*variables_reference),
|
||||
name.clone(),
|
||||
None,
|
||||
Some(false),
|
||||
*bytes,
|
||||
),
|
||||
DataBreakpointContext::Expression {
|
||||
expression,
|
||||
frame_id,
|
||||
} => (None, expression.clone(), *frame_id, Some(false), None),
|
||||
DataBreakpointContext::Address { address, bytes } => {
|
||||
(None, address.clone(), None, Some(true), *bytes)
|
||||
}
|
||||
};
|
||||
|
||||
dap::DataBreakpointInfoArguments {
|
||||
variables_reference,
|
||||
name,
|
||||
frame_id,
|
||||
bytes,
|
||||
as_address,
|
||||
mode: self.mode.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn response_from_dap(
|
||||
&self,
|
||||
message: <Self::DapRequest as dap::requests::Request>::Response,
|
||||
) -> Result<Self::Response> {
|
||||
Ok(message)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct SetDataBreakpointsCommand {
|
||||
pub breakpoints: Vec<dap::DataBreakpoint>,
|
||||
}
|
||||
|
||||
impl LocalDapCommand for SetDataBreakpointsCommand {
|
||||
type Response = Vec<dap::Breakpoint>;
|
||||
type DapRequest = dap::requests::SetDataBreakpoints;
|
||||
|
||||
fn is_supported(capabilities: &Capabilities) -> bool {
|
||||
capabilities.supports_data_breakpoints.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
|
||||
dap::SetDataBreakpointsArguments {
|
||||
breakpoints: self.breakpoints.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn response_from_dap(
|
||||
&self,
|
||||
message: <Self::DapRequest as dap::requests::Request>::Response,
|
||||
) -> Result<Self::Response> {
|
||||
Ok(message.breakpoints)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq)]
|
||||
pub(super) enum SetExceptionBreakpoints {
|
||||
Plain {
|
||||
|
@ -1776,7 +1901,7 @@ impl DapCommand for LocationsCommand {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub(crate) struct ReadMemory {
|
||||
pub(crate) memory_reference: String,
|
||||
pub(crate) offset: Option<u64>,
|
||||
|
@ -1829,25 +1954,6 @@ impl LocalDapCommand for ReadMemory {
|
|||
}
|
||||
}
|
||||
|
||||
impl LocalDapCommand for dap::DataBreakpointInfoArguments {
|
||||
type Response = dap::DataBreakpointInfoResponse;
|
||||
type DapRequest = dap::requests::DataBreakpointInfo;
|
||||
const CACHEABLE: bool = true;
|
||||
fn is_supported(capabilities: &Capabilities) -> bool {
|
||||
capabilities.supports_data_breakpoints.unwrap_or_default()
|
||||
}
|
||||
fn to_dap(&self) -> <Self::DapRequest as dap::requests::Request>::Arguments {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn response_from_dap(
|
||||
&self,
|
||||
message: <Self::DapRequest as dap::requests::Request>::Response,
|
||||
) -> Result<Self::Response> {
|
||||
Ok(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl LocalDapCommand for dap::WriteMemoryArguments {
|
||||
type Response = dap::WriteMemoryResponse;
|
||||
type DapRequest = dap::requests::WriteMemory;
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use crate::debugger::breakpoint_store::BreakpointSessionState;
|
||||
use crate::debugger::dap_command::ReadMemory;
|
||||
use crate::debugger::dap_command::{DataBreakpointContext, ReadMemory};
|
||||
use crate::debugger::memory::{self, Memory, MemoryIterator, MemoryPageBuilder, PageAddress};
|
||||
|
||||
use super::breakpoint_store::{
|
||||
BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint,
|
||||
};
|
||||
use super::dap_command::{
|
||||
self, Attach, ConfigurationDone, ContinueCommand, DisconnectCommand, EvaluateCommand,
|
||||
Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand, ModulesCommand,
|
||||
NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand, ScopesCommand,
|
||||
SetExceptionBreakpoints, SetVariableValueCommand, StackTraceCommand, StepBackCommand,
|
||||
StepCommand, StepInCommand, StepOutCommand, TerminateCommand, TerminateThreadsCommand,
|
||||
ThreadsCommand, VariablesCommand,
|
||||
self, Attach, ConfigurationDone, ContinueCommand, DataBreakpointInfoCommand, DisconnectCommand,
|
||||
EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,
|
||||
ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand,
|
||||
ScopesCommand, SetDataBreakpointsCommand, SetExceptionBreakpoints, SetVariableValueCommand,
|
||||
StackTraceCommand, StepBackCommand, StepCommand, StepInCommand, StepOutCommand,
|
||||
TerminateCommand, TerminateThreadsCommand, ThreadsCommand, VariablesCommand,
|
||||
};
|
||||
use super::dap_store::DapStore;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
|
@ -138,6 +138,13 @@ pub struct Watcher {
|
|||
pub presentation_hint: Option<VariablePresentationHint>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DataBreakpointState {
|
||||
pub dap: dap::DataBreakpoint,
|
||||
pub is_enabled: bool,
|
||||
pub context: Arc<DataBreakpointContext>,
|
||||
}
|
||||
|
||||
pub enum SessionState {
|
||||
Building(Option<Task<Result<()>>>),
|
||||
Running(RunningMode),
|
||||
|
@ -686,6 +693,7 @@ pub struct Session {
|
|||
pub(crate) breakpoint_store: Entity<BreakpointStore>,
|
||||
ignore_breakpoints: bool,
|
||||
exception_breakpoints: BTreeMap<String, (ExceptionBreakpointsFilter, IsEnabled)>,
|
||||
data_breakpoints: BTreeMap<String, DataBreakpointState>,
|
||||
background_tasks: Vec<Task<()>>,
|
||||
restart_task: Option<Task<()>>,
|
||||
task_context: TaskContext,
|
||||
|
@ -780,6 +788,7 @@ pub enum SessionEvent {
|
|||
request: RunInTerminalRequestArguments,
|
||||
sender: mpsc::Sender<Result<u32>>,
|
||||
},
|
||||
DataBreakpointInfo,
|
||||
ConsoleOutput,
|
||||
}
|
||||
|
||||
|
@ -856,6 +865,7 @@ impl Session {
|
|||
is_session_terminated: false,
|
||||
ignore_breakpoints: false,
|
||||
breakpoint_store,
|
||||
data_breakpoints: Default::default(),
|
||||
exception_breakpoints: Default::default(),
|
||||
label,
|
||||
adapter,
|
||||
|
@ -1670,6 +1680,7 @@ impl Session {
|
|||
self.invalidate_command_type::<ModulesCommand>();
|
||||
self.invalidate_command_type::<LoadedSourcesCommand>();
|
||||
self.invalidate_command_type::<ThreadsCommand>();
|
||||
self.invalidate_command_type::<DataBreakpointInfoCommand>();
|
||||
self.invalidate_command_type::<ReadMemory>();
|
||||
let executor = self.as_running().map(|running| running.executor.clone());
|
||||
if let Some(executor) = executor {
|
||||
|
@ -1906,6 +1917,10 @@ impl Session {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn data_breakpoints(&self) -> impl Iterator<Item = &DataBreakpointState> {
|
||||
self.data_breakpoints.values()
|
||||
}
|
||||
|
||||
pub fn exception_breakpoints(
|
||||
&self,
|
||||
) -> impl Iterator<Item = &(ExceptionBreakpointsFilter, IsEnabled)> {
|
||||
|
@ -1939,6 +1954,45 @@ impl Session {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn toggle_data_breakpoint(&mut self, id: &str, cx: &mut Context<'_, Session>) {
|
||||
if let Some(state) = self.data_breakpoints.get_mut(id) {
|
||||
state.is_enabled = !state.is_enabled;
|
||||
self.send_exception_breakpoints(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn send_data_breakpoints(&mut self, cx: &mut Context<Self>) {
|
||||
if let Some(mode) = self.as_running() {
|
||||
let breakpoints = self
|
||||
.data_breakpoints
|
||||
.values()
|
||||
.filter_map(|state| state.is_enabled.then(|| state.dap.clone()))
|
||||
.collect();
|
||||
let command = SetDataBreakpointsCommand { breakpoints };
|
||||
mode.request(command).detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_data_breakpoint(
|
||||
&mut self,
|
||||
context: Arc<DataBreakpointContext>,
|
||||
data_id: String,
|
||||
dap: dap::DataBreakpoint,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.data_breakpoints.remove(&data_id).is_none() {
|
||||
self.data_breakpoints.insert(
|
||||
data_id,
|
||||
DataBreakpointState {
|
||||
dap,
|
||||
is_enabled: true,
|
||||
context,
|
||||
},
|
||||
);
|
||||
}
|
||||
self.send_data_breakpoints(cx);
|
||||
}
|
||||
|
||||
pub fn breakpoints_enabled(&self) -> bool {
|
||||
self.ignore_breakpoints
|
||||
}
|
||||
|
@ -2500,6 +2554,20 @@ impl Session {
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn data_breakpoint_info(
|
||||
&mut self,
|
||||
context: Arc<DataBreakpointContext>,
|
||||
mode: Option<String>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Option<dap::DataBreakpointInfoResponse>> {
|
||||
let command = DataBreakpointInfoCommand {
|
||||
context: context.clone(),
|
||||
mode,
|
||||
};
|
||||
|
||||
self.request(command, |_, response, _| response.ok(), cx)
|
||||
}
|
||||
|
||||
pub fn set_variable_value(
|
||||
&mut self,
|
||||
stack_frame_id: u64,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue