Skip to main content

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}