midenc_debug/debug/
breakpoint.rs

1use std::{ops::Deref, str::FromStr};
2
3use glob::Pattern;
4use miden_processor::VmState;
5
6use super::ResolvedLocation;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Breakpoint {
10    pub id: u8,
11    pub creation_cycle: usize,
12    pub ty: BreakpointType,
13}
14
15impl Default for Breakpoint {
16    fn default() -> Self {
17        Self {
18            id: 0,
19            creation_cycle: 0,
20            ty: BreakpointType::Step,
21        }
22    }
23}
24
25impl Breakpoint {
26    /// Return the number of cycles this breakpoint indicates we should skip, or `None` if the
27    /// number of cycles is context-specific, or the breakpoint is triggered by something other
28    /// than cycle count.
29    pub fn cycles_to_skip(&self, current_cycle: usize) -> Option<usize> {
30        let cycles_passed = current_cycle - self.creation_cycle;
31        match &self.ty {
32            BreakpointType::Step => Some(1),
33            BreakpointType::StepN(n) => Some(n.saturating_sub(cycles_passed)),
34            BreakpointType::StepTo(to) if to >= &current_cycle => Some(to.abs_diff(current_cycle)),
35            _ => None,
36        }
37    }
38}
39impl Deref for Breakpoint {
40    type Target = BreakpointType;
41
42    #[inline]
43    fn deref(&self) -> &Self::Target {
44        &self.ty
45    }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum BreakpointType {
50    /// Break at next cycle
51    Step,
52    /// Skip N cycles
53    StepN(usize),
54    /// Break at a given cycle
55    StepTo(usize),
56    /// Break at the first cycle of the next instruction
57    Next,
58    /// Break when we exit the current call frame
59    Finish,
60    /// Break when any cycle corresponds to a source location whose file matches PATTERN
61    File(Pattern),
62    /// Break when any cycle corresponds to a source location whose file matches PATTERN and occurs
63    /// on LINE
64    Line { pattern: Pattern, line: u32 },
65    /// Break anytime the given operation occurs
66    Opcode(miden_core::Operation),
67    /// Break when any cycle causes us to push a frame for PROCEDURE on the call stack
68    Called(Pattern),
69}
70impl BreakpointType {
71    /// Return true if this breakpoint indicates we should break for `current_op`
72    pub fn should_break_for(&self, current_op: &miden_core::Operation) -> bool {
73        match self {
74            Self::Opcode(op) => current_op == op,
75            _ => false,
76        }
77    }
78
79    /// Return true if this breakpoint indicates we should break on entry to `procedure`
80    pub fn should_break_in(&self, procedure: &str) -> bool {
81        match self {
82            Self::Called(pattern) => pattern.matches(procedure),
83            _ => false,
84        }
85    }
86
87    /// Return true if this breakpoint indicates we should break at `loc`
88    pub fn should_break_at(&self, loc: &ResolvedLocation) -> bool {
89        match self {
90            Self::File(pattern) => pattern.matches_path(loc.source_file.path()),
91            Self::Line { pattern, line } if line == &loc.line => {
92                pattern.matches_path(loc.source_file.path())
93            }
94            _ => false,
95        }
96    }
97
98    /// Returns true if this breakpoint is internal to the debugger (i.e. not creatable via :b)
99    pub fn is_internal(&self) -> bool {
100        matches!(self, BreakpointType::Next | BreakpointType::Step | BreakpointType::Finish)
101    }
102
103    /// Returns true if this breakpoint is removed upon being hit
104    pub fn is_one_shot(&self) -> bool {
105        matches!(
106            self,
107            BreakpointType::Next
108                | BreakpointType::Finish
109                | BreakpointType::Step
110                | BreakpointType::StepN(_)
111                | BreakpointType::StepTo(_)
112        )
113    }
114}
115
116impl FromStr for BreakpointType {
117    type Err = String;
118
119    fn from_str(s: &str) -> Result<Self, Self::Err> {
120        let s = s.trim();
121
122        // b next
123        // b finish
124        // b after {n}
125        // b for {opcode}
126        // b at {cycle}
127        // b in {procedure}
128        // b {file}[:{line}]
129        if s == "next" {
130            return Ok(BreakpointType::Next);
131        }
132        if s == "finish" {
133            return Ok(BreakpointType::Finish);
134        }
135        if let Some(n) = s.strip_prefix("after ") {
136            let n = n.trim().parse::<usize>().map_err(|err| {
137                format!("invalid breakpoint expression: could not parse cycle count: {err}")
138            })?;
139            return Ok(BreakpointType::StepN(n));
140        }
141        if let Some(_opcode) = s.strip_prefix("for ") {
142            todo!()
143        }
144        if let Some(cycle) = s.strip_prefix("at ") {
145            let cycle = cycle.trim().parse::<usize>().map_err(|err| {
146                format!("invalid breakpoint expression: could not parse cycle value: {err}")
147            })?;
148            return Ok(BreakpointType::StepTo(cycle));
149        }
150        if let Some(procedure) = s.strip_prefix("in ") {
151            let pattern = Pattern::new(procedure.trim())
152                .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
153            return Ok(BreakpointType::Called(pattern));
154        }
155        match s.split_once(':') {
156            Some((file, line)) => {
157                let pattern = Pattern::new(file.trim())
158                    .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
159                let line = line.trim().parse::<u32>().map_err(|err| {
160                    format!("invalid breakpoint expression: could not parse line: {err}")
161                })?;
162                Ok(BreakpointType::Line { pattern, line })
163            }
164            None => {
165                let pattern = Pattern::new(s.trim())
166                    .map_err(|err| format!("invalid breakpoint expression: bad pattern: {err}"))?;
167                Ok(BreakpointType::File(pattern))
168            }
169        }
170    }
171}