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