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:
Anthony Eid
2025-07-14 17:45:46 +00:00
committed by GitHub
co-authored by Piotr Osiewicz Mikayla Maki Mikayla Maki
parent 8b6b039b63
commit fd5650d4ed
6 changed files with 588 additions and 87 deletions
+127 -21
View File
@@ -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;