rudy_db/
synthetic_methods.rs

1//! Synthetic methods that can be evaluated without execution
2//!
3//! These are methods we can compute directly from debug information
4//! without needing to execute code in the target process.
5//!
6//! ## Current Implementation
7//!
8//! We currently support synthetic methods for:
9//! - **Vec<T>**: `len()`, `capacity()`, `is_empty()`
10//! - **String**: `len()`, `is_empty()`
11//! - **Option<T>**: `is_some()`, `is_none()`
12//! - **Result<T,E>**: `is_ok()`, `is_err()` (partial - needs Result layout work)
13//! - **&[T]**: `len()`, `is_empty()`
14//! - **&str**: `len()`, `is_empty()`
15//! - **[T; N]**: `len()` (when array types are available in debug info)
16//!
17//! ## Future Methods to Implement
18//!
19//! ### Standard Collections
20//! - **HashMap<K,V>** / **BTreeMap<K,V>**: `len()`, `is_empty()`, `contains_key()`
21//! - **HashSet<T>** / **BTreeSet<T>**: `len()`, `is_empty()`, `contains()`
22//! - **VecDeque<T>**: `len()`, `capacity()`, `is_empty()`
23//! - **LinkedList<T>**: `len()`, `is_empty()`
24//!
25//! ### Smart Pointers
26//! - **Box<T>**: `is_null()` (check for null pointer)
27//! - **Rc<T>** / **Arc<T>**: `strong_count()`, `weak_count()`
28//! - **RefCell<T>**: `try_borrow()` (check if currently borrowed)
29//! - **Mutex<T>** / **RwLock<T>**: `is_poisoned()`
30//!
31//! ### Iterators
32//! - **Range<T>**: `len()`, `is_empty()`, `contains()`
33//! - **Chars**: `count()` (for string char iterators)
34//!
35//! ### Networking & I/O
36//! - **SocketAddr**: `ip()`, `port()`, `is_ipv4()`, `is_ipv6()`
37//! - **IpAddr**: `is_loopback()`, `is_multicast()`, `is_global()`
38//! - **Path** / **PathBuf**: `is_absolute()`, `is_relative()`, `exists()` (if we can stat)
39//!
40//! ### Time & Duration
41//! - **Duration**: `as_secs()`, `as_millis()`, `as_micros()`, `as_nanos()`, `is_zero()`
42//! - **Instant**: `elapsed()` (if we can get current time)
43//! - **SystemTime**: `duration_since()` (relative to UNIX_EPOCH)
44//!
45//! ### Concurrency Primitives
46//! - **AtomicBool** / **AtomicI32** etc: `load()` (read current value)
47//! - **Barrier**: `is_leader()` (for barrier synchronization)
48//!
49//! ### Custom Methods with Arguments
50//! - **Vec<T>**: `get(index)` - bounds-checked element access
51//! - **String**: `char_at(index)` - get character at byte index
52//! - **HashMap<K,V>**: `get(key)` - look up value by key
53//! - **Range<T>**: `contains(value)` - check if value is in range
54//!
55//! ### Complex Computed Properties
56//! - **String**: `char_len()` - length in Unicode characters (vs bytes)
57//! - **Vec<T>**: `remaining_capacity()` - capacity minus length
58//! - **Path**: `file_name()`, `extension()`, `parent()` (string operations on path)
59//!
60//! ## Implementation Notes
61//!
62//! Synthetic methods should only be implemented when:
63//! 1. The computation can be done entirely from memory layout and debug info
64//! 2. The result is deterministic and side-effect free
65//! 3. The performance is reasonable (no complex algorithms)
66//! 4. The method provides significant debugging value
67//!
68//! Methods requiring system calls, file I/O, network access, or complex
69//! computations should use actual method execution instead.
70
71use anyhow::{Result, anyhow};
72use rudy_dwarf::{Die, types::DieTypeDefinition};
73use rudy_types::{Layout, Location, StdLayout};
74
75use crate::{DataResolver, Value};
76
77/// A synthetic method that can be evaluated without execution
78#[derive(Debug, Clone)]
79pub struct SyntheticMethod {
80    /// The method name (e.g., "len", "is_empty")
81    pub name: &'static str,
82
83    /// Human-readable signature for display
84    pub signature: &'static str,
85
86    /// Whether this method requires arguments
87    pub takes_args: bool,
88}
89
90/// Get all synthetic methods available for a given type
91pub fn get_synthetic_methods<L: Location>(type_layout: &Layout<L>) -> Vec<SyntheticMethod> {
92    match type_layout {
93        Layout::Std(std_layout) => match std_layout {
94            StdLayout::Vec(_) => vec![
95                SyntheticMethod {
96                    name: "len",
97                    signature: "fn len(&self) -> usize",
98                    takes_args: false,
99                },
100                SyntheticMethod {
101                    name: "capacity",
102                    signature: "fn capacity(&self) -> usize",
103                    takes_args: false,
104                },
105                SyntheticMethod {
106                    name: "is_empty",
107                    signature: "fn is_empty(&self) -> bool",
108                    takes_args: false,
109                },
110            ],
111            StdLayout::String(_) => vec![
112                SyntheticMethod {
113                    name: "len",
114                    signature: "fn len(&self) -> usize",
115                    takes_args: false,
116                },
117                SyntheticMethod {
118                    name: "is_empty",
119                    signature: "fn is_empty(&self) -> bool",
120                    takes_args: false,
121                },
122            ],
123            StdLayout::Option(_) => vec![
124                SyntheticMethod {
125                    name: "is_some",
126                    signature: "fn is_some(&self) -> bool",
127                    takes_args: false,
128                },
129                SyntheticMethod {
130                    name: "is_none",
131                    signature: "fn is_none(&self) -> bool",
132                    takes_args: false,
133                },
134            ],
135            StdLayout::Result(_) => vec![
136                SyntheticMethod {
137                    name: "is_ok",
138                    signature: "fn is_ok(&self) -> bool",
139                    takes_args: false,
140                },
141                SyntheticMethod {
142                    name: "is_err",
143                    signature: "fn is_err(&self) -> bool",
144                    takes_args: false,
145                },
146            ],
147            StdLayout::Map(_) => vec![
148                // HashMap/BTreeMap methods require understanding internal structure
149                // which is complex and varies by implementation
150            ],
151            _ => vec![],
152        },
153        Layout::Primitive(prim_layout) => {
154            use rudy_types::PrimitiveLayout;
155            match prim_layout {
156                PrimitiveLayout::Slice(_) | PrimitiveLayout::StrSlice(_) => vec![
157                    SyntheticMethod {
158                        name: "len",
159                        signature: "fn len(&self) -> usize",
160                        takes_args: false,
161                    },
162                    SyntheticMethod {
163                        name: "is_empty",
164                        signature: "fn is_empty(&self) -> bool",
165                        takes_args: false,
166                    },
167                ],
168                PrimitiveLayout::Array(_) => vec![SyntheticMethod {
169                    name: "len",
170                    signature: "fn len(&self) -> usize",
171                    takes_args: false,
172                }],
173                _ => vec![],
174            }
175        }
176        _ => vec![],
177    }
178}
179
180/// Evaluate a synthetic method call
181pub fn evaluate_synthetic_method(
182    address: u64,
183    def: &DieTypeDefinition,
184    method: &str,
185    _args: &[Value], // For future use when we support methods with arguments
186    resolver: &dyn DataResolver,
187) -> Result<Value> {
188    match def.layout.as_ref() {
189        Layout::Std(std_layout) => match std_layout {
190            StdLayout::Vec(vec_layout) => {
191                evaluate_vec_method(address, vec_layout, method, resolver)
192            }
193            StdLayout::String(string_layout) => {
194                evaluate_string_method(address, string_layout, method, resolver)
195            }
196            StdLayout::Option(option_layout) => {
197                evaluate_option_method(address, option_layout, method, resolver)
198            }
199            StdLayout::Result(result_layout) => {
200                evaluate_result_method(address, result_layout, method, resolver)
201            }
202            StdLayout::Map(map_layout) => {
203                evaluate_map_method(address, map_layout, method, resolver)
204            }
205            _ => Err(anyhow!(
206                "No synthetic method '{}' for type {}",
207                method,
208                def.display_name()
209            )),
210        },
211        Layout::Primitive(prim_layout) => {
212            use rudy_types::PrimitiveLayout;
213            match prim_layout {
214                PrimitiveLayout::Slice(slice_layout) => {
215                    evaluate_slice_method(address, slice_layout, method, resolver)
216                }
217                PrimitiveLayout::StrSlice(_) => {
218                    evaluate_str_slice_method(address, method, resolver)
219                }
220                PrimitiveLayout::Array(array_layout) => evaluate_array_method(array_layout, method),
221                _ => Err(anyhow!(
222                    "No synthetic method '{}' for type {}",
223                    method,
224                    def.display_name()
225                )),
226            }
227        }
228        _ => Err(anyhow!(
229            "No synthetic methods for type {}",
230            def.display_name()
231        )),
232    }
233}
234
235fn evaluate_vec_method(
236    address: u64,
237    vec_layout: &rudy_types::VecLayout<Die>,
238    method: &str,
239    resolver: &dyn DataResolver,
240) -> Result<Value> {
241    match method {
242        "len" => {
243            let len_address = address + vec_layout.length_offset as u64;
244            let len_bytes = resolver.read_memory(len_address, std::mem::size_of::<usize>())?;
245            let len = usize_from_bytes(&len_bytes)?;
246            Ok(Value::Scalar {
247                ty: "usize".to_string(),
248                value: len.to_string(),
249            })
250        }
251        "capacity" => {
252            // Vec layout typically has: data_ptr, length, capacity
253            // capacity is usually after length
254            let cap_offset = vec_layout.capacity_offset;
255            let cap_address = address + cap_offset as u64;
256            let cap_bytes = resolver.read_memory(cap_address, std::mem::size_of::<usize>())?;
257            let cap = usize_from_bytes(&cap_bytes)?;
258            Ok(Value::Scalar {
259                ty: "usize".to_string(),
260                value: cap.to_string(),
261            })
262        }
263        "is_empty" => {
264            let len_address = address + vec_layout.length_offset as u64;
265            let len_bytes = resolver.read_memory(len_address, std::mem::size_of::<usize>())?;
266            let len = usize_from_bytes(&len_bytes)?;
267            Ok(Value::Scalar {
268                ty: "bool".to_string(),
269                value: (len == 0).to_string(),
270            })
271        }
272        _ => Err(anyhow!("Unknown synthetic method '{}' for Vec", method)),
273    }
274}
275
276fn evaluate_string_method(
277    address: u64,
278    string_layout: &rudy_types::StringLayout<Die>,
279    method: &str,
280    resolver: &dyn DataResolver,
281) -> Result<Value> {
282    // String is just a Vec<u8> internally
283    let vec_layout = &string_layout.0;
284    match method {
285        "len" => evaluate_vec_method(address, vec_layout, "len", resolver),
286        "is_empty" => evaluate_vec_method(address, vec_layout, "is_empty", resolver),
287        "capacity" => evaluate_vec_method(address, vec_layout, "capacity", resolver),
288        _ => Err(anyhow!("Unknown synthetic method '{}' for String", method)),
289    }
290}
291
292fn evaluate_option_method(
293    address: u64,
294    option_layout: &rudy_types::OptionLayout<Die>,
295    method: &str,
296    resolver: &dyn DataResolver,
297) -> Result<Value> {
298    // Read the discriminant to check Some vs None
299    let discriminant_bytes = resolver.read_memory(
300        address + option_layout.discriminant.offset as u64,
301        option_layout.discriminant.size(),
302    )?;
303
304    let discriminant_value = match discriminant_bytes.len() {
305        1 => discriminant_bytes[0] as u64,
306        2 => u16::from_le_bytes(discriminant_bytes.try_into().unwrap()) as u64,
307        4 => u32::from_le_bytes(discriminant_bytes.try_into().unwrap()) as u64,
308        8 => u64::from_le_bytes(discriminant_bytes.try_into().unwrap()),
309        _ => return Err(anyhow!("Unexpected discriminant size")),
310    };
311
312    // For Option, typically discriminant 0 = None, 1 = Some
313    // This is a common pattern but may vary based on compiler optimization
314    match method {
315        "is_some" => Ok(Value::Scalar {
316            ty: "bool".to_string(),
317            value: (discriminant_value != 0).to_string(),
318        }),
319        "is_none" => Ok(Value::Scalar {
320            ty: "bool".to_string(),
321            value: (discriminant_value == 0).to_string(),
322        }),
323        _ => Err(anyhow!("Unknown synthetic method '{}' for Option", method)),
324    }
325}
326
327fn evaluate_result_method(
328    address: u64,
329    result_layout: &rudy_types::ResultLayout<Die>,
330    method: &str,
331    resolver: &dyn DataResolver,
332) -> Result<Value> {
333    // Read the discriminant to check Ok vs Err
334    let discriminant_bytes = resolver.read_memory(
335        address + result_layout.discriminant.offset as u64,
336        result_layout.discriminant.size(),
337    )?;
338
339    let discriminant_value = match discriminant_bytes.len() {
340        1 => discriminant_bytes[0] as u64,
341        2 => u16::from_le_bytes(discriminant_bytes.clone().try_into().unwrap()) as u64,
342        4 => u32::from_le_bytes(discriminant_bytes.clone().try_into().unwrap()) as u64,
343        8 => u64::from_le_bytes(discriminant_bytes.clone().try_into().unwrap()),
344        _ => return Err(anyhow!("Unexpected discriminant size")),
345    };
346
347    // Debug: print the discriminant value and bytes
348    tracing::debug!(
349        "Result discriminant bytes: {:?}, value: {:#x}, at offset {}",
350        discriminant_bytes,
351        discriminant_value,
352        result_layout.discriminant.offset
353    );
354
355    // Result uses a special encoding where the discriminant is part of the payload
356    // For Result<T, E>, when it's Ok, the discriminant area contains the Ok value
357    // When it's Err, it uses a special marker (often 0x8000000000000000 for 64-bit)
358    // This is an optimization to avoid wasting space
359    match method {
360        "is_ok" => {
361            // Check if the high bit is NOT set (indicating Ok variant)
362            let is_ok = match discriminant_bytes.len() {
363                8 => (discriminant_value & 0x8000000000000000) == 0,
364                4 => (discriminant_value & 0x80000000) == 0,
365                _ => discriminant_value == 0,
366            };
367            Ok(Value::Scalar {
368                ty: "bool".to_string(),
369                value: is_ok.to_string(),
370            })
371        }
372        "is_err" => {
373            // Check if the high bit IS set (indicating Err variant)
374            let is_err = match discriminant_bytes.len() {
375                8 => (discriminant_value & 0x8000000000000000) != 0,
376                4 => (discriminant_value & 0x80000000) != 0,
377                _ => discriminant_value != 0,
378            };
379            Ok(Value::Scalar {
380                ty: "bool".to_string(),
381                value: is_err.to_string(),
382            })
383        }
384        _ => Err(anyhow!("Unknown synthetic method '{}' for Result", method)),
385    }
386}
387
388fn evaluate_map_method(
389    _address: u64,
390    _map_layout: &rudy_types::MapLayout<Die>,
391    method: &str,
392    _resolver: &dyn DataResolver,
393) -> Result<Value> {
394    // HashMap/BTreeMap methods are more complex to implement
395    // as they require understanding the internal structure
396    match method {
397        "len" | "is_empty" => Err(anyhow!(
398            "HashMap/BTreeMap synthetic methods not yet implemented"
399        )),
400        _ => Err(anyhow!("Unknown synthetic method '{}' for Map", method)),
401    }
402}
403
404fn evaluate_slice_method(
405    address: u64,
406    _slice_layout: &rudy_types::SliceLayout<Die>,
407    method: &str,
408    resolver: &dyn DataResolver,
409) -> Result<Value> {
410    match method {
411        "len" => {
412            // Slice is a fat pointer: (data_ptr, length)
413            // Length is at offset 8 (after the pointer)
414            let len_address = address + std::mem::size_of::<usize>() as u64;
415            let len_bytes = resolver.read_memory(len_address, std::mem::size_of::<usize>())?;
416            let len = usize_from_bytes(&len_bytes)?;
417            Ok(Value::Scalar {
418                ty: "usize".to_string(),
419                value: len.to_string(),
420            })
421        }
422        "is_empty" => {
423            let len_address = address + std::mem::size_of::<usize>() as u64;
424            let len_bytes = resolver.read_memory(len_address, std::mem::size_of::<usize>())?;
425            let len = usize_from_bytes(&len_bytes)?;
426            Ok(Value::Scalar {
427                ty: "bool".to_string(),
428                value: (len == 0).to_string(),
429            })
430        }
431        _ => Err(anyhow!("Unknown synthetic method '{}' for slice", method)),
432    }
433}
434
435fn evaluate_str_slice_method(
436    address: u64,
437    method: &str,
438    resolver: &dyn DataResolver,
439) -> Result<Value> {
440    // &str is a fat pointer just like slices: (data_ptr, length)
441    match method {
442        "len" => {
443            // Length is at offset 8 (after the pointer)
444            let len_address = address + std::mem::size_of::<usize>() as u64;
445            let len_bytes = resolver.read_memory(len_address, std::mem::size_of::<usize>())?;
446            let len = usize_from_bytes(&len_bytes)?;
447            Ok(Value::Scalar {
448                ty: "usize".to_string(),
449                value: len.to_string(),
450            })
451        }
452        "is_empty" => {
453            let len_address = address + std::mem::size_of::<usize>() as u64;
454            let len_bytes = resolver.read_memory(len_address, std::mem::size_of::<usize>())?;
455            let len = usize_from_bytes(&len_bytes)?;
456            Ok(Value::Scalar {
457                ty: "bool".to_string(),
458                value: (len == 0).to_string(),
459            })
460        }
461        _ => Err(anyhow!("Unknown synthetic method '{}' for &str", method)),
462    }
463}
464
465fn evaluate_array_method(
466    array_layout: &rudy_types::ArrayLayout<Die>,
467    method: &str,
468) -> Result<Value> {
469    match method {
470        "len" => Ok(Value::Scalar {
471            ty: "usize".to_string(),
472            value: array_layout.length.to_string(),
473        }),
474        _ => Err(anyhow!("Unknown synthetic method '{}' for array", method)),
475    }
476}
477
478fn usize_from_bytes(bytes: &[u8]) -> Result<usize> {
479    if bytes.len() == 8 {
480        Ok(u64::from_le_bytes(bytes.try_into().unwrap()) as usize)
481    } else if bytes.len() == 4 {
482        Ok(u32::from_le_bytes(bytes.try_into().unwrap()) as usize)
483    } else {
484        Err(anyhow!("Unexpected size for usize: {} bytes", bytes.len()))
485    }
486}