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}