Skip to main content

jdwp_client/
object.rs

1// ObjectReference command implementations
2//
3// Commands for working with object instances
4
5use crate::commands::{command_sets, object_reference_commands};
6use crate::connection::JdwpConnection;
7use crate::eval::write_untagged_value;
8use crate::protocol::{CommandPacket, JdwpResult, ReplyPacket};
9use crate::reader::{read_i32, read_u64, read_u8, read_value_by_tag};
10use crate::types::{FieldId, ObjectId, ReferenceTypeId, Value};
11use bytes::BufMut;
12use serde::{Deserialize, Serialize};
13
14/// Field value from an object
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct FieldValue {
17    pub field_id: FieldId,
18    pub value: Value,
19}
20
21impl JdwpConnection {
22    /// Get the reference type (class) of an object (ObjectReference.ReferenceType command)
23    ///
24    /// # Arguments
25    /// * `object_id` - The `ObjectId` of the object
26    ///
27    /// # Returns
28    /// The `ReferenceTypeId` of the object's class
29    ///
30    /// # Errors
31    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
32    pub async fn get_object_reference_type(&mut self, object_id: ObjectId) -> JdwpResult<ReferenceTypeId> {
33        let packet = self.reference_type_request(object_id);
34        let reply = self.send_command(packet).await?;
35        Self::decode_reference_type(&reply)
36    }
37
38    /// The reference type of each of `object_ids`, read as **independent reads** (PERF-1, #100).
39    ///
40    /// The licence is real here and worth naming: an object's class is fixed for the object's life, and
41    /// asking about one object tells you nothing you need in order to ask about another. So this is a wave.
42    ///
43    /// Positional and total — `result[i]` answers `object_ids[i]`, and one failure does not touch the rest.
44    /// A collected object answers `INVALID_OBJECT` in its own slot, which is exactly what it does on the
45    /// sequential path.
46    pub async fn read_reference_types_independently(
47        &self,
48        object_ids: &[ObjectId],
49    ) -> Vec<JdwpResult<ReferenceTypeId>> {
50        let packets = object_ids.iter().map(|&id| self.reference_type_request(id)).collect();
51        self.read_independently(packets)
52            .await
53            .into_iter()
54            .map(|reply| reply.and_then(|r| Self::decode_reference_type(&r)))
55            .collect()
56    }
57
58    /// The request half of `ObjectReference.ReferenceType`.
59    ///
60    /// Split out, with [`decode_reference_type`](Self::decode_reference_type), so the wave form and the
61    /// single form cannot drift: **one encoder and one decoder, two schedulers.** A pipelined path that
62    /// built its own packet would be a second implementation of the same command, and the first thing to
63    /// diverge would be a fallback or a bounds check that only one of them had.
64    fn reference_type_request(&self, object_id: ObjectId) -> CommandPacket {
65        let id = self.next_id();
66        let mut packet =
67            CommandPacket::new(id, command_sets::OBJECT_REFERENCE, object_reference_commands::REFERENCE_TYPE);
68        packet.data.put_u64(object_id);
69        packet
70    }
71
72    /// The decode half of `ObjectReference.ReferenceType`, error check included — so a wave and a single
73    /// read agree about what counts as a failure and not only about how to parse a success.
74    fn decode_reference_type(reply: &ReplyPacket) -> JdwpResult<ReferenceTypeId> {
75        reply.check_error()?;
76        let mut data = reply.data();
77        // Read type tag (byte) and class ID (objectID)
78        let _type_tag = read_u8(&mut data)?;
79        read_u64(&mut data)
80    }
81
82    /// Whether the object behind an id has been garbage collected
83    /// (`ObjectReference.IsCollected`, set 9 command 9).
84    ///
85    /// **The one command that answers "vanished" as a fact rather than as a failure.** A JDWP object id
86    /// is a weak reference — the JVM is free to collect the object while the debugger still holds the
87    /// number — and every other command answers [`ERR_INVALID_OBJECT`](crate::protocol::ERR_INVALID_OBJECT)
88    /// once that happens, which is the same code a *typo* produces. This one separates the two while the
89    /// JVM still remembers the id: `Ok(true)` is "it was here and it is gone", where an
90    /// `INVALID_OBJECT` **error** from this command means the JVM has no record of the id at all —
91    /// collected long enough ago that the mapping itself was dropped, or never valid.
92    ///
93    /// Deliberately not paired with `DisableCollection` / `EnableCollection` (commands 7 and 8): pinning
94    /// an object so its id stays readable makes the debugger the reason a live heap cannot be collected.
95    /// See ADR-0022.
96    ///
97    /// # Errors
98    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed. In particular
99    /// `INVALID_OBJECT` (20) for an id this JVM has no record of.
100    pub async fn is_collected(&mut self, object_id: ObjectId) -> JdwpResult<bool> {
101        let id = self.next_id();
102        let mut packet =
103            CommandPacket::new(id, command_sets::OBJECT_REFERENCE, object_reference_commands::IS_COLLECTED);
104        packet.data.put_u64(object_id);
105
106        let reply = self.send_command(packet).await?;
107        reply.check_error()?;
108
109        let mut data = reply.data();
110        Ok(read_u8(&mut data)? != 0)
111    }
112
113    /// Get field values from an object (ObjectReference.GetValues command)
114    ///
115    /// # Arguments
116    /// * `object_id` - The `ObjectId` of the object
117    /// * `field_ids` - Vector of `FieldIds` to retrieve
118    ///
119    /// # Returns
120    /// Vector of Values corresponding to the requested fields
121    ///
122    /// # Example
123    /// ```no_run
124    /// # use jdwp_client::types::{FieldId, ObjectId};
125    /// # async fn demo(
126    /// #     mut connection: jdwp_client::JdwpConnection,
127    /// #     object_id: ObjectId, field_id1: FieldId, field_id2: FieldId,
128    /// # ) -> jdwp_client::JdwpResult<()> {
129    /// let fields = vec![field_id1, field_id2];
130    /// let values = connection.get_object_values(object_id, fields).await?;
131    /// # Ok(()) }
132    /// ```
133    ///
134    /// # Errors
135    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
136    pub async fn get_object_values(
137        &mut self,
138        object_id: ObjectId,
139        field_ids: Vec<FieldId>,
140    ) -> JdwpResult<Vec<Value>> {
141        let packet = self.object_values_request(object_id, &field_ids);
142        let reply = self.send_command(packet).await?;
143        Self::decode_object_values(&reply)
144    }
145
146    /// One object's fields per entry of `reads`, all read as **independent reads** (PERF-1, #100).
147    ///
148    /// Independent because each read names its own object and its own field ids, and a field read changes
149    /// nothing. **What is not independent is how `reads` was built**: the field ids for an object come from
150    /// its type, and the type comes from a read of its own. That prior read cannot join this wave — see
151    /// `project_query_rows` in the server, where the two waves are deliberately two.
152    ///
153    /// Positional and total, like [`read_reference_types_independently`](Self::read_reference_types_independently).
154    pub async fn read_object_values_independently(
155        &self,
156        reads: &[(ObjectId, Vec<FieldId>)],
157    ) -> Vec<JdwpResult<Vec<Value>>> {
158        let packets = reads
159            .iter()
160            .map(|(object_id, field_ids)| self.object_values_request(*object_id, field_ids))
161            .collect();
162        self.read_independently(packets)
163            .await
164            .into_iter()
165            .map(|reply| reply.and_then(|r| Self::decode_object_values(&r)))
166            .collect()
167    }
168
169    /// The request half of `ObjectReference.GetValues`. See
170    /// [`reference_type_request`](Self::reference_type_request) for why it is split out.
171    fn object_values_request(&self, object_id: ObjectId, field_ids: &[FieldId]) -> CommandPacket {
172        let id = self.next_id();
173        let mut packet =
174            CommandPacket::new(id, command_sets::OBJECT_REFERENCE, object_reference_commands::GET_VALUES);
175
176        // Write object ID
177        packet.data.put_u64(object_id);
178
179        // Write number of fields
180        packet.data.put_i32(i32::try_from(field_ids.len()).unwrap_or(i32::MAX));
181
182        // Write each field ID
183        for field_id in field_ids {
184            packet.data.put_u64(*field_id);
185        }
186
187        packet
188    }
189
190    /// The decode half of `ObjectReference.GetValues`, error check included.
191    fn decode_object_values(reply: &ReplyPacket) -> JdwpResult<Vec<Value>> {
192        reply.check_error()?;
193
194        let mut data = reply.data();
195
196        // Read number of values (should match field_ids.len())
197        let values_count = read_i32(&mut data)?;
198        let mut values = Vec::with_capacity(usize::try_from(values_count).unwrap_or(0));
199
200        for _ in 0..values_count {
201            let tag = read_u8(&mut data)?;
202            let value_data = read_value_by_tag(tag, &mut data)?;
203
204            values.push(Value { tag, data: value_data });
205        }
206
207        Ok(values)
208    }
209
210    /// Get static field values from a reference type (ReferenceType.GetValues command)
211    ///
212    /// Unlike `get_object_values` (which reads instance fields off an object), this reads
213    /// **static** fields directly off a class — no object instance and no suspended thread
214    /// required. Use it to read things like `ConfigDefaultUtils.dsUrlMotor`.
215    ///
216    /// # Arguments
217    /// * `ref_type_id` - The `ReferenceTypeId` of the class (from `classes_by_signature`)
218    /// * `field_ids` - Vector of static `FieldIds` to retrieve (from `get_fields`)
219    ///
220    /// # Returns
221    /// Vector of Values corresponding to the requested fields
222    ///
223    /// # Errors
224    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
225    pub async fn get_reference_values(
226        &mut self,
227        ref_type_id: ReferenceTypeId,
228        field_ids: Vec<FieldId>,
229    ) -> JdwpResult<Vec<Value>> {
230        use crate::commands::reference_type_commands;
231        let id = self.next_id();
232        let mut packet =
233            CommandPacket::new(id, command_sets::REFERENCE_TYPE, reference_type_commands::GET_VALUES);
234
235        // Write reference type ID
236        packet.data.put_u64(ref_type_id);
237
238        // Write number of fields
239        packet.data.put_i32(i32::try_from(field_ids.len()).unwrap_or(i32::MAX));
240
241        // Write each field ID
242        for field_id in &field_ids {
243            packet.data.put_u64(*field_id);
244        }
245
246        let reply = self.send_command(packet).await?;
247        reply.check_error()?;
248
249        let mut data = reply.data();
250
251        // Read number of values (should match field_ids.len())
252        let values_count = read_i32(&mut data)?;
253        let mut values = Vec::with_capacity(usize::try_from(values_count).unwrap_or(0));
254
255        for _ in 0..values_count {
256            let tag = read_u8(&mut data)?;
257            let value_data = read_value_by_tag(tag, &mut data)?;
258
259            values.push(Value { tag, data: value_data });
260        }
261
262        Ok(values)
263    }
264
265    /// Write static field(s) on a class (ClassType.SetValues command).
266    ///
267    /// Each value is written *untagged* — its wire type comes from the field's declared type — so
268    /// coerce every value to match its field first (see the mcp-server field-write path). Lets you
269    /// flip a static like `ConfigDefaultUtils.dsInfra` on a running JVM.
270    ///
271    /// # Errors
272    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
273    pub async fn set_reference_values(
274        &mut self,
275        class_id: ReferenceTypeId,
276        updates: Vec<(FieldId, Value)>,
277    ) -> JdwpResult<()> {
278        // ClassType.SetValues = command set 3 (CLASS_TYPE), command 2.
279        const CLASS_TYPE_SET_VALUES: u8 = 2;
280        self.guard_mutation("a static field write")?;
281        let id = self.next_id();
282        let mut packet = CommandPacket::new(id, command_sets::CLASS_TYPE, CLASS_TYPE_SET_VALUES);
283        packet.data.put_u64(class_id);
284        packet.data.put_i32(i32::try_from(updates.len()).unwrap_or(i32::MAX));
285        for (field_id, value) in &updates {
286            packet.data.put_u64(*field_id);
287            write_untagged_value(&mut packet.data, value);
288        }
289        let reply = self.send_command(packet).await?;
290        reply.check_error()?;
291        Ok(())
292    }
293
294    /// Write instance field(s) on an object (ObjectReference.SetValues command).
295    ///
296    /// Like `set_reference_values`, values are untagged and must already be coerced to each
297    /// field's declared type.
298    ///
299    /// # Errors
300    /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
301    pub async fn set_object_values(
302        &mut self,
303        object_id: ObjectId,
304        updates: Vec<(FieldId, Value)>,
305    ) -> JdwpResult<()> {
306        self.guard_mutation("an instance field write")?;
307        let id = self.next_id();
308        let mut packet =
309            CommandPacket::new(id, command_sets::OBJECT_REFERENCE, object_reference_commands::SET_VALUES);
310        packet.data.put_u64(object_id);
311        packet.data.put_i32(i32::try_from(updates.len()).unwrap_or(i32::MAX));
312        for (field_id, value) in &updates {
313            packet.data.put_u64(*field_id);
314            write_untagged_value(&mut packet.data, value);
315        }
316        let reply = self.send_command(packet).await?;
317        reply.check_error()?;
318        Ok(())
319    }
320}
321
322#[cfg(test)]
323mod tests {
324
325    #[test]
326    fn test_object_values_packet() {
327        // Test that packet is constructed correctly
328    }
329}