Skip to main content

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}