Skip to main content

jdwp_client/
eval.rs

1// Primitives for expression evaluation: type signatures, superclass walking,
2// `this` object, and method invocation (instance and static).
3
4use crate::commands::{
5    command_sets, object_reference_commands, reference_type_commands, stack_frame_commands,
6};
7use crate::connection::JdwpConnection;
8use crate::protocol::{CommandPacket, JdwpResult};
9use crate::reader::{read_string, read_u64, read_u8, read_value_by_tag};
10use crate::types::{ClassId, FrameId, MethodId, ObjectId, ReferenceTypeId, ThreadId, Value, ValueData};
11use bytes::BufMut;
12
13// ClassType.Superclass lives in command set 3 (CLASS_TYPE), command 1.
14const CLASS_TYPE_SUPERCLASS: u8 = 1;
15// ClassType.InvokeMethod is command 3 of the same set.
16const CLASS_TYPE_INVOKE_METHOD: u8 = 3;
17// InvokeMethod option: run only the invoked thread, not every suspended thread.
18const INVOKE_SINGLE_THREADED: i32 = 1;
19
20impl JdwpConnection {
21    /// ReferenceType.Signature — JNI signature of a type, e.g. "Lbr/com/x/WSReserva;".
22    ///
23    /// # Errors
24    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
25    pub async fn get_signature(&mut self, ref_type_id: ReferenceTypeId) -> JdwpResult<String> {
26        // A loaded type's signature never changes, so this is the cheapest cache hit available and the
27        // most frequently asked question in the whole tool.
28        if let Some(hit) = self.types().signature(ref_type_id) {
29            return Ok(hit);
30        }
31        let packet = self.signature_request(ref_type_id);
32        let reply = self.send_command(packet).await?;
33        let sig = Self::decode_signature_reply(&reply)?;
34        self.types().put_signature(ref_type_id, &sig);
35        Ok(sig)
36    }
37
38    /// The signature of each of `ref_type_ids`, read as **independent reads** (PERF-1, #100).
39    ///
40    /// **Cache-aware, and that is the whole of its packet story.** `get_signature` is the most frequently
41    /// asked question in the tool precisely because it is nearly always a `TypeCache` hit, so a wave that
42    /// asked the JVM for every id would turn free lookups into packets — the one way this could cost more
43    /// than the loop it replaces. Only the misses are waved; every answer, hit or miss, comes back in its
44    /// own position, and the misses are written back to the cache exactly as the single read writes them.
45    ///
46    /// Independent because a loaded type's signature never changes and no id's answer is needed to ask
47    /// about another.
48    pub async fn read_signatures_independently(
49        &self,
50        ref_type_ids: &[ReferenceTypeId],
51    ) -> Vec<JdwpResult<String>> {
52        // Positions that are not already known, deduplicated — the same type appears in many frames of a
53        // stack, and asking twice in one wave is asking twice.
54        let mut wanted: Vec<ReferenceTypeId> = Vec::new();
55        for &id in ref_type_ids {
56            if self.types().signature(id).is_none() && !wanted.contains(&id) {
57                wanted.push(id);
58            }
59        }
60        if !wanted.is_empty() {
61            let packets = wanted.iter().map(|&id| self.signature_request(id)).collect();
62            for (&type_id, reply) in wanted.iter().zip(self.read_independently(packets).await) {
63                if let Ok(sig) = reply.and_then(|r| Self::decode_signature_reply(&r)) {
64                    self.types().put_signature(type_id, &sig);
65                }
66            }
67        }
68        // Read back through the cache, so a hit and a freshly-waved answer are the same answer and there is
69        // one place that decides what "unknown" looks like.
70        ref_type_ids
71            .iter()
72            .map(|&id| {
73                self.types().signature(id).ok_or_else(|| {
74                    crate::protocol::JdwpError::Protocol(format!(
75                        "the signature of type {id} could not be read"
76                    ))
77                })
78            })
79            .collect()
80    }
81
82    /// The request half of `ReferenceType.Signature`.
83    fn signature_request(&self, ref_type_id: ReferenceTypeId) -> CommandPacket {
84        let id = self.next_id();
85        let mut packet =
86            CommandPacket::new(id, command_sets::REFERENCE_TYPE, reference_type_commands::SIGNATURE);
87        packet.data.put_u64(ref_type_id);
88        packet
89    }
90
91    /// The decode half of `ReferenceType.Signature`, error check included. It deliberately does **not**
92    /// populate the cache: the wave writes back per id and the single read writes back for one, and a
93    /// decoder that also wrote would make which of them did it ambiguous.
94    fn decode_signature_reply(reply: &crate::protocol::ReplyPacket) -> JdwpResult<String> {
95        reply.check_error()?;
96        let mut data = reply.data();
97        read_string(&mut data)
98    }
99
100    /// ClassType.Superclass — direct superclass of a class (None for java.lang.Object).
101    ///
102    /// # Errors
103    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
104    pub async fn get_superclass(&mut self, class_id: ClassId) -> JdwpResult<Option<ClassId>> {
105        match self.types().superclass(class_id) {
106            crate::connection::CachedSuperclass::Root => return Ok(None),
107            crate::connection::CachedSuperclass::Parent(p) => return Ok(Some(p)),
108            crate::connection::CachedSuperclass::Unknown => {}
109        }
110        let id = self.next_id();
111        let mut packet = CommandPacket::new(id, command_sets::CLASS_TYPE, CLASS_TYPE_SUPERCLASS);
112        packet.data.put_u64(class_id);
113        let reply = self.send_command(packet).await?;
114        reply.check_error()?;
115        let mut data = reply.data();
116        let sc = read_u64(&mut data)?;
117        let parent = if sc == 0 { None } else { Some(sc) };
118        self.types().put_superclass(class_id, parent);
119        Ok(parent)
120    }
121
122    /// StackFrame.ThisObject — the `this` reference for a frame (0 = static method).
123    ///
124    /// # Errors
125    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
126    pub async fn get_this_object(&mut self, thread_id: ThreadId, frame_id: FrameId) -> JdwpResult<ObjectId> {
127        let id = self.next_id();
128        let mut packet = CommandPacket::new(id, command_sets::STACK_FRAME, stack_frame_commands::THIS_OBJECT);
129        packet.data.put_u64(thread_id);
130        packet.data.put_u64(frame_id);
131        let reply = self.send_command(packet).await?;
132        reply.check_error()?;
133        let mut data = reply.data();
134        let _tag = read_u8(&mut data)?;
135        read_u64(&mut data)
136    }
137
138    /// ObjectReference.InvokeMethod — invoke an instance method on a suspended thread.
139    /// Returns (return value, exception object id) — exception id 0 means no exception.
140    /// Uses `INVOKE_SINGLE_THREADED` so only the target thread runs during the call.
141    ///
142    /// # Errors
143    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed, or
144    /// [`JdwpError::ReadOnly`](crate::JdwpError::ReadOnly) if the connection refuses invocation
145    /// ([`set_read_only`](Self::set_read_only)).
146    pub async fn invoke_method(
147        &mut self,
148        object_id: ObjectId,
149        thread_id: ThreadId,
150        class_id: ClassId,
151        method_id: MethodId,
152        args: Vec<Value>,
153    ) -> JdwpResult<(Value, ObjectId)> {
154        self.guard_mutation("an instance method invocation")?;
155        let id = self.next_id();
156        let mut packet =
157            CommandPacket::new(id, command_sets::OBJECT_REFERENCE, object_reference_commands::INVOKE_METHOD);
158        packet.data.put_u64(object_id);
159        packet.data.put_u64(thread_id);
160        packet.data.put_u64(class_id);
161        packet.data.put_u64(method_id);
162        packet.data.put_i32(i32::try_from(args.len()).unwrap_or(i32::MAX));
163        for a in &args {
164            write_tagged_value(&mut packet.data, a);
165        }
166        packet.data.put_i32(INVOKE_SINGLE_THREADED);
167
168        // Under the invocation budget, not the generic reply timeout — see `send_invoke`.
169        let reply = self.send_invoke(packet).await?;
170        reply.check_error()?;
171        read_invoke_reply(reply.data())
172    }
173
174    /// `ClassType.InvokeMethod` — invoke a *static* method on a suspended thread.
175    /// Returns (return value, exception object id) — exception id 0 means no exception.
176    ///
177    /// `class_id` must be the class that declares `method_id` (walk the superclass chain to find
178    /// it, as `ObjectReference.InvokeMethod` requires too). Uses `INVOKE_SINGLE_THREADED` so only
179    /// the target thread runs during the call.
180    ///
181    /// # Errors
182    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed, or
183    /// [`JdwpError::ReadOnly`](crate::JdwpError::ReadOnly) if the connection refuses invocation
184    /// ([`set_read_only`](Self::set_read_only)).
185    pub async fn invoke_static_method(
186        &mut self,
187        class_id: ClassId,
188        thread_id: ThreadId,
189        method_id: MethodId,
190        args: Vec<Value>,
191    ) -> JdwpResult<(Value, ObjectId)> {
192        self.guard_mutation("a static method invocation")?;
193        let id = self.next_id();
194        let mut packet = CommandPacket::new(id, command_sets::CLASS_TYPE, CLASS_TYPE_INVOKE_METHOD);
195        packet.data.put_u64(class_id);
196        packet.data.put_u64(thread_id);
197        packet.data.put_u64(method_id);
198        packet.data.put_i32(i32::try_from(args.len()).unwrap_or(i32::MAX));
199        for a in &args {
200            write_tagged_value(&mut packet.data, a);
201        }
202        packet.data.put_i32(INVOKE_SINGLE_THREADED);
203
204        // Under the invocation budget, not the generic reply timeout — see `send_invoke`.
205        let reply = self.send_invoke(packet).await?;
206        reply.check_error()?;
207        read_invoke_reply(reply.data())
208    }
209}
210
211/// Parse an `InvokeMethod` reply body: a tagged return value followed by a tagged exception
212/// reference (object id 0 = the method returned normally). Shared by the instance and static
213/// invoke commands, whose replies are identical.
214fn read_invoke_reply(mut data: &[u8]) -> JdwpResult<(Value, ObjectId)> {
215    let ret_tag = read_u8(&mut data)?;
216    let ret = Value { tag: ret_tag, data: read_value_by_tag(ret_tag, &mut data)? };
217    let _exc_tag = read_u8(&mut data)?;
218    let exc_id = read_u64(&mut data)?;
219    Ok((ret, exc_id))
220}
221
222pub(crate) fn write_tagged_value<B: BufMut>(buf: &mut B, v: &Value) {
223    buf.put_u8(v.tag);
224    write_untagged_value(buf, v);
225}
226
227/// Write a value's raw bytes with NO leading type tag. JDWP `SetValues` commands
228/// (ClassType.SetValues for statics, ObjectReference.SetValues for instance fields) take
229/// "untagged-value"s whose type is inferred from the field being written, so the value must
230/// already be coerced to the field's declared type.
231pub(crate) fn write_untagged_value<B: BufMut>(buf: &mut B, v: &Value) {
232    match &v.data {
233        ValueData::Byte(x) => buf.put_i8(*x),
234        ValueData::Char(x) => buf.put_u16(*x),
235        ValueData::Float(x) => buf.put_f32(*x),
236        ValueData::Double(x) => buf.put_f64(*x),
237        ValueData::Int(x) => buf.put_i32(*x),
238        ValueData::Long(x) => buf.put_i64(*x),
239        ValueData::Short(x) => buf.put_i16(*x),
240        ValueData::Boolean(x) => buf.put_u8(u8::from(*x)),
241        ValueData::Object(x) => buf.put_u64(*x),
242        ValueData::Void => {}
243    }
244}