Skip to main content

jdwp_client/
extra.rs

1// Additional JDWP commands: single-stepping, clear-all-breakpoints, breakpoint
2// modifiers (count/thread), array access, string creation, and setting frame values.
3
4use crate::commands::{command_sets, event_commands, event_kinds, step_depths, step_sizes, vm_commands};
5use crate::connection::JdwpConnection;
6use crate::eval::{write_tagged_value, write_untagged_value};
7use crate::eventrequest::{EventFilters, SuspendPolicy};
8use crate::protocol::{CommandPacket, JdwpResult};
9use crate::reader::{read_i32, read_u64, read_u8, read_value_by_tag};
10use crate::types::{FrameId, MethodId, ObjectId, ReferenceTypeId, ThreadId, Value, ValueData};
11use bytes::BufMut;
12
13// JDWP modifier kinds
14const MOD_COUNT: u8 = 1;
15const MOD_THREAD_ONLY: u8 = 3;
16/// `ClassOnly` (4): restrict the request to classes matching a pattern.
17const MOD_CLASS_ONLY: u8 = 4;
18/// `ClassExclude` (6): drop events from classes matching a pattern. One modifier per pattern —
19/// JDWP takes a single string each, so N exclusions are N modifiers on one request.
20const MOD_CLASS_EXCLUDE: u8 = 6;
21const MOD_LOCATION_ONLY: u8 = 7;
22const MOD_STEP: u8 = 10;
23/// `InstanceOnly` (11): restrict the request to hits whose `this` is one specific object (FILT-9).
24const MOD_INSTANCE_ONLY: u8 = 11;
25// ArrayReference command set (13)
26const ARRAY_LENGTH: u8 = 1;
27const ARRAY_GET_VALUES: u8 = 2;
28const ARRAY_SET_VALUES: u8 = 3;
29
30/// Write a JDWP string: a big-endian `u32` byte length, then the UTF-8 bytes.
31fn write_jdwp_string(packet: &mut CommandPacket, s: &str) {
32    let b = s.as_bytes();
33    packet.data.put_u32(u32::try_from(b.len()).unwrap_or(u32::MAX));
34    packet.data.extend_from_slice(b);
35}
36
37/// Step depth selector for `set_step`.
38#[derive(Debug, Clone, Copy)]
39pub enum StepDepth {
40    Into,
41    Over,
42    Out,
43}
44
45impl JdwpConnection {
46    /// Set a breakpoint with optional Count (stop on Nth hit) and `ThreadOnly` filters.
47    ///
48    /// # Errors
49    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
50    pub async fn set_breakpoint_ex(
51        &mut self,
52        class_id: ReferenceTypeId,
53        method_id: MethodId,
54        bytecode_index: u64,
55        suspend_policy: SuspendPolicy,
56        filters: EventFilters,
57    ) -> JdwpResult<i32> {
58        let id = self.next_id();
59        let mut packet = CommandPacket::new(id, command_sets::EVENT_REQUEST, event_commands::SET);
60        packet.data.put_u8(event_kinds::BREAKPOINT);
61        packet.data.put_u8(suspend_policy as u8);
62
63        let n_mods = 1
64            + i32::from(filters.count.is_some())
65            + i32::from(filters.thread.is_some())
66            + i32::from(filters.instance.is_some());
67        packet.data.put_i32(n_mods);
68
69        // LocationOnly
70        packet.data.put_u8(MOD_LOCATION_ONLY);
71        packet.data.put_u8(1); // class type tag
72        packet.data.put_u64(class_id);
73        packet.data.put_u64(method_id);
74        packet.data.put_u64(bytecode_index);
75
76        if let Some(c) = filters.count {
77            packet.data.put_u8(MOD_COUNT);
78            packet.data.put_i32(c);
79        }
80        if let Some(t) = filters.thread {
81            packet.data.put_u8(MOD_THREAD_ONLY);
82            packet.data.put_u64(t);
83        }
84        if let Some(o) = filters.instance {
85            packet.data.put_u8(MOD_INSTANCE_ONLY);
86            packet.data.put_u64(o);
87        }
88
89        let reply = self.send_command(packet).await?;
90        reply.check_error()?;
91        let mut data = reply.data();
92        read_i32(&mut data)
93    }
94
95    /// Set a single-step request (EventRequest.Set, `SINGLE_STEP`). Returns the request id;
96    /// clear it with `clear_step` before resuming again, or stepping will run away.
97    ///
98    /// # Errors
99    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
100    pub async fn set_step(&mut self, thread: ThreadId, depth: StepDepth) -> JdwpResult<i32> {
101        self.set_step_ex(thread, depth, &[], &[]).await
102    }
103
104    /// Set a single-step request with `ClassExclude` / `ClassOnly` filtering (STEP-1).
105    ///
106    /// `exclude` drops events from classes matching each pattern; `only` restricts the request to
107    /// classes matching each pattern. Patterns are JDWP's own form — an exact class name, or one with a
108    /// single leading or trailing `*` (`java.*`, `*.OrderService`) — and the JVM matches them against
109    /// the **dotted** class name.
110    ///
111    /// **One modifier per pattern.** JDWP's `ClassExclude` carries a single string, so N exclusions
112    /// occupy N of the request's modifier slots and the count written into the packet has to include
113    /// all of them. Getting that count wrong does not produce a complaint about the modifier: the JVM
114    /// reads the next bytes as another modifier and answers `INTERNAL` (113), which says nothing about
115    /// the cause — the same failure the `Count`/`ThreadOnly` pair already carries a warning about.
116    ///
117    /// **How many the JVM tolerates, measured rather than assumed** (Temurin 17, HotSpot): a step
118    /// request with **5000** `ClassExclude` modifiers was accepted without complaint, as were 255, 256
119    /// and 1000 — so there is no practical cap to defend against and no error path to translate. The
120    /// count field is an `i32` and the packet is length-prefixed; nothing here bounds it before that.
121    /// Worth measuring because the byte-level failure mode above gives no signal, so a cap discovered in
122    /// production would have looked like a bug in this function rather than a limit.
123    ///
124    /// # Errors
125    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
126    pub async fn set_step_ex(
127        &mut self,
128        thread: ThreadId,
129        depth: StepDepth,
130        exclude: &[String],
131        only: &[String],
132    ) -> JdwpResult<i32> {
133        let id = self.next_id();
134        let mut packet = CommandPacket::new(id, command_sets::EVENT_REQUEST, event_commands::SET);
135        packet.data.put_u8(event_kinds::SINGLE_STEP);
136        packet.data.put_u8(SuspendPolicy::All as u8);
137        // Step, plus one modifier per pattern.
138        let n_mods = 1 + exclude.len() + only.len();
139        packet.data.put_i32(i32::try_from(n_mods).unwrap_or(i32::MAX));
140        packet.data.put_u8(MOD_STEP);
141        packet.data.put_u64(thread);
142        packet.data.put_i32(step_sizes::LINE);
143        packet.data.put_i32(match depth {
144            StepDepth::Into => step_depths::INTO,
145            StepDepth::Over => step_depths::OVER,
146            StepDepth::Out => step_depths::OUT,
147        });
148        for pat in only {
149            packet.data.put_u8(MOD_CLASS_ONLY);
150            write_jdwp_string(&mut packet, pat);
151        }
152        for pat in exclude {
153            packet.data.put_u8(MOD_CLASS_EXCLUDE);
154            write_jdwp_string(&mut packet, pat);
155        }
156        let reply = self.send_command(packet).await?;
157        reply.check_error()?;
158        let mut data = reply.data();
159        read_i32(&mut data)
160    }
161
162    /// Clear a single-step request (EventRequest.Clear, `SINGLE_STEP`).
163    ///
164    /// # Errors
165    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
166    pub async fn clear_step(&mut self, request_id: i32) -> JdwpResult<()> {
167        let id = self.next_id();
168        let mut packet = CommandPacket::new(id, command_sets::EVENT_REQUEST, event_commands::CLEAR);
169        packet.data.put_u8(event_kinds::SINGLE_STEP);
170        packet.data.put_i32(request_id);
171        let reply = self.send_command(packet).await?;
172        reply.check_error()?;
173        Ok(())
174    }
175
176    /// Clear all breakpoints (EventRequest.ClearAllBreakpoints).
177    ///
178    /// # Errors
179    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
180    pub async fn clear_all_breakpoints(&mut self) -> JdwpResult<()> {
181        let id = self.next_id();
182        let packet =
183            CommandPacket::new(id, command_sets::EVENT_REQUEST, event_commands::CLEAR_ALL_BREAKPOINTS);
184        let reply = self.send_command(packet).await?;
185        reply.check_error()?;
186        Ok(())
187    }
188
189    /// ArrayReference.Length.
190    ///
191    /// # Errors
192    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
193    pub async fn get_array_length(&mut self, array_id: ObjectId) -> JdwpResult<i32> {
194        let id = self.next_id();
195        let mut packet = CommandPacket::new(id, command_sets::ARRAY_REFERENCE, ARRAY_LENGTH);
196        packet.data.put_u64(array_id);
197        let reply = self.send_command(packet).await?;
198        reply.check_error()?;
199        let mut data = reply.data();
200        read_i32(&mut data)
201    }
202
203    /// ArrayReference.GetValues — returns `length` elements starting at `first`.
204    ///
205    /// # Errors
206    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
207    pub async fn get_array_values(
208        &mut self,
209        array_id: ObjectId,
210        first: i32,
211        length: i32,
212    ) -> JdwpResult<Vec<Value>> {
213        let id = self.next_id();
214        let mut packet = CommandPacket::new(id, command_sets::ARRAY_REFERENCE, ARRAY_GET_VALUES);
215        packet.data.put_u64(array_id);
216        packet.data.put_i32(first);
217        packet.data.put_i32(length);
218        let reply = self.send_command(packet).await?;
219        reply.check_error()?;
220        let mut data = reply.data();
221
222        // ArrayRegion: component tag, count, then values. Object elements are tagged
223        // (tag+data); primitive elements are untagged (just data of the region tag).
224        let region_tag = read_u8(&mut data)?;
225        let count = read_i32(&mut data)?;
226        let is_object = matches!(region_tag, 76 | 115 | 116 | 103 | 108 | 99 | 91);
227        let mut out = Vec::with_capacity(usize::try_from(count.max(0)).unwrap_or(0));
228        for _ in 0..count {
229            if is_object {
230                let t = read_u8(&mut data)?;
231                out.push(Value { tag: t, data: read_value_by_tag(t, &mut data)? });
232            } else {
233                out.push(Value { tag: region_tag, data: read_value_by_tag(region_tag, &mut data)? });
234            }
235        }
236        Ok(out)
237    }
238
239    /// `ArrayReference.SetValues` — overwrite `values.len()` elements starting at `first`.
240    ///
241    /// Values go on the wire **untagged**, so each must already be coerced to the array's component
242    /// type: writing an `int` into a `long[]` with the wrong width corrupts the element rather than
243    /// failing. The caller reads the component type from the array's signature and coerces first.
244    ///
245    /// # Errors
246    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails, including `INVALID_LENGTH` when the range
247    /// runs past the end of the array.
248    pub async fn set_array_values(
249        &mut self,
250        array_id: ObjectId,
251        first: i32,
252        values: &[Value],
253    ) -> JdwpResult<()> {
254        self.guard_mutation("an array element write")?;
255        let id = self.next_id();
256        let mut packet = CommandPacket::new(id, command_sets::ARRAY_REFERENCE, ARRAY_SET_VALUES);
257        packet.data.put_u64(array_id);
258        packet.data.put_i32(first);
259        packet.data.put_i32(i32::try_from(values.len()).unwrap_or(i32::MAX));
260        for v in values {
261            write_untagged_value(&mut packet.data, v);
262        }
263        let reply = self.send_command(packet).await?;
264        reply.check_error()?;
265        Ok(())
266    }
267
268    /// VirtualMachine.CreateString — mirror a string into the target VM, returning its id.
269    ///
270    /// # Errors
271    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
272    pub async fn create_string(&mut self, s: &str) -> JdwpResult<ObjectId> {
273        let id = self.next_id();
274        let mut packet = CommandPacket::new(id, command_sets::VIRTUAL_MACHINE, vm_commands::CREATE_STRING);
275        let bytes = s.as_bytes();
276        packet.data.put_i32(i32::try_from(bytes.len()).unwrap_or(i32::MAX));
277        packet.data.put_slice(bytes);
278        let reply = self.send_command(packet).await?;
279        reply.check_error()?;
280        let mut data = reply.data();
281        read_u64(&mut data)
282    }
283
284    /// StackFrame.SetValues — set a single local variable slot to `value`.
285    ///
286    /// # Errors
287    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
288    pub async fn set_frame_value(
289        &mut self,
290        thread_id: ThreadId,
291        frame_id: FrameId,
292        slot: i32,
293        value: &Value,
294    ) -> JdwpResult<()> {
295        self.guard_mutation("a local variable write")?;
296        let id = self.next_id();
297        let mut packet = CommandPacket::new(id, command_sets::STACK_FRAME, 2 /* SetValues */);
298        packet.data.put_u64(thread_id);
299        packet.data.put_u64(frame_id);
300        packet.data.put_i32(1); // one slot
301        packet.data.put_i32(slot);
302        write_tagged_value(&mut packet.data, value);
303        let reply = self.send_command(packet).await?;
304        reply.check_error()?;
305        Ok(())
306    }
307}
308
309/// Helper to build a primitive/object `Value` for invoke/set arguments.
310#[must_use]
311pub const fn value_int(v: i32) -> Value {
312    Value { tag: 73, data: ValueData::Int(v) }
313}
314#[must_use]
315pub const fn value_long(v: i64) -> Value {
316    Value { tag: 74, data: ValueData::Long(v) }
317}
318#[must_use]
319pub const fn value_bool(v: bool) -> Value {
320    Value { tag: 90, data: ValueData::Boolean(v) }
321}
322/// A `float` argument, tag `F`. Kept distinct from [`value_double`] because the JVM distinguishes them:
323/// `f(float)` and `f(double)` are different overloads, and the tag is what tells them apart.
324#[must_use]
325pub const fn value_float(v: f32) -> Value {
326    Value { tag: 70, data: ValueData::Float(v) }
327}
328#[must_use]
329pub const fn value_double(v: f64) -> Value {
330    Value { tag: 68, data: ValueData::Double(v) }
331}
332/// A `char` argument, tag `C`. The wire carries a UTF-16 code unit, which is what a Java `char` is —
333/// so a literal outside the BMP has no single-`char` spelling and is refused where it is parsed.
334#[must_use]
335pub const fn value_char(v: u16) -> Value {
336    Value { tag: 67, data: ValueData::Char(v) }
337}
338#[must_use]
339pub const fn value_null() -> Value {
340    Value { tag: 76, data: ValueData::Object(0) }
341}
342#[must_use]
343pub const fn value_object(id: ObjectId) -> Value {
344    Value { tag: 76, data: ValueData::Object(id) }
345}