jdwp_client/method.rs
1// Method command implementations
2//
3// Commands for working with methods (line tables, variable tables, etc.)
4
5use crate::commands::{command_sets, method_commands};
6use crate::connection::JdwpConnection;
7use crate::protocol::{CommandPacket, JdwpResult, ERR_NOT_IMPLEMENTED};
8use crate::reader::{read_i32, read_string, read_u64};
9use crate::types::{MethodId, ReferenceTypeId, Variable};
10use bytes::BufMut;
11use serde::{Deserialize, Serialize};
12
13/// Line table entry - maps source line to bytecode index
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct LineTableEntry {
16 pub line_code_index: u64, // bytecode index
17 pub line_number: i32, // source line number
18}
19
20/// Complete line table for a method
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct LineTable {
23 pub start: u64, // starting bytecode index
24 pub end: u64, // ending bytecode index
25 pub lines: Vec<LineTableEntry>,
26}
27
28impl JdwpConnection {
29 /// Get line table for a method (Method.LineTable command)
30 /// Maps source code line numbers to bytecode positions
31 ///
32 /// # Errors
33 /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
34 pub async fn get_line_table(
35 &mut self,
36 ref_type_id: ReferenceTypeId,
37 method_id: MethodId,
38 ) -> JdwpResult<LineTable> {
39 let packet = self.line_table_request(ref_type_id, method_id);
40 let reply = self.send_command(packet).await?;
41 Self::decode_line_table(&reply)
42 }
43
44 /// A line table for each `(type, method)` pair, read as **independent reads** (PERF-1, #100).
45 ///
46 /// Independent because a method's line table is fixed for the loaded method and naming one method
47 /// tells you nothing you need in order to name another.
48 ///
49 /// **Deduplicate before calling this.** A recursive stack has the same pair many times over, and this
50 /// will read it many times over — the licence is about issuing reads together, not about needing fewer
51 /// of them. `dump_frame_method`'s cache and `stack_method_tables`'s dedupe are where that is decided.
52 pub async fn read_line_tables_independently(
53 &self,
54 pairs: &[(ReferenceTypeId, MethodId)],
55 ) -> Vec<JdwpResult<LineTable>> {
56 let packets = pairs.iter().map(|&(t, m)| self.line_table_request(t, m)).collect();
57 self.read_independently(packets)
58 .await
59 .into_iter()
60 .map(|reply| reply.and_then(|r| Self::decode_line_table(&r)))
61 .collect()
62 }
63
64 /// The request half of `Method.LineTable`. See `reference_type_request` in `object.rs` for why the
65 /// halves are split out rather than duplicated.
66 fn line_table_request(&self, ref_type_id: ReferenceTypeId, method_id: MethodId) -> CommandPacket {
67 let id = self.next_id();
68 let mut packet = CommandPacket::new(id, command_sets::METHOD, method_commands::LINE_TABLE);
69 // Write reference type ID and method ID (both 8 bytes)
70 packet.data.put_u64(ref_type_id);
71 packet.data.put_u64(method_id);
72 packet
73 }
74
75 /// The decode half of `Method.LineTable`, error check included.
76 fn decode_line_table(reply: &crate::protocol::ReplyPacket) -> JdwpResult<LineTable> {
77 reply.check_error()?;
78
79 let mut data = reply.data();
80
81 // Read start and end indices
82 let start = read_u64(&mut data)?;
83 let end = read_u64(&mut data)?;
84
85 // Read line table entries
86 let lines_count = read_i32(&mut data)?;
87 let mut lines = Vec::with_capacity(usize::try_from(lines_count).unwrap_or(0));
88
89 for _ in 0..lines_count {
90 let line_code_index = read_u64(&mut data)?;
91 let line_number = read_i32(&mut data)?;
92
93 lines.push(LineTableEntry { line_code_index, line_number });
94 }
95
96 Ok(LineTable { start, end, lines })
97 }
98
99 /// A method's bytecode, exactly as the JVM holds it (`Method.Bytecodes`, command 3).
100 ///
101 /// The evidence a line table cannot give (DISC-9, #63): an edit that changes a method's code without
102 /// moving any line — `<` to `<=`, a changed constant, a swapped operator — leaves the line table
103 /// identical and the code array different. That is also the commonest edit in a redeploy loop, so it
104 /// is the case a line-table comparison is quietest about.
105 ///
106 /// Gated on `canGetBytecodes` (see [`VmCapabilities`](crate::vm::VmCapabilities)); a JVM without it
107 /// answers `NOT_IMPLEMENTED`, which is worth reporting as "cannot tell" rather than as a match. An
108 /// abstract or native method has no code and answers `ABSENT_INFORMATION` for the same reason.
109 ///
110 /// # Errors
111 /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
112 pub async fn get_bytecodes(
113 &mut self,
114 ref_type_id: ReferenceTypeId,
115 method_id: MethodId,
116 ) -> JdwpResult<Vec<u8>> {
117 let id = self.next_id();
118 let mut packet = CommandPacket::new(id, command_sets::METHOD, method_commands::BYTECODES);
119
120 packet.data.put_u64(ref_type_id);
121 packet.data.put_u64(method_id);
122
123 let reply = self.send_command(packet).await?;
124 reply.check_error()?;
125
126 let mut data = reply.data();
127 let count = read_i32(&mut data)?;
128 let count = usize::try_from(count).unwrap_or(0);
129 // Read against what the reply actually holds rather than trusting the count, which is the same
130 // rule `read_string` follows for a lying length: a truncated reply must error, not over-read.
131 data.get(..count).map(<[u8]>::to_vec).ok_or_else(|| {
132 crate::protocol::JdwpError::Protocol(format!(
133 "Method.Bytecodes claimed {count} byte(s) but the reply holds {}",
134 data.len()
135 ))
136 })
137 }
138
139 /// Get variable table for a method (Method.VariableTable command)
140 /// Returns info about local variables (names, types, slots)
141 ///
142 /// # Errors
143 /// Returns a [`JdwpError`](crate::JdwpError) if the JDWP request fails or the reply cannot be parsed.
144 pub async fn get_variable_table(
145 &mut self,
146 ref_type_id: ReferenceTypeId,
147 method_id: MethodId,
148 ) -> JdwpResult<Vec<Variable>> {
149 // `VariableTableWithGeneric` rather than `VariableTable` (DISC-12, #95): a local declared
150 // `List<Reserva>` is the commonest place a caller needs the element type, and it is the only place
151 // the *use-site* argument exists — a runtime object's class carries none.
152 //
153 // The fallback matters more here than for methods and fields. This command needs the same debug
154 // information its plain twin does, so `ABSENT_INFORMATION` is an ordinary answer for a `-g:none`
155 // build and is left to the caller exactly as before; `NOT_IMPLEMENTED` is the one that falls back.
156 match self.read_variable_table(ref_type_id, method_id, true).await {
157 Ok(v) => Ok(v),
158 Err(crate::JdwpError::JdwpErrorCode(code, _)) if code == ERR_NOT_IMPLEMENTED => {
159 self.read_variable_table(ref_type_id, method_id, false).await
160 }
161 Err(e) => Err(e),
162 }
163 }
164
165 /// One read of a method's variable table, with or without the generic signature column.
166 ///
167 /// The generic reply inserts one string per entry between the signature and the length — see
168 /// `read_methods` in `reftype.rs` for why the layout and the command are decided by one flag in one
169 /// function rather than by two loops that have to be kept in step.
170 async fn read_variable_table(
171 &mut self,
172 ref_type_id: ReferenceTypeId,
173 method_id: MethodId,
174 with_generic: bool,
175 ) -> JdwpResult<Vec<Variable>> {
176 let packet = self.variable_table_request(ref_type_id, method_id, with_generic);
177 let reply = self.send_command(packet).await?;
178 Self::decode_variable_table(&reply, with_generic)
179 }
180
181 /// A variable table for each `(type, method)` pair, read as **independent reads** (PERF-1, #100).
182 ///
183 /// **Two waves, because the fallback is per pair.** `get_variable_table` prefers
184 /// `VariableTableWithGeneric` and falls back to the plain command on `NOT_IMPLEMENTED`; a wave has to
185 /// reproduce that or a JVM without the generic command would lose every variable name at once instead
186 /// of one call at a time. So the generic wave goes out, the pairs that answered `NOT_IMPLEMENTED` are
187 /// collected, and those — usually none — go out as a second wave.
188 ///
189 /// `ABSENT_INFORMATION` is **not** a fallback case and is passed through per pair, exactly as the
190 /// single-read path leaves it: a `-g:none` build has no variable names and that is an answer, not an
191 /// error to retry.
192 ///
193 /// Deduplicate before calling, for the reason
194 /// [`read_line_tables_independently`](Self::read_line_tables_independently) gives.
195 pub async fn read_variable_tables_independently(
196 &self,
197 pairs: &[(ReferenceTypeId, MethodId)],
198 ) -> Vec<JdwpResult<Vec<Variable>>> {
199 let generic = self.variable_table_wave(pairs, true).await;
200
201 // The pairs the JVM refused the generic command for, with where each sits in the answer.
202 let retry: Vec<(usize, (ReferenceTypeId, MethodId))> = generic
203 .iter()
204 .enumerate()
205 .filter(|(_, r)| {
206 matches!(r, Err(crate::JdwpError::JdwpErrorCode(code, _)) if *code == ERR_NOT_IMPLEMENTED)
207 })
208 .filter_map(|(at, _)| pairs.get(at).map(|&pair| (at, pair)))
209 .collect();
210 if retry.is_empty() {
211 return generic;
212 }
213
214 let plain_pairs: Vec<(ReferenceTypeId, MethodId)> = retry.iter().map(|&(_, pair)| pair).collect();
215 let mut out = generic;
216 for ((at, _), answer) in retry.into_iter().zip(self.variable_table_wave(&plain_pairs, false).await) {
217 if let Some(slot) = out.get_mut(at) {
218 *slot = answer;
219 }
220 }
221 out
222 }
223
224 /// One wave of variable-table reads, with or without the generic signature column.
225 async fn variable_table_wave(
226 &self,
227 pairs: &[(ReferenceTypeId, MethodId)],
228 with_generic: bool,
229 ) -> Vec<JdwpResult<Vec<Variable>>> {
230 let packets = pairs.iter().map(|&(t, m)| self.variable_table_request(t, m, with_generic)).collect();
231 self.read_independently(packets)
232 .await
233 .into_iter()
234 .map(|reply| reply.and_then(|r| Self::decode_variable_table(&r, with_generic)))
235 .collect()
236 }
237
238 /// The request half of `Method.VariableTable[WithGeneric]`.
239 fn variable_table_request(
240 &self,
241 ref_type_id: ReferenceTypeId,
242 method_id: MethodId,
243 with_generic: bool,
244 ) -> CommandPacket {
245 let command = if with_generic {
246 method_commands::VARIABLE_TABLE_WITH_GENERIC
247 } else {
248 method_commands::VARIABLE_TABLE
249 };
250 let id = self.next_id();
251 let mut packet = CommandPacket::new(id, command_sets::METHOD, command);
252
253 // Write reference type ID and method ID
254 packet.data.put_u64(ref_type_id);
255 packet.data.put_u64(method_id);
256 packet
257 }
258
259 /// The decode half of `Method.VariableTable[WithGeneric]`, error check included. `with_generic` decides
260 /// the layout for the same reason it decides the command — one flag in one function, never two loops.
261 fn decode_variable_table(
262 reply: &crate::protocol::ReplyPacket,
263 with_generic: bool,
264 ) -> JdwpResult<Vec<Variable>> {
265 reply.check_error()?;
266
267 let mut data = reply.data();
268
269 // Read arg count (we don't use this)
270 let _arg_count = read_i32(&mut data)?;
271
272 // Read variables
273 let vars_count = read_i32(&mut data)?;
274 let mut variables = Vec::with_capacity(usize::try_from(vars_count).unwrap_or(0));
275
276 for _ in 0..vars_count {
277 let code_index = read_u64(&mut data)?;
278 let name = read_string(&mut data)?;
279 let signature = read_string(&mut data)?;
280 let generic_signature =
281 if with_generic { crate::reader::some_if_present(read_string(&mut data)?) } else { None };
282 let length = crate::reader::read_u32(&mut data)?;
283 let slot = crate::reader::read_u32(&mut data)?;
284
285 variables.push(Variable { code_index, name, signature, generic_signature, length, slot });
286 }
287
288 Ok(variables)
289 }
290}