jdwp_client/stackframe.rs
1// StackFrame command implementations
2//
3// Commands for inspecting stack frame variables
4
5use crate::commands::{command_sets, stack_frame_commands};
6use crate::connection::JdwpConnection;
7use crate::protocol::{CommandPacket, JdwpResult};
8use crate::reader::{read_u8, read_value_by_tag};
9use crate::types::{FrameId, ThreadId, Value};
10use bytes::BufMut;
11
12/// Variable slot information for `GetValues`
13#[derive(Debug, Clone, Copy)]
14pub struct VariableSlot {
15 pub slot: i32,
16 pub sig_byte: u8,
17}
18
19impl JdwpConnection {
20 /// Get values for variable slots in a frame (StackFrame.GetValues command)
21 ///
22 /// # Errors
23 /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
24 pub async fn get_frame_values(
25 &mut self,
26 thread_id: ThreadId,
27 frame_id: FrameId,
28 slots: Vec<VariableSlot>,
29 ) -> JdwpResult<Vec<Value>> {
30 let packet = self.frame_values_request(thread_id, frame_id, &slots);
31 let reply = self.send_command(packet).await?;
32 Self::decode_frame_values(&reply)
33 }
34
35 /// The named slots of each `(thread, frame, slots)` read, issued as **independent reads** (PERF-1, #100).
36 ///
37 /// **The licence here is narrower than it looks, and the narrowing is caller-visible.** Reading one
38 /// frame's locals does not disturb another's, so a set of frames on a *suspended* thread is independent.
39 /// But a frame **id** is only valid until a method is invoked on its thread, and JDWP invalidates every
40 /// id on that thread when one is — so a wave built before any invocation is fine, and a wave built
41 /// across invocations reads stale ids. `debug.get_stack` therefore uses this on its shallow path and
42 /// not on the deep one, where rendering a value may invoke `toString()`. See `render_frame_variables`.
43 pub async fn read_frame_values_independently(
44 &self,
45 reads: &[(ThreadId, FrameId, Vec<VariableSlot>)],
46 ) -> Vec<JdwpResult<Vec<Value>>> {
47 let packets = reads.iter().map(|(t, f, slots)| self.frame_values_request(*t, *f, slots)).collect();
48 self.read_independently(packets)
49 .await
50 .into_iter()
51 .map(|reply| reply.and_then(|r| Self::decode_frame_values(&r)))
52 .collect()
53 }
54
55 /// The request half of `StackFrame.GetValues`.
56 fn frame_values_request(
57 &self,
58 thread_id: ThreadId,
59 frame_id: FrameId,
60 slots: &[VariableSlot],
61 ) -> CommandPacket {
62 let id = self.next_id();
63 let mut packet = CommandPacket::new(id, command_sets::STACK_FRAME, stack_frame_commands::GET_VALUES);
64
65 // Write thread ID and frame ID
66 packet.data.put_u64(thread_id);
67 packet.data.put_u64(frame_id);
68
69 // Number of slots to retrieve
70 packet.data.put_i32(i32::try_from(slots.len()).unwrap_or(i32::MAX));
71
72 // Write each slot
73 for slot in slots {
74 packet.data.put_i32(slot.slot);
75 packet.data.put_u8(slot.sig_byte);
76 }
77
78 packet
79 }
80
81 /// The decode half of `StackFrame.GetValues`, error check included.
82 fn decode_frame_values(reply: &crate::protocol::ReplyPacket) -> JdwpResult<Vec<Value>> {
83 reply.check_error()?;
84
85 let mut data = reply.data();
86
87 // Read number of values (should match slots.len())
88 let values_count = crate::reader::read_i32(&mut data)?;
89 let mut values = Vec::with_capacity(usize::try_from(values_count).unwrap_or(0));
90
91 for _ in 0..values_count {
92 let tag = read_u8(&mut data)?;
93 let value_data = read_value_by_tag(tag, &mut data)?;
94
95 values.push(Value { tag, data: value_data });
96 }
97
98 Ok(values)
99 }
100
101 /// Pop `frame_id` and every frame above it off a suspended thread's stack (StackFrame.PopFrames,
102 /// command 4).
103 ///
104 /// The thread resumes at the *call site* of the popped method with its operand stack restored, so
105 /// the next `resume` re-executes the call. That is what makes it the other half of
106 /// [`redefine_classes`](Self::redefine_classes): a frame already on the stack keeps running the
107 /// bytecode it entered with, and popping it is how the new bytecode gets entered without re-issuing
108 /// the request that reached the breakpoint.
109 ///
110 /// Requires `canPopFrames` (see [`capabilities_new`](Self::capabilities_new)) and a **suspended**
111 /// thread. Three refusals are worth telling apart, and the JDWP codes already do:
112 /// `THREAD_NOT_SUSPENDED` (13), `NO_MORE_FRAMES` (31) for the bottom frame of a stack, and
113 /// `OPAQUE_FRAME` (32) for a native one.
114 ///
115 /// Side effects are the caller's problem and cannot be undone: anything the popped invocation wrote
116 /// to a field, a file or the network stays written. Only the frame is rewound.
117 ///
118 /// # Errors
119 /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the JVM refuses to pop the frame.
120 pub async fn pop_frames(&mut self, thread_id: ThreadId, frame_id: FrameId) -> JdwpResult<()> {
121 // SAFE-9: at the wire, not above it (ADR-0001). A pop changes what a running thread does next,
122 // and whatever the popped invocation already wrote stays written — so it is refused for a
123 // different reason than a redefinition, but just as firmly.
124 self.guard_mutation("a frame pop")?;
125
126 let id = self.next_id();
127 let mut packet = CommandPacket::new(id, command_sets::STACK_FRAME, stack_frame_commands::POP_FRAMES);
128
129 packet.data.put_u64(thread_id);
130 packet.data.put_u64(frame_id);
131
132 let reply = self.send_command(packet).await?;
133 reply.check_error()?;
134 Ok(())
135 }
136}