diff --git a/src/debugger/debugee/dwarf/eval.rs b/src/debugger/debugee/dwarf/eval.rs index 234f45f..e73a5bb 100644 --- a/src/debugger/debugee/dwarf/eval.rs +++ b/src/debugger/debugee/dwarf/eval.rs @@ -11,6 +11,7 @@ use crate::debugger::error::Error::{ }; use crate::debugger::register::{DwarfRegisterMap, RegisterMap}; use crate::debugger::{debugee, ExplorationContext}; +use crate::version::Version; use bytes::{BufMut, Bytes, BytesMut}; use gimli::{ DebugAddr, Encoding, EndianSlice, EvaluationResult, Expression, Location, Piece, Register, @@ -24,8 +25,20 @@ use std::collections::hash_map::Entry; use std::collections::HashMap; use std::mem; +pub struct EvaluationContext<'a> { + pub evaluator: &'a ExpressionEvaluator<'a>, + pub expl_ctx: &'a ExplorationContext, +} + +impl<'a> EvaluationContext<'a> { + pub fn rustc_version(&self) -> Option { + self.evaluator.unit().rustc_version() + } +} + /// Resolve requirements that the `ExpressionEvaluator` may need. Relevant for the current breakpoint. /// Some options are lazy to avoid overhead on recalculation. +#[derive(Clone)] struct RequirementsResolver<'a> { debugee: &'a Debugee, cfa: RefCell>, @@ -164,6 +177,7 @@ impl ExternalRequirementsResolver { } } +#[derive(Clone)] pub struct ExpressionEvaluator<'a> { encoding: Encoding, unit: &'a Unit, diff --git a/src/debugger/debugee/dwarf/mod.rs b/src/debugger/debugee/dwarf/mod.rs index 660ff92..80d1921 100644 --- a/src/debugger/debugee/dwarf/mod.rs +++ b/src/debugger/debugee/dwarf/mod.rs @@ -11,9 +11,9 @@ pub use self::unwind::DwarfUnwinder; use crate::debugger::address::{GlobalAddress, RelocatedAddress}; use crate::debugger::debugee::dwarf::eval::AddressKind; +use crate::debugger::debugee::dwarf::eval::EvaluationContext; use crate::debugger::debugee::dwarf::location::Location as DwarfLocation; use crate::debugger::debugee::dwarf::r#type::ComplexType; -use crate::debugger::debugee::dwarf::r#type::EvaluationContext; use crate::debugger::debugee::dwarf::symbol::SymbolTab; use crate::debugger::debugee::dwarf::unit::{ DieRef, DieVariant, DwarfUnitParser, Entry, FunctionDie, Node, ParameterDie, @@ -26,7 +26,7 @@ use crate::debugger::error::Error::{ DebugIDFormat, FBANotAnExpression, FunctionNotFound, NoFBA, NoFunctionRanges, UnitNotFound, }; use crate::debugger::register::{DwarfRegisterMap, RegisterMap}; -use crate::debugger::variable::select::ObjectBinaryRepr; +use crate::debugger::variable::ObjectBinaryRepr; use crate::debugger::ExplorationContext; use crate::{muted_error, resolve_unit_call, version_switch, weak_error}; use fallible_iterator::FallibleIterator; @@ -512,7 +512,7 @@ impl DebugInformation { &self, location: Location, name: &str, - ) -> Result>, Error> { + ) -> Result>, Error> { let units = self.get_units()?; let mut found = vec![]; @@ -944,11 +944,11 @@ impl NamespaceHierarchy { } } -pub struct ContextualDieRef<'a, T> { - pub debug_info: &'a DebugInformation, +pub struct ContextualDieRef<'node, 'dbg: 'node, T> { + pub debug_info: &'dbg DebugInformation, pub unit_idx: usize, - pub node: &'a Node, - pub die: &'a T, + pub node: &'node Node, + pub die: &'node T, } #[macro_export] @@ -958,26 +958,26 @@ macro_rules! ctx_resolve_unit_call { }}; } -impl<'a, T> Clone for ContextualDieRef<'a, T> { +impl<'node, 'dbg, T> Clone for ContextualDieRef<'node, 'dbg, T> { fn clone(&self) -> Self { *self } } -impl<'a, T> Copy for ContextualDieRef<'a, T> {} +impl<'node, 'dbg, T> Copy for ContextualDieRef<'node, 'dbg, T> {} -impl<'a, T> ContextualDieRef<'a, T> { +impl<'node, 'dbg, T> ContextualDieRef<'node, 'dbg, T> { pub fn namespaces(&self) -> NamespaceHierarchy { let entries = ctx_resolve_unit_call!(self, entries,); NamespaceHierarchy::for_node(self.node, entries) } - pub fn unit(&self) -> &'a Unit { + pub fn unit(&self) -> &'dbg Unit { self.debug_info.unit_ensure(self.unit_idx) } } -impl<'ctx> ContextualDieRef<'ctx, FunctionDie> { +impl<'ctx> ContextualDieRef<'ctx, 'ctx, FunctionDie> { pub fn full_name(&self) -> Option { self.die .base_attributes @@ -1007,7 +1007,7 @@ impl<'ctx> ContextualDieRef<'ctx, FunctionDie> { pub fn local_variables<'this>( &'this self, pc: GlobalAddress, - ) -> Vec> { + ) -> Vec> { let mut result = vec![]; let mut queue = VecDeque::from(self.node.children.clone()); while let Some(idx) = queue.pop_front() { @@ -1033,7 +1033,7 @@ impl<'ctx> ContextualDieRef<'ctx, FunctionDie> { &'this self, pc: GlobalAddress, needle: &str, - ) -> Option> { + ) -> Option> { let mut queue = VecDeque::from(self.node.children.clone()); while let Some(idx) = queue.pop_front() { let entry = ctx_resolve_unit_call!(self, entry, idx); @@ -1054,7 +1054,7 @@ impl<'ctx> ContextualDieRef<'ctx, FunctionDie> { None } - pub fn parameters(&self) -> Vec> { + pub fn parameters(&self) -> Vec> { let mut result = vec![]; for &idx in &self.node.children { let entry = ctx_resolve_unit_call!(self, entry, idx); @@ -1139,7 +1139,7 @@ impl<'ctx> ContextualDieRef<'ctx, FunctionDie> { } } -impl<'ctx> ContextualDieRef<'ctx, VariableDie> { +impl<'ctx> ContextualDieRef<'ctx, 'ctx, VariableDie> { pub fn ranges(&self) -> Option<&[Range]> { if let Some(lb_idx) = self.die.lexical_block_idx { let entry = ctx_resolve_unit_call!(self, entry, lb_idx); @@ -1160,7 +1160,7 @@ impl<'ctx> ContextualDieRef<'ctx, VariableDie> { .unwrap_or(true) } - pub fn assume_parent_function(&self) -> Option> { + pub fn assume_parent_function(&self) -> Option> { let mut mb_parent = self.node.parent; while let Some(p) = mb_parent { @@ -1181,7 +1181,7 @@ impl<'ctx> ContextualDieRef<'ctx, VariableDie> { } } -impl<'ctx> ContextualDieRef<'ctx, ParameterDie> { +impl<'ctx> ContextualDieRef<'ctx, 'ctx, ParameterDie> { /// Return max range (with max `end` address) of an underlying function. /// If it's possible, `end` address in range equals to function epilog begin. pub fn max_range(&self) -> Option { @@ -1205,7 +1205,7 @@ impl<'ctx> ContextualDieRef<'ctx, ParameterDie> { } } -impl<'ctx, D: AsAllocatedData> ContextualDieRef<'ctx, D> { +impl<'ctx, D: AsAllocatedData> ContextualDieRef<'ctx, 'ctx, D> { pub fn r#type(&self) -> Option { let parser = r#type::TypeParser::new(); Some(parser.parse(*self, self.die.type_ref()?)) @@ -1227,7 +1227,7 @@ impl<'ctx, D: AsAllocatedData> ContextualDieRef<'ctx, D> { evaluator: &evaluator, expl_ctx: ctx, }, - r#type.root, + r#type.root(), )? as usize; let (address, raw_data) = weak_error!(eval_result.into_raw_bytes(type_size, AddressKind::MemoryAddress))?; diff --git a/src/debugger/debugee/dwarf/type.rs b/src/debugger/debugee/dwarf/type.rs index 507abee..497507c 100644 --- a/src/debugger/debugee/dwarf/type.rs +++ b/src/debugger/debugee/dwarf/type.rs @@ -1,4 +1,4 @@ -use crate::debugger::debugee::dwarf::eval::{AddressKind, ExpressionEvaluator}; +use crate::debugger::debugee::dwarf::eval::{AddressKind, EvaluationContext}; use crate::debugger::debugee::dwarf::unit::{ ArrayDie, AtomicDie, BaseTypeDie, ConstTypeDie, DieRef, DieVariant, EnumTypeDie, PointerType, RestrictDie, StructTypeDie, SubroutineDie, TypeDefDie, TypeMemberDie, UnionTypeDie, @@ -6,9 +6,7 @@ use crate::debugger::debugee::dwarf::unit::{ }; use crate::debugger::debugee::dwarf::{eval, ContextualDieRef, EndianArcSlice, NamespaceHierarchy}; use crate::debugger::error::Error; -use crate::debugger::variable::select::ObjectBinaryRepr; -use crate::debugger::ExplorationContext; -use crate::version::Version; +use crate::debugger::variable::ObjectBinaryRepr; use crate::{ctx_resolve_unit_call, weak_error}; use bytes::Bytes; use gimli::{AttributeValue, DwAte, Expression}; @@ -16,6 +14,7 @@ use log::warn; use std::cell::Cell; use std::collections::{HashMap, HashSet, VecDeque}; use std::mem; +use std::rc::Rc; use strum_macros::Display; use uuid::Uuid; @@ -54,6 +53,11 @@ impl TypeIdentity { self.name.is_none() } + #[inline(always)] + pub fn namespace(&self) -> &NamespaceHierarchy { + &self.namespace + } + #[inline(always)] pub fn name(&self) -> Option<&str> { self.name.as_deref() @@ -90,18 +94,7 @@ impl TypeIdentity { } } -pub struct EvaluationContext<'a> { - pub evaluator: &'a ExpressionEvaluator<'a>, - pub expl_ctx: &'a ExplorationContext, -} - -impl<'a> EvaluationContext<'a> { - pub fn rustc_version(&self) -> Option { - self.evaluator.unit().rustc_version() - } -} - -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct MemberLocationExpression { expr: Expression, } @@ -120,13 +113,13 @@ impl MemberLocationExpression { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum MemberLocation { Offset(i64), Expr(MemberLocationExpression), } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct StructureMember { pub in_struct_location: Option, pub name: Option, @@ -174,7 +167,7 @@ impl StructureMember { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ArrayBoundValueExpression { expr: Expression, } @@ -188,7 +181,7 @@ impl ArrayBoundValueExpression { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum ArrayBoundValue { Const(i64), Expr(ArrayBoundValueExpression), @@ -203,13 +196,13 @@ impl ArrayBoundValue { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum UpperBound { UpperBound(ArrayBoundValue), Count(ArrayBoundValue), } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ArrayType { pub namespaces: NamespaceHierarchy, byte_size: Option, @@ -275,7 +268,7 @@ impl ArrayType { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ScalarType { pub namespaces: NamespaceHierarchy, pub name: Option, @@ -293,7 +286,7 @@ impl ScalarType { } /// List of type modifiers -#[derive(Display, Clone, Copy, PartialEq)] +#[derive(Display, Clone, Copy, PartialEq, Debug)] #[strum(serialize_all = "snake_case")] pub enum CModifier { TypeDef, @@ -303,7 +296,7 @@ pub enum CModifier { Restrict, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum TypeDeclaration { Scalar(ScalarType), Array(ArrayType), @@ -355,14 +348,20 @@ pub enum TypeDeclaration { /// Type representation. This is a graph of types where vertexes is a type declaration and edges /// is a dependencies between types. Type linking implemented by `TypeId` references. -/// Root is an identity of a main type. -#[derive(Clone)] +/// Root is id of a main type. +#[derive(Clone, Debug)] pub struct ComplexType { pub types: HashMap, - pub root: TypeId, + root: TypeId, } impl ComplexType { + /// Return root type id. + #[inline(always)] + pub fn root(&self) -> TypeId { + self.root + } + /// Return name of some of a type existed in a complex type. pub fn identity(&self, typ: TypeId) -> TypeIdentity { let Some(r#type) = self.types.get(&typ) else { @@ -448,7 +447,7 @@ impl ComplexType { } } - /// Returns size of some of type existed in a complex type. + /// Return size of a type existed from a complex type. pub fn type_size_in_bytes(&self, eval_ctx: &EvaluationContext, typ: TypeId) -> Option { match &self.types.get(&typ)? { TypeDeclaration::Scalar(s) => s.byte_size, @@ -569,7 +568,11 @@ impl TypeParser { } /// Parse a `ComplexType` from a DIEs. - pub fn parse(self, ctx_die: ContextualDieRef<'_, T>, root_id: TypeId) -> ComplexType { + pub fn parse<'dbg, T>( + self, + ctx_die: ContextualDieRef<'dbg, 'dbg, T>, + root_id: TypeId, + ) -> ComplexType { let mut this = self; this.parse_inner(ctx_die, root_id); ComplexType { @@ -578,7 +581,7 @@ impl TypeParser { } } - fn parse_inner(&mut self, ctx_die: ContextualDieRef<'_, T>, type_ref: DieRef) { + fn parse_inner(&mut self, ctx_die: ContextualDieRef<'_, '_, T>, type_ref: DieRef) { // guard from recursion types parsing if self.known_type_ids.contains(&type_ref) { return; @@ -670,7 +673,10 @@ impl TypeParser { } } - fn parse_base_type(&mut self, ctx_die: ContextualDieRef<'_, BaseTypeDie>) -> TypeDeclaration { + fn parse_base_type( + &mut self, + ctx_die: ContextualDieRef<'_, '_, BaseTypeDie>, + ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); TypeDeclaration::Scalar(ScalarType { namespaces: ctx_die.namespaces(), @@ -680,7 +686,10 @@ impl TypeParser { }) } - fn parse_array(&mut self, ctx_die: ContextualDieRef<'_, ArrayDie>) -> TypeDeclaration { + fn parse_array<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, ArrayDie>, + ) -> TypeDeclaration { let mb_type_ref = ctx_die.die.type_ref; if let Some(reference) = mb_type_ref { self.parse_inner(ctx_die, reference); @@ -748,7 +757,10 @@ impl TypeParser { /// Convert DW_TAG_structure_type into TypeDeclaration. /// In rust DW_TAG_structure_type DIE can be interpreter as enum, see https://github.com/rust-lang/rust/issues/32920 - fn parse_struct(&mut self, ctx_die: ContextualDieRef<'_, StructTypeDie>) -> TypeDeclaration { + fn parse_struct<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, StructTypeDie>, + ) -> TypeDeclaration { let is_enum = ctx_die.node.children.iter().any(|c_idx| { let entry = ctx_resolve_unit_call!(ctx_die, entry, *c_idx); matches!(entry.die, DieVariant::VariantPart(_)) @@ -763,7 +775,7 @@ impl TypeParser { fn parse_struct_struct( &mut self, - ctx_die: ContextualDieRef<'_, StructTypeDie>, + ctx_die: ContextualDieRef<'_, '_, StructTypeDie>, ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let members = ctx_die @@ -808,7 +820,10 @@ impl TypeParser { } } - fn parse_member(&mut self, ctx_die: ContextualDieRef<'_, TypeMemberDie>) -> StructureMember { + fn parse_member<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, TypeMemberDie>, + ) -> StructureMember { let loc = ctx_die.die.location.as_ref().map(|attr| attr.value()); let in_struct_location = if let Some(offset) = loc.as_ref().and_then(|l| l.sdata_value()) { Some(MemberLocation::Offset(offset)) @@ -834,7 +849,7 @@ impl TypeParser { fn parse_struct_enum( &mut self, - ctx_die: ContextualDieRef<'_, StructTypeDie>, + ctx_die: ContextualDieRef<'_, '_, StructTypeDie>, ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); @@ -912,7 +927,10 @@ impl TypeParser { } } - fn parse_enum(&mut self, ctx_die: ContextualDieRef<'_, EnumTypeDie>) -> TypeDeclaration { + fn parse_enum<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, EnumTypeDie>, + ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let mb_discr_type = ctx_die.die.type_ref; @@ -946,7 +964,7 @@ impl TypeParser { } } - fn parse_union(&mut self, ctx_die: ContextualDieRef<'_, UnionTypeDie>) -> TypeDeclaration { + fn parse_union(&mut self, ctx_die: ContextualDieRef<'_, '_, UnionTypeDie>) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let members = ctx_die .node @@ -974,7 +992,10 @@ impl TypeParser { } } - fn parse_pointer(&mut self, ctx_die: ContextualDieRef<'_, PointerType>) -> TypeDeclaration { + fn parse_pointer<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, PointerType>, + ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let mb_type_ref = ctx_die.die.type_ref; @@ -989,9 +1010,9 @@ impl TypeParser { } } - fn parse_subroutine( + fn parse_subroutine<'dbg>( &mut self, - ctx_die: ContextualDieRef<'_, SubroutineDie>, + ctx_die: ContextualDieRef<'dbg, 'dbg, SubroutineDie>, ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let mb_ret_type_ref = ctx_die.die.return_type_ref; @@ -1009,7 +1030,10 @@ impl TypeParser { macro_rules! parse_modifier_fn { ($fn_name: ident, $die: ty, $modifier: expr) => { - fn $fn_name(&mut self, ctx_die: ContextualDieRef<'_, $die>) -> TypeDeclaration { + fn $fn_name<'dbg>( + &mut self, + ctx_die: ContextualDieRef<'dbg, 'dbg, $die>, + ) -> TypeDeclaration { let name = ctx_die.die.base_attributes.name.clone(); let mb_type_ref = ctx_die.die.type_ref; if let Some(inner_type) = mb_type_ref { @@ -1037,4 +1061,4 @@ impl TypeParser { /// A cache structure for types. /// Every type identified by its `TypeId` and DWARF unit uuid. -pub type TypeCache = HashMap<(Uuid, TypeId), ComplexType>; +pub type TypeCache = HashMap<(Uuid, TypeId), Rc>; diff --git a/src/debugger/debugee/dwarf/unit/mod.rs b/src/debugger/debugee/dwarf/unit/mod.rs index 1db17fc..5d9c605 100644 --- a/src/debugger/debugee/dwarf/unit/mod.rs +++ b/src/debugger/debugee/dwarf/unit/mod.rs @@ -405,7 +405,7 @@ pub struct Node { } impl Node { - pub fn new_leaf(parent: Option) -> Node { + pub const fn new_leaf(parent: Option) -> Node { Self { parent, children: vec![], diff --git a/src/debugger/error.rs b/src/debugger/error.rs index 1e4f2db..29036d8 100644 --- a/src/debugger/error.rs +++ b/src/debugger/error.rs @@ -1,7 +1,7 @@ use crate::debugger::address::GlobalAddress; use crate::debugger::debugee::dwarf::unit::DieRef; use crate::debugger::debugee::RendezvousError; -use crate::debugger::variable::ParsingError; +use crate::debugger::variable::value::ParsingError; use gimli::UnitOffset; use nix::unistd::Pid; use std::str::Utf8Error; diff --git a/src/debugger/mod.rs b/src/debugger/mod.rs index 7398773..41a43cf 100644 --- a/src/debugger/mod.rs +++ b/src/debugger/mod.rs @@ -44,8 +44,9 @@ use crate::debugger::process::{Child, Installed}; use crate::debugger::register::debug::BreakCondition; use crate::debugger::register::{DwarfRegisterMap, Register, RegisterMap}; use crate::debugger::step::StepResult; -use crate::debugger::variable::select::{Selector, DQE}; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::dqe::{Dqe, Selector}; +use crate::debugger::variable::execute::QueryResult; +use crate::debugger::variable::value::Value; use crate::debugger::watchpoint::WatchpointRegistry; use crate::debugger::Error::Syscall; use crate::oracle::Oracle; @@ -93,6 +94,7 @@ pub trait EventHook { /// * `num`: breakpoint number /// * `place`: breakpoint number /// * `condition`: reason of a watchpoint activation + /// * `dqe_string`: stringifed data query expression (if exist) /// * `old_value`: previous expression or mem location value /// * `new_value`: current expression or mem location value /// * `end_of_scope`: true if watchpoint activated cause end of scope is reached @@ -103,8 +105,9 @@ pub trait EventHook { num: u32, place: Option, condition: BreakCondition, - old_value: Option<&VariableIR>, - new_value: Option<&VariableIR>, + dqe_string: Option<&str>, + old_value: Option<&Value>, + new_value: Option<&Value>, end_of_scope: bool, ) -> anyhow::Result<()>; @@ -163,8 +166,9 @@ impl EventHook for NopHook { _: u32, _: Option, _: BreakCondition, - _: Option<&VariableIR>, - _: Option<&VariableIR>, + _: Option<&str>, + _: Option<&Value>, + _: Option<&Value>, _: bool, ) -> anyhow::Result<()> { Ok(()) @@ -872,13 +876,12 @@ impl Debugger { } /// Reads all local variables from current function in current thread. - pub fn read_local_variables(&self) -> Result, Error> { + pub fn read_local_variables(&self) -> Result, Error> { disable_when_not_stared!(self); - let evaluator = - variable::select::SelectExpressionEvaluator::new(self, DQE::Variable(Selector::Any)); - let eval_result = evaluator.evaluate()?; - Ok(eval_result.into_iter().map(|res| res.variable).collect()) + let executor = variable::execute::DqeExecutor::new(self); + let eval_result = executor.query(&Dqe::Variable(Selector::Any))?; + Ok(eval_result) } /// Reads any variable from the current thread, uses a select expression to filter variables @@ -887,11 +890,11 @@ impl Debugger { /// # Arguments /// /// * `select_expr`: data query expression - pub fn read_variable(&self, select_expr: DQE) -> Result, Error> { + pub fn read_variable(&self, select_expr: Dqe) -> Result, Error> { disable_when_not_stared!(self); - let evaluator = variable::select::SelectExpressionEvaluator::new(self, select_expr); - let eval_result = evaluator.evaluate()?; - Ok(eval_result.into_iter().map(|res| res.variable).collect()) + let executor = variable::execute::DqeExecutor::new(self); + let eval_result = executor.query(&select_expr)?; + Ok(eval_result) } /// Reads any variable from the current thread, uses a select expression to filter variables @@ -900,10 +903,10 @@ impl Debugger { /// # Arguments /// /// * `select_expr`: data query expression - pub fn read_variable_names(&self, select_expr: DQE) -> Result, Error> { + pub fn read_variable_names(&self, select_expr: Dqe) -> Result, Error> { disable_when_not_stared!(self); - let evaluator = variable::select::SelectExpressionEvaluator::new(self, select_expr); - evaluator.evaluate_names() + let executor = variable::execute::DqeExecutor::new(self); + executor.query_names(&select_expr) } /// Reads any argument from the current function, uses a select expression to filter variables @@ -912,11 +915,11 @@ impl Debugger { /// # Arguments /// /// * `select_expr`: data query expression - pub fn read_argument(&self, select_expr: DQE) -> Result, Error> { + pub fn read_argument(&self, select_expr: Dqe) -> Result, Error> { disable_when_not_stared!(self); - let evaluator = variable::select::SelectExpressionEvaluator::new(self, select_expr); - let eval_result = evaluator.evaluate_on_arguments()?; - Ok(eval_result.into_iter().map(|res| res.variable).collect()) + let executor = variable::execute::DqeExecutor::new(self); + let eval_result = executor.query_arguments(&select_expr)?; + Ok(eval_result) } /// Reads any argument from the current function, uses a select expression to filter arguments @@ -925,10 +928,10 @@ impl Debugger { /// # Arguments /// /// * `select_expr`: data query expression - pub fn read_argument_names(&self, select_expr: DQE) -> Result, Error> { + pub fn read_argument_names(&self, select_expr: Dqe) -> Result, Error> { disable_when_not_stared!(self); - let evaluator = variable::select::SelectExpressionEvaluator::new(self, select_expr); - evaluator.evaluate_on_arguments_names() + let executor = variable::execute::DqeExecutor::new(self); + executor.query_arguments_names(&select_expr) } /// Return following register value. diff --git a/src/debugger/variable/dqe.rs b/src/debugger/variable/dqe.rs new file mode 100644 index 0000000..d5563cc --- /dev/null +++ b/src/debugger/variable/dqe.rs @@ -0,0 +1,226 @@ +use itertools::Itertools; +use std::collections::HashMap; +use std::fmt::{Debug, Display, Formatter}; + +/// Literal object. +/// Using it for a searching element by key in key-value containers. +#[derive(PartialEq, Clone)] +pub enum Literal { + String(String), + Int(i64), + Float(f64), + Address(usize), + Bool(bool), + EnumVariant(String, Option>), + Array(Box<[LiteralOrWildcard]>), + AssocArray(HashMap), +} + +impl Display for Literal { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Literal::String(str) => f.write_fmt(format_args!("\"{str}\"")), + Literal::Int(i) => f.write_str(&i.to_string()), + Literal::Float(float) => f.write_str(&float.to_string()), + Literal::Address(addr) => f.write_fmt(format_args!("{addr:#016X}")), + Literal::Bool(b) => f.write_fmt(format_args!("{b}")), + Literal::EnumVariant(variant, data) => { + if let Some(data) = data { + f.write_fmt(format_args!("{variant}({data})")) + } else { + f.write_fmt(format_args!("{variant}")) + } + } + Literal::Array(array) => { + let body = array + .iter() + .map(|item| match item { + LiteralOrWildcard::Literal(lit) => lit.to_string(), + LiteralOrWildcard::Wildcard => "*".to_string(), + }) + .join(", "); + f.write_fmt(format_args!("{{ {body} }}")) + } + Literal::AssocArray(assoc_array) => { + let body = assoc_array + .iter() + .map(|(key, value)| { + let value_string = match value { + LiteralOrWildcard::Literal(lit) => lit.to_string(), + LiteralOrWildcard::Wildcard => "*".to_string(), + }; + format!("\"{key}\": {value_string}") + }) + .join(", "); + + f.write_fmt(format_args!("{{ {body} }}")) + } + } + } +} + +impl Debug for Literal { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_string()) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum LiteralOrWildcard { + Literal(Literal), + Wildcard, +} + +macro_rules! impl_equal { + ($lhs: expr, $rhs: expr, $lit: path) => { + if let $lit(lhs) = $lhs { + lhs == &$rhs + } else { + false + } + }; +} + +impl Literal { + pub fn equal_with_string(&self, rhs: &str) -> bool { + impl_equal!(self, rhs, Literal::String) + } + + pub fn equal_with_address(&self, rhs: usize) -> bool { + impl_equal!(self, rhs, Literal::Address) + } + + pub fn equal_with_bool(&self, rhs: bool) -> bool { + impl_equal!(self, rhs, Literal::Bool) + } + + pub fn equal_with_int(&self, rhs: i64) -> bool { + impl_equal!(self, rhs, Literal::Int) + } + + pub fn equal_with_float(&self, rhs: f64) -> bool { + const EPS: f64 = 0.0000001f64; + if let Literal::Float(float) = self { + let diff = (*float - rhs).abs(); + diff < EPS + } else { + false + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum Selector { + Name { var_name: String, local_only: bool }, + Any, +} + +impl Selector { + pub fn by_name(name: impl ToString, local_only: bool) -> Self { + Self::Name { + var_name: name.to_string(), + local_only, + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct PointerCast { + pub ptr: usize, + pub ty: String, +} + +impl PointerCast { + pub fn new(ptr: usize, ty: impl ToString) -> Self { + Self { + ptr, + ty: ty.to_string(), + } + } +} + +/// Data query expression. +/// List of operations for select variables and their properties. +/// +/// Expression can be parsed from an input string like `*(*variable1.field2)[1]` +/// (see [`crate::ui::command`] module) +/// +/// Supported operations are: dereference, get an element by index, get field by name, make slice from a pointer. +#[derive(Debug, PartialEq, Clone)] +pub enum Dqe { + /// Select variables or arguments from debugee state. + Variable(Selector), + /// Cast raw memory address to a typed pointer. + PtrCast(PointerCast), + /// Get structure field (or similar, for example, values from hashmap with string keys). + Field(Box, String), + /// Get an element from array (or vector, vecdeq, etc.) by its index. + Index(Box, Literal), + /// Get array (or vector, vecdeq, etc.) slice. + Slice(Box, Option, Option), + /// Dereference pointer value. + Deref(Box), + /// Get address of value. + Address(Box), + /// Get canonic value (actual for specialized value, typically return underlying structure). + Canonic(Box), +} + +impl Dqe { + /// Return boxed expression. + pub fn boxed(self) -> Box { + Box::new(self) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::collections::HashMap; + + #[test] + fn test_literal_display() { + struct TestCase { + literal: Literal, + expect: &'static str, + } + let test_cases = &[ + TestCase { + literal: Literal::String("abc".to_string()), + expect: "\"abc\"", + }, + TestCase { + literal: Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Wildcard, + ])), + expect: "{ 1, 1, * }", + }, + TestCase { + literal: Literal::Address(101), + expect: "0x00000000000065", + }, + TestCase { + literal: Literal::EnumVariant( + "Some".to_string(), + Some(Box::new(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Bool(true)), + ])))), + ), + expect: "Some({ true })", + }, + TestCase { + literal: Literal::AssocArray(HashMap::from([( + "__1".to_string(), + LiteralOrWildcard::Literal(Literal::String("abc".to_string())), + )])), + expect: "{ \"__1\": \"abc\" }", + }, + ]; + + for tc in test_cases { + assert_eq!(tc.literal.to_string(), tc.expect); + } + } +} diff --git a/src/debugger/variable/execute.rs b/src/debugger/variable/execute.rs new file mode 100644 index 0000000..55f390b --- /dev/null +++ b/src/debugger/variable/execute.rs @@ -0,0 +1,453 @@ +use crate::debugger::debugee::dwarf::eval::{EvaluationContext, ExpressionEvaluator}; +use crate::debugger::debugee::dwarf::r#type::ComplexType; +use crate::debugger::debugee::dwarf::unit::{ParameterDie, VariableDie}; +use crate::debugger::debugee::dwarf::{AsAllocatedData, ContextualDieRef, DebugInformation}; +use crate::debugger::error::Error; +use crate::debugger::error::Error::FunctionNotFound; +use crate::debugger::variable::dqe::{Dqe, PointerCast, Selector}; +use crate::debugger::variable::r#virtual::VirtualVariableDie; +use crate::debugger::variable::value::parser::{ParseContext, ValueModifiers, ValueParser}; +use crate::debugger::variable::value::Value; +use crate::debugger::variable::{Identity, ObjectBinaryRepr}; +use crate::debugger::Debugger; +use crate::{ctx_resolve_unit_call, weak_error}; +use bytes::Bytes; +use gimli::Range; +use std::collections::hash_map::Entry; +use std::fmt::Debug; +use std::rc::Rc; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum QueryResultKind { + /// Result value is an argument or variable + Root, + /// Result value calculated using DQE + Expression, +} + +/// Result of DQE evaluation. +#[derive(Clone)] +pub struct QueryResult<'a> { + value: Option, + scope: Option>, + kind: QueryResultKind, + base_type: Rc, + identity: Identity, + eval_ctx_builder: EvaluationContextBuilder<'a>, +} + +impl<'a> QueryResult<'a> { + /// Return underlying typed value representation. + #[inline(always)] + pub fn value(&self) -> &Value { + self.value.as_ref().expect("should be `Some`") + } + + /// Return underlying typed value representation. + #[inline(always)] + pub fn into_value(mut self) -> Value { + self.value.take().expect("should be `Some`") + } + + /// Return underlying value and result identity (variable or argument identity). + #[inline(always)] + pub fn into_identified_value(mut self) -> (Identity, Value) { + (self.identity, self.value.take().expect("should be `Some`")) + } + + /// Return result kind: + /// - `Root` kind means that value is an argument or variable + /// - `Expression` kind means that value calculated using DQE + #[inline(always)] + pub fn kind(&self) -> QueryResultKind { + self.kind + } + + /// Return type graph using for parse a result. + #[inline(always)] + pub fn type_graph(&self) -> &ComplexType { + self.base_type.as_ref() + } + + /// Return result identity. + #[inline(always)] + pub fn identity(&self) -> &Identity { + &self.identity + } + + /// Return variable or argument scope. Scope is a PC ranges where value is valid, + /// `None` for global or virtual variables. + #[inline(always)] + pub fn scope(&self) -> &Option> { + &self.scope + } + + /// Evaluate any function with evaluation context. + pub fn with_eval_ctx T>(&self, cb: F) -> T { + self.eval_ctx_builder.with_eval_ctx(cb) + } + + /// Modify the underlying value and return a new result extended from the current one. + pub fn modify_value Option>( + mut self, + cb: F, + ) -> Option { + let value = self.value.take().expect("should be `Some`"); + let type_graph = self.type_graph(); + let eval_cb = |ctx: &EvaluationContext| { + let parse_ctx = &ParseContext { + evaluation_context: ctx, + type_graph, + }; + cb(parse_ctx, value) + }; + let new_value = self.eval_ctx_builder.with_eval_ctx(eval_cb)?; + self.value = Some(new_value); + Some(self) + } +} + +impl<'a> PartialEq for QueryResult<'a> { + fn eq(&self, other: &Self) -> bool { + self.value == other.value && self.identity == other.identity + } +} + +#[derive(Clone)] +enum EvaluationContextBuilder<'a> { + Ready(&'a Debugger, ExpressionEvaluator<'a>), + Virtual { + debugger: &'a Debugger, + debug_info: &'a DebugInformation, + unit_idx: usize, + die: VirtualVariableDie, + }, +} + +impl<'a> EvaluationContextBuilder<'a> { + fn with_eval_ctx T>(&self, cb: F) -> T { + let evaluator; + let ctx = match self { + EvaluationContextBuilder::Ready(debugger, evaluator) => EvaluationContext { + evaluator, + expl_ctx: debugger.exploration_ctx(), + }, + EvaluationContextBuilder::Virtual { + debugger, + debug_info, + unit_idx, + die, + } => { + let var = ContextualDieRef { + debug_info, + unit_idx: *unit_idx, + node: VirtualVariableDie::ANY_NODE, + die, + }; + evaluator = ctx_resolve_unit_call!(var, evaluator, &debugger.debugee); + EvaluationContext { + evaluator: &evaluator, + expl_ctx: debugger.exploration_ctx(), + } + } + }; + cb(&ctx) + } +} + +macro_rules! type_from_cache { + ($variable: expr, $cache: expr) => { + $variable + .die + .type_ref() + .and_then( + |type_ref| match $cache.entry(($variable.unit().id, type_ref)) { + Entry::Occupied(o) => Some(Rc::clone(o.get())), + Entry::Vacant(v) => $variable.r#type().map(|t| { + let t = Rc::new(t); + v.insert(t.clone()); + t + }), + }, + ) + .ok_or_else(|| { + crate::debugger::variable::value::ParsingError::Assume( + crate::debugger::variable::value::AssumeError::NoType("variable"), + ) + }) + }; +} + +/// Evaluate DQE at current location. +pub struct DqeExecutor<'a> { + debugger: &'a Debugger, +} + +impl<'dbg> DqeExecutor<'dbg> { + pub fn new(debugger: &'dbg Debugger) -> Self { + Self { debugger } + } + + fn variable_die_by_selector( + &self, + selector: &Selector, + ) -> Result>, Error> { + let ctx = self.debugger.exploration_ctx(); + + let debugee = &self.debugger.debugee; + let current_func = debugee + .debug_info(ctx.location().pc)? + .find_function_by_pc(ctx.location().global_pc)? + .ok_or(FunctionNotFound(ctx.location().global_pc))?; + + let vars = match selector { + Selector::Name { + var_name, + local_only: local, + } => { + let local_variants = current_func + .local_variable(ctx.location().global_pc, var_name) + .map(|v| vec![v]) + .unwrap_or_default(); + + let local = *local; + + // local variables is in priority anyway, if there are no local variables and + // selector allow non-locals then try to search in a whole object + if !local && local_variants.is_empty() { + debugee + .debug_info(ctx.location().pc)? + .find_variables(ctx.location(), var_name)? + } else { + local_variants + } + } + Selector::Any => current_func.local_variables(ctx.location().global_pc), + }; + + Ok(vars) + } + + fn param_die_by_selector( + &self, + selector: &Selector, + ) -> Result>, Error> { + let expl_ctx_loc = self.debugger.exploration_ctx().location(); + let debugee = &self.debugger.debugee; + let current_function = debugee + .debug_info(expl_ctx_loc.pc)? + .find_function_by_pc(expl_ctx_loc.global_pc)? + .ok_or(FunctionNotFound(expl_ctx_loc.global_pc))?; + let params = current_function.parameters(); + let params = match selector { + Selector::Name { var_name, .. } => params + .into_iter() + .filter(|param| param.die.base_attributes.name.as_ref() == Some(var_name)) + .collect::>(), + Selector::Any => params, + }; + Ok(params) + } + + /// Select variables or arguments from debugee state. + fn apply_select_die( + &self, + selector: &Selector, + on_args: bool, + ) -> Result>, Error> { + fn root_from_die<'dbg, T: AsAllocatedData>( + debugger: &'dbg Debugger, + die: &ContextualDieRef<'_, 'dbg, T>, + ranges: Option>, + ) -> Option> { + let mut type_cache = debugger.type_cache.borrow_mut(); + let r#type = weak_error!(type_from_cache!(die, type_cache))?; + + let evaluator = ctx_resolve_unit_call!(die, evaluator, &debugger.debugee); + let context_builder = EvaluationContextBuilder::Ready(debugger, evaluator); + + let value = context_builder.with_eval_ctx(|eval_ctx| { + let data = die.read_value(debugger.exploration_ctx(), &debugger.debugee, &r#type); + + let parser = ValueParser::new(); + let parse_ctx = &ParseContext { + evaluation_context: eval_ctx, + type_graph: &r#type, + }; + let modifiers = &ValueModifiers::from_identity(parse_ctx, Identity::from_die(die)); + parser.parse(parse_ctx, data, modifiers) + })?; + + Some(QueryResult { + value: Some(value), + scope: ranges, + kind: QueryResultKind::Root, + base_type: r#type, + identity: Identity::from_die(die), + eval_ctx_builder: context_builder, + }) + } + + match on_args { + true => { + let params = self.param_die_by_selector(selector)?; + Ok(params + .iter() + .filter_map(|arg_die| { + root_from_die( + self.debugger, + arg_die, + arg_die.max_range().map(|r| { + let scope: Box<[Range]> = Box::new([r]); + scope + }), + ) + }) + .collect()) + } + false => { + let vars = self.variable_die_by_selector(selector)?; + Ok(vars + .iter() + .filter_map(|var_die| { + root_from_die(self.debugger, var_die, var_die.ranges().map(Box::from)) + }) + .collect()) + } + } + } + + /// Create virtual DIE from an existing type, + /// then return a query result with a value from this DIE and address in debugee memory. + fn apply_ptr_cast_op(&self, ptr_cast: &PointerCast) -> Result, Error> { + let mut var_die = VirtualVariableDie::workpiece(); + let var_die_ref = var_die.init_with_type(&self.debugger.debugee, &ptr_cast.ty)?; + + let mut type_cache = self.debugger.type_cache.borrow_mut(); + let r#type = type_from_cache!(var_die_ref, type_cache)?; + + let context_builder = EvaluationContextBuilder::Virtual { + debugger: self.debugger, + debug_info: var_die_ref.debug_info, + unit_idx: var_die_ref.unit_idx, + die: VirtualVariableDie::workpiece(), + }; + + let value = context_builder.with_eval_ctx(|eval_ctx| { + let data = ObjectBinaryRepr { + raw_data: Bytes::copy_from_slice(&ptr_cast.ptr.to_le_bytes()), + address: None, + size: std::mem::size_of::(), + }; + + let parser = ValueParser::new(); + let ctx = &ParseContext { + evaluation_context: eval_ctx, + type_graph: &r#type, + }; + parser.parse(ctx, Some(data), &ValueModifiers::default()) + }); + + Ok(QueryResult { + value, + scope: None, + kind: QueryResultKind::Expression, + base_type: r#type, + identity: Identity::default(), + eval_ctx_builder: context_builder, + }) + } + + fn apply_dqe(&self, dqe: &Dqe, on_args: bool) -> Result>, Error> { + match dqe { + Dqe::Variable(selector) => self.apply_select_die(selector, on_args), + Dqe::PtrCast(ptr_cast) => self.apply_ptr_cast_op(ptr_cast).map(|q| vec![q]), + Dqe::Field(next, field) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|_, val| val.field(field))) + .collect()) + } + Dqe::Index(next, idx) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|_, val| val.index(idx))) + .collect()) + } + Dqe::Slice(next, left, right) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|ctx, val| val.slice(ctx, *left, *right))) + .collect()) + } + Dqe::Deref(next) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|ctx, val| val.deref(ctx))) + .collect()) + } + Dqe::Address(next) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|ctx, val| val.address(ctx))) + .collect()) + } + Dqe::Canonic(next) => { + let results = self.apply_dqe(next, on_args)?; + Ok(results + .into_iter() + .filter_map(|q| q.modify_value(|_, val| Some(val.canonic()))) + .collect()) + } + } + } + + /// Query variables and returns matched list. + pub fn query(&self, dqe: &Dqe) -> Result>, Error> { + self.apply_dqe(dqe, false) + } + + /// Query only variable names. + /// Only filter expression supported. + /// + /// # Panics + /// + /// This method will panic if select expression + /// contains any operators excluding a variable selector. + pub fn query_names(&self, dqe: &Dqe) -> Result, Error> { + match dqe { + Dqe::Variable(selector) => { + let vars = self.variable_die_by_selector(selector)?; + Ok(vars + .into_iter() + .filter_map(|die| die.die.name().map(ToOwned::to_owned)) + .collect()) + } + _ => unreachable!("unexpected expression variant"), + } + } + + /// Same as [`DqeExecutor::query`] but for function arguments. + pub fn query_arguments(&self, dqe: &Dqe) -> Result>, Error> { + self.apply_dqe(dqe, true) + } + + /// Same as [`DqeExecutor::query_names`] but for function arguments. + pub fn query_arguments_names(&self, dqe: &Dqe) -> Result, Error> { + match dqe { + Dqe::Variable(selector) => { + let params = self.param_die_by_selector(selector)?; + Ok(params + .into_iter() + .filter_map(|die| die.die.name().map(ToOwned::to_owned)) + .collect()) + } + _ => unreachable!("unexpected expression variant"), + } + } +} diff --git a/src/debugger/variable/mod.rs b/src/debugger/variable/mod.rs index 7fc42d4..a0d1087 100644 --- a/src/debugger/variable/mod.rs +++ b/src/debugger/variable/mod.rs @@ -1,80 +1,27 @@ -use crate::debugger::debugee::dwarf::r#type::{ - ArrayType, CModifier, EvaluationContext, ScalarType, StructureMember, TypeId, TypeIdentity, -}; -use crate::debugger::debugee::dwarf::r#type::{ComplexType, TypeDeclaration}; use crate::debugger::debugee::dwarf::{AsAllocatedData, ContextualDieRef, NamespaceHierarchy}; -use crate::debugger::variable::render::RenderRepr; -use crate::debugger::variable::select::{Literal, LiteralOrWildcard, ObjectBinaryRepr}; -use crate::debugger::variable::specialization::{ - HashSetVariable, StrVariable, StringVariable, VariableParserExtension, -}; -use crate::{debugger, version_switch, weak_error}; use bytes::Bytes; -use gimli::{ - DW_ATE_address, DW_ATE_boolean, DW_ATE_float, DW_ATE_signed, DW_ATE_signed_char, - DW_ATE_unsigned, DW_ATE_unsigned_char, DW_ATE_ASCII, DW_ATE_UTF, -}; -use log::warn; -pub use specialization::SpecializedVariableIR; -use std::collections::{HashMap, VecDeque}; use std::fmt::{Debug, Display, Formatter}; -use std::string::FromUtf8Error; -use uuid::Uuid; +pub mod dqe; +pub mod execute; pub mod render; -pub mod select; -mod specialization; +pub mod value; +mod r#virtual; -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum AssumeError { - #[error("field `{0}` not found")] - FieldNotFound(&'static str), - #[error("field `{0}` not a number")] - FieldNotANumber(&'static str), - #[error("incomplete interpretation of `{0}`")] - IncompleteInterp(&'static str), - #[error("not data for {0}")] - NoData(&'static str), - #[error("not type for {0}")] - NoType(&'static str), - #[error("underline data not a string")] - DataNotAString(#[from] FromUtf8Error), - #[error("undefined size of type `{}`", .0.name_fmt())] - UnknownSize(TypeIdentity), - #[error("type parameter `{0}` not found")] - TypeParameterNotFound(&'static str), - #[error("unknown type for type parameter `{0}`")] - TypeParameterTypeNotFound(&'static str), - #[error("unexpected type for {0}")] - UnexpectedType(&'static str), - #[error("unexpected binary representation of {0}, expect {1} got {2} bytes")] - UnexpectedBinaryRepr(&'static str, usize, usize), -} - -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum ParsingError { - #[error(transparent)] - Assume(#[from] AssumeError), - #[error("unsupported language version")] - UnsupportedVersion, - #[error("error while reading from debugee memory: {0}")] - ReadDebugeeMemory(#[from] nix::Error), -} - -/// Identifier of a variable. -/// Consists name and namespace of the variable. -#[derive(Clone, Default, PartialEq)] -pub struct VariableIdentity { +/// Identifier of a query result. +/// Consists name and namespace of the variable or argument. +#[derive(Clone, Default, PartialEq, Debug)] +pub struct Identity { namespace: NamespaceHierarchy, pub name: Option, } -impl VariableIdentity { +impl Identity { pub fn new(namespace: NamespaceHierarchy, name: Option) -> Self { Self { namespace, name } } - pub fn from_variable_die(var: &ContextualDieRef) -> Self { + pub fn from_die(var: &ContextualDieRef) -> Self { Self::new(var.namespaces(), var.die.name().map(String::from)) } @@ -86,7 +33,7 @@ impl VariableIdentity { } } -impl Display for VariableIdentity { +impl Display for Identity { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let namespaces = if self.namespace.is_empty() { String::default() @@ -95,2592 +42,19 @@ impl Display for VariableIdentity { }; match self.name.as_deref() { - None => f.write_fmt(format_args!("{namespaces}{{unknown}}")), + None => Ok(()), Some(name) => f.write_fmt(format_args!("{namespaces}{name}")), } } } -#[derive(Clone, Debug, PartialEq)] -pub enum SupportedScalar { - I8(i8), - I16(i16), - I32(i32), - I64(i64), - I128(i128), - Isize(isize), - U8(u8), - U16(u16), - U32(u32), - U64(u64), - U128(u128), - Usize(usize), - F32(f32), - F64(f64), - Bool(bool), - Char(char), - Empty(), -} - -impl Display for SupportedScalar { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - SupportedScalar::I8(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::I16(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::I32(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::I64(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::I128(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::Isize(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::U8(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::U16(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::U32(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::U64(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::U128(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::Usize(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::F32(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::F64(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::Bool(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::Char(scalar) => f.write_str(&format!("{scalar}")), - SupportedScalar::Empty() => f.write_str("()"), - } - } -} - -impl SupportedScalar { - fn equal_with_literal(&self, lhs: &Literal) -> bool { - match self { - SupportedScalar::I8(i) => lhs.equal_with_int(*i as i64), - SupportedScalar::I16(i) => lhs.equal_with_int(*i as i64), - SupportedScalar::I32(i) => lhs.equal_with_int(*i as i64), - SupportedScalar::I64(i) => lhs.equal_with_int(*i), - SupportedScalar::I128(i) => lhs.equal_with_int(*i as i64), - SupportedScalar::Isize(i) => lhs.equal_with_int(*i as i64), - SupportedScalar::U8(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::U16(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::U32(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::U64(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::U128(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::Usize(u) => lhs.equal_with_int(*u as i64), - SupportedScalar::F32(f) => lhs.equal_with_float(*f as f64), - SupportedScalar::F64(f) => lhs.equal_with_float(*f), - SupportedScalar::Bool(b) => lhs.equal_with_bool(*b), - SupportedScalar::Char(c) => lhs.equal_with_string(&c.to_string()), - SupportedScalar::Empty() => false, - } - } -} - -/// Represents scalars: integer's, float's, bool, char and () types. -#[derive(Clone, PartialEq)] -pub struct ScalarVariable { - pub identity: VariableIdentity, - pub value: Option, - pub raw_address: Option, - pub type_ident: TypeIdentity, - pub type_id: Option, -} - -impl ScalarVariable { - fn try_as_number(&self) -> Option { - match self.value { - Some(SupportedScalar::I8(num)) => Some(num as i64), - Some(SupportedScalar::I16(num)) => Some(num as i64), - Some(SupportedScalar::I32(num)) => Some(num as i64), - Some(SupportedScalar::I64(num)) => Some(num), - Some(SupportedScalar::Isize(num)) => Some(num as i64), - Some(SupportedScalar::U8(num)) => Some(num as i64), - Some(SupportedScalar::U16(num)) => Some(num as i64), - Some(SupportedScalar::U32(num)) => Some(num as i64), - Some(SupportedScalar::U64(num)) => Some(num as i64), - Some(SupportedScalar::Usize(num)) => Some(num as i64), - _ => None, - } - } -} - -/// Structure member representation. -#[derive(Clone, PartialEq, Debug)] -pub struct Member { - pub field_name: Option, - pub value: VariableIR, -} - -/// Represents structures. -#[derive(Clone, Default, PartialEq)] -pub struct StructVariable { - pub identity: VariableIdentity, - pub type_ident: TypeIdentity, - pub type_id: Option, - /// Structure members. - pub members: Vec, - /// Map of type parameters of a structure type. - pub type_params: HashMap>, - pub raw_address: Option, -} - -impl StructVariable { - pub fn field(self, field_name: &str) -> Option { - self.members.into_iter().find_map(|member| { - if member.field_name.as_deref() == Some(field_name) { - Some(member.value) - } else { - None - } - }) - } -} - -/// Array item representation. -#[derive(Clone, PartialEq, Debug)] -pub struct ArrayItem { - pub index: i64, - pub value: VariableIR, -} - -/// Represents arrays. -#[derive(Clone, PartialEq)] -pub struct ArrayVariable { - pub identity: VariableIdentity, - pub type_ident: TypeIdentity, - pub type_id: Option, - /// Array items. - pub items: Option>, - pub raw_address: Option, -} - -impl ArrayVariable { - fn slice(&mut self, left: Option, right: Option) { - if let Some(items) = self.items.as_mut() { - if let Some(left) = left { - items.drain(..left); - } - - if let Some(right) = right { - let remove_range = right - left.unwrap_or_default()..; - if remove_range.start < items.len() { - items.drain(remove_range); - }; - } - } - } -} - -/// Simple c-style enums (each option in which does not contain the underlying values). -#[derive(Clone, PartialEq)] -pub struct CEnumVariable { - pub identity: VariableIdentity, - pub type_ident: TypeIdentity, - pub type_id: Option, - /// String representation of selected variant. - pub value: Option, - pub raw_address: Option, -} - -/// Represents all enum's that more complex then c-style enums. -#[derive(Clone, PartialEq)] -pub struct RustEnumVariable { - pub identity: VariableIdentity, - pub type_ident: TypeIdentity, - pub type_id: Option, - /// Variable IR representation of selected variant. - pub value: Option>, - pub raw_address: Option, -} - -/// Raw pointers, references, Box. -#[derive(Clone, PartialEq)] -pub struct PointerVariable { - pub identity: VariableIdentity, - pub type_ident: TypeIdentity, - pub type_id: Option, - /// Raw pointer to underline value. - pub value: Option<*const ()>, - /// Underline type identity. - pub target_type: Option, - pub target_type_size: Option, - pub raw_address: Option, -} - -impl PointerVariable { - /// Dereference pointer and return variable IR that represents underline value. - pub fn deref( - &self, - eval_ctx: &EvaluationContext, - parser: &VariableParser, - ) -> Option { - let target_type = self.target_type?; - let deref_size = self - .target_type_size - .or_else(|| parser.r#type.type_size_in_bytes(eval_ctx, target_type)); - - let target_type_decl = parser.r#type.types.get(&target_type); - if matches!(target_type_decl, Some(TypeDeclaration::Subroutine { .. })) { - // this variable is a fn pointer - don't deref it - return None; - } - - self.value.and_then(|ptr| { - let data = deref_size.and_then(|sz| { - let raw_data = debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), - ptr as usize, - sz as usize, - ) - .ok()?; - - Some(ObjectBinaryRepr { - raw_data: Bytes::from(raw_data), - address: Some(ptr as usize), - size: sz as usize, - }) - }); - parser.parse_inner(eval_ctx, VariableIdentity::default(), data, target_type) - }) - } - - /// Interpret a pointer as a pointer on first array element. - /// Returns variable IR that represents an array. - pub fn slice( - &self, - eval_ctx: &EvaluationContext, - parser: &VariableParser, - left: Option, - right: usize, - ) -> Option { - let target_type = self.target_type?; - let deref_size = parser.r#type.type_size_in_bytes(eval_ctx, target_type)? as usize; - - self.value.and_then(|ptr| { - let left = left.unwrap_or_default(); - let base_addr = ptr as usize + deref_size * left; - let raw_data = weak_error!(debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), - base_addr, - deref_size * (right - left) - ))?; - let raw_data = bytes::Bytes::from(raw_data); - let mut identity = self.identity.clone(); - identity.name = identity.name.map(|n| format!("[*{n}]")); - - let items = raw_data - .chunks(deref_size) - .enumerate() - .filter_map(|(i, chunk)| { - let data = ObjectBinaryRepr { - raw_data: raw_data.slice_ref(chunk), - address: Some(base_addr + (i * deref_size)), - size: deref_size, - }; - ArrayItem { - index: i as i64, - value: parser.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(None), - Some(data), - target_type, - ), - } - }) - .collect::>(); - - Some(VariableIR::Array(ArrayVariable { - identity, - items: Some(items), - type_id: None, - type_ident: parser.r#type.identity(target_type).as_array_type(), - raw_address: Some(base_addr), - })) - }) - } -} - -/// Represents subroutine. -#[derive(Clone, PartialEq)] -pub struct SubroutineVariable { - pub identity: VariableIdentity, - pub type_id: Option, - pub return_type_ident: Option, +/// Object binary representation in debugee memory. +pub struct ObjectBinaryRepr { + /// Binary representation. + pub raw_data: Bytes, + /// Possible address of object data in debugee memory. + /// It may not exist if there is no debug information, or if an object is allocated in registers. pub address: Option, -} - -/// Represent a variable with C modifiers (volatile, const, typedef, etc.) -#[derive(Clone, PartialEq)] -pub struct CModifiedVariable { - pub identity: VariableIdentity, - pub type_ident: TypeIdentity, - pub type_id: Option, - pub modifier: CModifier, - pub value: Option>, - pub address: Option, -} - -/// Variable intermediate representation. -#[derive(Clone, PartialEq)] -pub enum VariableIR { - Scalar(ScalarVariable), - Struct(StructVariable), - Array(ArrayVariable), - CEnum(CEnumVariable), - RustEnum(RustEnumVariable), - Pointer(PointerVariable), - Subroutine(SubroutineVariable), - Specialized { - value: Option, - original: StructVariable, - }, - CModifiedVariable(CModifiedVariable), -} - -// SAFETY: this enum may contain a raw pointers on memory in a debugee process, -// it is safe to dereference it using public API of *Variable structures. -unsafe impl Send for VariableIR {} - -impl Debug for VariableIR { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(self.name().as_deref().unwrap_or_default()) - } -} - -impl VariableIR { - /// Return address in debugee memory for variable data. - pub fn in_memory_location(&self) -> Option { - match self { - VariableIR::Scalar(s) => s.raw_address, - VariableIR::Struct(s) => s.raw_address, - VariableIR::Array(a) => a.raw_address, - VariableIR::CEnum(ce) => ce.raw_address, - VariableIR::RustEnum(re) => re.raw_address, - VariableIR::Pointer(p) => p.raw_address, - VariableIR::Subroutine(s) => s.address, - VariableIR::Specialized { - original: origin, .. - } => origin.raw_address, - VariableIR::CModifiedVariable(cmv) => cmv.address, - } - } - - pub fn type_id(&self) -> Option { - match self { - VariableIR::Scalar(s) => s.type_id, - VariableIR::Struct(s) => s.type_id, - VariableIR::Array(a) => a.type_id, - VariableIR::CEnum(ce) => ce.type_id, - VariableIR::RustEnum(re) => re.type_id, - VariableIR::Pointer(p) => p.type_id, - VariableIR::Subroutine(s) => s.type_id, - VariableIR::Specialized { - original: origin, .. - } => origin.type_id, - VariableIR::CModifiedVariable(cmv) => cmv.type_id, - } - } - - /// Visit variable children in BFS order. - fn bfs_iterator(&self) -> BfsIterator { - BfsIterator { - queue: VecDeque::from([(FieldOrIndex::Root(self.name()), self)]), - } - } - - /// Returns i64 value representation or error if cast fail. - fn assume_field_as_scalar_number(&self, field_name: &'static str) -> Result { - let ir = self - .bfs_iterator() - .find_map(|(field_or_idx, child)| { - (field_or_idx == FieldOrIndex::Field(Some(field_name))).then_some(child) - }) - .ok_or(AssumeError::FieldNotFound(field_name))?; - if let VariableIR::Scalar(s) = ir { - Ok(s.try_as_number() - .ok_or(AssumeError::FieldNotANumber(field_name))?) - } else { - Err(AssumeError::FieldNotANumber(field_name)) - } - } - - /// Returns value as a raw pointer or error if cast fails. - fn assume_field_as_pointer(&self, field_name: &'static str) -> Result<*const (), AssumeError> { - self.bfs_iterator() - .find_map(|(field_or_idx, child)| { - if let VariableIR::Pointer(pointer) = child { - if field_or_idx == FieldOrIndex::Field(Some(field_name)) { - return pointer.value; - } - } - None - }) - .ok_or(AssumeError::IncompleteInterp("pointer")) - } - - /// Returns value as enum or error if cast fail. - fn assume_field_as_rust_enum( - &self, - field_name: &'static str, - ) -> Result { - self.bfs_iterator() - .find_map(|(field_or_idx, child)| { - if let VariableIR::RustEnum(r_enum) = child { - if field_or_idx == FieldOrIndex::Field(Some(field_name)) { - return Some(r_enum.clone()); - } - } - None - }) - .ok_or(AssumeError::IncompleteInterp("pointer")) - } - - /// Returns value as structure or error if cast fail. - fn assume_field_as_struct( - &self, - field_name: &'static str, - ) -> Result { - self.bfs_iterator() - .find_map(|(field_or_idx, child)| { - if let VariableIR::Struct(structure) = child { - if field_or_idx == FieldOrIndex::Field(Some(field_name)) { - return Some(structure.clone()); - } - } - None - }) - .ok_or(AssumeError::IncompleteInterp("structure")) - } - - /// Returns variable identity. - fn identity(&self) -> &VariableIdentity { - match self { - VariableIR::Scalar(s) => &s.identity, - VariableIR::Struct(s) => &s.identity, - VariableIR::Array(a) => &a.identity, - VariableIR::CEnum(e) => &e.identity, - VariableIR::RustEnum(e) => &e.identity, - VariableIR::Pointer(p) => &p.identity, - VariableIR::Specialized { - original: origin, .. - } => &origin.identity, - VariableIR::Subroutine(s) => &s.identity, - VariableIR::CModifiedVariable(v) => &v.identity, - } - } - - pub fn identity_mut(&mut self) -> &mut VariableIdentity { - match self { - VariableIR::Scalar(s) => &mut s.identity, - VariableIR::Struct(s) => &mut s.identity, - VariableIR::Array(a) => &mut a.identity, - VariableIR::CEnum(e) => &mut e.identity, - VariableIR::RustEnum(e) => &mut e.identity, - VariableIR::Pointer(p) => &mut p.identity, - VariableIR::Specialized { - original: origin, .. - } => &mut origin.identity, - VariableIR::Subroutine(s) => &mut s.identity, - VariableIR::CModifiedVariable(v) => &mut v.identity, - } - } - - /// If a variable has a specialized ir (vectors, strings, etc.) then return - /// an underlying structure - fn canonic(self) -> Self { - match self { - VariableIR::Specialized { - original: origin, .. - } => VariableIR::Struct(origin), - _ => self, - } - } - - /// Try to dereference variable and returns underline variable IR. - /// Return `None` if dereference not allowed. - fn deref(self, eval_ctx: &EvaluationContext, variable_parser: &VariableParser) -> Option { - match self { - VariableIR::Pointer(ptr) => ptr.deref(eval_ctx, variable_parser), - VariableIR::RustEnum(r_enum) => r_enum - .value - .and_then(|v| v.value.deref(eval_ctx, variable_parser)), - VariableIR::Specialized { - value: Some(SpecializedVariableIR::Rc(ptr)), - .. - } - | VariableIR::Specialized { - value: Some(SpecializedVariableIR::Arc(ptr)), - .. - } => ptr.deref(eval_ctx, variable_parser), - VariableIR::Specialized { - value: Some(SpecializedVariableIR::Tls(tls_var)), - .. - } => tls_var - .inner_value - .and_then(|inner| inner.deref(eval_ctx, variable_parser)), - VariableIR::Specialized { - value: Some(SpecializedVariableIR::Cell(cell)), - .. - } - | VariableIR::Specialized { - value: Some(SpecializedVariableIR::RefCell(cell)), - .. - } => cell.deref(eval_ctx, variable_parser), - _ => None, - } - } - - /// Return address (as pointer variable) of raw data in debugee memory. - fn address( - self, - eval_ctx: &EvaluationContext, - variable_parser: &VariableParser, - ) -> Option { - let addr = self.in_memory_location()?; - Some(VariableIR::Pointer(PointerVariable { - identity: VariableIdentity::no_namespace(None), - type_ident: self.r#type().as_address_type(), - value: Some(addr as *const ()), - target_type: self.type_id(), - target_type_size: self - .type_id() - .and_then(|t| variable_parser.r#type.type_size_in_bytes(eval_ctx, t)), - raw_address: None, - type_id: None, - })) - } - - /// Return variable field, `None` if field is not allowed for a variable type. - /// Supported: structures, rust-style enums, hashmaps, btree-maps. - fn field(self, field_name: &str) -> Option { - match self { - VariableIR::Struct(structure) => structure.field(field_name), - VariableIR::RustEnum(r_enum) => r_enum.value.and_then(|v| v.value.field(field_name)), - VariableIR::Specialized { - value: specialized, .. - } => match specialized { - Some(SpecializedVariableIR::HashMap(map)) - | Some(SpecializedVariableIR::BTreeMap(map)) => { - map.kv_items.into_iter().find_map(|(key, value)| match key { - VariableIR::Specialized { - value: specialized, .. - } => match specialized { - Some(SpecializedVariableIR::String(string_key)) => { - (string_key.value == field_name).then_some(value) - } - Some(SpecializedVariableIR::Str(string_key)) => { - (string_key.value == field_name).then_some(value) - } - _ => None, - }, - _ => None, - }) - } - Some(SpecializedVariableIR::Tls(tls_var)) => tls_var - .inner_value - .and_then(|inner| inner.field(field_name)), - Some(SpecializedVariableIR::Cell(cell)) - | Some(SpecializedVariableIR::RefCell(cell)) => cell.field(field_name), - _ => None, - }, - _ => None, - } - } - - /// Return variable element by its index, `None` if indexing is not allowed for a variable type. - /// Supported: array, rust-style enums, vector, hashmap, hashset, btreemap, btreeset. - fn index(self, idx: &Literal) -> Option { - match self { - VariableIR::Array(array) => array.items.and_then(|mut items| { - if let Literal::Int(idx) = idx { - let idx = *idx as usize; - if idx < items.len() { - return Some(items.swap_remove(idx).value); - } - } - None - }), - VariableIR::RustEnum(r_enum) => r_enum.value.and_then(|v| v.value.index(idx)), - VariableIR::Specialized { - value: Some(spec_val), - .. - } => match spec_val { - SpecializedVariableIR::Vector(mut vec) - | SpecializedVariableIR::VecDeque(mut vec) => { - let inner_array = vec.structure.members.swap_remove(0).value; - inner_array.index(idx) - } - SpecializedVariableIR::Tls(tls_var) => { - tls_var.inner_value.and_then(|inner| inner.index(idx)) - } - SpecializedVariableIR::Cell(cell) | SpecializedVariableIR::RefCell(cell) => { - cell.index(idx) - } - SpecializedVariableIR::BTreeMap(map) | SpecializedVariableIR::HashMap(map) => { - for (k, v) in map.kv_items { - if k.match_literal(idx) { - return Some(v); - } - } - - None - } - SpecializedVariableIR::BTreeSet(set) | SpecializedVariableIR::HashSet(set) => { - let found = set.items.into_iter().any(|it| it.match_literal(idx)); - - Some(VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::no_namespace("bool"), - value: Some(SupportedScalar::Bool(found)), - raw_address: None, - })) - } - _ => None, - }, - _ => None, - } - } - - fn slice( - self, - eval_ctx: &EvaluationContext, - variable_parser: &VariableParser, - left: Option, - right: Option, - ) -> Option { - match self { - VariableIR::Array(mut array) => { - array.slice(left, right); - Some(VariableIR::Array(array)) - } - VariableIR::Pointer(ptr) => { - // for pointer the right bound must always be specified - let right = right?; - ptr.slice(eval_ctx, variable_parser, left, right) - } - VariableIR::Specialized { - value: Some(spec_val), - original, - } => match spec_val { - SpecializedVariableIR::Rc(ptr) | SpecializedVariableIR::Arc(ptr) => { - // for pointer the right bound must always be specified - let right = right?; - ptr.slice(eval_ctx, variable_parser, left, right) - } - SpecializedVariableIR::Vector(mut vec) => { - vec.slice(left, right); - Some(VariableIR::Specialized { - value: Some(SpecializedVariableIR::Vector(vec)), - original, - }) - } - SpecializedVariableIR::VecDeque(mut vec) => { - vec.slice(left, right); - Some(VariableIR::Specialized { - value: Some(SpecializedVariableIR::VecDeque(vec)), - original, - }) - } - SpecializedVariableIR::Tls(mut tls_var) => { - let inner = tls_var.inner_value.take()?; - inner.slice(eval_ctx, variable_parser, left, right) - } - SpecializedVariableIR::Cell(cell) | SpecializedVariableIR::RefCell(cell) => { - cell.slice(eval_ctx, variable_parser, left, right) - } - _ => None, - }, - _ => None, - } - } - - /// Match variable with a literal object. - /// Return true if variable matched to literal. - fn match_literal(self, literal: &Literal) -> bool { - match self { - VariableIR::Scalar(ScalarVariable { - value: Some(scalar), - .. - }) => scalar.equal_with_literal(literal), - VariableIR::Pointer(PointerVariable { - value: Some(ptr), .. - }) => literal.equal_with_address(ptr as usize), - VariableIR::Array(ArrayVariable { - items: Some(items), .. - }) => { - let Literal::Array(arr_literal) = literal else { - return false; - }; - if arr_literal.len() != items.len() { - return false; - } - - for (i, item) in items.into_iter().enumerate() { - match &arr_literal[i] { - LiteralOrWildcard::Literal(lit) => { - if !item.value.match_literal(lit) { - return false; - } - } - LiteralOrWildcard::Wildcard => continue, - } - } - true - } - VariableIR::Struct(StructVariable { members, .. }) => { - match literal { - Literal::Array(array_literal) => { - // structure must be a tuple - if array_literal.len() != members.len() { - return false; - } - - for (i, member) in members.into_iter().enumerate() { - let field_literal = &array_literal[i]; - match field_literal { - LiteralOrWildcard::Literal(lit) => { - if !member.value.match_literal(lit) { - return false; - } - } - LiteralOrWildcard::Wildcard => continue, - } - } - - true - } - Literal::AssocArray(struct_literal) => { - // default structure - if struct_literal.len() != members.len() { - return false; - } - - for member in members { - let Some(member_name) = member.field_name else { - return false; - }; - - let Some(field_literal) = struct_literal.get(&member_name) else { - return false; - }; - - match field_literal { - LiteralOrWildcard::Literal(lit) => { - if !member.value.match_literal(lit) { - return false; - } - } - LiteralOrWildcard::Wildcard => continue, - } - } - true - } - _ => false, - } - } - VariableIR::Specialized { - value: Some(spec), .. - } => match spec { - SpecializedVariableIR::String(StringVariable { value, .. }) => { - literal.equal_with_string(&value) - } - SpecializedVariableIR::Str(StrVariable { value, .. }) => { - literal.equal_with_string(&value) - } - SpecializedVariableIR::Uuid(bytes) => { - let uuid = Uuid::from_bytes(bytes); - literal.equal_with_string(&uuid.to_string()) - } - SpecializedVariableIR::Cell(cell) | SpecializedVariableIR::RefCell(cell) => { - cell.match_literal(literal) - } - SpecializedVariableIR::Rc(PointerVariable { - value: Some(ptr), .. - }) - | SpecializedVariableIR::Arc(PointerVariable { - value: Some(ptr), .. - }) => literal.equal_with_address(ptr as usize), - SpecializedVariableIR::Vector(mut v) | SpecializedVariableIR::VecDeque(mut v) => { - let inner_array = v.structure.members.swap_remove(0).value; - debug_assert!(matches!(inner_array, VariableIR::Array(_))); - inner_array.match_literal(literal) - } - SpecializedVariableIR::HashSet(HashSetVariable { items, .. }) - | SpecializedVariableIR::BTreeSet(HashSetVariable { items, .. }) => { - let Literal::Array(arr_literal) = literal else { - return false; - }; - if arr_literal.len() != items.len() { - return false; - } - let mut arr_literal = arr_literal.to_vec(); - - for item in items { - let mut item_found = false; - - // try to find equals item - let mb_literal_idx = arr_literal.iter().position(|lit| { - if let LiteralOrWildcard::Literal(lit) = lit { - item.clone().match_literal(lit) - } else { - false - } - }); - if let Some(literal_idx) = mb_literal_idx { - arr_literal.swap_remove(literal_idx); - item_found = true; - } - - // try to find wildcard - if !item_found { - let mb_wildcard_idx = arr_literal - .iter() - .position(|lit| matches!(lit, LiteralOrWildcard::Wildcard)); - if let Some(wildcard_idx) = mb_wildcard_idx { - arr_literal.swap_remove(wildcard_idx); - item_found = true; - } - } - - // still not found - set aren't equal - if !item_found { - return false; - } - } - true - } - _ => false, - }, - VariableIR::CEnum(CEnumVariable { - value: Some(ref value), - .. - }) => { - let Literal::EnumVariant(variant, None) = literal else { - return false; - }; - value == variant - } - VariableIR::RustEnum(RustEnumVariable { - value: Some(value), .. - }) => { - let Literal::EnumVariant(variant, variant_value) = literal else { - return false; - }; - - if value.field_name.as_ref() != Some(variant) { - return false; - } - - match variant_value { - None => true, - Some(lit) => value.value.match_literal(lit), - } - } - _ => false, - } - } -} - -pub struct VariableParser<'a> { - r#type: &'a ComplexType, -} - -impl<'a> VariableParser<'a> { - pub fn new(r#type: &'a ComplexType) -> Self { - Self { r#type } - } - - fn parse_scalar( - &self, - identity: VariableIdentity, - data: Option, - type_id: TypeId, - r#type: &ScalarType, - ) -> ScalarVariable { - fn render_scalar(data: Option) -> Option { - data.as_ref().map(|v| scalar_from_bytes::(&v.raw_data)) - } - let in_debugee_loc = data.as_ref().and_then(|d| d.address); - #[allow(non_upper_case_globals)] - let value_view = r#type.encoding.and_then(|encoding| match encoding { - DW_ATE_address => render_scalar::(data).map(SupportedScalar::Usize), - DW_ATE_signed_char => render_scalar::(data).map(SupportedScalar::I8), - DW_ATE_unsigned_char => render_scalar::(data).map(SupportedScalar::U8), - DW_ATE_signed => match r#type.byte_size.unwrap_or(0) { - 0 => Some(SupportedScalar::Empty()), - 1 => render_scalar::(data).map(SupportedScalar::I8), - 2 => render_scalar::(data).map(SupportedScalar::I16), - 4 => render_scalar::(data).map(SupportedScalar::I32), - 8 => { - if r#type.name.as_deref() == Some("isize") { - render_scalar::(data).map(SupportedScalar::Isize) - } else { - render_scalar::(data).map(SupportedScalar::I64) - } - } - 16 => render_scalar::(data).map(SupportedScalar::I128), - _ => { - warn!( - "parse scalar: unexpected signed size: {size:?}", - size = r#type.byte_size - ); - None - } - }, - DW_ATE_unsigned => match r#type.byte_size.unwrap_or(0) { - 0 => Some(SupportedScalar::Empty()), - 1 => render_scalar::(data).map(SupportedScalar::U8), - 2 => render_scalar::(data).map(SupportedScalar::U16), - 4 => render_scalar::(data).map(SupportedScalar::U32), - 8 => { - if r#type.name.as_deref() == Some("usize") { - render_scalar::(data).map(SupportedScalar::Usize) - } else { - render_scalar::(data).map(SupportedScalar::U64) - } - } - 16 => render_scalar::(data).map(SupportedScalar::U128), - _ => { - warn!( - "parse scalar: unexpected unsigned size: {size:?}", - size = r#type.byte_size - ); - None - } - }, - DW_ATE_float => match r#type.byte_size.unwrap_or(0) { - 4 => render_scalar::(data).map(SupportedScalar::F32), - 8 => render_scalar::(data).map(SupportedScalar::F64), - _ => { - warn!( - "parse scalar: unexpected float size: {size:?}", - size = r#type.byte_size - ); - None - } - }, - DW_ATE_boolean => render_scalar::(data).map(SupportedScalar::Bool), - DW_ATE_UTF => render_scalar::(data).map(|char| { - // WAITFORFIX: https://github.com/rust-lang/rust/issues/113819 - // this check is meaningfully here cause in case above there is a random bytes here, - // and it may lead to panic in other places - // (specially when someone tries to render this char) - if String::from_utf8(char.to_string().into_bytes()).is_err() { - SupportedScalar::Char('?') - } else { - SupportedScalar::Char(char) - } - }), - DW_ATE_ASCII => render_scalar::(data).map(SupportedScalar::Char), - _ => { - warn!("parse scalar: unexpected base type encoding: {encoding}"); - None - } - }); - - ScalarVariable { - identity, - type_ident: r#type.identity(), - type_id: Some(type_id), - value: value_view, - raw_address: in_debugee_loc, - } - } - - fn parse_struct_variable( - &self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - data: Option, - type_id: TypeId, - type_params: HashMap>, - members: &[StructureMember], - ) -> StructVariable { - let children = members - .iter() - .filter_map(|member| self.parse_struct_member(eval_ctx, member, data.as_ref())) - .collect(); - - StructVariable { - identity, - type_id: Some(type_id), - type_ident: self.r#type.identity(type_id), - members: children, - type_params, - raw_address: data.and_then(|d| d.address), - } - } - - fn parse_struct_member( - &self, - eval_ctx: &EvaluationContext, - member: &StructureMember, - parent_data: Option<&ObjectBinaryRepr>, - ) -> Option { - let name = member.name.clone(); - let Some(type_ref) = member.type_ref else { - warn!( - "parse structure: unknown type for member {}", - name.as_deref().unwrap_or_default() - ); - return None; - }; - let member_val = parent_data.and_then(|data| member.value(eval_ctx, self.r#type, data)); - let value = self.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(None), - member_val, - type_ref, - )?; - Some(Member { - field_name: member.name.clone(), - value, - }) - } - - fn parse_array( - &self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - data: Option, - type_id: TypeId, - array_decl: &ArrayType, - ) -> ArrayVariable { - let items = array_decl.bounds(eval_ctx).and_then(|bounds| { - let len = bounds.1 - bounds.0; - let data = data.as_ref()?; - let el_size = (array_decl.size_in_bytes(eval_ctx, self.r#type)? / len as u64) as usize; - let bytes = &data.raw_data; - let el_type_id = array_decl.element_type?; - - let (mut bytes_chunks, mut empty_chunks); - let raw_items_iter: &mut dyn Iterator = if el_size != 0 { - bytes_chunks = bytes.chunks(el_size).enumerate(); - &mut bytes_chunks - } else { - // if an item type is zst - let v: Vec<&[u8]> = vec![&[]; len as usize]; - empty_chunks = v.into_iter().enumerate(); - &mut empty_chunks - }; - - Some( - raw_items_iter - .filter_map(|(i, chunk)| { - let offset = i * el_size; - let data = ObjectBinaryRepr { - raw_data: bytes.slice_ref(chunk), - address: data.address.map(|addr| addr + offset), - size: el_size, - }; - - ArrayItem { - index: bounds.0 + i as i64, - value: self.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(None), - Some(data), - el_type_id, - ), - } - }) - .collect::>(), - ) - }); - - ArrayVariable { - identity, - items, - type_id: Some(type_id), - type_ident: self.r#type.identity(type_id), - raw_address: data.and_then(|d| d.address), - } - } - - fn parse_c_enum( - &self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - data: Option, - type_id: TypeId, - discr_type: Option, - enumerators: &HashMap, - ) -> CEnumVariable { - let in_debugee_loc = data.as_ref().and_then(|d| d.address); - let mb_discr = discr_type.and_then(|type_id| { - self.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(None), - data, - type_id, - ) - }); - - let value = mb_discr.and_then(|discr| { - if let VariableIR::Scalar(scalar) = discr { - scalar.try_as_number() - } else { - None - } - }); - - CEnumVariable { - identity, - type_ident: self.r#type.identity(type_id), - type_id: Some(type_id), - value: value.and_then(|val| enumerators.get(&val).cloned()), - raw_address: in_debugee_loc, - } - } - - fn parse_rust_enum( - &self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - data: Option, - type_id: TypeId, - discr_member: Option<&StructureMember>, - enumerators: &HashMap, StructureMember>, - ) -> RustEnumVariable { - let discr_value = discr_member.and_then(|member| { - let discr = self - .parse_struct_member(eval_ctx, member, data.as_ref())? - .value; - if let VariableIR::Scalar(scalar) = discr { - return scalar.try_as_number(); - } - None - }); - - let enumerator = - discr_value.and_then(|v| enumerators.get(&Some(v)).or_else(|| enumerators.get(&None))); - - let enumerator = enumerator.and_then(|member| { - Some(Box::new(self.parse_struct_member( - eval_ctx, - member, - data.as_ref(), - )?)) - }); - - RustEnumVariable { - identity, - type_id: Some(type_id), - type_ident: self.r#type.identity(type_id), - value: enumerator, - raw_address: data.and_then(|d| d.address), - } - } - - fn parse_pointer( - &self, - identity: VariableIdentity, - data: Option, - type_id: TypeId, - target_type: Option, - ) -> PointerVariable { - let mb_ptr = data - .as_ref() - .map(|v| scalar_from_bytes::<*const ()>(&v.raw_data)); - - let mut type_ident = self.r#type.identity(type_id); - if type_ident.is_unknown() { - if let Some(target_type) = target_type { - type_ident = self.r#type.identity(target_type).as_deref_type(); - } - } - - PointerVariable { - identity, - type_id: Some(type_id), - type_ident, - value: mb_ptr, - target_type, - target_type_size: None, - raw_address: data.and_then(|d| d.address), - } - } - - fn parse_inner( - &self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - data: Option, - type_id: TypeId, - ) -> Option { - match &self.r#type.types[&type_id] { - TypeDeclaration::Scalar(scalar_type) => Some(VariableIR::Scalar(self.parse_scalar( - identity, - data, - type_id, - scalar_type, - ))), - TypeDeclaration::Structure { - namespaces: type_ns_h, - members, - type_params, - name: struct_name, - .. - } => { - let struct_var = self.parse_struct_variable( - eval_ctx, - identity, - data, - type_id, - type_params.clone(), - members, - ); - - let parser_ext = VariableParserExtension::new(self); - // Reinterpret structure if underline data type is: - // - Vector - // - String - // - &str - // - tls variable - // - hashmaps - // - hashset - // - btree map - // - btree set - // - vecdeque - // - cell/refcell - // - rc/arc - if struct_name.as_deref() == Some("&str") { - return Some(VariableIR::Specialized { - value: parser_ext.parse_str(eval_ctx, &struct_var), - original: struct_var, - }); - }; - - if struct_name.as_deref() == Some("String") { - return Some(VariableIR::Specialized { - value: parser_ext.parse_string(eval_ctx, &struct_var), - original: struct_var, - }); - }; - - if struct_name.as_ref().map(|name| name.starts_with("Vec")) == Some(true) - && type_ns_h.contains(&["vec"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_vector(eval_ctx, &struct_var, type_params), - original: struct_var, - }); - }; - - let rust_version = eval_ctx.rustc_version().unwrap_or_default(); - let type_is_tls = version_switch!( - rust_version, - (1, 0, 0) ..= (1, 76, u32::MAX) => type_ns_h.contains(&["std", "sys", "common", "thread_local", "fast_local"]), - (1, 77, 0) ..= (1, 77, u32::MAX) => type_ns_h.contains(&["std", "sys", "pal", "common", "thread_local", "fast_local"]), - (1, 78, 0) ..= (1, 80, u32::MAX) => type_ns_h.contains(&["std", "sys", "thread_local", "fast_local"]), - (1, 81, 0) ..= (1, u32::MAX, u32::MAX) => type_ns_h.contains(&["std", "sys", "thread_local"]), - ).unwrap_or_default(); - let var_name_is_tls = struct_var.identity.namespace.contains(&["__getit"]) - && (struct_var.identity.name.as_deref() == Some("VAL") - || struct_var.identity.name.as_deref() == Some("__KEY")); - if type_is_tls || var_name_is_tls { - let is_const_initialized = struct_var.identity.name.as_deref() == Some("VAL"); - return version_switch!( - rust_version, - (1, 0, 0) ..= (1, 79, u32::MAX) => Some(VariableIR::Specialized(parser_ext.parse_tls_old(struct_var, type_params))), - (1, 80, 0) ..= (1, u32::MAX, u32::MAX) => Some(VariableIR::Specialized(parser_ext.parse_tls(struct_var, type_params)?)), - ) - .expect("infallible: all rustc versions are covered"); - } - - if struct_name.as_ref().map(|name| name.starts_with("HashMap")) == Some(true) - && type_ns_h.contains(&["collections", "hash", "map"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_hashmap(eval_ctx, &struct_var), - original: struct_var, - }); - }; - - if struct_name.as_ref().map(|name| name.starts_with("HashSet")) == Some(true) - && type_ns_h.contains(&["collections", "hash", "set"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_hashset(eval_ctx, &struct_var), - original: struct_var, - }); - }; - - if struct_name - .as_ref() - .map(|name| name.starts_with("BTreeMap")) - == Some(true) - && type_ns_h.contains(&["collections", "btree", "map"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_btree_map( - eval_ctx, - &struct_var, - type_id, - type_params, - ), - original: struct_var, - }); - }; - - if struct_name - .as_ref() - .map(|name| name.starts_with("BTreeSet")) - == Some(true) - && type_ns_h.contains(&["collections", "btree", "set"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_btree_set(&struct_var), - original: struct_var, - }); - }; - - if struct_name - .as_ref() - .map(|name| name.starts_with("VecDeque")) - == Some(true) - && type_ns_h.contains(&["collections", "vec_deque"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_vec_dequeue(eval_ctx, &struct_var, type_params), - original: struct_var, - }); - }; - - if struct_name.as_ref().map(|name| name.starts_with("Cell")) == Some(true) - && type_ns_h.contains(&["cell"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_cell(&struct_var), - original: struct_var, - }); - }; - - if struct_name.as_ref().map(|name| name.starts_with("RefCell")) == Some(true) - && type_ns_h.contains(&["cell"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_cell(&struct_var), - original: struct_var, - }); - }; - - if struct_name - .as_ref() - .map(|name| name.starts_with("Rc<") | name.starts_with("Weak<")) - == Some(true) - && type_ns_h.contains(&["rc"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_rc(&struct_var), - original: struct_var, - }); - }; - - if struct_name - .as_ref() - .map(|name| name.starts_with("Arc<") | name.starts_with("Weak<")) - == Some(true) - && type_ns_h.contains(&["sync"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_rc(&struct_var), - original: struct_var, - }); - }; - - if struct_name.as_ref().map(|name| name == "Uuid") == Some(true) - && type_ns_h.contains(&["uuid"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_uuid(&struct_var), - original: struct_var, - }); - }; - - if struct_name.as_ref().map(|name| name == "Instant") == Some(true) - && type_ns_h.contains(&["std", "time"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_uuid(&struct_var), - original: struct_var, - }); - }; - - if struct_name.as_ref().map(|name| name == "SystemTime") == Some(true) - && type_ns_h.contains(&["std", "time"]) - { - return Some(VariableIR::Specialized { - value: parser_ext.parse_sys_time(&struct_var), - original: struct_var, - }); - }; - - Some(VariableIR::Struct(struct_var)) - } - TypeDeclaration::Array(decl) => Some(VariableIR::Array( - self.parse_array(eval_ctx, identity, data, type_id, decl), - )), - TypeDeclaration::CStyleEnum { - discr_type, - enumerators, - .. - } => Some(VariableIR::CEnum(self.parse_c_enum( - eval_ctx, - identity, - data, - type_id, - *discr_type, - enumerators, - ))), - TypeDeclaration::RustEnum { - discr_type, - enumerators, - .. - } => Some(VariableIR::RustEnum(self.parse_rust_enum( - eval_ctx, - identity, - data, - type_id, - discr_type.as_ref().map(|t| t.as_ref()), - enumerators, - ))), - TypeDeclaration::Pointer { target_type, .. } => Some(VariableIR::Pointer( - self.parse_pointer(identity, data, type_id, *target_type), - )), - TypeDeclaration::Union { members, .. } => { - let struct_var = self.parse_struct_variable( - eval_ctx, - identity, - data, - type_id, - HashMap::new(), - members, - ); - Some(VariableIR::Struct(struct_var)) - } - TypeDeclaration::Subroutine { return_type, .. } => { - let ret_type = return_type.map(|t_id| self.r#type.identity(t_id)); - let fn_var = SubroutineVariable { - identity, - type_id: Some(type_id), - return_type_ident: ret_type, - address: data.and_then(|d| d.address), - }; - Some(VariableIR::Subroutine(fn_var)) - } - TypeDeclaration::ModifiedType { - inner, modifier, .. - } => { - let in_debugee_loc = data.as_ref().and_then(|d| d.address); - Some(VariableIR::CModifiedVariable(CModifiedVariable { - identity: identity.clone(), - type_id: Some(type_id), - type_ident: self.r#type.identity(type_id), - modifier: *modifier, - value: inner.and_then(|inner_type| { - Some(Box::new( - self.parse_inner(eval_ctx, identity, data, inner_type)?, - )) - }), - address: in_debugee_loc, - })) - } - } - } - - pub fn parse( - self, - eval_ctx: &EvaluationContext, - identity: VariableIdentity, - value: Option, - ) -> Option { - self.parse_inner(eval_ctx, identity, value, self.r#type.root) - } -} - -/// Iterator for visits underline values in BFS order. -struct BfsIterator<'a> { - queue: VecDeque<(FieldOrIndex<'a>, &'a VariableIR)>, -} - -#[derive(PartialEq, Debug)] -enum FieldOrIndex<'a> { - Field(Option<&'a str>), - Index(i64), - Root(Option), -} - -impl<'a> Iterator for BfsIterator<'a> { - type Item = (FieldOrIndex<'a>, &'a VariableIR); - - fn next(&mut self) -> Option { - let (field_or_idx, next_value) = self.queue.pop_front()?; - - match next_value { - VariableIR::Struct(r#struct) => { - r#struct.members.iter().for_each(|member| { - let item = ( - FieldOrIndex::Field(member.field_name.as_deref()), - &member.value, - ); - - self.queue.push_back(item) - }); - } - VariableIR::Array(array) => { - if let Some(items) = array.items.as_ref() { - items.iter().for_each(|item| { - let item = (FieldOrIndex::Index(item.index), &item.value); - self.queue.push_back(item) - }) - } - } - VariableIR::RustEnum(r#enum) => { - if let Some(enumerator) = r#enum.value.as_ref() { - let item = ( - FieldOrIndex::Field(enumerator.field_name.as_deref()), - &enumerator.value, - ); - self.queue.push_back(item) - } - } - VariableIR::Pointer(_) => {} - VariableIR::Specialized { - original: origin, .. - } => origin.members.iter().for_each(|member| { - let item = ( - FieldOrIndex::Field(member.field_name.as_deref()), - &member.value, - ); - self.queue.push_back(item) - }), - _ => {} - } - - Some((field_or_idx, next_value)) - } -} - -#[inline(never)] -fn scalar_from_bytes(bytes: &Bytes) -> T { - let ptr = bytes.as_ptr(); - unsafe { std::ptr::read_unaligned::(ptr as *const T) } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::debugger::variable::specialization::VecVariable; - - #[test] - fn test_bfs_iterator() { - struct TestCase { - variable: VariableIR, - expected_order: Vec>, - } - - let test_cases = vec![ - TestCase { - variable: VariableIR::Struct(StructVariable { - identity: VariableIdentity::no_namespace(Some("struct_1".to_owned())), - type_ident: TypeIdentity::unknown(), - type_id: None, - members: vec![ - Member { - field_name: Some("array_1".to_owned()), - value: VariableIR::Array(ArrayVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::unknown(), - items: Some(vec![ - ArrayItem { - index: 1, - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::default(), - type_ident: TypeIdentity::unknown(), - value: None, - raw_address: None, - type_id: None, - }), - }, - ArrayItem { - index: 2, - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::default(), - type_ident: TypeIdentity::unknown(), - value: None, - raw_address: None, - type_id: None, - }), - }, - ]), - raw_address: None, - }), - }, - Member { - field_name: Some("array_2".to_owned()), - value: VariableIR::Array(ArrayVariable { - identity: VariableIdentity::default(), - type_ident: TypeIdentity::unknown(), - type_id: None, - items: Some(vec![ - ArrayItem { - index: 3, - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some( - "scalar_3".to_owned(), - )), - type_ident: TypeIdentity::unknown(), - value: None, - raw_address: None, - type_id: None, - }), - }, - ArrayItem { - index: 4, - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some( - "scalar_4".to_owned(), - )), - type_ident: TypeIdentity::unknown(), - value: None, - raw_address: None, - type_id: None, - }), - }, - ]), - raw_address: None, - }), - }, - ], - type_params: Default::default(), - raw_address: None, - }), - expected_order: vec![ - FieldOrIndex::Root(Some("struct_1".to_string())), - FieldOrIndex::Field(Some("array_1")), - FieldOrIndex::Field(Some("array_2")), - FieldOrIndex::Index(1), - FieldOrIndex::Index(2), - FieldOrIndex::Index(3), - FieldOrIndex::Index(4), - ], - }, - TestCase { - variable: VariableIR::Struct(StructVariable { - identity: VariableIdentity::no_namespace(Some("struct_1".to_owned())), - type_id: None, - type_ident: TypeIdentity::unknown(), - members: vec![ - Member { - field_name: Some("struct_2".to_owned()), - value: VariableIR::Struct(StructVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::unknown(), - members: vec![ - Member { - field_name: Some("scalar_1".to_owned()), - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::unknown(), - value: None, - raw_address: None, - }), - }, - Member { - field_name: Some("enum_1".to_owned()), - value: VariableIR::RustEnum(RustEnumVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::unknown(), - value: Some(Box::new(Member { - field_name: Some("scalar_2".to_owned()), - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::unknown(), - value: None, - raw_address: None, - }), - })), - raw_address: None, - }), - }, - Member { - field_name: Some("scalar_3".to_owned()), - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::unknown(), - value: None, - raw_address: None, - }), - }, - ], - type_params: Default::default(), - raw_address: None, - }), - }, - Member { - field_name: Some("pointer_1".to_owned()), - value: VariableIR::Pointer(PointerVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::unknown(), - value: None, - target_type: None, - target_type_size: None, - raw_address: None, - }), - }, - ], - type_params: Default::default(), - raw_address: None, - }), - expected_order: vec![ - FieldOrIndex::Root(Some("struct_1".to_string())), - FieldOrIndex::Field(Some("struct_2")), - FieldOrIndex::Field(Some("pointer_1")), - FieldOrIndex::Field(Some("scalar_1")), - FieldOrIndex::Field(Some("enum_1")), - FieldOrIndex::Field(Some("scalar_3")), - FieldOrIndex::Field(Some("scalar_2")), - ], - }, - ]; - - for tc in test_cases { - let iter = tc.variable.bfs_iterator(); - let names: Vec<_> = iter.map(|(field_or_idx, _)| field_or_idx).collect(); - assert_eq!(tc.expected_order, names); - } - } - - // test helpers -------------------------------------------------------------------------------- - // - fn make_scalar_var_ir( - name: Option<&str>, - type_name: &str, - scalar: SupportedScalar, - ) -> VariableIR { - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - type_id: None, - type_ident: TypeIdentity::no_namespace(type_name), - value: Some(scalar), - raw_address: None, - }) - } - - fn make_str_var_member(name: Option<&str>, val: &str) -> VariableIR { - VariableIR::Specialized { - value: Some(SpecializedVariableIR::Str(StrVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - value: val.to_string(), - })), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - } - } - - fn make_string_var_ir(name: Option<&str>, val: &str) -> VariableIR { - VariableIR::Specialized { - value: Some(SpecializedVariableIR::String(StringVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - value: val.to_string(), - })), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - } - } - - fn make_vec_var_ir(name: Option<&str>, items: Vec) -> VecVariable { - let items_len = items.len(); - VecVariable { - structure: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - type_id: None, - type_ident: TypeIdentity::no_namespace("vec"), - members: vec![ - Member { - field_name: None, - value: VariableIR::Array(ArrayVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::no_namespace("[item]"), - items: Some(items), - raw_address: None, - }), - }, - Member { - field_name: Some("cap".to_string()), - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::no_namespace("usize"), - value: Some(SupportedScalar::Usize(items_len)), - raw_address: None, - }), - }, - ], - type_params: HashMap::default(), - raw_address: None, - }, - } - } - - fn make_vector_var_ir(name: Option<&str>, items: Vec) -> VariableIR { - VariableIR::Specialized { - value: Some(SpecializedVariableIR::Vector(make_vec_var_ir(name, items))), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - } - } - - fn make_vecdeque_var_ir(name: Option<&str>, items: Vec) -> VariableIR { - VariableIR::Specialized { - value: Some(SpecializedVariableIR::VecDeque(make_vec_var_ir( - name, items, - ))), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - } - } - - fn make_hashset_var_ir(name: Option<&str>, items: Vec) -> VariableIR { - VariableIR::Specialized { - value: Some(SpecializedVariableIR::HashSet(HashSetVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - type_ident: TypeIdentity::no_namespace("hashset"), - items, - })), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - } - } - - fn make_btreeset_var_ir(name: Option<&str>, items: Vec) -> VariableIR { - VariableIR::Specialized { - value: Some(SpecializedVariableIR::BTreeSet(HashSetVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - type_ident: TypeIdentity::no_namespace("btreeset"), - items, - })), - original: StructVariable { - identity: VariableIdentity::no_namespace(name.map(ToString::to_string)), - ..Default::default() - }, - } - } - //---------------------------------------------------------------------------------------------- - - #[test] - fn test_equal_with_literal() { - struct TestCase { - variable: VariableIR, - eq_literal: Literal, - neq_literals: Vec, - } - - let test_cases = [ - TestCase { - variable: make_scalar_var_ir(None, "i8", SupportedScalar::I8(8)), - eq_literal: Literal::Int(8), - neq_literals: vec![Literal::Int(9)], - }, - TestCase { - variable: make_scalar_var_ir(None, "i32", SupportedScalar::I32(32)), - eq_literal: Literal::Int(32), - neq_literals: vec![Literal::Int(33)], - }, - TestCase { - variable: make_scalar_var_ir(None, "isize", SupportedScalar::Isize(-1234)), - eq_literal: Literal::Int(-1234), - neq_literals: vec![Literal::Int(-1233)], - }, - TestCase { - variable: make_scalar_var_ir(None, "u8", SupportedScalar::U8(8)), - eq_literal: Literal::Int(8), - neq_literals: vec![Literal::Int(9)], - }, - TestCase { - variable: make_scalar_var_ir(None, "u32", SupportedScalar::U32(32)), - eq_literal: Literal::Int(32), - neq_literals: vec![Literal::Int(33)], - }, - TestCase { - variable: make_scalar_var_ir(None, "usize", SupportedScalar::Usize(1234)), - eq_literal: Literal::Int(1234), - neq_literals: vec![Literal::Int(1235)], - }, - TestCase { - variable: make_scalar_var_ir(None, "f32", SupportedScalar::F32(1.1)), - eq_literal: Literal::Float(1.1), - neq_literals: vec![Literal::Float(1.2)], - }, - TestCase { - variable: make_scalar_var_ir(None, "f64", SupportedScalar::F64(-2.2)), - eq_literal: Literal::Float(-2.2), - neq_literals: vec![Literal::Float(2.2)], - }, - TestCase { - variable: make_scalar_var_ir(None, "bool", SupportedScalar::Bool(true)), - eq_literal: Literal::Bool(true), - neq_literals: vec![Literal::Bool(false)], - }, - TestCase { - variable: make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - eq_literal: Literal::String("b".into()), - neq_literals: vec![Literal::String("c".into())], - }, - TestCase { - variable: VariableIR::Pointer(PointerVariable { - identity: VariableIdentity::default(), - target_type: None, - type_id: None, - type_ident: TypeIdentity::no_namespace("ptr"), - value: Some(123usize as *const ()), - raw_address: None, - target_type_size: None, - }), - eq_literal: Literal::Address(123), - neq_literals: vec![Literal::Address(124), Literal::Int(123)], - }, - TestCase { - variable: VariableIR::Pointer(PointerVariable { - identity: VariableIdentity::default(), - target_type: None, - type_id: None, - type_ident: TypeIdentity::no_namespace("MyPtr"), - value: Some(123usize as *const ()), - raw_address: None, - target_type_size: None, - }), - eq_literal: Literal::Address(123), - neq_literals: vec![Literal::Address(124), Literal::Int(123)], - }, - TestCase { - variable: VariableIR::CEnum(CEnumVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::no_namespace("MyEnum"), - value: Some("Variant1".into()), - raw_address: None, - }), - eq_literal: Literal::EnumVariant("Variant1".to_string(), None), - neq_literals: vec![ - Literal::EnumVariant("Variant2".to_string(), None), - Literal::String("Variant1".to_string()), - ], - }, - TestCase { - variable: VariableIR::RustEnum(RustEnumVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::no_namespace("MyEnum"), - value: Some(Box::new(Member { - field_name: Some("Variant1".to_string()), - value: VariableIR::Struct(StructVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::unknown(), - members: vec![Member { - field_name: Some("Variant1".to_string()), - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::default(), - type_id: None, - type_ident: TypeIdentity::no_namespace("int"), - value: Some(SupportedScalar::I64(100)), - raw_address: None, - }), - }], - type_params: Default::default(), - raw_address: None, - }), - })), - raw_address: None, - }), - eq_literal: Literal::EnumVariant( - "Variant1".to_string(), - Some(Box::new(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(100)), - ])))), - ), - neq_literals: vec![ - Literal::EnumVariant("Variant1".to_string(), Some(Box::new(Literal::Int(101)))), - Literal::EnumVariant("Variant2".to_string(), Some(Box::new(Literal::Int(100)))), - Literal::String("Variant1".to_string()), - ], - }, - ]; - - for tc in test_cases { - assert!(tc.variable.clone().match_literal(&tc.eq_literal)); - for neq_lit in tc.neq_literals { - assert!(!tc.variable.clone().match_literal(&neq_lit)); - } - } - } - - #[test] - fn test_equal_with_complex_literal() { - struct TestCase { - variable: VariableIR, - eq_literals: Vec, - neq_literals: Vec, - } - - let test_cases = [ - TestCase { - variable: make_str_var_member(None, "str1"), - eq_literals: vec![Literal::String("str1".to_string())], - neq_literals: vec![Literal::String("str2".to_string()), Literal::Int(1)], - }, - TestCase { - variable: make_string_var_ir(None, "string1"), - eq_literals: vec![Literal::String("string1".to_string())], - neq_literals: vec![Literal::String("string2".to_string()), Literal::Int(1)], - }, - TestCase { - variable: VariableIR::Specialized { - value: Some(SpecializedVariableIR::Uuid([ - 0xd0, 0x60, 0x66, 0x29, 0x78, 0x6a, 0x44, 0xbe, 0x9d, 0x49, 0xb7, 0x02, - 0x0f, 0x3e, 0xb0, 0x5a, - ])), - original: StructVariable::default(), - }, - eq_literals: vec![Literal::String( - "d0606629-786a-44be-9d49-b7020f3eb05a".to_string(), - )], - neq_literals: vec![Literal::String( - "d0606629-786a-44be-9d49-b7020f3eb05b".to_string(), - )], - }, - TestCase { - variable: make_vector_var_ir( - None, - vec![ - ArrayItem { - index: 0, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('a')), - }, - ArrayItem { - index: 1, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - }, - ArrayItem { - index: 2, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - }, - ArrayItem { - index: 3, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - }, - ], - ), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - ], - }, - TestCase { - variable: make_vector_var_ir( - None, - vec![ - ArrayItem { - index: 0, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('a')), - }, - ArrayItem { - index: 1, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - }, - ArrayItem { - index: 2, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - }, - ArrayItem { - index: 3, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - }, - ], - ), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - ], - }, - TestCase { - variable: make_vecdeque_var_ir( - None, - vec![ - ArrayItem { - index: 0, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('a')), - }, - ArrayItem { - index: 1, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - }, - ArrayItem { - index: 2, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - }, - ArrayItem { - index: 3, - value: make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - }, - ], - ), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - LiteralOrWildcard::Wildcard, - ])), - ], - }, - TestCase { - variable: make_hashset_var_ir( - None, - vec![ - make_scalar_var_ir(None, "char", SupportedScalar::Char('a')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - ], - ), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - }, - TestCase { - variable: make_btreeset_var_ir( - None, - vec![ - make_scalar_var_ir(None, "char", SupportedScalar::Char('a')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('b')), - make_scalar_var_ir(None, "char", SupportedScalar::Char('c')), - ], - ), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("c".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("a".to_string())), - LiteralOrWildcard::Literal(Literal::String("b".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - }, - TestCase { - variable: VariableIR::Specialized { - value: Some(SpecializedVariableIR::Cell(Box::new(make_scalar_var_ir( - None, - "int", - SupportedScalar::I64(100), - )))), - original: StructVariable::default(), - }, - eq_literals: vec![Literal::Int(100)], - neq_literals: vec![Literal::Int(101), Literal::Float(100.1)], - }, - TestCase { - variable: VariableIR::Array(ArrayVariable { - identity: Default::default(), - type_id: None, - type_ident: TypeIdentity::no_namespace("array_str"), - items: Some(vec![ - ArrayItem { - index: 0, - value: make_str_var_member(None, "ab"), - }, - ArrayItem { - index: 1, - value: make_str_var_member(None, "cd"), - }, - ArrayItem { - index: 2, - value: make_str_var_member(None, "ef"), - }, - ]), - raw_address: None, - }), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("ab".to_string())), - LiteralOrWildcard::Literal(Literal::String("cd".to_string())), - LiteralOrWildcard::Literal(Literal::String("ef".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("ab".to_string())), - LiteralOrWildcard::Literal(Literal::String("cd".to_string())), - LiteralOrWildcard::Wildcard, - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("ab".to_string())), - LiteralOrWildcard::Literal(Literal::String("cd".to_string())), - LiteralOrWildcard::Literal(Literal::String("gj".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("ab".to_string())), - LiteralOrWildcard::Literal(Literal::String("cd".to_string())), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("ab".to_string())), - LiteralOrWildcard::Literal(Literal::String("cd".to_string())), - LiteralOrWildcard::Literal(Literal::String("ef".to_string())), - LiteralOrWildcard::Literal(Literal::String("gj".to_string())), - ])), - ], - }, - TestCase { - variable: VariableIR::Struct(StructVariable { - identity: Default::default(), - type_id: None, - type_ident: TypeIdentity::no_namespace("MyStruct"), - members: vec![ - Member { - field_name: Some("str_field".to_string()), - value: make_str_var_member(None, "str1"), - }, - Member { - field_name: Some("vec_field".to_string()), - value: make_vector_var_ir( - None, - vec![ - ArrayItem { - index: 0, - value: make_scalar_var_ir(None, "", SupportedScalar::I8(1)), - }, - ArrayItem { - index: 1, - value: make_scalar_var_ir(None, "", SupportedScalar::I8(2)), - }, - ], - ), - }, - Member { - field_name: Some("bool_field".to_string()), - value: make_scalar_var_ir(None, "", SupportedScalar::Bool(true)), - }, - ], - type_params: Default::default(), - raw_address: None, - }), - eq_literals: vec![ - Literal::AssocArray(HashMap::from([ - ( - "str_field".to_string(), - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - ), - ( - "vec_field".to_string(), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Literal(Literal::Int(2)), - ]))), - ), - ( - "bool_field".to_string(), - LiteralOrWildcard::Literal(Literal::Bool(true)), - ), - ])), - Literal::AssocArray(HashMap::from([ - ( - "str_field".to_string(), - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - ), - ( - "vec_field".to_string(), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Wildcard, - ]))), - ), - ("bool_field".to_string(), LiteralOrWildcard::Wildcard), - ])), - ], - neq_literals: vec![ - Literal::AssocArray(HashMap::from([ - ( - "str_field".to_string(), - LiteralOrWildcard::Literal(Literal::String("str2".to_string())), - ), - ( - "vec_field".to_string(), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Literal(Literal::Int(2)), - ]))), - ), - ( - "bool_field".to_string(), - LiteralOrWildcard::Literal(Literal::Bool(true)), - ), - ])), - Literal::AssocArray(HashMap::from([ - ( - "str_field".to_string(), - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - ), - ( - "vec_field".to_string(), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - ]))), - ), - ( - "bool_field".to_string(), - LiteralOrWildcard::Literal(Literal::Bool(true)), - ), - ])), - ], - }, - TestCase { - variable: VariableIR::Struct(StructVariable { - identity: Default::default(), - type_id: None, - type_ident: TypeIdentity::no_namespace("MyTuple"), - members: vec![ - Member { - field_name: None, - value: make_str_var_member(None, "str1"), - }, - Member { - field_name: None, - value: make_vector_var_ir( - None, - vec![ - ArrayItem { - index: 0, - value: make_scalar_var_ir(None, "", SupportedScalar::I8(1)), - }, - ArrayItem { - index: 1, - value: make_scalar_var_ir(None, "", SupportedScalar::I8(2)), - }, - ], - ), - }, - Member { - field_name: None, - value: make_scalar_var_ir(None, "", SupportedScalar::Bool(true)), - }, - ], - type_params: Default::default(), - raw_address: None, - }), - eq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Literal(Literal::Int(2)), - ]))), - LiteralOrWildcard::Literal(Literal::Bool(true)), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Wildcard, - ]))), - LiteralOrWildcard::Wildcard, - ])), - ], - neq_literals: vec![ - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - LiteralOrWildcard::Literal(Literal::Int(2)), - ]))), - LiteralOrWildcard::Literal(Literal::Bool(false)), - ])), - Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::String("str1".to_string())), - LiteralOrWildcard::Literal(Literal::Array(Box::new([ - LiteralOrWildcard::Literal(Literal::Int(1)), - ]))), - LiteralOrWildcard::Literal(Literal::Bool(true)), - ])), - ], - }, - ]; - - for tc in test_cases { - for eq_lit in tc.eq_literals { - assert!(tc.variable.clone().match_literal(&eq_lit)); - } - for neq_lit in tc.neq_literals { - assert!(!tc.variable.clone().match_literal(&neq_lit)); - } - } - } + /// Binary size. + pub size: usize, } diff --git a/src/debugger/variable/render.rs b/src/debugger/variable/render.rs index 401e3a6..9d1906d 100644 --- a/src/debugger/variable/render.rs +++ b/src/debugger/variable/render.rs @@ -1,6 +1,5 @@ use crate::debugger::debugee::dwarf::r#type::TypeIdentity; -use crate::debugger::variable::{ArrayItem, VariableIR}; -use crate::debugger::variable::{Member, SpecializedVariableIR}; +use crate::debugger::variable::value::{ArrayItem, Member, SpecializedValue, Value}; use nix::errno::Errno; use nix::libc; use nix::sys::time::TimeSpec; @@ -11,14 +10,23 @@ use std::mem::MaybeUninit; use std::ops::Sub; use std::time::Duration; +/// Layout of a value from debugee program. +/// Used by UI for representing value to a user. pub enum ValueLayout<'a> { + /// Value already rendered, just print it! PreRendered(Cow<'a, str>), + /// Value is an address in debugee memory. Referential(*const ()), - Wrapped(&'a VariableIR), + /// Value wraps another value. + Wrapped(&'a Value), + /// Value is a structure. Structure(&'a [Member]), + /// Value is a list with indexed elements. IndexedList(&'a [ArrayItem]), - NonIndexedList(&'a [VariableIR]), - Map(&'a [(VariableIR, VariableIR)]), + /// Value is an unordered list. + NonIndexedList(&'a [Value]), + /// Value is a map where keys and values are values too. + Map(&'a [(Value, Value)]), } impl<'a> Debug for ValueLayout<'a> { @@ -26,14 +34,14 @@ impl<'a> Debug for ValueLayout<'a> { match self { ValueLayout::PreRendered(s) => f.debug_tuple("PreRendered").field(s).finish(), ValueLayout::Referential(addr) => f.debug_tuple("Referential").field(addr).finish(), - ValueLayout::Wrapped(v) => f.debug_tuple("Wrapped").field(v).finish(), + ValueLayout::Wrapped(v) => f.debug_tuple("Wrapped").field(&v).finish(), ValueLayout::Structure(members) => { f.debug_struct("Nested").field("members", members).finish() } ValueLayout::Map(kvs) => { let mut list = f.debug_list(); for kv in kvs.iter() { - list.entry(kv); + list.entry(&kv); } list.finish() } @@ -47,123 +55,112 @@ impl<'a> Debug for ValueLayout<'a> { } } -pub trait RenderRepr { - fn name(&self) -> Option; +pub trait RenderValue { + /// Return type identity for rendering. fn r#type(&self) -> &TypeIdentity; - fn value(&self) -> Option; -} -impl RenderRepr for VariableIR { - fn name(&self) -> Option { - self.identity() - .name - .is_some() - .then(|| self.identity().to_string()) - } + /// Return value layout for rendering. + fn value_layout(&self) -> Option; +} +impl RenderValue for Value { fn r#type(&self) -> &TypeIdentity { static STRING_TYPE: Lazy = Lazy::new(|| TypeIdentity::no_namespace("String")); static STR_TYPE: Lazy = Lazy::new(|| TypeIdentity::no_namespace("&str")); static UNKNOWN_TYPE: Lazy = Lazy::new(TypeIdentity::unknown); match self { - VariableIR::Scalar(s) => &s.type_ident, - VariableIR::Struct(s) => &s.type_ident, - VariableIR::Array(a) => &a.type_ident, - VariableIR::CEnum(e) => &e.type_ident, - VariableIR::RustEnum(e) => &e.type_ident, - VariableIR::Pointer(p) => &p.type_ident, - VariableIR::Specialized { + Value::Scalar(s) => &s.type_ident, + Value::Struct(s) => &s.type_ident, + Value::Array(a) => &a.type_ident, + Value::CEnum(e) => &e.type_ident, + Value::RustEnum(e) => &e.type_ident, + Value::Pointer(p) => &p.type_ident, + Value::Specialized { value: Some(spec_val), original, } => match spec_val { - SpecializedVariableIR::Vector(vec) | SpecializedVariableIR::VecDeque(vec) => { + SpecializedValue::Vector(vec) | SpecializedValue::VecDeque(vec) => { &vec.structure.type_ident } - SpecializedVariableIR::String { .. } => &STRING_TYPE, - SpecializedVariableIR::Str { .. } => &STR_TYPE, - SpecializedVariableIR::Tls(value) => &value.inner_type, - SpecializedVariableIR::HashMap(map) => &map.type_ident, - SpecializedVariableIR::HashSet(set) => &set.type_ident, - SpecializedVariableIR::BTreeMap(map) => &map.type_ident, - SpecializedVariableIR::BTreeSet(set) => &set.type_ident, - SpecializedVariableIR::Cell(_) | SpecializedVariableIR::RefCell(_) => { - &original.type_ident - } - SpecializedVariableIR::Rc(_) | SpecializedVariableIR::Arc(_) => { - &original.type_ident - } - SpecializedVariableIR::Uuid(_) => &original.type_ident, - SpecializedVariableIR::SystemTime(_) => &original.type_ident, - SpecializedVariableIR::Instant(_) => &original.type_ident, + SpecializedValue::String { .. } => &STRING_TYPE, + SpecializedValue::Str { .. } => &STR_TYPE, + SpecializedValue::Tls(value) => &value.inner_type, + SpecializedValue::HashMap(map) => &map.type_ident, + SpecializedValue::HashSet(set) => &set.type_ident, + SpecializedValue::BTreeMap(map) => &map.type_ident, + SpecializedValue::BTreeSet(set) => &set.type_ident, + SpecializedValue::Cell(_) | SpecializedValue::RefCell(_) => &original.type_ident, + SpecializedValue::Rc(_) | SpecializedValue::Arc(_) => &original.type_ident, + SpecializedValue::Uuid(_) => &original.type_ident, + SpecializedValue::SystemTime(_) => &original.type_ident, + SpecializedValue::Instant(_) => &original.type_ident, }, - VariableIR::Specialized { original, .. } => &original.type_ident, - VariableIR::Subroutine(_) => { + Value::Specialized { original, .. } => &original.type_ident, + Value::Subroutine(_) => { // currently this line is unreachable because dereference of fn pointer is forbidden &UNKNOWN_TYPE } - VariableIR::CModifiedVariable(v) => &v.type_ident, + Value::CModifiedVariable(v) => &v.type_ident, } } - fn value(&self) -> Option { + fn value_layout(&self) -> Option { let value_repr = match self { - VariableIR::Scalar(scalar) => { + Value::Scalar(scalar) => { ValueLayout::PreRendered(Cow::Owned(scalar.value.as_ref()?.to_string())) } - VariableIR::Struct(r#struct) => ValueLayout::Structure(r#struct.members.as_ref()), - VariableIR::Array(array) => ValueLayout::IndexedList(array.items.as_deref()?), - VariableIR::CEnum(r#enum) => { - ValueLayout::PreRendered(Cow::Borrowed(r#enum.value.as_ref()?)) - } - VariableIR::RustEnum(r#enum) => { + Value::Struct(r#struct) => ValueLayout::Structure(r#struct.members.as_ref()), + Value::Array(array) => ValueLayout::IndexedList(array.items.as_deref()?), + Value::CEnum(r#enum) => ValueLayout::PreRendered(Cow::Borrowed(r#enum.value.as_ref()?)), + Value::RustEnum(r#enum) => { let enum_val = &r#enum.value.as_ref()?.value; ValueLayout::Wrapped(enum_val) } - VariableIR::Pointer(pointer) => { + Value::Pointer(pointer) => { let ptr = pointer.value?; ValueLayout::Referential(ptr) } - VariableIR::Specialized { + Value::Specialized { value: Some(spec_val), .. } => match spec_val { - SpecializedVariableIR::Vector(vec) | SpecializedVariableIR::VecDeque(vec) => { + SpecializedValue::Vector(vec) | SpecializedValue::VecDeque(vec) => { ValueLayout::Structure(vec.structure.members.as_ref()) } - SpecializedVariableIR::String(string) => { + SpecializedValue::String(string) => { ValueLayout::PreRendered(Cow::Borrowed(&string.value)) } - SpecializedVariableIR::Str(string) => { + SpecializedValue::Str(string) => { ValueLayout::PreRendered(Cow::Borrowed(&string.value)) } - SpecializedVariableIR::Tls(tls_value) => match tls_value.inner_value.as_ref() { + SpecializedValue::Tls(tls_value) => match tls_value.inner_value.as_ref() { None => ValueLayout::PreRendered(Cow::Borrowed("uninit")), - Some(tls_inner_val) => tls_inner_val.value()?, + Some(tls_inner_val) => tls_inner_val.value_layout()?, }, - SpecializedVariableIR::HashMap(map) => ValueLayout::Map(&map.kv_items), - SpecializedVariableIR::HashSet(set) => ValueLayout::NonIndexedList(&set.items), - SpecializedVariableIR::BTreeMap(map) => ValueLayout::Map(&map.kv_items), - SpecializedVariableIR::BTreeSet(set) => ValueLayout::NonIndexedList(&set.items), - SpecializedVariableIR::Cell(cell) | SpecializedVariableIR::RefCell(cell) => { - cell.value()? + SpecializedValue::HashMap(map) => ValueLayout::Map(&map.kv_items), + SpecializedValue::HashSet(set) => ValueLayout::NonIndexedList(&set.items), + SpecializedValue::BTreeMap(map) => ValueLayout::Map(&map.kv_items), + SpecializedValue::BTreeSet(set) => ValueLayout::NonIndexedList(&set.items), + SpecializedValue::Cell(cell) | SpecializedValue::RefCell(cell) => { + cell.value_layout()? } - SpecializedVariableIR::Rc(ptr) | SpecializedVariableIR::Arc(ptr) => { + SpecializedValue::Rc(ptr) | SpecializedValue::Arc(ptr) => { let ptr = ptr.value?; ValueLayout::Referential(ptr) } - SpecializedVariableIR::Uuid(bytes) => { + SpecializedValue::Uuid(bytes) => { let uuid = uuid::Uuid::from_slice(bytes).expect("infallible"); ValueLayout::PreRendered(Cow::Owned(uuid.to_string())) } - SpecializedVariableIR::SystemTime((sec, n_sec)) => { + SpecializedValue::SystemTime((sec, n_sec)) => { let mb_dt = chrono::NaiveDateTime::from_timestamp_opt(*sec, *n_sec); let dt_rendered = mb_dt .map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string()) .unwrap_or("Broken date time".to_string()); ValueLayout::PreRendered(Cow::Owned(dt_rendered)) } - SpecializedVariableIR::Instant((sec, n_sec)) => { + SpecializedValue::Instant((sec, n_sec)) => { let now = now_timespec().expect("broken system clock"); let instant = TimeSpec::new(*sec, *n_sec as i64); let render = if now > instant { @@ -176,14 +173,14 @@ impl RenderRepr for VariableIR { ValueLayout::PreRendered(Cow::Owned(render)) } }, - VariableIR::Specialized { original, .. } => { + Value::Specialized { original, .. } => { ValueLayout::Structure(original.members.as_ref()) } - VariableIR::Subroutine(_) => { + Value::Subroutine(_) => { // currently this line is unreachable because dereference of fn pointer is forbidden return None; } - VariableIR::CModifiedVariable(v) => ValueLayout::Wrapped(v.value.as_ref()?), + Value::CModifiedVariable(v) => ValueLayout::Wrapped(v.value.as_ref()?), }; Some(value_repr) } diff --git a/src/debugger/variable/select.rs b/src/debugger/variable/select.rs index 0b540af..e69de29 100644 --- a/src/debugger/variable/select.rs +++ b/src/debugger/variable/select.rs @@ -1,499 +0,0 @@ -use crate::debugger::debugee::dwarf; -use crate::debugger::debugee::dwarf::r#type::ComplexType; -use crate::debugger::debugee::dwarf::unit::{DieRef, Node, VariableDie}; -use crate::debugger::debugee::dwarf::{ - AsAllocatedData, ContextualDieRef, EndianArcSlice, NamespaceHierarchy, -}; -use crate::debugger::error::Error; -use crate::debugger::error::Error::FunctionNotFound; -use crate::debugger::variable::{AssumeError, ParsingError, VariableIR, VariableIdentity}; -use crate::debugger::Error::TypeNotFound; -use crate::debugger::{variable, Debugger}; -use crate::{ctx_resolve_unit_call, weak_error}; -use bytes::Bytes; -use gimli::{Attribute, DebugInfoOffset, Range, UnitOffset}; -use std::collections::hash_map::Entry; -use std::collections::HashMap; - -/// This die not exists in debug information. -/// It may be used to represent variables that are -/// declared by user, for example, using pointer cast operator. -struct VirtualVariableDie { - type_ref: DieRef, -} - -impl VirtualVariableDie { - fn of_unknown_type() -> Self { - Self { - type_ref: DieRef::Unit(UnitOffset(0)), - } - } -} - -impl AsAllocatedData for VirtualVariableDie { - fn name(&self) -> Option<&str> { - None - } - - fn type_ref(&self) -> Option { - Some(self.type_ref) - } - - fn location(&self) -> Option<&Attribute> { - None - } -} - -/// Literal object. Using it for a searching element by key in key-value containers. -#[derive(Debug, PartialEq, Clone)] -pub enum Literal { - String(String), - Int(i64), - Float(f64), - Address(usize), - Bool(bool), - EnumVariant(String, Option>), - Array(Box<[LiteralOrWildcard]>), - AssocArray(HashMap), -} - -#[derive(Debug, PartialEq, Clone)] -pub enum LiteralOrWildcard { - Literal(Literal), - Wildcard, -} - -macro_rules! impl_equal { - ($lhs: expr, $rhs: expr, $lit: path) => { - if let $lit(lhs) = $lhs { - lhs == &$rhs - } else { - false - } - }; -} - -impl Literal { - pub fn equal_with_string(&self, rhs: &str) -> bool { - impl_equal!(self, rhs, Literal::String) - } - - pub fn equal_with_address(&self, rhs: usize) -> bool { - impl_equal!(self, rhs, Literal::Address) - } - - pub fn equal_with_bool(&self, rhs: bool) -> bool { - impl_equal!(self, rhs, Literal::Bool) - } - - pub fn equal_with_int(&self, rhs: i64) -> bool { - impl_equal!(self, rhs, Literal::Int) - } - - pub fn equal_with_float(&self, rhs: f64) -> bool { - const EPS: f64 = 0.0000001f64; - if let Literal::Float(float) = self { - let diff = (*float - rhs).abs(); - diff < EPS - } else { - false - } - } -} - -/// Object binary representation in debugee memory. -pub struct ObjectBinaryRepr { - /// Binary representation. - pub raw_data: Bytes, - /// Possible address of object data in debugee memory. - /// It may not exist if there is no debug information, or if an object is allocated in registers. - pub address: Option, - /// Binary size. - pub size: usize, -} - -#[derive(Debug, PartialEq, Clone)] -pub enum Selector { - Name { var_name: String, local_only: bool }, - Any, -} - -impl Selector { - pub fn by_name(name: impl ToString, local_only: bool) -> Self { - Self::Name { - var_name: name.to_string(), - local_only, - } - } -} - -/// Data query expression. -/// List of operations for select variables and their properties. -/// -/// Expression can be parsed from an input string like `*(*variable1.field2)[1]` -/// (see [`crate::ui::command`] module) -/// -/// Supported operations are: dereference, get an element by index, get field by name, make slice from a pointer. -#[derive(Debug, PartialEq, Clone)] -pub enum DQE { - Variable(Selector), - PtrCast(usize, String), - Field(Box, String), - Index(Box, Literal), - Slice(Box, Option, Option), - Deref(Box), - Address(Box), - Canonic(Box), -} - -impl DQE { - /// Return boxed expression. - pub fn boxed(self) -> Box { - Box::new(self) - } -} - -/// Result of DQE evaluation. -pub struct ScopedVariable { - /// Variable intermediate representation. - pub variable: VariableIR, - /// PC ranges where value is valid, `None` for global or virtual variables. - pub scope: Option>, -} - -/// Evaluate `Expression` at current breakpoint (for current debugee location). -pub struct SelectExpressionEvaluator<'a> { - debugger: &'a Debugger, - expression: DQE, -} - -macro_rules! type_from_cache { - ($variable: expr, $cache: expr) => { - $variable - .die - .type_ref() - .and_then( - |type_ref| match $cache.entry(($variable.unit().id, type_ref)) { - Entry::Occupied(o) => Some(&*o.into_mut()), - Entry::Vacant(v) => $variable.r#type().map(|t| &*v.insert(t)), - }, - ) - .ok_or_else(|| ParsingError::Assume(AssumeError::NoType("variable"))) - }; -} - -impl<'a> SelectExpressionEvaluator<'a> { - pub fn new(debugger: &'a Debugger, expression: DQE) -> Self { - Self { - debugger, - expression, - } - } - - fn extract_variable_by_selector( - &self, - selector: &Selector, - ) -> Result>, Error> { - let ctx = self.debugger.exploration_ctx(); - - let debugee = &self.debugger.debugee; - let current_func = debugee - .debug_info(ctx.location().pc)? - .find_function_by_pc(ctx.location().global_pc)? - .ok_or(FunctionNotFound(ctx.location().global_pc))?; - - let vars = match selector { - Selector::Name { - var_name, - local_only: local, - } => { - let local_variants = current_func - .local_variable(ctx.location().global_pc, var_name) - .map(|v| vec![v]) - .unwrap_or_default(); - - let local = *local; - - // local variables is in priority anyway, if there are no local variables and - // selector allow non-locals then try to search in a whole object - if !local && local_variants.is_empty() { - debugee - .debug_info(ctx.location().pc)? - .find_variables(ctx.location(), var_name)? - } else { - local_variants - } - } - Selector::Any => current_func.local_variables(ctx.location().global_pc), - }; - - Ok(vars) - } - - fn fill_virtual_ptr_variable( - &self, - vv: &'a mut VirtualVariableDie, - node: &'a Node, - type_name: &str, - ) -> Result, Error> { - let debugee = &self.debugger.debugee; - let (debug_info, offset_of_unit, offset_of_die) = debugee - .debug_info_all() - .iter() - .find_map(|&debug_info| { - let (offset_of_unit, offset_of_die) = debug_info.find_type_die_ref(type_name)?; - Some((debug_info, offset_of_unit, offset_of_die)) - }) - .ok_or(TypeNotFound)?; - let unit = debug_info - .find_unit(DebugInfoOffset(offset_of_unit.0 + offset_of_die.0)) - .ok_or(TypeNotFound)?; - - vv.type_ref = DieRef::Unit(offset_of_die); - let var = ContextualDieRef { - debug_info, - unit_idx: unit.idx(), - node, - die: vv, - }; - - Ok(var) - } - - /// Evaluate only variable names. - /// Only filter expression supported. - /// - /// # Panics - /// - /// This method will panic - /// if select expression contains any operators excluding a variable selector. - pub fn evaluate_names(&self) -> Result, Error> { - match &self.expression { - DQE::Variable(selector) => { - let vars = self.extract_variable_by_selector(selector)?; - Ok(vars - .into_iter() - .filter_map(|die| die.die.name().map(ToOwned::to_owned)) - .collect()) - } - _ => unreachable!("unexpected expression variant"), - } - } - - fn evaluate_inner(&self, expression: &DQE) -> Result, Error> { - // evaluate variable one by one in `evaluate_single_variable` method - // here just filter variables - match expression { - DQE::Variable(selector) => { - let vars = self.extract_variable_by_selector(selector)?; - let mut type_cache = self.debugger.type_cache.borrow_mut(); - - Ok(vars - .iter() - .filter_map(|var| { - let r#type = weak_error!(type_from_cache!(var, type_cache))?; - let var_ir = - self.evaluate_single_variable(&self.expression, var, r#type)?; - Some(ScopedVariable { - variable: var_ir, - scope: var.ranges().map(Box::from), - }) - }) - .collect()) - } - DQE::PtrCast(_, target_type_name) => { - let vars_ir = self.evaluate_from_ptr_cast(target_type_name)?; - Ok(vars_ir - .into_iter() - .map(|var_ir| ScopedVariable { - variable: var_ir, - scope: None, - }) - .collect()) - } - DQE::Field(expr, _) - | DQE::Index(expr, _) - | DQE::Slice(expr, _, _) - | DQE::Deref(expr) - | DQE::Address(expr) - | DQE::Canonic(expr) => self.evaluate_inner(expr), - } - } - - /// Create virtual DIE from type name and constant address. Evaluate expression then on this DIE. - fn evaluate_from_ptr_cast(&self, type_name: &str) -> Result, Error> { - let any_node = Node::new_leaf(None); - let mut var_die = VirtualVariableDie::of_unknown_type(); - let var_die_ref = self.fill_virtual_ptr_variable(&mut var_die, &any_node, type_name)?; - - let mut type_cache = self.debugger.type_cache.borrow_mut(); - let r#type = type_from_cache!(var_die_ref, type_cache)?; - - if let Some(v) = self.evaluate_single_variable(&self.expression, &var_die_ref, r#type) { - return Ok(vec![v]); - } - Ok(vec![]) - } - - /// Evaluate a select expression and returns list of matched variables. - pub fn evaluate(&self) -> Result, Error> { - self.evaluate_inner(&self.expression) - } - - /// Same as [`SelectExpressionEvaluator::evaluate_names`] but for function arguments. - pub fn evaluate_on_arguments_names(&self) -> Result, Error> { - match &self.expression { - DQE::Variable(selector) => { - let expl_ctx_loc = self.debugger.exploration_ctx().location(); - let current_function = self - .debugger - .debugee - .debug_info(expl_ctx_loc.pc)? - .find_function_by_pc(expl_ctx_loc.global_pc)? - .ok_or(FunctionNotFound(expl_ctx_loc.global_pc))?; - let params = current_function.parameters(); - - let params = match selector { - Selector::Name { var_name, .. } => params - .into_iter() - .filter(|param| param.die.base_attributes.name.as_ref() == Some(var_name)) - .collect::>(), - Selector::Any => params, - }; - - Ok(params - .into_iter() - .filter_map(|die| die.die.name().map(ToOwned::to_owned)) - .collect()) - } - _ => unreachable!("unexpected expression variant"), - } - } - - /// Same as [`SelectExpressionEvaluator::evaluate`] but for function arguments. - pub fn evaluate_on_arguments(&self) -> Result, Error> { - self.evaluate_on_arguments_inner(&self.expression) - } - - fn evaluate_on_arguments_inner(&self, expression: &DQE) -> Result, Error> { - match expression { - DQE::Variable(selector) => { - let expl_ctx_loc = self.debugger.exploration_ctx().location(); - let debugee = &self.debugger.debugee; - let current_function = debugee - .debug_info(expl_ctx_loc.pc)? - .find_function_by_pc(expl_ctx_loc.global_pc)? - .ok_or(FunctionNotFound(expl_ctx_loc.global_pc))?; - let params = current_function.parameters(); - - let params = match selector { - Selector::Name { var_name, .. } => params - .into_iter() - .filter(|param| param.die.base_attributes.name.as_ref() == Some(var_name)) - .collect::>(), - Selector::Any => params, - }; - - let mut type_cache = self.debugger.type_cache.borrow_mut(); - - Ok(params - .iter() - .filter_map(|var| { - let r#type = weak_error!(type_from_cache!(var, type_cache))?; - let var_ir = - self.evaluate_single_variable(&self.expression, var, r#type)?; - Some(ScopedVariable { - variable: var_ir, - scope: var.max_range().map(|r| { - let scope: Box<[Range]> = Box::new([r]); - scope - }), - }) - }) - .collect()) - } - DQE::PtrCast(_, target_type_name) => { - let vars = self.evaluate_from_ptr_cast(target_type_name)?; - Ok(vars - .into_iter() - .map(|v| ScopedVariable { - variable: v, - scope: None, - }) - .collect()) - } - DQE::Field(expr, _) - | DQE::Index(expr, _) - | DQE::Slice(expr, _, _) - | DQE::Deref(expr) - | DQE::Address(expr) - | DQE::Canonic(expr) => self.evaluate_on_arguments_inner(expr), - } - } - - fn evaluate_single_variable( - &self, - expression: &DQE, - variable_die: &ContextualDieRef, - r#type: &ComplexType, - ) -> Option { - let parser = variable::VariableParser::new(r#type); - - let evaluator = ctx_resolve_unit_call!(variable_die, evaluator, &self.debugger.debugee); - let evaluation_context = &dwarf::r#type::EvaluationContext { - evaluator: &evaluator, - expl_ctx: self.debugger.exploration_ctx(), - }; - - match expression { - DQE::Variable(_) => { - let data = variable_die.read_value( - self.debugger.exploration_ctx(), - &self.debugger.debugee, - r#type, - ); - parser.parse( - evaluation_context, - VariableIdentity::from_variable_die(variable_die), - data, - ) - } - DQE::PtrCast(addr, ..) => { - let data = ObjectBinaryRepr { - raw_data: Bytes::copy_from_slice(&(*addr).to_le_bytes()), - address: None, - size: std::mem::size_of::(), - }; - parser.parse( - evaluation_context, - VariableIdentity::new(NamespaceHierarchy::default(), None), - Some(data), - ) - } - DQE::Field(expr, field) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - var.field(field) - } - DQE::Index(expr, idx) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - var.index(idx) - } - DQE::Slice(expr, left, right) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - var.slice(evaluation_context, &parser, *left, *right) - } - DQE::Deref(expr) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - var.deref(evaluation_context, &parser) - } - DQE::Address(expr) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - var.address(evaluation_context, &parser) - } - DQE::Canonic(expr) => { - let var = self.evaluate_single_variable(expr, variable_die, r#type)?; - Some(var.canonic()) - } - } - } -} diff --git a/src/debugger/variable/value/bfs.rs b/src/debugger/variable/value/bfs.rs new file mode 100644 index 0000000..6930466 --- /dev/null +++ b/src/debugger/variable/value/bfs.rs @@ -0,0 +1,242 @@ +use crate::debugger::variable::value::Value; +use std::collections::VecDeque; + +#[derive(PartialEq, Debug)] +pub(super) enum FieldOrIndex<'a> { + Field(Option<&'a str>), + Index(i64), + Root, +} + +/// Iterator for visits underline values in BFS order. +pub(super) struct BfsIterator<'a> { + pub(super) queue: VecDeque<(FieldOrIndex<'a>, &'a Value)>, +} + +impl<'a> Iterator for BfsIterator<'a> { + type Item = (FieldOrIndex<'a>, &'a Value); + + fn next(&mut self) -> Option { + let (field_or_idx, next_value) = self.queue.pop_front()?; + + match next_value { + Value::Struct(r#struct) => { + r#struct.members.iter().for_each(|member| { + let item = ( + FieldOrIndex::Field(member.field_name.as_deref()), + &member.value, + ); + + self.queue.push_back(item) + }); + } + Value::Array(array) => { + if let Some(items) = array.items.as_ref() { + items.iter().for_each(|item| { + let item = (FieldOrIndex::Index(item.index), &item.value); + self.queue.push_back(item) + }) + } + } + Value::RustEnum(r#enum) => { + if let Some(enumerator) = r#enum.value.as_ref() { + let item = ( + FieldOrIndex::Field(enumerator.field_name.as_deref()), + &enumerator.value, + ); + self.queue.push_back(item) + } + } + Value::Pointer(_) => {} + Value::Specialized { + original: origin, .. + } => origin.members.iter().for_each(|member| { + let item = ( + FieldOrIndex::Field(member.field_name.as_deref()), + &member.value, + ); + self.queue.push_back(item) + }), + _ => {} + } + + Some((field_or_idx, next_value)) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::debugger::debugee::dwarf::r#type::TypeIdentity; + use crate::debugger::variable::value::{ + ArrayItem, ArrayValue, Member, PointerValue, RustEnumValue, ScalarValue, StructValue, + }; + + #[test] + fn test_bfs_iterator() { + struct TestCase { + variable: Value, + expected_order: Vec>, + } + + let test_cases = vec![ + TestCase { + variable: Value::Struct(StructValue { + type_ident: TypeIdentity::unknown(), + type_id: None, + members: vec![ + Member { + field_name: Some("array_1".to_owned()), + value: Value::Array(ArrayValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + items: Some(vec![ + ArrayItem { + index: 1, + value: Value::Scalar(ScalarValue { + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + type_id: None, + }), + }, + ArrayItem { + index: 2, + value: Value::Scalar(ScalarValue { + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + type_id: None, + }), + }, + ]), + raw_address: None, + }), + }, + Member { + field_name: Some("array_2".to_owned()), + value: Value::Array(ArrayValue { + type_ident: TypeIdentity::unknown(), + type_id: None, + items: Some(vec![ + ArrayItem { + index: 3, + value: Value::Scalar(ScalarValue { + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + type_id: None, + }), + }, + ArrayItem { + index: 4, + value: Value::Scalar(ScalarValue { + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + type_id: None, + }), + }, + ]), + raw_address: None, + }), + }, + ], + type_params: Default::default(), + raw_address: None, + }), + expected_order: vec![ + FieldOrIndex::Root, + FieldOrIndex::Field(Some("array_1")), + FieldOrIndex::Field(Some("array_2")), + FieldOrIndex::Index(1), + FieldOrIndex::Index(2), + FieldOrIndex::Index(3), + FieldOrIndex::Index(4), + ], + }, + TestCase { + variable: Value::Struct(StructValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + members: vec![ + Member { + field_name: Some("struct_2".to_owned()), + value: Value::Struct(StructValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + members: vec![ + Member { + field_name: Some("scalar_1".to_owned()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + }), + }, + Member { + field_name: Some("enum_1".to_owned()), + value: Value::RustEnum(RustEnumValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + value: Some(Box::new(Member { + field_name: Some("scalar_2".to_owned()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + }), + })), + raw_address: None, + }), + }, + Member { + field_name: Some("scalar_3".to_owned()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + value: None, + raw_address: None, + }), + }, + ], + type_params: Default::default(), + raw_address: None, + }), + }, + Member { + field_name: Some("pointer_1".to_owned()), + value: Value::Pointer(PointerValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + value: None, + target_type: None, + target_type_size: None, + raw_address: None, + }), + }, + ], + type_params: Default::default(), + raw_address: None, + }), + expected_order: vec![ + FieldOrIndex::Root, + FieldOrIndex::Field(Some("struct_2")), + FieldOrIndex::Field(Some("pointer_1")), + FieldOrIndex::Field(Some("scalar_1")), + FieldOrIndex::Field(Some("enum_1")), + FieldOrIndex::Field(Some("scalar_3")), + FieldOrIndex::Field(Some("scalar_2")), + ], + }, + ]; + + for tc in test_cases { + let iter = tc.variable.bfs_iterator(); + let names: Vec<_> = iter.map(|(field_or_idx, _)| field_or_idx).collect(); + assert_eq!(tc.expected_order, names); + } + } +} diff --git a/src/debugger/variable/value/mod.rs b/src/debugger/variable/value/mod.rs new file mode 100644 index 0000000..055ebe2 --- /dev/null +++ b/src/debugger/variable/value/mod.rs @@ -0,0 +1,1694 @@ +use crate::debugger::debugee::dwarf::r#type::{CModifier, TypeId, TypeIdentity}; +use crate::debugger::variable::dqe::{Literal, LiteralOrWildcard}; +use crate::debugger::variable::render::RenderValue; +use crate::debugger::variable::value::bfs::BfsIterator; +use crate::debugger::variable::value::bfs::FieldOrIndex; +use crate::debugger::variable::value::parser::{ParseContext, ValueParser}; +use crate::debugger::variable::value::specialization::{ + HashSetVariable, StrVariable, StringVariable, +}; +use crate::debugger::variable::ObjectBinaryRepr; +use crate::debugger::TypeDeclaration; +use crate::{debugger, weak_error}; +use bytes::Bytes; +use std::collections::{HashMap, VecDeque}; +use std::fmt::{Debug, Display, Formatter}; +use std::string::FromUtf8Error; +use uuid::Uuid; + +mod bfs; +pub(super) mod parser; +mod specialization; + +pub use crate::debugger::variable::value::specialization::SpecializedValue; + +#[derive(Debug, thiserror::Error, PartialEq)] +pub enum AssumeError { + #[error("field `{0}` not found")] + FieldNotFound(&'static str), + #[error("field `{0}` not a number")] + FieldNotANumber(&'static str), + #[error("incomplete interpretation of `{0}`")] + IncompleteInterp(&'static str), + #[error("not data for {0}")] + NoData(&'static str), + #[error("not type for {0}")] + NoType(&'static str), + #[error("underline data not a string")] + DataNotAString(#[from] FromUtf8Error), + #[error("undefined size of type `{}`", .0.name_fmt())] + UnknownSize(TypeIdentity), + #[error("type parameter `{0}` not found")] + TypeParameterNotFound(&'static str), + #[error("unknown type for type parameter `{0}`")] + TypeParameterTypeNotFound(&'static str), + #[error("unexpected type for {0}")] + UnexpectedType(&'static str), + #[error("unexpected binary representation of {0}, expect {1} got {2} bytes")] + UnexpectedBinaryRepr(&'static str, usize, usize), +} + +#[derive(Debug, thiserror::Error, PartialEq)] +pub enum ParsingError { + #[error(transparent)] + Assume(#[from] AssumeError), + #[error("unsupported language version")] + UnsupportedVersion, + #[error("error while reading from debugee memory: {0}")] + ReadDebugeeMemory(#[from] nix::Error), +} + +#[derive(Clone, Debug, PartialEq)] +pub enum SupportedScalar { + I8(i8), + I16(i16), + I32(i32), + I64(i64), + I128(i128), + Isize(isize), + U8(u8), + U16(u16), + U32(u32), + U64(u64), + U128(u128), + Usize(usize), + F32(f32), + F64(f64), + Bool(bool), + Char(char), + Empty(), +} + +impl Display for SupportedScalar { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + SupportedScalar::I8(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::I16(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::I32(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::I64(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::I128(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::Isize(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::U8(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::U16(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::U32(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::U64(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::U128(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::Usize(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::F32(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::F64(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::Bool(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::Char(scalar) => f.write_str(&format!("{scalar}")), + SupportedScalar::Empty() => f.write_str("()"), + } + } +} + +impl SupportedScalar { + fn equal_with_literal(&self, lhs: &Literal) -> bool { + match self { + SupportedScalar::I8(i) => lhs.equal_with_int(*i as i64), + SupportedScalar::I16(i) => lhs.equal_with_int(*i as i64), + SupportedScalar::I32(i) => lhs.equal_with_int(*i as i64), + SupportedScalar::I64(i) => lhs.equal_with_int(*i), + SupportedScalar::I128(i) => lhs.equal_with_int(*i as i64), + SupportedScalar::Isize(i) => lhs.equal_with_int(*i as i64), + SupportedScalar::U8(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::U16(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::U32(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::U64(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::U128(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::Usize(u) => lhs.equal_with_int(*u as i64), + SupportedScalar::F32(f) => lhs.equal_with_float(*f as f64), + SupportedScalar::F64(f) => lhs.equal_with_float(*f), + SupportedScalar::Bool(b) => lhs.equal_with_bool(*b), + SupportedScalar::Char(c) => lhs.equal_with_string(&c.to_string()), + SupportedScalar::Empty() => false, + } + } +} + +/// Represents scalars: integer's, float's, bool, char and () types. +#[derive(Clone, PartialEq)] +pub struct ScalarValue { + pub value: Option, + pub raw_address: Option, + pub type_ident: TypeIdentity, + pub type_id: Option, +} + +impl ScalarValue { + fn try_as_number(&self) -> Option { + match self.value { + Some(SupportedScalar::I8(num)) => Some(num as i64), + Some(SupportedScalar::I16(num)) => Some(num as i64), + Some(SupportedScalar::I32(num)) => Some(num as i64), + Some(SupportedScalar::I64(num)) => Some(num), + Some(SupportedScalar::Isize(num)) => Some(num as i64), + Some(SupportedScalar::U8(num)) => Some(num as i64), + Some(SupportedScalar::U16(num)) => Some(num as i64), + Some(SupportedScalar::U32(num)) => Some(num as i64), + Some(SupportedScalar::U64(num)) => Some(num as i64), + Some(SupportedScalar::Usize(num)) => Some(num as i64), + _ => None, + } + } +} + +/// Structure member representation. +#[derive(Clone, PartialEq, Debug)] +pub struct Member { + pub field_name: Option, + pub value: Value, +} + +/// Represents structures. +#[derive(Clone, Default, PartialEq)] +pub struct StructValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + /// Structure members. + pub members: Vec, + /// Map of type parameters of a structure type. + pub type_params: HashMap>, + pub raw_address: Option, +} + +impl StructValue { + pub fn field(self, field_name: &str) -> Option { + self.members.into_iter().find_map(|member| { + if member.field_name.as_deref() == Some(field_name) { + Some(member.value) + } else { + None + } + }) + } +} + +/// Array item representation. +#[derive(Clone, PartialEq, Debug)] +pub struct ArrayItem { + pub index: i64, + pub value: Value, +} + +/// Represents arrays. +#[derive(Clone, PartialEq)] +pub struct ArrayValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + /// Array items. + pub items: Option>, + pub raw_address: Option, +} + +impl ArrayValue { + fn slice(&mut self, left: Option, right: Option) { + if let Some(items) = self.items.as_mut() { + if let Some(left) = left { + items.drain(..left); + } + + if let Some(right) = right { + let remove_range = right - left.unwrap_or_default()..; + if remove_range.start < items.len() { + items.drain(remove_range); + }; + } + } + } +} + +/// Simple c-style enums (each option in which does not contain the underlying values). +#[derive(Clone, PartialEq)] +pub struct CEnumValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + /// String representation of selected variant. + pub value: Option, + pub raw_address: Option, +} + +/// Represents all enum's that more complex than c-style enums. +#[derive(Clone, PartialEq)] +pub struct RustEnumValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + /// Variable IR representation of selected variant. + pub value: Option>, + pub raw_address: Option, +} + +/// Raw pointers, references, Box. +#[derive(Clone, PartialEq)] +pub struct PointerValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + /// Raw pointer to underline value. + pub value: Option<*const ()>, + /// Underline type identity. + pub target_type: Option, + pub target_type_size: Option, + pub raw_address: Option, +} + +impl PointerValue { + /// Dereference pointer and return variable IR that represents underline value. + pub fn deref(&self, ctx: &ParseContext) -> Option { + let target_type = self.target_type?; + let deref_size = self.target_type_size.or_else(|| { + ctx.type_graph + .type_size_in_bytes(ctx.evaluation_context, target_type) + }); + + let target_type_decl = ctx.type_graph.types.get(&target_type); + if matches!(target_type_decl, Some(TypeDeclaration::Subroutine { .. })) { + // this variable is a fn pointer - don't deref it + return None; + } + + self.value.and_then(|ptr| { + let data = deref_size.and_then(|sz| { + let raw_data = debugger::read_memory_by_pid( + ctx.evaluation_context.expl_ctx.pid_on_focus(), + ptr as usize, + sz as usize, + ) + .ok()?; + + Some(ObjectBinaryRepr { + raw_data: Bytes::from(raw_data), + address: Some(ptr as usize), + size: sz as usize, + }) + }); + let parser = ValueParser::new(); + parser.parse_inner(ctx, data, target_type) + }) + } + + /// Interpret a pointer as a pointer on first array element. + /// Returns variable IR that represents an array. + pub fn slice(&self, ctx: &ParseContext, left: Option, right: usize) -> Option { + let target_type = self.target_type?; + let deref_size = + ctx.type_graph + .type_size_in_bytes(ctx.evaluation_context, target_type)? as usize; + + self.value.and_then(|ptr| { + let left = left.unwrap_or_default(); + let base_addr = ptr as usize + deref_size * left; + let raw_data = weak_error!(debugger::read_memory_by_pid( + ctx.evaluation_context.expl_ctx.pid_on_focus(), + base_addr, + deref_size * (right - left) + ))?; + let raw_data = bytes::Bytes::from(raw_data); + + let parser = ValueParser::new(); + let items = raw_data + .chunks(deref_size) + .enumerate() + .filter_map(|(i, chunk)| { + let data = ObjectBinaryRepr { + raw_data: raw_data.slice_ref(chunk), + address: Some(base_addr + (i * deref_size)), + size: deref_size, + }; + Some(ArrayItem { + index: i as i64, + value: parser.parse_inner(ctx, Some(data), target_type)?, + }) + }) + .collect::>(); + + Some(Value::Array(ArrayValue { + items: Some(items), + type_id: None, + type_ident: ctx.type_graph.identity(target_type).as_array_type(), + raw_address: Some(base_addr), + })) + }) + } +} + +/// Represents subroutine. +#[derive(Clone, PartialEq)] +pub struct SubroutineValue { + pub type_id: Option, + pub return_type_ident: Option, + pub address: Option, +} + +/// Represent a variable with C modifiers (volatile, const, typedef, etc.) +#[derive(Clone, PartialEq)] +pub struct CModifiedValue { + pub type_ident: TypeIdentity, + pub type_id: Option, + pub modifier: CModifier, + pub value: Option>, + pub address: Option, +} + +/// Program typed value representation. +#[derive(Clone, PartialEq)] +pub enum Value { + Scalar(ScalarValue), + Struct(StructValue), + Array(ArrayValue), + CEnum(CEnumValue), + RustEnum(RustEnumValue), + Pointer(PointerValue), + Subroutine(SubroutineValue), + Specialized { + value: Option, + original: StructValue, + }, + CModifiedVariable(CModifiedValue), +} + +// SAFETY: this enum may contain a raw pointers on memory in a debugee process, +// it is safe to dereference it using public API of *Variable structures. +unsafe impl Send for Value {} + +impl Debug for Value { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.as_literal() { + None => Ok(()), + Some(lit) => f.write_fmt(format_args!("{lit}")), + } + } +} + +impl Value { + /// Return literal equals representation of a value. + pub fn as_literal(&self) -> Option { + match self { + Value::Scalar(scalar) => { + if let Some(i) = scalar.try_as_number() { + return Some(Literal::Int(i)); + } + + match scalar.value.as_ref()? { + SupportedScalar::F32(f) => Some(Literal::Float(*f as f64)), + SupportedScalar::F64(f) => Some(Literal::Float(*f)), + SupportedScalar::Bool(b) => Some(Literal::Bool(*b)), + SupportedScalar::Char(c) => Some(Literal::String(c.to_string())), + SupportedScalar::Empty() => None, + _ => None, + } + } + Value::Struct(s) => { + let mut assoc_array = HashMap::new(); + for member in &s.members { + let field = member.field_name.as_ref()?.clone(); + let literal = member.value.as_literal()?; + assoc_array.insert(field, LiteralOrWildcard::Literal(literal)); + } + Some(Literal::AssocArray(assoc_array)) + } + Value::Array(arr) => { + let mut array = vec![]; + for item in arr.items.as_ref()? { + array.push(LiteralOrWildcard::Literal(item.value.as_literal()?)) + } + Some(Literal::Array(array.into_boxed_slice())) + } + Value::CEnum(e) => Some(Literal::EnumVariant(e.value.as_ref()?.to_string(), None)), + Value::RustEnum(e) => { + let member = e.value.as_ref()?; + Some(Literal::EnumVariant( + member.field_name.as_ref()?.clone(), + member.value.as_literal().map(Box::new), + )) + } + Value::Pointer(ptr) => Some(Literal::Address(ptr.value? as usize)), + Value::Subroutine(_) => None, + Value::Specialized { value, .. } => match value.as_ref()? { + SpecializedValue::Vector(vec) | SpecializedValue::VecDeque(vec) => { + let member = vec.structure.members.first(); + let array = member?; + array.value.as_literal() + } + SpecializedValue::HashMap(map) | SpecializedValue::BTreeMap(map) => { + let mut assoc_array = HashMap::new(); + for (key, val) in &map.kv_items { + let Some(Literal::String(key_str)) = key.as_literal() else { + return None; + }; + let value = val.as_literal()?; + assoc_array.insert(key_str, LiteralOrWildcard::Literal(value)); + } + Some(Literal::AssocArray(assoc_array)) + } + SpecializedValue::HashSet(set) | SpecializedValue::BTreeSet(set) => { + let mut array = vec![]; + for item in &set.items { + array.push(LiteralOrWildcard::Literal(item.as_literal()?)) + } + Some(Literal::Array(array.into_boxed_slice())) + } + SpecializedValue::String(str) => Some(Literal::String(str.value.clone())), + SpecializedValue::Str(str) => Some(Literal::String(str.value.clone())), + SpecializedValue::Tls(tls) => tls.inner_value.as_ref()?.as_literal(), + SpecializedValue::Cell(c) => c.as_literal(), + SpecializedValue::RefCell(c) => c.as_literal(), + SpecializedValue::Rc(ptr) => Some(Literal::Address(ptr.raw_address?)), + SpecializedValue::Arc(ptr) => Some(Literal::Address(ptr.raw_address?)), + SpecializedValue::Uuid(uuid) => { + let uuid = Uuid::from_bytes(*uuid); + Some(Literal::String(uuid.to_string())) + } + SpecializedValue::SystemTime(time) => { + let time = chrono::NaiveDateTime::from_timestamp_opt(time.0, time.1)?; + Some(Literal::String( + time.format("%Y-%m-%d %H:%M:%S").to_string(), + )) + } + SpecializedValue::Instant(_) => None, + }, + Value::CModifiedVariable(val) => Some(val.value.as_ref()?.as_literal()?), + } + } + + /// Return address in debugee memory for variable data. + pub fn in_memory_location(&self) -> Option { + match self { + Value::Scalar(s) => s.raw_address, + Value::Struct(s) => s.raw_address, + Value::Array(a) => a.raw_address, + Value::CEnum(ce) => ce.raw_address, + Value::RustEnum(re) => re.raw_address, + Value::Pointer(p) => p.raw_address, + Value::Subroutine(s) => s.address, + Value::Specialized { + original: origin, .. + } => origin.raw_address, + Value::CModifiedVariable(cmv) => cmv.address, + } + } + + pub fn type_id(&self) -> Option { + match self { + Value::Scalar(s) => s.type_id, + Value::Struct(s) => s.type_id, + Value::Array(a) => a.type_id, + Value::CEnum(ce) => ce.type_id, + Value::RustEnum(re) => re.type_id, + Value::Pointer(p) => p.type_id, + Value::Subroutine(s) => s.type_id, + Value::Specialized { + original: origin, .. + } => origin.type_id, + Value::CModifiedVariable(cmv) => cmv.type_id, + } + } + + /// Visit variable children in BFS order. + fn bfs_iterator(&self) -> BfsIterator { + BfsIterator { + queue: VecDeque::from([(FieldOrIndex::Root, self)]), + } + } + + /// Returns i64 value representation or error if cast fail. + fn assume_field_as_scalar_number(&self, field_name: &'static str) -> Result { + let val = self + .bfs_iterator() + .find_map(|(field_or_idx, child)| { + (field_or_idx == FieldOrIndex::Field(Some(field_name))).then_some(child) + }) + .ok_or(AssumeError::FieldNotFound(field_name))?; + if let Value::Scalar(s) = val { + Ok(s.try_as_number() + .ok_or(AssumeError::FieldNotANumber(field_name))?) + } else { + Err(AssumeError::FieldNotANumber(field_name)) + } + } + + /// Returns value as a raw pointer or error if cast fails. + fn assume_field_as_pointer(&self, field_name: &'static str) -> Result<*const (), AssumeError> { + self.bfs_iterator() + .find_map(|(field_or_idx, child)| { + if let Value::Pointer(pointer) = child { + if field_or_idx == FieldOrIndex::Field(Some(field_name)) { + return pointer.value; + } + } + None + }) + .ok_or(AssumeError::IncompleteInterp("pointer")) + } + + /// Returns value as enum or error if cast fail. + fn assume_field_as_rust_enum( + &self, + field_name: &'static str, + ) -> Result { + self.bfs_iterator() + .find_map(|(field_or_idx, child)| { + if let Value::RustEnum(r_enum) = child { + if field_or_idx == FieldOrIndex::Field(Some(field_name)) { + return Some(r_enum.clone()); + } + } + None + }) + .ok_or(AssumeError::IncompleteInterp("pointer")) + } + + /// Returns value as structure or error if cast fail. + fn assume_field_as_struct(&self, field_name: &'static str) -> Result { + self.bfs_iterator() + .find_map(|(field_or_idx, child)| { + if let Value::Struct(structure) = child { + if field_or_idx == FieldOrIndex::Field(Some(field_name)) { + return Some(structure.clone()); + } + } + None + }) + .ok_or(AssumeError::IncompleteInterp("structure")) + } + + /// Return an underlying structure for specialized values (vectors, strings, etc.). + pub fn canonic(self) -> Self { + match self { + Value::Specialized { + original: origin, .. + } => Value::Struct(origin), + _ => self, + } + } + + /// Try to dereference variable and returns underline variable IR. + /// Return `None` if dereference not allowed. + pub fn deref(self, ctx: &ParseContext) -> Option { + match self { + Value::Pointer(ptr) => ptr.deref(ctx), + Value::RustEnum(r_enum) => r_enum.value.and_then(|v| v.value.deref(ctx)), + Value::Specialized { + value: Some(SpecializedValue::Rc(ptr)), + .. + } + | Value::Specialized { + value: Some(SpecializedValue::Arc(ptr)), + .. + } => ptr.deref(ctx), + Value::Specialized { + value: Some(SpecializedValue::Tls(tls_var)), + .. + } => tls_var.inner_value.and_then(|inner| inner.deref(ctx)), + Value::Specialized { + value: Some(SpecializedValue::Cell(cell)), + .. + } + | Value::Specialized { + value: Some(SpecializedValue::RefCell(cell)), + .. + } => cell.deref(ctx), + _ => None, + } + } + + /// Return address (as pointer variable) of raw data in debugee memory. + pub fn address(self, ctx: &ParseContext) -> Option { + let addr = self.in_memory_location()?; + Some(Value::Pointer(PointerValue { + type_ident: self.r#type().as_address_type(), + value: Some(addr as *const ()), + target_type: self.type_id(), + target_type_size: self + .type_id() + .and_then(|t| ctx.type_graph.type_size_in_bytes(ctx.evaluation_context, t)), + raw_address: None, + type_id: None, + })) + } + + /// Return variable field, `None` if field is not allowed for a variable type. + /// Supported: structures, rust-style enums, hashmaps, btree-maps. + pub fn field(self, field_name: &str) -> Option { + match self { + Value::Struct(structure) => structure.field(field_name), + Value::RustEnum(r_enum) => r_enum.value.and_then(|v| v.value.field(field_name)), + Value::Specialized { + value: specialized, .. + } => match specialized { + Some(SpecializedValue::HashMap(map)) | Some(SpecializedValue::BTreeMap(map)) => { + map.kv_items.into_iter().find_map(|(key, value)| match key { + Value::Specialized { + value: specialized, .. + } => match specialized { + Some(SpecializedValue::String(string_key)) => { + (string_key.value == field_name).then_some(value) + } + Some(SpecializedValue::Str(string_key)) => { + (string_key.value == field_name).then_some(value) + } + _ => None, + }, + _ => None, + }) + } + Some(SpecializedValue::Tls(tls_var)) => tls_var + .inner_value + .and_then(|inner| inner.field(field_name)), + Some(SpecializedValue::Cell(cell)) | Some(SpecializedValue::RefCell(cell)) => { + cell.field(field_name) + } + _ => None, + }, + _ => None, + } + } + + /// Return variable element by its index, `None` if indexing is not allowed for a variable type. + /// Supported: array, rust-style enums, vector, hashmap, hashset, btreemap, btreeset. + pub fn index(self, idx: &Literal) -> Option { + match self { + Value::Array(array) => array.items.and_then(|mut items| { + if let Literal::Int(idx) = idx { + let idx = *idx as usize; + if idx < items.len() { + return Some(items.swap_remove(idx).value); + } + } + None + }), + Value::RustEnum(r_enum) => r_enum.value.and_then(|v| v.value.index(idx)), + Value::Specialized { + value: Some(spec_val), + .. + } => match spec_val { + SpecializedValue::Vector(mut vec) | SpecializedValue::VecDeque(mut vec) => { + let inner_array = vec.structure.members.swap_remove(0).value; + inner_array.index(idx) + } + SpecializedValue::Tls(tls_var) => { + tls_var.inner_value.and_then(|inner| inner.index(idx)) + } + SpecializedValue::Cell(cell) | SpecializedValue::RefCell(cell) => cell.index(idx), + SpecializedValue::BTreeMap(map) | SpecializedValue::HashMap(map) => { + for (k, v) in map.kv_items { + if k.match_literal(idx) { + return Some(v); + } + } + + None + } + SpecializedValue::BTreeSet(set) | SpecializedValue::HashSet(set) => { + let found = set.items.into_iter().any(|it| it.match_literal(idx)); + + Some(Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("bool"), + value: Some(SupportedScalar::Bool(found)), + raw_address: None, + })) + } + _ => None, + }, + _ => None, + } + } + + pub fn slice( + self, + ctx: &ParseContext, + left: Option, + right: Option, + ) -> Option { + match self { + Value::Array(mut array) => { + array.slice(left, right); + Some(Value::Array(array)) + } + Value::Pointer(ptr) => { + // for pointer the right bound must always be specified + let right = right?; + ptr.slice(ctx, left, right) + } + Value::Specialized { + value: Some(spec_val), + original, + } => match spec_val { + SpecializedValue::Rc(ptr) | SpecializedValue::Arc(ptr) => { + // for pointer the right bound must always be specified + let right = right?; + ptr.slice(ctx, left, right) + } + SpecializedValue::Vector(mut vec) => { + vec.slice(left, right); + Some(Value::Specialized { + value: Some(SpecializedValue::Vector(vec)), + original, + }) + } + SpecializedValue::VecDeque(mut vec) => { + vec.slice(left, right); + Some(Value::Specialized { + value: Some(SpecializedValue::VecDeque(vec)), + original, + }) + } + SpecializedValue::Tls(mut tls_var) => { + let inner = tls_var.inner_value.take()?; + inner.slice(ctx, left, right) + } + SpecializedValue::Cell(cell) | SpecializedValue::RefCell(cell) => { + cell.slice(ctx, left, right) + } + _ => None, + }, + _ => None, + } + } + + /// Match variable with a literal object. + /// Return true if variable matched to literal. + fn match_literal(self, literal: &Literal) -> bool { + match self { + Value::Scalar(ScalarValue { + value: Some(scalar), + .. + }) => scalar.equal_with_literal(literal), + Value::Pointer(PointerValue { + value: Some(ptr), .. + }) => literal.equal_with_address(ptr as usize), + Value::Array(ArrayValue { + items: Some(items), .. + }) => { + let Literal::Array(arr_literal) = literal else { + return false; + }; + if arr_literal.len() != items.len() { + return false; + } + + for (i, item) in items.into_iter().enumerate() { + match &arr_literal[i] { + LiteralOrWildcard::Literal(lit) => { + if !item.value.match_literal(lit) { + return false; + } + } + LiteralOrWildcard::Wildcard => continue, + } + } + true + } + Value::Struct(StructValue { members, .. }) => { + match literal { + Literal::Array(array_literal) => { + // structure must be a tuple + if array_literal.len() != members.len() { + return false; + } + + for (i, member) in members.into_iter().enumerate() { + let field_literal = &array_literal[i]; + match field_literal { + LiteralOrWildcard::Literal(lit) => { + if !member.value.match_literal(lit) { + return false; + } + } + LiteralOrWildcard::Wildcard => continue, + } + } + + true + } + Literal::AssocArray(struct_literal) => { + // default structure + if struct_literal.len() != members.len() { + return false; + } + + for member in members { + let Some(member_name) = member.field_name else { + return false; + }; + + let Some(field_literal) = struct_literal.get(&member_name) else { + return false; + }; + + match field_literal { + LiteralOrWildcard::Literal(lit) => { + if !member.value.match_literal(lit) { + return false; + } + } + LiteralOrWildcard::Wildcard => continue, + } + } + true + } + _ => false, + } + } + Value::Specialized { + value: Some(spec), .. + } => match spec { + SpecializedValue::String(StringVariable { value, .. }) => { + literal.equal_with_string(&value) + } + SpecializedValue::Str(StrVariable { value, .. }) => { + literal.equal_with_string(&value) + } + SpecializedValue::Uuid(bytes) => { + let uuid = Uuid::from_bytes(bytes); + literal.equal_with_string(&uuid.to_string()) + } + SpecializedValue::Cell(cell) | SpecializedValue::RefCell(cell) => { + cell.match_literal(literal) + } + SpecializedValue::Rc(PointerValue { + value: Some(ptr), .. + }) + | SpecializedValue::Arc(PointerValue { + value: Some(ptr), .. + }) => literal.equal_with_address(ptr as usize), + SpecializedValue::Vector(mut v) | SpecializedValue::VecDeque(mut v) => { + let inner_array = v.structure.members.swap_remove(0).value; + debug_assert!(matches!(inner_array, Value::Array(_))); + inner_array.match_literal(literal) + } + SpecializedValue::HashSet(HashSetVariable { items, .. }) + | SpecializedValue::BTreeSet(HashSetVariable { items, .. }) => { + let Literal::Array(arr_literal) = literal else { + return false; + }; + if arr_literal.len() != items.len() { + return false; + } + let mut arr_literal = arr_literal.to_vec(); + + for item in items { + let mut item_found = false; + + // try to find equals item + let mb_literal_idx = arr_literal.iter().position(|lit| { + if let LiteralOrWildcard::Literal(lit) = lit { + item.clone().match_literal(lit) + } else { + false + } + }); + if let Some(literal_idx) = mb_literal_idx { + arr_literal.swap_remove(literal_idx); + item_found = true; + } + + // try to find wildcard + if !item_found { + let mb_wildcard_idx = arr_literal + .iter() + .position(|lit| matches!(lit, LiteralOrWildcard::Wildcard)); + if let Some(wildcard_idx) = mb_wildcard_idx { + arr_literal.swap_remove(wildcard_idx); + item_found = true; + } + } + + // still not found - set aren't equal + if !item_found { + return false; + } + } + true + } + SpecializedValue::Tls(inner) => inner + .inner_value + .map(|v| v.match_literal(literal)) + .unwrap_or_default(), + SpecializedValue::SystemTime(time) => { + let Some(time) = chrono::NaiveDateTime::from_timestamp_opt(time.0, time.1) + else { + return false; + }; + + literal.equal_with_string(&time.format("%Y-%m-%d %H:%M:%S").to_string()) + } + _ => false, + }, + Value::CEnum(CEnumValue { + value: Some(ref value), + .. + }) => { + let Literal::EnumVariant(variant, None) = literal else { + return false; + }; + value == variant + } + Value::RustEnum(RustEnumValue { + value: Some(value), .. + }) => { + let Literal::EnumVariant(variant, variant_value) = literal else { + return false; + }; + + if value.field_name.as_ref() != Some(variant) { + return false; + } + + match variant_value { + None => true, + Some(lit) => value.value.match_literal(lit), + } + } + _ => false, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::debugger::variable::value::specialization::VecValue; + + // test helpers -------------------------------------------------------------------------------- + // + fn make_scalar_val(type_name: &str, scalar: SupportedScalar) -> Value { + Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::no_namespace(type_name), + value: Some(scalar), + raw_address: None, + }) + } + + fn make_str_val(val: &str) -> Value { + Value::Specialized { + value: Some(SpecializedValue::Str(StrVariable { + value: val.to_string(), + })), + original: StructValue { + ..Default::default() + }, + } + } + + fn make_string_val(val: &str) -> Value { + Value::Specialized { + value: Some(SpecializedValue::String(StringVariable { + value: val.to_string(), + })), + original: StructValue { + ..Default::default() + }, + } + } + + fn make_vec_val(items: Vec) -> VecValue { + let items_len = items.len(); + VecValue { + structure: StructValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("vec"), + members: vec![ + Member { + field_name: None, + value: Value::Array(ArrayValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("[item]"), + items: Some(items), + raw_address: None, + }), + }, + Member { + field_name: Some("cap".to_string()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("usize"), + value: Some(SupportedScalar::Usize(items_len)), + raw_address: None, + }), + }, + ], + type_params: HashMap::default(), + raw_address: None, + }, + } + } + + fn make_vector_val(items: Vec) -> Value { + Value::Specialized { + value: Some(SpecializedValue::Vector(make_vec_val(items))), + original: StructValue { + ..Default::default() + }, + } + } + + fn make_vecdeque_val(items: Vec) -> Value { + Value::Specialized { + value: Some(SpecializedValue::VecDeque(make_vec_val(items))), + original: StructValue { + ..Default::default() + }, + } + } + + fn make_hashset_val(items: Vec) -> Value { + Value::Specialized { + value: Some(SpecializedValue::HashSet(HashSetVariable { + type_ident: TypeIdentity::no_namespace("hashset"), + items, + })), + original: StructValue { + ..Default::default() + }, + } + } + + fn make_btreeset_var_val(items: Vec) -> Value { + Value::Specialized { + value: Some(SpecializedValue::BTreeSet(HashSetVariable { + type_ident: TypeIdentity::no_namespace("btreeset"), + items, + })), + original: StructValue { + ..Default::default() + }, + } + } + //---------------------------------------------------------------------------------------------- + + #[test] + fn test_equal_with_literal() { + struct TestCase { + variable: Value, + eq_literal: Literal, + neq_literals: Vec, + } + + let test_cases = [ + TestCase { + variable: make_scalar_val("i8", SupportedScalar::I8(8)), + eq_literal: Literal::Int(8), + neq_literals: vec![Literal::Int(9)], + }, + TestCase { + variable: make_scalar_val("i32", SupportedScalar::I32(32)), + eq_literal: Literal::Int(32), + neq_literals: vec![Literal::Int(33)], + }, + TestCase { + variable: make_scalar_val("isize", SupportedScalar::Isize(-1234)), + eq_literal: Literal::Int(-1234), + neq_literals: vec![Literal::Int(-1233)], + }, + TestCase { + variable: make_scalar_val("u8", SupportedScalar::U8(8)), + eq_literal: Literal::Int(8), + neq_literals: vec![Literal::Int(9)], + }, + TestCase { + variable: make_scalar_val("u32", SupportedScalar::U32(32)), + eq_literal: Literal::Int(32), + neq_literals: vec![Literal::Int(33)], + }, + TestCase { + variable: make_scalar_val("usize", SupportedScalar::Usize(1234)), + eq_literal: Literal::Int(1234), + neq_literals: vec![Literal::Int(1235)], + }, + TestCase { + variable: make_scalar_val("f32", SupportedScalar::F32(1.1)), + eq_literal: Literal::Float(1.1), + neq_literals: vec![Literal::Float(1.2)], + }, + TestCase { + variable: make_scalar_val("f64", SupportedScalar::F64(-2.2)), + eq_literal: Literal::Float(-2.2), + neq_literals: vec![Literal::Float(2.2)], + }, + TestCase { + variable: make_scalar_val("bool", SupportedScalar::Bool(true)), + eq_literal: Literal::Bool(true), + neq_literals: vec![Literal::Bool(false)], + }, + TestCase { + variable: make_scalar_val("char", SupportedScalar::Char('b')), + eq_literal: Literal::String("b".into()), + neq_literals: vec![Literal::String("c".into())], + }, + TestCase { + variable: Value::Pointer(PointerValue { + target_type: None, + type_id: None, + type_ident: TypeIdentity::no_namespace("ptr"), + value: Some(123usize as *const ()), + raw_address: None, + target_type_size: None, + }), + eq_literal: Literal::Address(123), + neq_literals: vec![Literal::Address(124), Literal::Int(123)], + }, + TestCase { + variable: Value::Pointer(PointerValue { + target_type: None, + type_id: None, + type_ident: TypeIdentity::no_namespace("MyPtr"), + value: Some(123usize as *const ()), + raw_address: None, + target_type_size: None, + }), + eq_literal: Literal::Address(123), + neq_literals: vec![Literal::Address(124), Literal::Int(123)], + }, + TestCase { + variable: Value::CEnum(CEnumValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("MyEnum"), + value: Some("Variant1".into()), + raw_address: None, + }), + eq_literal: Literal::EnumVariant("Variant1".to_string(), None), + neq_literals: vec![ + Literal::EnumVariant("Variant2".to_string(), None), + Literal::String("Variant1".to_string()), + ], + }, + TestCase { + variable: Value::RustEnum(RustEnumValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("MyEnum"), + value: Some(Box::new(Member { + field_name: Some("Variant1".to_string()), + value: Value::Struct(StructValue { + type_id: None, + type_ident: TypeIdentity::unknown(), + members: vec![Member { + field_name: Some("Variant1".to_string()), + value: Value::Scalar(ScalarValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("int"), + value: Some(SupportedScalar::I64(100)), + raw_address: None, + }), + }], + type_params: Default::default(), + raw_address: None, + }), + })), + raw_address: None, + }), + eq_literal: Literal::EnumVariant( + "Variant1".to_string(), + Some(Box::new(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(100)), + ])))), + ), + neq_literals: vec![ + Literal::EnumVariant("Variant1".to_string(), Some(Box::new(Literal::Int(101)))), + Literal::EnumVariant("Variant2".to_string(), Some(Box::new(Literal::Int(100)))), + Literal::String("Variant1".to_string()), + ], + }, + ]; + + for tc in test_cases { + assert!(tc.variable.clone().match_literal(&tc.eq_literal)); + for neq_lit in tc.neq_literals { + assert!(!tc.variable.clone().match_literal(&neq_lit)); + } + } + } + + #[test] + fn test_equal_with_complex_literal() { + struct TestCase { + variable: Value, + eq_literals: Vec, + neq_literals: Vec, + } + + let test_cases = [ + TestCase { + variable: make_str_val("str1"), + eq_literals: vec![Literal::String("str1".to_string())], + neq_literals: vec![Literal::String("str2".to_string()), Literal::Int(1)], + }, + TestCase { + variable: make_string_val("string1"), + eq_literals: vec![Literal::String("string1".to_string())], + neq_literals: vec![Literal::String("string2".to_string()), Literal::Int(1)], + }, + TestCase { + variable: Value::Specialized { + value: Some(SpecializedValue::Uuid([ + 0xd0, 0x60, 0x66, 0x29, 0x78, 0x6a, 0x44, 0xbe, 0x9d, 0x49, 0xb7, 0x02, + 0x0f, 0x3e, 0xb0, 0x5a, + ])), + original: StructValue::default(), + }, + eq_literals: vec![Literal::String( + "d0606629-786a-44be-9d49-b7020f3eb05a".to_string(), + )], + neq_literals: vec![Literal::String( + "d0606629-786a-44be-9d49-b7020f3eb05b".to_string(), + )], + }, + TestCase { + variable: make_vector_val(vec![ + ArrayItem { + index: 0, + value: make_scalar_val("char", SupportedScalar::Char('a')), + }, + ArrayItem { + index: 1, + value: make_scalar_val("char", SupportedScalar::Char('b')), + }, + ArrayItem { + index: 2, + value: make_scalar_val("char", SupportedScalar::Char('c')), + }, + ArrayItem { + index: 3, + value: make_scalar_val("char", SupportedScalar::Char('c')), + }, + ]), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Wildcard, + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Wildcard, + ])), + ], + }, + TestCase { + variable: make_vecdeque_val(vec![ + ArrayItem { + index: 0, + value: make_scalar_val("char", SupportedScalar::Char('a')), + }, + ArrayItem { + index: 1, + value: make_scalar_val("char", SupportedScalar::Char('b')), + }, + ArrayItem { + index: 2, + value: make_scalar_val("char", SupportedScalar::Char('c')), + }, + ArrayItem { + index: 3, + value: make_scalar_val("char", SupportedScalar::Char('c')), + }, + ]), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Wildcard, + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + LiteralOrWildcard::Wildcard, + ])), + ], + }, + TestCase { + variable: make_hashset_val(vec![ + make_scalar_val("char", SupportedScalar::Char('a')), + make_scalar_val("char", SupportedScalar::Char('b')), + make_scalar_val("char", SupportedScalar::Char('c')), + ]), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Wildcard, + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + }, + TestCase { + variable: make_btreeset_var_val(vec![ + make_scalar_val("char", SupportedScalar::Char('a')), + make_scalar_val("char", SupportedScalar::Char('b')), + make_scalar_val("char", SupportedScalar::Char('c')), + ]), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("c".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Wildcard, + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("a".to_string())), + LiteralOrWildcard::Literal(Literal::String("b".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + }, + TestCase { + variable: Value::Specialized { + value: Some(SpecializedValue::Cell(Box::new(make_scalar_val( + "int", + SupportedScalar::I64(100), + )))), + original: StructValue::default(), + }, + eq_literals: vec![Literal::Int(100)], + neq_literals: vec![Literal::Int(101), Literal::Float(100.1)], + }, + TestCase { + variable: Value::Array(ArrayValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("array_str"), + items: Some(vec![ + ArrayItem { + index: 0, + value: make_str_val("ab"), + }, + ArrayItem { + index: 1, + value: make_str_val("cd"), + }, + ArrayItem { + index: 2, + value: make_str_val("ef"), + }, + ]), + raw_address: None, + }), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("ab".to_string())), + LiteralOrWildcard::Literal(Literal::String("cd".to_string())), + LiteralOrWildcard::Literal(Literal::String("ef".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("ab".to_string())), + LiteralOrWildcard::Literal(Literal::String("cd".to_string())), + LiteralOrWildcard::Wildcard, + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("ab".to_string())), + LiteralOrWildcard::Literal(Literal::String("cd".to_string())), + LiteralOrWildcard::Literal(Literal::String("gj".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("ab".to_string())), + LiteralOrWildcard::Literal(Literal::String("cd".to_string())), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("ab".to_string())), + LiteralOrWildcard::Literal(Literal::String("cd".to_string())), + LiteralOrWildcard::Literal(Literal::String("ef".to_string())), + LiteralOrWildcard::Literal(Literal::String("gj".to_string())), + ])), + ], + }, + TestCase { + variable: Value::Struct(StructValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("MyStruct"), + members: vec![ + Member { + field_name: Some("str_field".to_string()), + value: make_str_val("str1"), + }, + Member { + field_name: Some("vec_field".to_string()), + value: make_vector_val(vec![ + ArrayItem { + index: 0, + value: make_scalar_val("", SupportedScalar::I8(1)), + }, + ArrayItem { + index: 1, + value: make_scalar_val("", SupportedScalar::I8(2)), + }, + ]), + }, + Member { + field_name: Some("bool_field".to_string()), + value: make_scalar_val("", SupportedScalar::Bool(true)), + }, + ], + type_params: Default::default(), + raw_address: None, + }), + eq_literals: vec![ + Literal::AssocArray(HashMap::from([ + ( + "str_field".to_string(), + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + ), + ( + "vec_field".to_string(), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Literal(Literal::Int(2)), + ]))), + ), + ( + "bool_field".to_string(), + LiteralOrWildcard::Literal(Literal::Bool(true)), + ), + ])), + Literal::AssocArray(HashMap::from([ + ( + "str_field".to_string(), + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + ), + ( + "vec_field".to_string(), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Wildcard, + ]))), + ), + ("bool_field".to_string(), LiteralOrWildcard::Wildcard), + ])), + ], + neq_literals: vec![ + Literal::AssocArray(HashMap::from([ + ( + "str_field".to_string(), + LiteralOrWildcard::Literal(Literal::String("str2".to_string())), + ), + ( + "vec_field".to_string(), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Literal(Literal::Int(2)), + ]))), + ), + ( + "bool_field".to_string(), + LiteralOrWildcard::Literal(Literal::Bool(true)), + ), + ])), + Literal::AssocArray(HashMap::from([ + ( + "str_field".to_string(), + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + ), + ( + "vec_field".to_string(), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + ]))), + ), + ( + "bool_field".to_string(), + LiteralOrWildcard::Literal(Literal::Bool(true)), + ), + ])), + ], + }, + TestCase { + variable: Value::Struct(StructValue { + type_id: None, + type_ident: TypeIdentity::no_namespace("MyTuple"), + members: vec![ + Member { + field_name: None, + value: make_str_val("str1"), + }, + Member { + field_name: None, + value: make_vector_val(vec![ + ArrayItem { + index: 0, + value: make_scalar_val("", SupportedScalar::I8(1)), + }, + ArrayItem { + index: 1, + value: make_scalar_val("", SupportedScalar::I8(2)), + }, + ]), + }, + Member { + field_name: None, + value: make_scalar_val("", SupportedScalar::Bool(true)), + }, + ], + type_params: Default::default(), + raw_address: None, + }), + eq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Literal(Literal::Int(2)), + ]))), + LiteralOrWildcard::Literal(Literal::Bool(true)), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Wildcard, + ]))), + LiteralOrWildcard::Wildcard, + ])), + ], + neq_literals: vec![ + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + LiteralOrWildcard::Literal(Literal::Int(2)), + ]))), + LiteralOrWildcard::Literal(Literal::Bool(false)), + ])), + Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::String("str1".to_string())), + LiteralOrWildcard::Literal(Literal::Array(Box::new([ + LiteralOrWildcard::Literal(Literal::Int(1)), + ]))), + LiteralOrWildcard::Literal(Literal::Bool(true)), + ])), + ], + }, + ]; + + for tc in test_cases { + for eq_lit in tc.eq_literals { + assert!(tc.variable.clone().match_literal(&eq_lit)); + } + for neq_lit in tc.neq_literals { + assert!(!tc.variable.clone().match_literal(&neq_lit)); + } + } + } +} diff --git a/src/debugger/variable/value/parser.rs b/src/debugger/variable/value/parser.rs new file mode 100644 index 0000000..7f86602 --- /dev/null +++ b/src/debugger/variable/value/parser.rs @@ -0,0 +1,685 @@ +use crate::debugger::debugee::dwarf::eval::EvaluationContext; +use crate::debugger::debugee::dwarf::r#type::{ + ArrayType, ComplexType, ScalarType, StructureMember, TypeId, +}; +use crate::debugger::variable::value::specialization::VariableParserExtension; +use crate::debugger::variable::value::{ + ArrayItem, ArrayValue, CEnumValue, CModifiedValue, Member, PointerValue, RustEnumValue, + ScalarValue, SpecializedValue, StructValue, SubroutineValue, SupportedScalar, Value, +}; +use crate::debugger::variable::{Identity, ObjectBinaryRepr}; +use crate::debugger::TypeDeclaration; +use crate::version::Version; +use crate::version_switch; +use bytes::Bytes; +use gimli::{ + DW_ATE_address, DW_ATE_boolean, DW_ATE_float, DW_ATE_signed, DW_ATE_signed_char, + DW_ATE_unsigned, DW_ATE_unsigned_char, DW_ATE_ASCII, DW_ATE_UTF, +}; +use log::warn; +use std::collections::HashMap; +use std::fmt::Display; + +/// Additional information about value. +// +// FIXME: this modifier currently using only for TLS variables and should be deleted +// after minimal supported rust version will be greater than 1.80.0 +#[derive(Default)] +pub struct ValueModifiers { + tls: bool, + tls_const: bool, + const_tls_duplicate: bool, +} + +impl ValueModifiers { + pub fn from_identity(p_ctx: &ParseContext, ident: Identity) -> ValueModifiers { + let mut this = ValueModifiers::default(); + + let ver = p_ctx.evaluation_context.rustc_version().unwrap_or_default(); + if ver >= Version((1, 80, 0)) { + // not sure that value is tls, but some additional checks will be occurred on + // a value type at parsing stage + this.tls = ident.name.as_deref() == Some("VAL"); + + // This condition protects against duplication of the constant tls variables + if ident.namespace.contains(&["thread_local_const_init"]) + && !ident.namespace.contains(&["{closure#0}"]) + { + this.const_tls_duplicate = true; + } + } else { + let var_name_is_tls = ident.namespace.contains(&["__getit"]) + && (ident.name.as_deref() == Some("VAL") || ident.name.as_deref() == Some("__KEY")); + if var_name_is_tls { + this.tls = true; + if ident.name.as_deref() == Some("VAL") { + this.tls_const = true + } + } + } + + this + } +} + +pub struct ParseContext<'a> { + pub evaluation_context: &'a EvaluationContext<'a>, + pub type_graph: &'a ComplexType, +} + +/// Value parser object. +#[derive(Default)] +pub struct ValueParser; + +impl ValueParser { + /// Create parser for a type graph. + /// + /// # Arguments + /// + /// * `type_graph`: types that value parser can create + pub fn new() -> Self { + ValueParser + } + + fn parse_scalar( + &self, + data: Option, + type_id: TypeId, + r#type: &ScalarType, + ) -> ScalarValue { + fn render_scalar(data: Option) -> Option { + data.as_ref().map(|v| scalar_from_bytes::(&v.raw_data)) + } + let in_debugee_loc = data.as_ref().and_then(|d| d.address); + #[allow(non_upper_case_globals)] + let value_view = r#type.encoding.and_then(|encoding| match encoding { + DW_ATE_address => render_scalar::(data).map(SupportedScalar::Usize), + DW_ATE_signed_char => render_scalar::(data).map(SupportedScalar::I8), + DW_ATE_unsigned_char => render_scalar::(data).map(SupportedScalar::U8), + DW_ATE_signed => match r#type.byte_size.unwrap_or(0) { + 0 => Some(SupportedScalar::Empty()), + 1 => render_scalar::(data).map(SupportedScalar::I8), + 2 => render_scalar::(data).map(SupportedScalar::I16), + 4 => render_scalar::(data).map(SupportedScalar::I32), + 8 => { + if r#type.name.as_deref() == Some("isize") { + render_scalar::(data).map(SupportedScalar::Isize) + } else { + render_scalar::(data).map(SupportedScalar::I64) + } + } + 16 => render_scalar::(data).map(SupportedScalar::I128), + _ => { + warn!( + "parse scalar: unexpected signed size: {size:?}", + size = r#type.byte_size + ); + None + } + }, + DW_ATE_unsigned => match r#type.byte_size.unwrap_or(0) { + 0 => Some(SupportedScalar::Empty()), + 1 => render_scalar::(data).map(SupportedScalar::U8), + 2 => render_scalar::(data).map(SupportedScalar::U16), + 4 => render_scalar::(data).map(SupportedScalar::U32), + 8 => { + if r#type.name.as_deref() == Some("usize") { + render_scalar::(data).map(SupportedScalar::Usize) + } else { + render_scalar::(data).map(SupportedScalar::U64) + } + } + 16 => render_scalar::(data).map(SupportedScalar::U128), + _ => { + warn!( + "parse scalar: unexpected unsigned size: {size:?}", + size = r#type.byte_size + ); + None + } + }, + DW_ATE_float => match r#type.byte_size.unwrap_or(0) { + 4 => render_scalar::(data).map(SupportedScalar::F32), + 8 => render_scalar::(data).map(SupportedScalar::F64), + _ => { + warn!( + "parse scalar: unexpected float size: {size:?}", + size = r#type.byte_size + ); + None + } + }, + DW_ATE_boolean => render_scalar::(data).map(SupportedScalar::Bool), + DW_ATE_UTF => render_scalar::(data).map(|char| { + // WAITFORFIX: https://github.com/rust-lang/rust/issues/113819 + // this check is meaningfully here cause in case above there is a random bytes here, + // and it may lead to panic in other places + // (specially when someone tries to render this char) + if String::from_utf8(char.to_string().into_bytes()).is_err() { + SupportedScalar::Char('?') + } else { + SupportedScalar::Char(char) + } + }), + DW_ATE_ASCII => render_scalar::(data).map(SupportedScalar::Char), + _ => { + warn!("parse scalar: unexpected base type encoding: {encoding}"); + None + } + }); + + ScalarValue { + type_ident: r#type.identity(), + type_id: Some(type_id), + value: value_view, + raw_address: in_debugee_loc, + } + } + + fn parse_struct_variable( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + type_params: HashMap>, + members: &[StructureMember], + ) -> StructValue { + let children = members + .iter() + .filter_map(|member| self.parse_struct_member(ctx, member, data.as_ref())) + .collect(); + + StructValue { + type_id: Some(type_id), + type_ident: ctx.type_graph.identity(type_id), + members: children, + type_params, + raw_address: data.and_then(|d| d.address), + } + } + + fn parse_struct_member( + &self, + ctx: &ParseContext, + member: &StructureMember, + parent_data: Option<&ObjectBinaryRepr>, + ) -> Option { + let name = member.name.clone(); + let Some(type_ref) = member.type_ref else { + warn!( + "parse structure: unknown type for member {}", + name.as_deref().unwrap_or_default() + ); + return None; + }; + let member_val = + parent_data.and_then(|data| member.value(ctx.evaluation_context, ctx.type_graph, data)); + let value = self.parse_inner(ctx, member_val, type_ref)?; + Some(Member { + field_name: member.name.clone(), + value, + }) + } + + fn parse_array( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + array_decl: &ArrayType, + ) -> ArrayValue { + let items = array_decl + .bounds(ctx.evaluation_context) + .and_then(|bounds| { + let len = bounds.1 - bounds.0; + let data = data.as_ref()?; + let el_size = (array_decl.size_in_bytes(ctx.evaluation_context, ctx.type_graph)? + / len as u64) as usize; + let bytes = &data.raw_data; + let el_type_id = array_decl.element_type?; + + let (mut bytes_chunks, mut empty_chunks); + let raw_items_iter: &mut dyn Iterator = if el_size != 0 { + bytes_chunks = bytes.chunks(el_size).enumerate(); + &mut bytes_chunks + } else { + // if an item type is zst + let v: Vec<&[u8]> = vec![&[]; len as usize]; + empty_chunks = v.into_iter().enumerate(); + &mut empty_chunks + }; + + Some( + raw_items_iter + .filter_map(|(i, chunk)| { + let offset = i * el_size; + let data = ObjectBinaryRepr { + raw_data: bytes.slice_ref(chunk), + address: data.address.map(|addr| addr + offset), + size: el_size, + }; + + let value = self.parse_inner(ctx, Some(data), el_type_id)?; + Some(ArrayItem { + index: bounds.0 + i as i64, + value, + }) + }) + .collect::>(), + ) + }); + + ArrayValue { + items, + type_id: Some(type_id), + type_ident: ctx.type_graph.identity(type_id), + raw_address: data.and_then(|d| d.address), + } + } + + fn parse_c_enum( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + discr_type: Option, + enumerators: &HashMap, + ) -> CEnumValue { + let in_debugee_loc = data.as_ref().and_then(|d| d.address); + let mb_discr = discr_type.and_then(|type_id| self.parse_inner(ctx, data, type_id)); + + let value = mb_discr.and_then(|discr| { + if let Value::Scalar(scalar) = discr { + scalar.try_as_number() + } else { + None + } + }); + + CEnumValue { + type_ident: ctx.type_graph.identity(type_id), + type_id: Some(type_id), + value: value.and_then(|val| enumerators.get(&val).cloned()), + raw_address: in_debugee_loc, + } + } + + fn parse_rust_enum( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + discr_member: Option<&StructureMember>, + enumerators: &HashMap, StructureMember>, + ) -> RustEnumValue { + let discr_value = discr_member.and_then(|member| { + let discr = self.parse_struct_member(ctx, member, data.as_ref())?.value; + if let Value::Scalar(scalar) = discr { + return scalar.try_as_number(); + } + None + }); + + let enumerator = + discr_value.and_then(|v| enumerators.get(&Some(v)).or_else(|| enumerators.get(&None))); + + let enumerator = enumerator.and_then(|member| { + Some(Box::new(self.parse_struct_member( + ctx, + member, + data.as_ref(), + )?)) + }); + + RustEnumValue { + type_id: Some(type_id), + type_ident: ctx.type_graph.identity(type_id), + value: enumerator, + raw_address: data.and_then(|d| d.address), + } + } + + fn parse_pointer( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + target_type: Option, + ) -> PointerValue { + let mb_ptr = data + .as_ref() + .map(|v| scalar_from_bytes::<*const ()>(&v.raw_data)); + + let mut type_ident = ctx.type_graph.identity(type_id); + if type_ident.is_unknown() { + if let Some(target_type) = target_type { + type_ident = ctx.type_graph.identity(target_type).as_deref_type(); + } + } + + PointerValue { + type_id: Some(type_id), + type_ident, + value: mb_ptr, + target_type, + target_type_size: None, + raw_address: data.and_then(|d| d.address), + } + } + + fn parse_inner_with_modifiers( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + modifiers: &ValueModifiers, + ) -> Option { + let type_graph = ctx.type_graph; + match &type_graph.types[&type_id] { + TypeDeclaration::Scalar(scalar_type) => { + Some(Value::Scalar(self.parse_scalar(data, type_id, scalar_type))) + } + TypeDeclaration::Structure { + namespaces: type_ns_h, + members, + type_params, + name: struct_name, + .. + } => { + let struct_var = + self.parse_struct_variable(ctx, data, type_id, type_params.clone(), members); + + let parser_ext = VariableParserExtension::new(self); + // Reinterpret structure if underline data type is: + // - Vector + // - String + // - &str + // - tls variable + // - hashmaps + // - hashset + // - btree map + // - btree set + // - vecdeque + // - cell/refcell + // - rc/arc + // - uuid + // - SystemTime/Instant + if struct_name.as_deref() == Some("&str") { + return Some(Value::Specialized { + value: parser_ext.parse_str(ctx, &struct_var), + original: struct_var, + }); + }; + + if struct_name.as_deref() == Some("String") { + return Some(Value::Specialized { + value: parser_ext.parse_string(ctx, &struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name.starts_with("Vec")) == Some(true) + && type_ns_h.contains(&["vec"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_vector(ctx, &struct_var, type_params), + original: struct_var, + }); + }; + + let rust_version = ctx.evaluation_context.rustc_version().unwrap_or_default(); + let type_is_tls = version_switch!( + rust_version, + (1, 0, 0) ..= (1, 76, u32::MAX) => type_ns_h.contains(&["std", "sys", "common", "thread_local", "fast_local"]), + (1, 77, 0) ..= (1, 77, u32::MAX) => type_ns_h.contains(&["std", "sys", "pal", "common", "thread_local", "fast_local"]), + (1, 78, 0) ..= (1, u32::MAX, u32::MAX) => type_ns_h.contains(&["std", "sys", "thread_local", "fast_local"]), + ).unwrap_or_default(); + + if type_is_tls || modifiers.tls { + return if rust_version >= Version((1, 80, 0)) { + match parser_ext.parse_tls(ctx, &struct_var, type_params) { + Ok(Some(value)) => Some(Value::Specialized { + value: Some(SpecializedValue::Tls(value)), + original: struct_var, + }), + Ok(None) => None, + Err(e) => { + warn!(target: "debugger", "{:#}", e); + Some(Value::Specialized { + value: None, + original: struct_var, + }) + } + } + } else { + Some(Value::Specialized { + value: parser_ext.parse_tls_old( + ctx, + &struct_var, + type_params, + modifiers.tls_const, + ), + original: struct_var, + }) + }; + } + + if struct_name.as_ref().map(|name| name.starts_with("HashMap")) == Some(true) + && type_ns_h.contains(&["collections", "hash", "map"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_hashmap(ctx, &struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name.starts_with("HashSet")) == Some(true) + && type_ns_h.contains(&["collections", "hash", "set"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_hashset(ctx, &struct_var), + original: struct_var, + }); + }; + + if struct_name + .as_ref() + .map(|name| name.starts_with("BTreeMap")) + == Some(true) + && type_ns_h.contains(&["collections", "btree", "map"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_btree_map(ctx, &struct_var, type_id, type_params), + original: struct_var, + }); + }; + + if struct_name + .as_ref() + .map(|name| name.starts_with("BTreeSet")) + == Some(true) + && type_ns_h.contains(&["collections", "btree", "set"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_btree_set(&struct_var), + original: struct_var, + }); + }; + + if struct_name + .as_ref() + .map(|name| name.starts_with("VecDeque")) + == Some(true) + && type_ns_h.contains(&["collections", "vec_deque"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_vec_dequeue(ctx, &struct_var, type_params), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name.starts_with("Cell")) == Some(true) + && type_ns_h.contains(&["cell"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_cell(&struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name.starts_with("RefCell")) == Some(true) + && type_ns_h.contains(&["cell"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_refcell(&struct_var), + original: struct_var, + }); + }; + + if struct_name + .as_ref() + .map(|name| name.starts_with("Rc<") | name.starts_with("Weak<")) + == Some(true) + && type_ns_h.contains(&["rc"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_rc(&struct_var), + original: struct_var, + }); + }; + + if struct_name + .as_ref() + .map(|name| name.starts_with("Arc<") | name.starts_with("Weak<")) + == Some(true) + && type_ns_h.contains(&["sync"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_arc(&struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name == "Uuid") == Some(true) + && type_ns_h.contains(&["uuid"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_uuid(&struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name == "Instant") == Some(true) + && type_ns_h.contains(&["std", "time"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_instant(&struct_var), + original: struct_var, + }); + }; + + if struct_name.as_ref().map(|name| name == "SystemTime") == Some(true) + && type_ns_h.contains(&["std", "time"]) + { + return Some(Value::Specialized { + value: parser_ext.parse_sys_time(&struct_var), + original: struct_var, + }); + }; + + Some(Value::Struct(struct_var)) + } + TypeDeclaration::Array(decl) => { + Some(Value::Array(self.parse_array(ctx, data, type_id, decl))) + } + TypeDeclaration::CStyleEnum { + discr_type, + enumerators, + .. + } => Some(Value::CEnum(self.parse_c_enum( + ctx, + data, + type_id, + *discr_type, + enumerators, + ))), + TypeDeclaration::RustEnum { + discr_type, + enumerators, + .. + } => Some(Value::RustEnum(self.parse_rust_enum( + ctx, + data, + type_id, + discr_type.as_ref().map(|t| t.as_ref()), + enumerators, + ))), + TypeDeclaration::Pointer { target_type, .. } => Some(Value::Pointer( + self.parse_pointer(ctx, data, type_id, *target_type), + )), + TypeDeclaration::Union { members, .. } => { + let struct_var = + self.parse_struct_variable(ctx, data, type_id, HashMap::new(), members); + Some(Value::Struct(struct_var)) + } + TypeDeclaration::Subroutine { return_type, .. } => { + let ret_type = return_type.map(|t_id| ctx.type_graph.identity(t_id)); + let fn_var = SubroutineValue { + type_id: Some(type_id), + return_type_ident: ret_type, + address: data.and_then(|d| d.address), + }; + Some(Value::Subroutine(fn_var)) + } + TypeDeclaration::ModifiedType { + inner, modifier, .. + } => { + let in_debugee_loc = data.as_ref().and_then(|d| d.address); + Some(Value::CModifiedVariable(CModifiedValue { + type_id: Some(type_id), + type_ident: ctx.type_graph.identity(type_id), + modifier: *modifier, + value: inner.and_then(|inner_type| { + Some(Box::new(self.parse_inner(ctx, data, inner_type)?)) + }), + address: in_debugee_loc, + })) + } + } + } + + pub(super) fn parse_inner( + &self, + ctx: &ParseContext, + data: Option, + type_id: TypeId, + ) -> Option { + self.parse_inner_with_modifiers(ctx, data, type_id, &ValueModifiers::default()) + } + + /// Return a new value of a root type from the underlying type graph. + /// + /// # Arguments + /// + /// * `ctx`: parsing context + /// * `bin_data`: binary value representation from debugee memory + /// * `modifiers`: value addition info + pub fn parse( + self, + ctx: &ParseContext, + bin_data: Option, + modifiers: &ValueModifiers, + ) -> Option { + if modifiers.const_tls_duplicate { + return None; + } + + self.parse_inner_with_modifiers(ctx, bin_data, ctx.type_graph.root(), modifiers) + } +} + +#[inline(never)] +fn scalar_from_bytes(bytes: &Bytes) -> T { + let ptr = bytes.as_ptr(); + unsafe { std::ptr::read_unaligned::(ptr as *const T) } +} diff --git a/src/debugger/variable/specialization/btree.rs b/src/debugger/variable/value/specialization/btree.rs similarity index 97% rename from src/debugger/variable/specialization/btree.rs rename to src/debugger/variable/value/specialization/btree.rs index 4119c89..1179079 100644 --- a/src/debugger/variable/specialization/btree.rs +++ b/src/debugger/variable/value/specialization/btree.rs @@ -1,11 +1,10 @@ use crate::debugger; -use crate::debugger::debugee::dwarf::r#type::{ - ComplexType, EvaluationContext, StructureMember, TypeId, TypeIdentity, -}; -use crate::debugger::variable::select::ObjectBinaryRepr; -use crate::debugger::variable::AssumeError::NoType; -use crate::debugger::variable::ParsingError::ReadDebugeeMemory; -use crate::debugger::variable::{AssumeError, ParsingError}; +use crate::debugger::debugee::dwarf::eval::EvaluationContext; +use crate::debugger::debugee::dwarf::r#type::{ComplexType, StructureMember, TypeId, TypeIdentity}; +use crate::debugger::variable::value::AssumeError::NoType; +use crate::debugger::variable::value::ParsingError::ReadDebugeeMemory; +use crate::debugger::variable::value::{AssumeError, ParsingError}; +use crate::debugger::variable::ObjectBinaryRepr; use crate::debugger::TypeDeclaration; use fallible_iterator::FallibleIterator; use std::mem; diff --git a/src/debugger/variable/specialization/hashbrown.rs b/src/debugger/variable/value/specialization/hashbrown.rs similarity index 100% rename from src/debugger/variable/specialization/hashbrown.rs rename to src/debugger/variable/value/specialization/hashbrown.rs diff --git a/src/debugger/variable/specialization/mod.rs b/src/debugger/variable/value/specialization/mod.rs similarity index 56% rename from src/debugger/variable/specialization/mod.rs rename to src/debugger/variable/value/specialization/mod.rs index fd1103f..4c54317 100644 --- a/src/debugger/variable/specialization/mod.rs +++ b/src/debugger/variable/value/specialization/mod.rs @@ -1,30 +1,30 @@ -mod btree; -mod hashbrown; - -use crate::debugger::debugee::dwarf::r#type::{EvaluationContext, TypeId, TypeIdentity}; -use crate::debugger::variable::render::RenderRepr; -use crate::debugger::variable::select::ObjectBinaryRepr; -use crate::debugger::variable::specialization::btree::BTreeReflection; -use crate::debugger::variable::specialization::hashbrown::HashmapReflection; -use crate::debugger::variable::AssumeError::{ +use crate::debugger::debugee::dwarf::r#type::{TypeId, TypeIdentity}; +use crate::debugger::variable::render::RenderValue; +use crate::debugger::variable::value::parser::{ParseContext, ValueParser}; +use crate::debugger::variable::value::specialization::btree::BTreeReflection; +use crate::debugger::variable::value::specialization::hashbrown::HashmapReflection; +use crate::debugger::variable::value::AssumeError::{ TypeParameterNotFound, TypeParameterTypeNotFound, UnexpectedType, }; -use crate::debugger::variable::ParsingError::Assume; -use crate::debugger::variable::{ - ArrayItem, ArrayVariable, AssumeError, FieldOrIndex, Member, ParsingError, PointerVariable, - ScalarVariable, StructVariable, SupportedScalar, VariableIR, VariableIdentity, VariableParser, +use crate::debugger::variable::value::ParsingError::Assume; +use crate::debugger::variable::value::{ + ArrayItem, ArrayValue, AssumeError, FieldOrIndex, Member, ParsingError, ScalarValue, + SupportedScalar, }; +use crate::debugger::variable::value::{PointerValue, StructValue, Value}; +use crate::debugger::variable::ObjectBinaryRepr; use crate::{debugger, version_switch, weak_error}; use anyhow::Context; use bytes::Bytes; use fallible_iterator::FallibleIterator; -use itertools::Itertools; -use log::warn; use std::collections::HashMap; use AssumeError::{FieldNotFound, IncompleteInterp, UnknownSize}; +mod btree; +mod hashbrown; + /// During program execution, the debugger may encounter uninitialized variables. -/// For example look at this code: +/// For example, look at this code: /// ```rust /// let res: Result<(), String> = Ok(()); /// if let Err(e) = res { @@ -32,8 +32,8 @@ use AssumeError::{FieldNotFound, IncompleteInterp, UnknownSize}; /// } /// ``` /// -/// if stop debugger at line 2 and and consider a variable `e` - capacity of this vector -/// may be over 9000, this is obviously not the size that user expect. +/// if stop debugger at line 2 and consider a variable `e` - capacity of this vector +/// may be over 9000, this is obviously not the size that user expects. /// Therefore, artificial restrictions on size and capacity are introduced. This behavior may be /// changed in the future. const LEN_GUARD: i64 = 10_000; @@ -56,19 +56,19 @@ fn guard_cap(cap: i64) -> i64 { } #[derive(Clone, PartialEq)] -pub struct VecVariable { - pub structure: StructVariable, +pub struct VecValue { + pub structure: StructValue, } -impl VecVariable { +impl VecValue { pub fn slice(&mut self, left: Option, right: Option) { debug_assert!(matches!( self.structure.members.get_mut(0).map(|m| &m.value), - Some(VariableIR::Array(_)) + Some(Value::Array(_)) )); if let Some(Member { - value: VariableIR::Array(array), + value: Value::Array(array), .. }) = self.structure.members.get_mut(0) { @@ -79,41 +79,36 @@ impl VecVariable { #[derive(Clone, PartialEq)] pub struct StringVariable { - pub identity: VariableIdentity, pub value: String, } #[derive(Clone, PartialEq)] pub struct HashMapVariable { - pub identity: VariableIdentity, pub type_ident: TypeIdentity, - pub kv_items: Vec<(VariableIR, VariableIR)>, + pub kv_items: Vec<(Value, Value)>, } #[derive(Clone, PartialEq)] pub struct HashSetVariable { - pub identity: VariableIdentity, pub type_ident: TypeIdentity, - pub items: Vec, + pub items: Vec, } #[derive(Clone, PartialEq)] pub struct StrVariable { - pub identity: VariableIdentity, pub value: String, } #[derive(Clone, PartialEq)] pub struct TlsVariable { - pub identity: VariableIdentity, - pub inner_value: Option>, + pub inner_value: Option>, pub inner_type: TypeIdentity, } #[derive(Clone, PartialEq)] -pub enum SpecializedVariableIR { - Vector(VecVariable), - VecDeque(VecVariable), +pub enum SpecializedValue { + Vector(VecValue), + VecDeque(VecValue), HashMap(HashMapVariable), HashSet(HashSetVariable), BTreeMap(HashMapVariable), @@ -121,128 +116,122 @@ pub enum SpecializedVariableIR { String(StringVariable), Str(StrVariable), Tls(TlsVariable), - Cell(Box), - RefCell(Box), - Rc(PointerVariable), - Arc(PointerVariable), + Cell(Box), + RefCell(Box), + Rc(PointerValue), + Arc(PointerValue), Uuid([u8; 16]), SystemTime((i64, u32)), Instant((i64, u32)), } pub struct VariableParserExtension<'a> { - parser: &'a VariableParser<'a>, + parser: &'a ValueParser, } impl<'a> VariableParserExtension<'a> { - pub fn new(parser: &'a VariableParser) -> Self { + pub fn new(parser: &'a ValueParser) -> Self { Self { parser } } pub fn parse_str( &self, - eval_ctx: &EvaluationContext, - structure: &StructVariable, - ) -> Option { + ctx: &ParseContext, + structure: &StructValue, + ) -> Option { weak_error!(self - .parse_str_inner(eval_ctx, VariableIR::Struct(structure.clone())) + .parse_str_inner(ctx, Value::Struct(structure.clone())) .context("&str interpretation")) - .map(SpecializedVariableIR::Str) + .map(SpecializedValue::Str) } - fn parse_str_inner( - &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, - ) -> Result { - let len = ir.assume_field_as_scalar_number("length")?; + fn parse_str_inner(&self, ctx: &ParseContext, val: Value) -> Result { + let len = val.assume_field_as_scalar_number("length")?; let len = guard_len(len); - let data_ptr = ir.assume_field_as_pointer("data_ptr")?; + let data_ptr = val.assume_field_as_pointer("data_ptr")?; let data = debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), + ctx.evaluation_context.expl_ctx.pid_on_focus(), data_ptr as usize, len as usize, ) .map(Bytes::from)?; Ok(StrVariable { - identity: ir.identity().clone(), value: String::from_utf8(data.to_vec()).map_err(AssumeError::from)?, }) } pub fn parse_string( &self, - eval_ctx: &EvaluationContext, - structure: &StructVariable, - ) -> Option { + ctx: &ParseContext, + structure: &StructValue, + ) -> Option { weak_error!(self - .parse_string_inner(eval_ctx, VariableIR::Struct(structure.clone())) + .parse_string_inner(ctx, Value::Struct(structure.clone())) .context("String interpretation")) - .map(SpecializedVariableIR::String) + .map(SpecializedValue::String) } fn parse_string_inner( &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, + ctx: &ParseContext, + val: Value, ) -> Result { - let len = ir.assume_field_as_scalar_number("len")?; + let len = val.assume_field_as_scalar_number("len")?; let len = guard_len(len); - let data_ptr = ir.assume_field_as_pointer("pointer")?; + let data_ptr = val.assume_field_as_pointer("pointer")?; let data = debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), + ctx.evaluation_context.expl_ctx.pid_on_focus(), data_ptr as usize, len as usize, )?; Ok(StringVariable { - identity: ir.identity().clone(), value: String::from_utf8(data).map_err(AssumeError::from)?, }) } pub fn parse_vector( &self, - eval_ctx: &EvaluationContext, - structure: &StructVariable, + ctx: &ParseContext, + structure: &StructValue, type_params: &HashMap>, - ) -> Option { + ) -> Option { weak_error!(self - .parse_vector_inner(eval_ctx, VariableIR::Struct(structure.clone()), type_params) + .parse_vector_inner(ctx, Value::Struct(structure.clone()), type_params) .context("Vec interpretation")) - .map(SpecializedVariableIR::Vector) + .map(SpecializedValue::Vector) } fn parse_vector_inner( &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, + ctx: &ParseContext, + val: Value, type_params: &HashMap>, - ) -> Result { + ) -> Result { let inner_type = type_params .get("T") .ok_or(TypeParameterNotFound("T"))? .ok_or(TypeParameterTypeNotFound("T"))?; - let len = ir.assume_field_as_scalar_number("len")?; + let len = val.assume_field_as_scalar_number("len")?; let len = guard_len(len); - let cap = extract_capacity(eval_ctx, &ir)? as i64; + let cap = extract_capacity(ctx, &val)? as i64; let cap = guard_cap(cap); - let data_ptr = ir.assume_field_as_pointer("pointer")? as usize; + let data_ptr = val.assume_field_as_pointer("pointer")? as usize; - let el_type = self.parser.r#type; + let el_type = ctx.type_graph; let el_type_size = el_type - .type_size_in_bytes(eval_ctx, inner_type) + .type_size_in_bytes(ctx.evaluation_context, inner_type) .ok_or(UnknownSize(el_type.identity(inner_type)))? as usize; let raw_data = debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), + ctx.evaluation_context.expl_ctx.pid_on_focus(), data_ptr, len as usize * el_type_size, ) @@ -266,30 +255,23 @@ impl<'a> VariableParserExtension<'a> { address: Some(data_ptr + (i * el_type_size)), size: el_type_size, }; - ArrayItem { + Some(ArrayItem { index: i as i64, - value: self.parser.parse_inner( - eval_ctx, - VariableIdentity::default(), - Some(data), - inner_type, - ), - } + value: self.parser.parse_inner(ctx, Some(data), inner_type)?, + }) }) .collect::>(); - Ok(VecVariable { - structure: StructVariable { - identity: ir.identity().clone(), + Ok(VecValue { + structure: StructValue { type_id: None, - type_ident: ir.r#type().clone(), + type_ident: val.r#type().clone(), members: vec![ Member { field_name: Some("buf".to_owned()), - value: VariableIR::Array(ArrayVariable { - identity: VariableIdentity::default(), + value: Value::Array(ArrayValue { type_id: None, - type_ident: self.parser.r#type.identity(inner_type).as_array_type(), + type_ident: ctx.type_graph.identity(inner_type).as_array_type(), items: Some(items), // set to `None` because the address operator unavailable for spec vars raw_address: None, @@ -297,8 +279,7 @@ impl<'a> VariableParserExtension<'a> { }, Member { field_name: Some("cap".to_owned()), - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::default(), + value: Value::Scalar(ScalarValue { type_id: None, type_ident: TypeIdentity::no_namespace("usize"), value: Some(SupportedScalar::Usize(cap as usize)), @@ -316,63 +297,49 @@ impl<'a> VariableParserExtension<'a> { pub fn parse_tls_old( &self, - structure: &StructVariable, + ctx: &ParseContext, + structure: &StructValue, type_params: &HashMap>, is_const_initialized: bool, - ) -> Option { + ) -> Option { let tls_var = if is_const_initialized { - self.parse_const_init_tls_inner(VariableIR::Struct(structure.clone()), type_params) + self.parse_const_init_tls_inner(ctx, Value::Struct(structure.clone()), type_params) } else { - self.parse_tls_inner_old(VariableIR::Struct(structure.clone()), type_params) + self.parse_tls_inner_old(ctx, Value::Struct(structure.clone()), type_params) }; - weak_error!(tls_var.context("TLS variable interpretation")).map(SpecializedVariableIR::Tls) + weak_error!(tls_var.context("TLS variable interpretation")).map(SpecializedValue::Tls) } fn parse_const_init_tls_inner( &self, - ir: VariableIR, + ctx: &ParseContext, + inner: Value, type_params: &HashMap>, ) -> Result { - // we assume that tls variable name represents in dwarf - // as namespace flowed before "__getit" namespace - let namespace = &ir.identity().namespace; - let name = namespace - .iter() - .find_position(|&ns| ns == "__getit") - .map(|(pos, _)| namespace[pos - 1].clone()); - let value_type = type_params .get("T") .ok_or(TypeParameterNotFound("T"))? .ok_or(TypeParameterTypeNotFound("T"))?; - let value = ir.field("value"); + let value = inner.field("value"); Ok(TlsVariable { - identity: VariableIdentity::no_namespace(name), inner_value: value.map(Box::new), - inner_type: self.parser.r#type.identity(value_type), + inner_type: ctx.type_graph.identity(value_type), }) } fn parse_tls_inner_old( &self, - ir: VariableIR, + ctx: &ParseContext, + inner_val: Value, type_params: &HashMap>, ) -> Result { - // we assume that tls variable name represents in dwarf - // as namespace flowed before "__getit" namespace - let namespace = &ir.identity().namespace; - let name = namespace - .iter() - .find_position(|&ns| ns == "__getit") - .map(|(pos, _)| namespace[pos - 1].clone()); - let inner_type = type_params .get("T") .ok_or(TypeParameterNotFound("T"))? .ok_or(TypeParameterTypeNotFound("T"))?; - let inner = ir + let inner = inner_val .bfs_iterator() .find_map(|(field, child)| { (field == FieldOrIndex::Field(Some("inner"))).then_some(child) @@ -382,7 +349,7 @@ impl<'a> VariableParserExtension<'a> { let inner_value = inner_option.value.ok_or(IncompleteInterp("value"))?; // we assume that DWARF representation of tls variable contains ::Option - if let VariableIR::Struct(ref opt_variant) = inner_value.value { + if let Value::Struct(ref opt_variant) = inner_value.value { let tls_value = if opt_variant.type_ident.name() == Some("None") { None } else { @@ -399,9 +366,8 @@ impl<'a> VariableParserExtension<'a> { }; return Ok(TlsVariable { - identity: VariableIdentity::no_namespace(name), inner_value: tls_value, - inner_type: self.parser.r#type.identity(inner_type), + inner_type: ctx.type_graph.identity(inner_type), }); } @@ -412,125 +378,123 @@ impl<'a> VariableParserExtension<'a> { pub fn parse_tls( &self, - structure: StructVariable, + ctx: &ParseContext, + structure: &StructValue, type_params: &HashMap>, - ) -> Option { - let tls_var = self - .parse_tls_inner(VariableIR::Struct(structure.clone()), type_params) - .context("TLS variable interpretation"); - - let var = match tls_var { - Ok(Some(var)) => Some(var), - Ok(None) => return None, - Err(e) => { - let e = e.context("TLS variable interpretation"); - warn!(target: "debugger", "{:#}", e); - None - } - }; - - Some(SpecializedVariableIR::Tls { - tls_var: var, - original: structure, - }) + ) -> Result, ParsingError> { + if structure.type_ident.namespace().contains(&["eager"]) { + // constant tls + self.parse_const_tls_inner(ctx, Value::Struct(structure.clone()), type_params) + } else { + self.parse_tls_inner(ctx, Value::Struct(structure.clone()), type_params) + } } fn parse_tls_inner( &self, - ir: VariableIR, + ctx: &ParseContext, + inner_val: Value, type_params: &HashMap>, ) -> Result, ParsingError> { - // we assume that tls variable name represents in dwarf - // as namespace flowed before "__getit" namespace - let namespace = &ir.identity().namespace; - let name = namespace - .iter() - .find_position(|&ns| ns.contains("{constant#")) - .map(|(pos, _)| namespace[pos - 1].clone()); - let inner_type = type_params .get("T") .ok_or(TypeParameterNotFound("T"))? .ok_or(TypeParameterTypeNotFound("T"))?; - let state = ir + let state = inner_val .bfs_iterator() - .find(|child| child.name() == "state") + .find_map(|(field, child)| { + (field == FieldOrIndex::Field(Some("state"))).then_some(child) + }) .ok_or(FieldNotFound("state"))?; let state = state.assume_field_as_rust_enum("value")?; - if let Some(VariableIR::Struct(val)) = state.value.as_deref() { - let tls_val = if val.identity.name.as_deref() == Some("Alive") { - Some(Box::new(val.members[0].clone())) + if let Some(member) = state.value { + let tls_val = if member.field_name.as_deref() == Some("Alive") { + member.value.field("__0").map(Box::new) } else { return Ok(None); }; return Ok(Some(TlsVariable { - identity: VariableIdentity::no_namespace(name), inner_value: tls_val, - inner_type: self.parser.r#type.identity(inner_type), + inner_type: ctx.type_graph.identity(inner_type), })); }; + Ok(None) + } + + fn parse_const_tls_inner( + &self, + ctx: &ParseContext, + inner_val: Value, + type_params: &HashMap>, + ) -> Result, ParsingError> { + let inner_type = type_params + .get("T") + .ok_or(TypeParameterNotFound("T"))? + .ok_or(TypeParameterTypeNotFound("T"))?; + + if let Some(val) = inner_val.field("val") { + return Ok(Some(TlsVariable { + inner_value: val.field("value").map(Box::new), + inner_type: ctx.type_graph.identity(inner_type), + })); + } Err(ParsingError::Assume(IncompleteInterp( - "expect TLS inner value as option", + "expect TLS inner value as `val` field", ))) } pub fn parse_hashmap( &self, - eval_ctx: &EvaluationContext, - structure: &StructVariable, - ) -> Option { + ctx: &ParseContext, + structure: &StructValue, + ) -> Option { weak_error!(self - .parse_hashmap_inner(eval_ctx, VariableIR::Struct(structure.clone())) + .parse_hashmap_inner(ctx, Value::Struct(structure.clone())) .context("HashMap interpretation")) - .map(SpecializedVariableIR::HashMap) + .map(SpecializedValue::HashMap) } fn parse_hashmap_inner( &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, + ctx: &ParseContext, + val: Value, ) -> Result { - let ctrl = ir.assume_field_as_pointer("pointer")?; - let bucket_mask = ir.assume_field_as_scalar_number("bucket_mask")?; + let ctrl = val.assume_field_as_pointer("pointer")?; + let bucket_mask = val.assume_field_as_scalar_number("bucket_mask")?; - let table = ir.assume_field_as_struct("table")?; + let table = val.assume_field_as_struct("table")?; let kv_type = table .type_params .get("T") .ok_or(TypeParameterNotFound("T"))? .ok_or(TypeParameterTypeNotFound("T"))?; - let r#type = self.parser.r#type; + let r#type = ctx.type_graph; let kv_size = r#type - .type_size_in_bytes(eval_ctx, kv_type) + .type_size_in_bytes(ctx.evaluation_context, kv_type) .ok_or(UnknownSize(r#type.identity(kv_type)))?; let reflection = HashmapReflection::new(ctrl as *mut u8, bucket_mask as usize, kv_size as usize); - let iterator = reflection.iter(eval_ctx.expl_ctx.pid_on_focus())?; + let iterator = reflection.iter(ctx.evaluation_context.expl_ctx.pid_on_focus())?; let kv_items = iterator .map_err(ParsingError::from) .filter_map(|bucket| { - let raw_data = bucket.read(eval_ctx.expl_ctx.pid_on_focus()); + let raw_data = bucket.read(ctx.evaluation_context.expl_ctx.pid_on_focus()); let data = weak_error!(raw_data).map(|d| ObjectBinaryRepr { raw_data: Bytes::from(d), address: Some(bucket.location()), size: bucket.size(), }); - let tuple = self.parser.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(Some("kv".to_string())), - data, - kv_type, - ); + let tuple = self.parser.parse_inner(ctx, data, kv_type); - if let Some(VariableIR::Struct(mut tuple)) = tuple { + if let Some(Value::Struct(mut tuple)) = tuple { if tuple.members.len() == 2 { let v = tuple.members.pop(); let k = tuple.members.pop(); @@ -543,66 +507,58 @@ impl<'a> VariableParserExtension<'a> { .collect()?; Ok(HashMapVariable { - identity: ir.identity().clone(), - type_ident: ir.r#type().to_owned(), + type_ident: val.r#type().to_owned(), kv_items, }) } pub fn parse_hashset( &self, - eval_ctx: &EvaluationContext, - structure: &StructVariable, - ) -> Option { + ctx: &ParseContext, + structure: &StructValue, + ) -> Option { weak_error!(self - .parse_hashset_inner(eval_ctx, VariableIR::Struct(structure.clone())) + .parse_hashset_inner(ctx, Value::Struct(structure.clone())) .context("HashSet interpretation")) - .map(SpecializedVariableIR::HashSet) + .map(SpecializedValue::HashSet) } fn parse_hashset_inner( &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, + ctx: &ParseContext, + val: Value, ) -> Result { - let ctrl = ir.assume_field_as_pointer("pointer")?; - let bucket_mask = ir.assume_field_as_scalar_number("bucket_mask")?; + let ctrl = val.assume_field_as_pointer("pointer")?; + let bucket_mask = val.assume_field_as_scalar_number("bucket_mask")?; - let table = ir.assume_field_as_struct("table")?; + let table = val.assume_field_as_struct("table")?; let kv_type = table .type_params .get("T") .ok_or(TypeParameterNotFound("T"))? .ok_or(TypeParameterTypeNotFound("T"))?; - let r#type = self.parser.r#type; - let kv_size = self - .parser - .r#type - .type_size_in_bytes(eval_ctx, kv_type) + let r#type = ctx.type_graph; + let kv_size = r#type + .type_size_in_bytes(ctx.evaluation_context, kv_type) .ok_or_else(|| UnknownSize(r#type.identity(kv_type)))?; let reflection = HashmapReflection::new(ctrl as *mut u8, bucket_mask as usize, kv_size as usize); - let iterator = reflection.iter(eval_ctx.expl_ctx.pid_on_focus())?; + let iterator = reflection.iter(ctx.evaluation_context.expl_ctx.pid_on_focus())?; let items = iterator .map_err(ParsingError::from) .filter_map(|bucket| { - let raw_data = bucket.read(eval_ctx.expl_ctx.pid_on_focus()); + let raw_data = bucket.read(ctx.evaluation_context.expl_ctx.pid_on_focus()); let data = weak_error!(raw_data).map(|d| ObjectBinaryRepr { raw_data: Bytes::from(d), address: Some(bucket.location()), size: bucket.size(), }); - let tuple = self.parser.parse_inner( - eval_ctx, - VariableIdentity::no_namespace(Some("kv".to_string())), - data, - kv_type, - ); + let tuple = self.parser.parse_inner(ctx, data, kv_type); - if let Some(VariableIR::Struct(mut tuple)) = tuple { + if let Some(Value::Struct(mut tuple)) = tuple { if tuple.members.len() == 2 { let _ = tuple.members.pop(); let k = tuple.members.pop().unwrap(); @@ -615,39 +571,33 @@ impl<'a> VariableParserExtension<'a> { .collect()?; Ok(HashSetVariable { - identity: ir.identity().clone(), - type_ident: ir.r#type().to_owned(), + type_ident: val.r#type().to_owned(), items, }) } pub fn parse_btree_map( &self, - eval_ctx: &EvaluationContext, - structure: &StructVariable, + ctx: &ParseContext, + structure: &StructValue, identity: TypeId, type_params: &HashMap>, - ) -> Option { + ) -> Option { weak_error!(self - .parse_btree_map_inner( - eval_ctx, - VariableIR::Struct(structure.clone()), - identity, - type_params - ) + .parse_btree_map_inner(ctx, Value::Struct(structure.clone()), identity, type_params) .context("BTreeMap interpretation")) - .map(SpecializedVariableIR::BTreeMap) + .map(SpecializedValue::BTreeMap) } fn parse_btree_map_inner( &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, + ctx: &ParseContext, + val: Value, identity: TypeId, type_params: &HashMap>, ) -> Result { - let height = ir.assume_field_as_scalar_number("height")?; - let ptr = ir.assume_field_as_pointer("pointer")?; + let height = val.assume_field_as_scalar_number("height")?; + let ptr = val.assume_field_as_pointer("pointer")?; let k_type = type_params .get("K") @@ -659,49 +609,48 @@ impl<'a> VariableParserExtension<'a> { .ok_or(TypeParameterTypeNotFound("V"))?; let reflection = BTreeReflection::new( - self.parser.r#type, + ctx.type_graph, ptr, height as usize, identity, k_type, v_type, )?; - let iterator = reflection.iter(eval_ctx)?; + let iterator = reflection.iter(ctx.evaluation_context)?; let kv_items = iterator .map_err(ParsingError::from) - .map(|(k, v)| { - let key = - self.parser - .parse_inner(eval_ctx, VariableIdentity::default(), Some(k), k_type); + .filter_map(|(k, v)| { + let Some(key) = self.parser.parse_inner(ctx, Some(k), k_type) else { + return Ok(None); + }; - let value = - self.parser - .parse_inner(eval_ctx, VariableIdentity::default(), Some(v), v_type); + let Some(value) = self.parser.parse_inner(ctx, Some(v), v_type) else { + return Ok(None); + }; Ok(Some((key, value))) }) .collect::>()?; Ok(HashMapVariable { - identity: ir.identity().clone(), - type_ident: ir.r#type().to_owned(), + type_ident: val.r#type().to_owned(), kv_items, }) } - pub fn parse_btree_set(&self, structure: &StructVariable) -> Option { + pub fn parse_btree_set(&self, structure: &StructValue) -> Option { weak_error!(self - .parse_btree_set_inner(VariableIR::Struct(structure.clone())) + .parse_btree_set_inner(Value::Struct(structure.clone())) .context("BTreeSet interpretation")) - .map(SpecializedVariableIR::BTreeSet) + .map(SpecializedValue::BTreeSet) } - fn parse_btree_set_inner(&self, ir: VariableIR) -> Result { - let inner_map = ir + fn parse_btree_set_inner(&self, val: Value) -> Result { + let inner_map = val .bfs_iterator() .find_map(|(field_or_idx, child)| { - if let VariableIR::Specialized { - value: Some(SpecializedVariableIR::BTreeMap(ref map)), + if let Value::Specialized { + value: Some(SpecializedValue::BTreeMap(ref map)), .. } = child { @@ -711,51 +660,50 @@ impl<'a> VariableParserExtension<'a> { } None }) - .ok_or(IncompleteInterp("BTreeMap"))?; + .ok_or(IncompleteInterp("BTreeSet"))?; Ok(HashSetVariable { - identity: ir.identity().clone(), - type_ident: ir.r#type().to_owned(), + type_ident: val.r#type().to_owned(), items: inner_map.kv_items.into_iter().map(|(k, _)| k).collect(), }) } pub fn parse_vec_dequeue( &self, - eval_ctx: &EvaluationContext, - structure: &StructVariable, + ctx: &ParseContext, + structure: &StructValue, type_params: &HashMap>, - ) -> Option { + ) -> Option { weak_error!(self - .parse_vec_dequeue_inner(eval_ctx, VariableIR::Struct(structure.clone()), type_params) + .parse_vec_dequeue_inner(ctx, Value::Struct(structure.clone()), type_params) .context("VeqDequeue interpretation")) - .map(SpecializedVariableIR::VecDeque) + .map(SpecializedValue::VecDeque) } fn parse_vec_dequeue_inner( &self, - eval_ctx: &EvaluationContext, - ir: VariableIR, + ctx: &ParseContext, + val: Value, type_params: &HashMap>, - ) -> Result { + ) -> Result { let inner_type = type_params .get("T") .ok_or(TypeParameterNotFound("T"))? .ok_or(TypeParameterTypeNotFound("T"))?; - let len = ir.assume_field_as_scalar_number("len")? as usize; + let len = val.assume_field_as_scalar_number("len")? as usize; let len = guard_len(len as i64) as usize; - let r#type = self.parser.r#type; + let r#type = ctx.type_graph; let el_type_size = r#type - .type_size_in_bytes(eval_ctx, inner_type) + .type_size_in_bytes(ctx.evaluation_context, inner_type) .ok_or_else(|| UnknownSize(r#type.identity(inner_type)))? as usize; let cap = if el_type_size == 0 { usize::MAX } else { - extract_capacity(eval_ctx, &ir)? + extract_capacity(ctx, &val)? }; - let head = ir.assume_field_as_scalar_number("head")? as usize; + let head = val.assume_field_as_scalar_number("head")? as usize; let wrapped_start = if head >= cap { head - cap } else { head }; let head_len = cap - wrapped_start; @@ -767,10 +715,10 @@ impl<'a> VariableParserExtension<'a> { (wrapped_start..cap, 0..tail_len) }; - let data_ptr = ir.assume_field_as_pointer("pointer")? as usize; + let data_ptr = val.assume_field_as_pointer("pointer")? as usize; let data = debugger::read_memory_by_pid( - eval_ctx.expl_ctx.pid_on_focus(), + ctx.evaluation_context.expl_ctx.pid_on_focus(), data_ptr, cap * el_type_size, ) @@ -789,30 +737,23 @@ impl<'a> VariableParserExtension<'a> { size: el_type_size, }; - ArrayItem { + Some(ArrayItem { index: i as i64, - value: self.parser.parse_inner( - eval_ctx, - VariableIdentity::default(), - Some(el_data), - inner_type, - ), - } + value: self.parser.parse_inner(ctx, Some(el_data), inner_type)?, + }) }) .collect::>(); - Ok(VecVariable { - structure: StructVariable { - identity: ir.identity().clone(), + Ok(VecValue { + structure: StructValue { type_id: None, - type_ident: ir.r#type().to_owned(), + type_ident: val.r#type().to_owned(), members: vec![ Member { field_name: Some("buf".to_owned()), - value: VariableIR::Array(ArrayVariable { - identity: VariableIdentity::default(), + value: Value::Array(ArrayValue { type_id: None, - type_ident: self.parser.r#type.identity(inner_type).as_array_type(), + type_ident: ctx.type_graph.identity(inner_type).as_array_type(), items: Some(items), // set to `None` because the address operator unavailable for spec vars raw_address: None, @@ -820,8 +761,7 @@ impl<'a> VariableParserExtension<'a> { }, Member { field_name: Some("cap".to_owned()), - value: VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::default(), + value: Value::Scalar(ScalarValue { type_id: None, type_ident: TypeIdentity::no_namespace("usize"), value: Some(SupportedScalar::Usize(if el_type_size == 0 { @@ -841,16 +781,16 @@ impl<'a> VariableParserExtension<'a> { }) } - pub fn parse_cell(&self, structure: &StructVariable) -> Option { + pub fn parse_cell(&self, structure: &StructValue) -> Option { weak_error!(self - .parse_cell_inner(VariableIR::Struct(structure.clone())) + .parse_cell_inner(Value::Struct(structure.clone())) .context("Cell interpretation")) .map(Box::new) - .map(SpecializedVariableIR::Cell) + .map(SpecializedValue::Cell) } - fn parse_cell_inner(&self, ir: VariableIR) -> Result { - let unsafe_cell = ir.assume_field_as_struct("value")?; + fn parse_cell_inner(&self, val: Value) -> Result { + let unsafe_cell = val.assume_field_as_struct("value")?; let member = unsafe_cell .members .first() @@ -858,20 +798,20 @@ impl<'a> VariableParserExtension<'a> { Ok(member.value.clone()) } - pub fn parse_refcell(&self, structure: &StructVariable) -> Option { + pub fn parse_refcell(&self, structure: &StructValue) -> Option { weak_error!(self - .parse_refcell_inner(VariableIR::Struct(structure.clone())) + .parse_refcell_inner(Value::Struct(structure.clone())) .context("RefCell interpretation")) .map(Box::new) - .map(SpecializedVariableIR::RefCell) + .map(SpecializedValue::RefCell) } - fn parse_refcell_inner(&self, ir: VariableIR) -> Result { - let borrow = ir + fn parse_refcell_inner(&self, val: Value) -> Result { + let borrow = val .bfs_iterator() .find_map(|(_, child)| { - if let VariableIR::Specialized { - value: Some(SpecializedVariableIR::Cell(val)), + if let Value::Specialized { + value: Some(SpecializedValue::Cell(val)), .. } = child { @@ -880,21 +820,20 @@ impl<'a> VariableParserExtension<'a> { None }) .ok_or(IncompleteInterp("Cell"))?; - let VariableIR::Scalar(var) = *borrow else { + let Value::Scalar(var) = *borrow else { return Err(IncompleteInterp("Cell").into()); }; - let borrow = VariableIR::Scalar(var); + let borrow = Value::Scalar(var); - let unsafe_cell = ir.assume_field_as_struct("value")?; + let unsafe_cell = val.assume_field_as_struct("value")?; let value = unsafe_cell .members .first() .ok_or(IncompleteInterp("UnsafeCell"))?; - Ok(VariableIR::Struct(StructVariable { - identity: ir.identity().clone(), + Ok(Value::Struct(StructValue { type_id: None, - type_ident: ir.r#type().to_owned(), + type_ident: val.r#type().to_owned(), members: vec![ Member { field_name: Some("borrow".to_string()), @@ -908,21 +847,20 @@ impl<'a> VariableParserExtension<'a> { })) } - pub fn parse_rc(&self, structure: &StructVariable) -> Option { + pub fn parse_rc(&self, structure: &StructValue) -> Option { weak_error!(self - .parse_rc_inner(VariableIR::Struct(structure.clone())) + .parse_rc_inner(Value::Struct(structure.clone())) .context("Rc interpretation")) - .map(SpecializedVariableIR::Rc) + .map(SpecializedValue::Rc) } - fn parse_rc_inner(&self, ir: VariableIR) -> Result { - Ok(ir + fn parse_rc_inner(&self, val: Value) -> Result { + Ok(val .bfs_iterator() .find_map(|(field_or_idx, child)| { - if let VariableIR::Pointer(pointer) = child { + if let Value::Pointer(pointer) = child { if field_or_idx == FieldOrIndex::Field(Some("pointer")) { - let mut new_pointer = pointer.clone(); - new_pointer.identity = ir.identity().clone(); + let new_pointer = pointer.clone(); return Some(new_pointer); } } @@ -931,21 +869,20 @@ impl<'a> VariableParserExtension<'a> { .ok_or(IncompleteInterp("rc"))?) } - pub fn parse_arc(&self, structure: &StructVariable) -> Option { + pub fn parse_arc(&self, structure: &StructValue) -> Option { weak_error!(self - .parse_arc_inner(VariableIR::Struct(structure.clone())) + .parse_arc_inner(Value::Struct(structure.clone())) .context("Arc interpretation")) - .map(SpecializedVariableIR::Arc) + .map(SpecializedValue::Arc) } - fn parse_arc_inner(&self, ir: VariableIR) -> Result { - Ok(ir + fn parse_arc_inner(&self, val: Value) -> Result { + Ok(val .bfs_iterator() .find_map(|(field_or_idx, child)| { - if let VariableIR::Pointer(pointer) = child { + if let Value::Pointer(pointer) = child { if field_or_idx == FieldOrIndex::Field(Some("pointer")) { - let mut new_pointer = pointer.clone(); - new_pointer.identity = ir.identity().clone(); + let new_pointer = pointer.clone(); return Some(new_pointer); } } @@ -954,16 +891,16 @@ impl<'a> VariableParserExtension<'a> { .ok_or(IncompleteInterp("Arc"))?) } - pub fn parse_uuid(&self, structure: &StructVariable) -> Option { + pub fn parse_uuid(&self, structure: &StructValue) -> Option { weak_error!(self - .parse_uuid_inner(&structure) + .parse_uuid_inner(structure) .context("Uuid interpretation")) - .map(SpecializedVariableIR::Uuid) + .map(SpecializedValue::Uuid) } - fn parse_uuid_inner(&self, structure: &StructVariable) -> Result<[u8; 16], ParsingError> { + fn parse_uuid_inner(&self, structure: &StructValue) -> Result<[u8; 16], ParsingError> { let member0 = structure.members.first().ok_or(FieldNotFound("member 0"))?; - let VariableIR::Array(ref arr) = member0.value else { + let Value::Array(ref arr) = member0.value else { return Err(UnexpectedType("uuid struct member must be an array").into()); }; let items = arr @@ -976,7 +913,7 @@ impl<'a> VariableParserExtension<'a> { let mut bytes_repr = [0; 16]; for (i, item) in items.iter().enumerate() { - let VariableIR::Scalar(ScalarVariable { + let Value::Scalar(ScalarValue { value: Some(SupportedScalar::U8(byte)), .. }) = item.value @@ -989,12 +926,12 @@ impl<'a> VariableParserExtension<'a> { Ok(bytes_repr) } - fn parse_timespec(&self, timespec: &StructVariable) -> Result<(i64, u32), ParsingError> { + fn parse_timespec(&self, timespec: &StructValue) -> Result<(i64, u32), ParsingError> { let &[Member { - value: VariableIR::Scalar(secs), + value: Value::Scalar(secs), .. }, Member { - value: VariableIR::Struct(n_secs), + value: Value::Struct(n_secs), .. }] = ×pec.members.as_slice() else { @@ -1003,7 +940,7 @@ impl<'a> VariableParserExtension<'a> { }; let &[Member { - value: VariableIR::Scalar(n_secs), + value: Value::Scalar(n_secs), .. }] = &n_secs.members.as_slice() else { @@ -1021,16 +958,16 @@ impl<'a> VariableParserExtension<'a> { Ok((secs, n_secs)) } - pub fn parse_sys_time(&self, structure: &StructVariable) -> Option { + pub fn parse_sys_time(&self, structure: &StructValue) -> Option { weak_error!(self - .parse_sys_time_inner(&structure) + .parse_sys_time_inner(structure) .context("SystemTime interpretation")) - .map(SpecializedVariableIR::SystemTime) + .map(SpecializedValue::SystemTime) } - fn parse_sys_time_inner(&self, structure: &StructVariable) -> Result<(i64, u32), ParsingError> { + fn parse_sys_time_inner(&self, structure: &StructValue) -> Result<(i64, u32), ParsingError> { let &[Member { - value: VariableIR::Struct(time_instant), + value: Value::Struct(time_instant), .. }] = &structure.members.as_slice() else { @@ -1039,7 +976,7 @@ impl<'a> VariableParserExtension<'a> { }; let &[Member { - value: VariableIR::Struct(timespec), + value: Value::Struct(timespec), .. }] = &time_instant.members.as_slice() else { @@ -1050,16 +987,16 @@ impl<'a> VariableParserExtension<'a> { self.parse_timespec(timespec) } - pub fn parse_instant(&self, structure: &StructVariable) -> Option { + pub fn parse_instant(&self, structure: &StructValue) -> Option { weak_error!(self - .parse_instant_inner(&structure) + .parse_instant_inner(structure) .context("Instant interpretation")) - .map(SpecializedVariableIR::Instant) + .map(SpecializedValue::Instant) } - fn parse_instant_inner(&self, structure: &StructVariable) -> Result<(i64, u32), ParsingError> { + fn parse_instant_inner(&self, structure: &StructValue) -> Result<(i64, u32), ParsingError> { let &[Member { - value: VariableIR::Struct(time_instant), + value: Value::Struct(time_instant), .. }] = &structure.members.as_slice() else { @@ -1068,7 +1005,7 @@ impl<'a> VariableParserExtension<'a> { }; let &[Member { - value: VariableIR::Struct(timespec), + value: Value::Struct(timespec), .. }] = &time_instant.members.as_slice() else { @@ -1080,22 +1017,24 @@ impl<'a> VariableParserExtension<'a> { } } -fn extract_capacity(eval_ctx: &EvaluationContext, ir: &VariableIR) -> Result { - let rust_version = eval_ctx +fn extract_capacity(ctx: &ParseContext, val: &Value) -> Result { + let rust_version = ctx + .evaluation_context .rustc_version() .ok_or(ParsingError::UnsupportedVersion)?; version_switch!( - rust_version, - (1, 0, 0) ..= (1, 75, u32::MAX) => ir.assume_field_as_scalar_number("cap")? as usize, - (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => { - let cap_s = ir.assume_field_as_struct("cap")?; - let cap = &cap_s.members.first().ok_or(IncompleteInterp("Vec"))?.value; - if let VariableIR::Scalar(ScalarVariable {value: Some(SupportedScalar::Usize(cap)), ..}) = cap { - Ok(*cap) - } else { - Err(AssumeError::FieldNotANumber("cap")) - }? - }, - ).ok_or(ParsingError::UnsupportedVersion) + rust_version, + (1, 0, 0) ..= (1, 75, u32::MAX) => val.assume_field_as_scalar_number("cap")? as usize, + (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => { + let cap_s = val.assume_field_as_struct("cap")?; + let cap = &cap_s.members.first().ok_or(IncompleteInterp("Vec"))?.value; + if let Value::Scalar(ScalarValue {value: Some(SupportedScalar::Usize(cap)), ..}) = cap { + Ok(*cap) + } else { + Err(AssumeError::FieldNotANumber("cap")) + }? + }, + ) + .ok_or(ParsingError::UnsupportedVersion) } diff --git a/src/debugger/variable/virtual.rs b/src/debugger/variable/virtual.rs new file mode 100644 index 0000000..22ea555 --- /dev/null +++ b/src/debugger/variable/virtual.rs @@ -0,0 +1,67 @@ +use crate::debugger::debugee::dwarf::unit::{DieRef, Node}; +use crate::debugger::debugee::dwarf::{AsAllocatedData, ContextualDieRef, EndianArcSlice}; +use crate::debugger::debugee::Debugee; +use crate::debugger::Error; +use crate::debugger::Error::TypeNotFound; +use gimli::{Attribute, DebugInfoOffset, UnitOffset}; + +/// This DIE does not actually exist in debug information. +/// It may be used to represent variables that are +/// declared by user, for example, using pointer cast operator. +#[derive(Clone, Copy)] +pub struct VirtualVariableDie { + type_ref: DieRef, +} + +impl VirtualVariableDie { + pub(super) const ANY_NODE: &'static Node = &Node::new_leaf(None); + + /// Create blank virtual variable DIE. + pub fn workpiece() -> Self { + Self { + type_ref: DieRef::Unit(UnitOffset(0)), + } + } + + /// Initialize virtual variable with a concrete type. + /// Return reference to virtual DIE. + pub fn init_with_type<'this, 'dbg>( + &'this mut self, + debugee: &'dbg Debugee, + type_name: &str, + ) -> Result, Error> { + let (debug_info, offset_of_unit, offset_of_die) = debugee + .debug_info_all() + .iter() + .find_map(|&debug_info| { + let (offset_of_unit, offset_of_die) = debug_info.find_type_die_ref(type_name)?; + Some((debug_info, offset_of_unit, offset_of_die)) + }) + .ok_or(TypeNotFound)?; + let unit = debug_info + .find_unit(DebugInfoOffset(offset_of_unit.0 + offset_of_die.0)) + .ok_or(TypeNotFound)?; + + self.type_ref = DieRef::Unit(offset_of_die); + Ok(ContextualDieRef { + debug_info, + unit_idx: unit.idx(), + node: VirtualVariableDie::ANY_NODE, + die: self, + }) + } +} + +impl AsAllocatedData for VirtualVariableDie { + fn name(&self) -> Option<&str> { + None + } + + fn type_ref(&self) -> Option { + Some(self.type_ref) + } + + fn location(&self) -> Option<&Attribute> { + None + } +} diff --git a/src/debugger/watchpoint.rs b/src/debugger/watchpoint.rs index e55bb83..277f5fd 100644 --- a/src/debugger/watchpoint.rs +++ b/src/debugger/watchpoint.rs @@ -8,8 +8,9 @@ use crate::debugger::register::debug::{ BreakCondition, BreakSize, DebugRegisterNumber, HardwareDebugState, }; use crate::debugger::unwind::FrameID; -use crate::debugger::variable::select::{ScopedVariable, SelectExpressionEvaluator, DQE}; -use crate::debugger::variable::{ScalarVariable, SupportedScalar, VariableIR, VariableIdentity}; +use crate::debugger::variable::dqe::Dqe; +use crate::debugger::variable::execute::{DqeExecutor, QueryResult}; +use crate::debugger::variable::value::{ScalarValue, SupportedScalar, Value}; use crate::debugger::Error::Hook; use crate::debugger::{Debugger, Error, ExplorationContext, Tracee}; use crate::{debugger, disable_when_not_stared, weak_error}; @@ -24,9 +25,9 @@ struct ExpressionTarget { /// Original DQE string. source_string: String, /// Address DQE. - dqe: DQE, + dqe: Dqe, /// Last evaluated underlying DQE result. - last_value: Option, + last_value: Option, /// ID of in-focus frame at the time when watchpoint was created. /// Whether `None` when underlying expression has a global or undefined scope. frame_id: Option, @@ -38,8 +39,8 @@ struct ExpressionTarget { } impl ExpressionTarget { - fn underlying_dqe(&self) -> &DQE { - let DQE::Address(ref underlying_dqe) = self.dqe else { + fn underlying_dqe(&self) -> &Dqe { + let Dqe::Address(ref underlying_dqe) = self.dqe else { unreachable!("infallible: watchpoint always contains an address DQE"); }; underlying_dqe @@ -49,12 +50,12 @@ impl ExpressionTarget { #[derive(Debug)] struct AddressTarget { /// Last seen dereferenced value. - /// This is [`VariableIR::Scalar`] with one of u8, u16, u32 or u64 underlying value. - last_value: Option, + /// This is [`Value::Scalar`] with one of u8, u16, u32 or u64 underlying value. + last_value: Option, } impl AddressTarget { - fn refresh_last_value(&mut self, pid: Pid, hw: &HardwareBreakpoint) -> Option { + fn refresh_last_value(&mut self, pid: Pid, hw: &HardwareBreakpoint) -> Option { let read_size = match hw.size { BreakSize::Bytes1 => 1, BreakSize::Bytes2 => 2, @@ -94,8 +95,7 @@ impl AddressTarget { )), ), }; - VariableIR::Scalar(ScalarVariable { - identity: VariableIdentity::no_namespace(Some("data".to_string())), + Value::Scalar(ScalarValue { value: Some(u), type_ident: TypeIdentity::no_namespace(t), type_id: None, @@ -223,20 +223,16 @@ where } impl Watchpoint { - fn evaluate_dqe( - debugger: &Debugger, - expr_source: &str, - dqe: DQE, - ) -> Result { - let expr_evaluator = SelectExpressionEvaluator::new(debugger, dqe); + fn execute_dqe(debugger: &Debugger, dqe: Dqe) -> Result { + let executor = DqeExecutor::new(debugger); // trying to evaluate at variables first, // if a result is empty, try to evaluate at function arguments - let mut evaluation_on_vars_results = expr_evaluator.evaluate()?; + let mut evaluation_on_vars_results = executor.query(&dqe)?; let mut evaluation_on_args_results; - let mut expr_result = match evaluation_on_vars_results.len() { + let expr_result = match evaluation_on_vars_results.len() { 0 => { - evaluation_on_args_results = expr_evaluator.evaluate_on_arguments()?; + evaluation_on_args_results = executor.query_arguments(&dqe)?; match evaluation_on_args_results.len() { 0 => return Err(Error::WatchSubjectNotFound), 1 => evaluation_on_args_results.pop().expect("infallible"), @@ -246,7 +242,6 @@ impl Watchpoint { 1 => evaluation_on_vars_results.pop().expect("infallible"), _ => return Err(Error::WatchpointCollision), }; - expr_result.variable.identity_mut().name = Some(expr_source.to_string()); Ok(expr_result) } @@ -261,14 +256,14 @@ impl Watchpoint { pub fn from_dqe( debugger: &mut Debugger, expr_source: &str, - dqe: DQE, + dqe: Dqe, condition: BreakCondition, ) -> Result<(HardwareDebugState, Self), Error> { // wrap expression with address operation - let dqe = DQE::Address(dqe.boxed()); + let dqe = Dqe::Address(dqe.boxed()); - let address_dqe_result = Self::evaluate_dqe(debugger, expr_source, dqe.clone())?; - let VariableIR::Pointer(ptr) = &address_dqe_result.variable else { + let address_dqe_result = Self::execute_dqe(debugger, dqe.clone())?; + let Value::Pointer(ptr) = address_dqe_result.value() else { unreachable!("infallible: address DQE always return a pointer") }; @@ -285,7 +280,7 @@ impl Watchpoint { let mut end_of_scope_brkpt = None; let mut frame_id = None; - if let Some(ref scope) = address_dqe_result.scope { + if let Some(scope) = address_dqe_result.scope() { // take a current frame id let expl_ctx = debugger.exploration_ctx(); let frame_num = expl_ctx.frame_num(); @@ -386,8 +381,8 @@ impl Watchpoint { companion: end_of_scope_brkpt, }; let underlying_dqe = target.underlying_dqe().clone(); - let var = Self::evaluate_dqe(debugger, expr_source, underlying_dqe) - .map(|ev| ev.variable) + let var = Self::execute_dqe(debugger, underlying_dqe) + .map(|ev| ev.into_value()) .ok(); target.last_value = var; @@ -447,7 +442,7 @@ impl Watchpoint { self.number } - fn last_value(&self) -> Option<&VariableIR> { + fn last_value(&self) -> Option<&Value> { match &self.subject { Subject::Expression(e) => e.last_value.as_ref(), Subject::Address(_) => None, @@ -639,9 +634,9 @@ impl WatchpointRegistry { &mut self, tracee_ctl: &TraceeCtl, breakpoints: &mut BreakpointRegistry, - dqe: DQE, + dqe: Dqe, ) -> Result, Error> { - let needle = DQE::Address(dqe.boxed()); + let needle = Dqe::Address(dqe.boxed()); let Some(to_remove) = self.watchpoints.iter().position(|wp| { if let Subject::Expression(ExpressionTarget { dqe: wp_dqe, .. }) = &wp.subject { &needle == wp_dqe @@ -730,7 +725,7 @@ impl Debugger { pub fn set_watchpoint_on_expr( &mut self, expr_source: &str, - dqe: DQE, + dqe: Dqe, condition: BreakCondition, ) -> Result { disable_when_not_stared!(self); @@ -790,7 +785,7 @@ impl Debugger { /// # Arguments /// /// * `dqe`: DQE - pub fn remove_watchpoint_by_expr(&mut self, dqe: DQE) -> Result, Error> { + pub fn remove_watchpoint_by_expr(&mut self, dqe: Dqe) -> Result, Error> { let breakpoints = &mut self.breakpoints; self.watchpoints .remove_by_dqe(self.debugee.tracee_ctl(), breakpoints, dqe) @@ -821,15 +816,16 @@ impl Debugger { match &wp.subject { Subject::Expression(target) => { let dqe = target.underlying_dqe().clone(); - let expr_str = target.source_string.clone(); let current_tid = self.exploration_ctx().pid_on_focus(); let new_value = match target.frame_id { - None => Watchpoint::evaluate_dqe(self, &expr_str, dqe), + None => { + Watchpoint::execute_dqe(self, dqe).map(|qr| qr.into_value()) + } // frame_id is actual if current tid and expression tid are equals, // otherwise evaluate as is Some(_) if target.tid != current_tid => { - Watchpoint::evaluate_dqe(self, &expr_str, dqe) + Watchpoint::execute_dqe(self, dqe).map(|qr| qr.into_value()) } Some(frame_id) => { let bt = self.backtrace(current_tid)?; @@ -846,12 +842,13 @@ impl Debugger { ); let ctx = ExplorationContext::new(loc, num as u32); call_with_context(self, ctx, |debugger| { - Watchpoint::evaluate_dqe(debugger, &expr_str, dqe) + Watchpoint::execute_dqe(debugger, dqe) + .map(|qr| qr.into_value()) }) } }; - let new_value = new_value.ok().map(|expr| expr.variable); + let new_value = new_value.ok(); let wp_mut = self .watchpoints @@ -876,6 +873,7 @@ impl Debugger { number, place, wp_mut.hw.condition, + Some(&t.source_string), old.as_ref(), t.last_value.as_ref(), false, @@ -905,6 +903,7 @@ impl Debugger { number, place, wp_mut.hw.condition, + None, old.as_ref(), t.last_value.as_ref(), false, @@ -926,12 +925,22 @@ impl Debugger { weak_error!(dwarf.find_place_from_pc(pc.into_global(&self.debugee)?)).flatten(); for wp in watchpoints { + let dqe_string = + if let Subject::Expression(ExpressionTarget { source_string, .. }) = + &wp.subject + { + Some(source_string.as_str()) + } else { + None + }; + self.hooks .on_watchpoint( pc, wp.number(), place.clone(), wp.hw.condition, + dqe_string, wp.last_value(), None, true, diff --git a/src/oracle/builtin/tokio.rs b/src/oracle/builtin/tokio.rs index 6ff6c1e..12ec3cf 100644 --- a/src/oracle/builtin/tokio.rs +++ b/src/oracle/builtin/tokio.rs @@ -1,7 +1,7 @@ use crate::debugger::unwind::{Backtrace, FrameSpan}; -use crate::debugger::variable::select::{Selector, DQE}; -use crate::debugger::variable::{ - Member, ScalarVariable, StructVariable, SupportedScalar, VariableIR, +use crate::debugger::variable::dqe::{Dqe, PointerCast, Selector}; +use crate::debugger::variable::value::{ + Member, PointerValue, ScalarValue, StructValue, SupportedScalar, Value, }; use crate::debugger::CreateTransparentBreakpointRequest; use crate::debugger::{Debugger, Error}; @@ -284,11 +284,11 @@ impl ConsolePlugin for TokioOracle { impl TokioOracle { /// Return underline value of loom `AtomicUsize` structure. - fn extract_value_from_atomic_usize(&self, val: &StructVariable) -> Option { - if let VariableIR::Struct(ref inner) = val.members.first()?.value { - if let VariableIR::Struct(ref value) = inner.members.first()?.value { - if let VariableIR::Struct(ref v) = value.members.first()?.value { - if let VariableIR::Scalar(ref value) = v.members.first()?.value { + fn extract_value_from_atomic_usize(&self, val: &StructValue) -> Option { + if let Value::Struct(ref inner) = val.members.first()?.value { + if let Value::Struct(ref value) = inner.members.first()?.value { + if let Value::Struct(ref v) = value.members.first()?.value { + if let Value::Scalar(ref value) = v.members.first()?.value { if let Some(SupportedScalar::Usize(usize)) = value.value { return Some(usize); } @@ -309,21 +309,21 @@ impl TokioOracle { .filter(|(_, task)| task.dropped_at.is_none()) .for_each(|(_, task)| { if let Some(ptr) = task.ptr { - let var = dbg.read_variable(DQE::Deref( - DQE::PtrCast( + let var = dbg.read_variable(Dqe::Deref( + Dqe::PtrCast(PointerCast::new( ptr as usize, - "*const tokio::runtime::task::core::Header".to_string(), - ) + "*const tokio::runtime::task::core::Header", + )) .boxed(), )); - if let Ok(Some(VariableIR::Struct(header_struct))) = - var.as_ref().map(|v| v.first()) + if let Ok(Some(Value::Struct(header_struct))) = + var.as_ref().map(|v| v.first().map(|v| v.value())) { for member in &header_struct.members { if let Member { field_name, - value: VariableIR::Struct(ref state_member), + value: Value::Struct(ref state_member), } = member { if field_name.as_deref() != Some("state") { @@ -333,7 +333,7 @@ impl TokioOracle { let val = state_member.members.first(); if let Some(Member { - value: VariableIR::Struct(val), + value: Value::Struct(val), .. }) = val { @@ -350,47 +350,51 @@ impl TokioOracle { /// Read `self` function argument, interpret it as a task and return (task_id, task pointer) pair. fn get_header_from_self(dbg: &mut Debugger) -> Result, Error> { - let header_pointer_expr = DQE::Field( - DQE::Field( - DQE::Variable(Selector::by_name("self", true)).boxed(), + let header_pointer_expr = Dqe::Field( + Dqe::Field( + Dqe::Variable(Selector::by_name("self", true)).boxed(), "ptr".to_string(), ) .boxed(), "pointer".to_string(), ); - let header_args = dbg.read_argument(header_pointer_expr.clone())?; - let VariableIR::Pointer(header_pointer) = &header_args[0] else { + let mut header_args = dbg.read_argument(header_pointer_expr)?; + let Some(header) = header_args.pop() else { return Ok(None); }; - let id_offset_args = dbg.read_argument(DQE::Field( - DQE::Deref( - DQE::Field( - DQE::Deref(header_pointer_expr.boxed()).boxed(), - "vtable".to_string(), - ) - .boxed(), - ) - .boxed(), - "id_offset".to_string(), - ))?; + let Value::Pointer(PointerValue { + value: Some(header_ptr), + .. + }) = header.value() + else { + return Ok(None); + }; + let header_ptr = *header_ptr; + + let Some(id_offset) = header.modify_value(|ctx, value| { + let deref = value.deref(ctx)?; + let vtable = deref.field("vtable")?; + let deref = vtable.deref(ctx)?; + deref.field("id_offset") + }) else { + return Ok(None); + }; - let Some(VariableIR::Scalar(ScalarVariable { + let Value::Scalar(ScalarValue { value: Some(SupportedScalar::Usize(id_offset)), .. - })) = &id_offset_args.first() + }) = id_offset.value() else { return Ok(None); }; - if let Some(header_ptr) = header_pointer.value { - let id_addr = header_ptr as usize + *id_offset; + let id_addr = header_ptr as usize + *id_offset; - if let Ok(memory) = dbg.read_memory(id_addr, size_of::()) { - let task_id = u64::from_ne_bytes(memory.try_into().unwrap()); - return Ok(Some((task_id, header_ptr))); - } + if let Ok(memory) = dbg.read_memory(id_addr, size_of::()) { + let task_id = u64::from_ne_bytes(memory.try_into().unwrap()); + return Ok(Some((task_id, header_ptr))); } Ok(None) @@ -415,12 +419,12 @@ impl TokioOracle { } fn on_new(&self, debugger: &mut Debugger) -> Result<(), Error> { - let id_args = debugger.read_argument(DQE::Field( - Box::new(DQE::Variable(Selector::by_name("id", true))), + let id_args = debugger.read_argument(Dqe::Field( + Box::new(Dqe::Variable(Selector::by_name("id", true))), "__0".to_string(), ))?; - if let VariableIR::Scalar(scalar) = &id_args[0] { + if let Value::Scalar(scalar) = id_args[0].value() { if let Some(SupportedScalar::U64(id_value)) = scalar.value { let bt = debugger .backtrace(debugger.exploration_ctx().pid_on_focus()) diff --git a/src/ui/command/arguments.rs b/src/ui/command/arguments.rs index 9858f50..c8f76c7 100644 --- a/src/ui/command/arguments.rs +++ b/src/ui/command/arguments.rs @@ -1,5 +1,5 @@ -use crate::debugger::variable::select::DQE; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::dqe::Dqe; +use crate::debugger::variable::execute::QueryResult; use crate::debugger::Debugger; use crate::ui::command; @@ -12,7 +12,7 @@ impl<'a> Handler<'a> { Self { dbg: debugger } } - pub fn handle(&self, select_expression: DQE) -> command::CommandResult> { + pub fn handle(&self, select_expression: Dqe) -> command::CommandResult> { Ok(self.dbg.read_argument(select_expression)?) } } diff --git a/src/ui/command/mod.rs b/src/ui/command/mod.rs index 60b2944..eff96c5 100644 --- a/src/ui/command/mod.rs +++ b/src/ui/command/mod.rs @@ -25,7 +25,7 @@ pub mod thread; pub mod variables; pub mod watch; -use crate::debugger::variable::select::DQE; +use crate::debugger::variable::dqe::Dqe; use crate::debugger::Error; #[derive(thiserror::Error, Debug)] @@ -43,8 +43,8 @@ pub type CommandResult = Result; /// External commands that can be processed by the debugger. #[derive(Debug, Clone)] pub enum Command { - PrintVariables(DQE), - PrintArguments(DQE), + PrintVariables(Dqe), + PrintArguments(Dqe), PrintBacktrace(backtrace::Command), Continue, Frame(frame::Command), diff --git a/src/ui/command/parser/expression.rs b/src/ui/command/parser/expression.rs index f38ef2a..dc94243 100644 --- a/src/ui/command/parser/expression.rs +++ b/src/ui/command/parser/expression.rs @@ -1,5 +1,5 @@ //! data query expressions parser. -use crate::debugger::variable::select::{Literal, LiteralOrWildcard, Selector, DQE}; +use crate::debugger::variable::dqe::{Dqe, Literal, LiteralOrWildcard, PointerCast, Selector}; use crate::ui::command::parser::{hex, rust_identifier}; use chumsky::prelude::*; use chumsky::Parser; @@ -7,7 +7,7 @@ use std::collections::HashMap; type Err<'a> = extra::Err>; -fn ptr_cast<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> + Clone { +fn ptr_cast<'a>() -> impl Parser<'a, &'a str, Dqe, Err<'a>> + Clone { let op = |c| just(c).padded(); // try to interp any string between brackets as a type @@ -33,7 +33,7 @@ fn ptr_cast<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> + Clone { let type_p = any.delimited_by(op('('), op(')')); type_p .then(hex()) - .map(|(r#type, ptr)| DQE::PtrCast(ptr, r#type.trim().to_string())) + .map(|(r#type, ptr)| Dqe::PtrCast(PointerCast::new(ptr, r#type.trim()))) .labelled("pointer cast") } @@ -123,10 +123,10 @@ fn literal<'a>() -> impl Parser<'a, &'a str, Literal, Err<'a>> + Clone { literal } -pub fn parser<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> { +pub fn parser<'a>() -> impl Parser<'a, &'a str, Dqe, Err<'a>> { let base_selector = rust_identifier() .padded() - .map(|name: &str| DQE::Variable(Selector::by_name(name, false))) + .map(|name: &str| Dqe::Variable(Selector::by_name(name, false))) .or(ptr_cast()); let expr = recursive(|expr| { @@ -142,8 +142,8 @@ pub fn parser<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> { let field_op = op('.') .ignore_then(field) - .map(|field: &str| -> Box DQE> { - Box::new(move |r| DQE::Field(Box::new(r), field.to_string())) + .map(|field: &str| -> Box Dqe> { + Box::new(move |r| Dqe::Field(Box::new(r), field.to_string())) }) .boxed(); @@ -151,8 +151,8 @@ pub fn parser<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> { .padded() .labelled("index value") .delimited_by(op('['), op(']')) - .map(|idx| -> Box DQE> { - Box::new(move |r: DQE| DQE::Index(Box::new(r), idx)) + .map(|idx| -> Box Dqe> { + Box::new(move |r: Dqe| Dqe::Index(Box::new(r), idx)) }) .boxed(); @@ -166,8 +166,8 @@ pub fn parser<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> { .then(mb_usize) .labelled("slice range (start..end)") .delimited_by(op('['), op(']')) - .map(|(from, to)| -> Box DQE> { - Box::new(move |r: DQE| DQE::Slice(Box::new(r), from, to)) + .map(|(from, to)| -> Box Dqe> { + Box::new(move |r: Dqe| Dqe::Slice(Box::new(r), from, to)) }) .boxed(); @@ -177,9 +177,9 @@ pub fn parser<'a>() -> impl Parser<'a, &'a str, DQE, Err<'a>> { ); op('*') - .to(DQE::Deref as fn(_) -> _) - .or(op('&').to(DQE::Address as fn(_) -> _)) - .or(op('~').to(DQE::Canonic as fn(_) -> _)) + .to(Dqe::Deref as fn(_) -> _) + .or(op('&').to(Dqe::Address as fn(_) -> _)) + .or(op('~').to(Dqe::Canonic as fn(_) -> _)) .repeated() .foldr(expr, |op, rhs| op(Box::new(rhs))) }); @@ -195,23 +195,23 @@ mod test { fn test_ptr_cast_parser() { struct TestCase { string: &'static str, - result: Result, + result: Result, } let cases = vec![ TestCase { string: "(*SomeStruct) 0x12345", - result: Ok(DQE::PtrCast(0x12345, "*SomeStruct".to_string())), + result: Ok(Dqe::PtrCast(PointerCast::new(0x12345, "*SomeStruct"))), }, TestCase { string: " ( &u32 )0x12345", - result: Ok(DQE::PtrCast(0x12345, "&u32".to_string())), + result: Ok(Dqe::PtrCast(PointerCast::new(0x12345, "&u32"))), }, TestCase { string: "(*const abc::def::SomeType) 0x123AABCD", - result: Ok(DQE::PtrCast( + result: Ok(Dqe::PtrCast(PointerCast::new( 0x123AABCD, - "*const abc::def::SomeType".to_string(), - )), + "*const abc::def::SomeType", + ))), }, TestCase { string: " ( &u32 )12345", @@ -219,7 +219,7 @@ mod test { }, TestCase { string: "(*const i32)0x007FFFFFFFDC94", - result: Ok(DQE::PtrCast(0x7FFFFFFFDC94, "*const i32".to_string())), + result: Ok(Dqe::PtrCast(PointerCast::new(0x7FFFFFFFDC94, "*const i32"))), }, ]; @@ -379,40 +379,40 @@ mod test { fn test_expr_parsing() { struct TestCase { string: &'static str, - expr: DQE, + expr: Dqe, } let test_cases = vec![ TestCase { string: "var1", - expr: DQE::Variable(Selector::by_name("var1", false)), + expr: Dqe::Variable(Selector::by_name("var1", false)), }, TestCase { string: "*var1", - expr: DQE::Deref(DQE::Variable(Selector::by_name("var1", false)).boxed()), + expr: Dqe::Deref(Dqe::Variable(Selector::by_name("var1", false)).boxed()), }, TestCase { string: "~var1", - expr: DQE::Canonic(DQE::Variable(Selector::by_name("var1", false)).boxed()), + expr: Dqe::Canonic(Dqe::Variable(Selector::by_name("var1", false)).boxed()), }, TestCase { string: "**var1", - expr: DQE::Deref( - DQE::Deref(DQE::Variable(Selector::by_name("var1", false)).boxed()).boxed(), + expr: Dqe::Deref( + Dqe::Deref(Dqe::Variable(Selector::by_name("var1", false)).boxed()).boxed(), ), }, TestCase { string: "~*var1", - expr: DQE::Canonic( - DQE::Deref(DQE::Variable(Selector::by_name("var1", false)).boxed()).boxed(), + expr: Dqe::Canonic( + Dqe::Deref(Dqe::Variable(Selector::by_name("var1", false)).boxed()).boxed(), ), }, TestCase { string: "**var1.field1.field2", - expr: DQE::Deref( - DQE::Deref( - DQE::Field( - DQE::Field( - DQE::Variable(Selector::by_name("var1", false)).boxed(), + expr: Dqe::Deref( + Dqe::Deref( + Dqe::Field( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), ) .boxed(), @@ -425,11 +425,11 @@ mod test { }, TestCase { string: "**(var1.field1.field2)", - expr: DQE::Deref( - DQE::Deref( - DQE::Field( - DQE::Field( - DQE::Variable(Selector::by_name("var1", false)).boxed(), + expr: Dqe::Deref( + Dqe::Deref( + Dqe::Field( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), ) .boxed(), @@ -442,10 +442,10 @@ mod test { }, TestCase { string: "(**var1).field1.field2", - expr: DQE::Field( - DQE::Field( - DQE::Deref( - DQE::Deref(DQE::Variable(Selector::by_name("var1", false)).boxed()) + expr: Dqe::Field( + Dqe::Field( + Dqe::Deref( + Dqe::Deref(Dqe::Variable(Selector::by_name("var1", false)).boxed()) .boxed(), ) .boxed(), @@ -457,13 +457,13 @@ mod test { }, TestCase { string: "*(*(var1.field1)).field2[1][2]", - expr: DQE::Deref( - DQE::Index( - DQE::Index( - DQE::Field( - DQE::Deref( - DQE::Field( - DQE::Variable(Selector::by_name("var1", false)).boxed(), + expr: Dqe::Deref( + Dqe::Index( + Dqe::Index( + Dqe::Field( + Dqe::Deref( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), ) .boxed(), @@ -482,9 +482,9 @@ mod test { }, TestCase { string: "var1.field1[5..]", - expr: DQE::Slice( - DQE::Field( - DQE::Variable(Selector::by_name("var1", false)).boxed(), + expr: Dqe::Slice( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), ) .boxed(), @@ -494,9 +494,9 @@ mod test { }, TestCase { string: "var1.field1[..5]", - expr: DQE::Slice( - DQE::Field( - DQE::Variable(Selector::by_name("var1", false)).boxed(), + expr: Dqe::Slice( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), ) .boxed(), @@ -506,9 +506,9 @@ mod test { }, TestCase { string: "var1.field1[5..5]", - expr: DQE::Slice( - DQE::Field( - DQE::Variable(Selector::by_name("var1", false)).boxed(), + expr: Dqe::Slice( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), ) .boxed(), @@ -518,9 +518,9 @@ mod test { }, TestCase { string: "var1.field1[..]", - expr: DQE::Slice( - DQE::Field( - DQE::Variable(Selector::by_name("var1", false)).boxed(), + expr: Dqe::Slice( + Dqe::Field( + Dqe::Variable(Selector::by_name("var1", false)).boxed(), "field1".to_string(), ) .boxed(), @@ -530,9 +530,9 @@ mod test { }, TestCase { string: "enum1.0.a", - expr: DQE::Field( - DQE::Field( - DQE::Variable(Selector::by_name("enum1", false)).boxed(), + expr: Dqe::Field( + Dqe::Field( + Dqe::Variable(Selector::by_name("enum1", false)).boxed(), "0".to_string(), ) .boxed(), @@ -541,32 +541,34 @@ mod test { }, TestCase { string: "(*mut SomeType)0x123AABCD", - expr: DQE::PtrCast(0x123AABCD, "*mut SomeType".to_string()), + expr: Dqe::PtrCast(PointerCast::new(0x123AABCD, "*mut SomeType")), }, TestCase { string: "(&abc::def::SomeType)0x123AABCD", - expr: DQE::PtrCast(0x123AABCD, "&abc::def::SomeType".to_string()), + expr: Dqe::PtrCast(PointerCast::new(0x123AABCD, "&abc::def::SomeType")), }, TestCase { string: "(*const abc::def::SomeType) 0x123AABCD", - expr: DQE::PtrCast(0x123AABCD, "*const abc::def::SomeType".to_string()), + expr: Dqe::PtrCast(PointerCast::new(0x123AABCD, "*const abc::def::SomeType")), }, TestCase { string: "*((*const abc::def::SomeType) 0x123AABCD)", - expr: DQE::Deref( - DQE::PtrCast(0x123AABCD, "*const abc::def::SomeType".to_string()).boxed(), + expr: Dqe::Deref( + Dqe::PtrCast(PointerCast::new(0x123AABCD, "*const abc::def::SomeType")).boxed(), ), }, TestCase { string: "*(*const i32)0x007FFFFFFFDC94", - expr: DQE::Deref(DQE::PtrCast(0x7FFFFFFFDC94, "*const i32".to_string()).boxed()), + expr: Dqe::Deref( + Dqe::PtrCast(PointerCast::new(0x7FFFFFFFDC94, "*const i32")).boxed(), + ), }, TestCase { string: "var.arr[0].some_val", - expr: DQE::Field( - DQE::Index( - DQE::Field( - DQE::Variable(Selector::by_name("var", false)).boxed(), + expr: Dqe::Field( + Dqe::Index( + Dqe::Field( + Dqe::Variable(Selector::by_name("var", false)).boxed(), "arr".to_string(), ) .boxed(), @@ -578,12 +580,12 @@ mod test { }, TestCase { string: "arr[0][..][1..][0].some_val", - expr: DQE::Field( - DQE::Index( - DQE::Slice( - DQE::Slice( - DQE::Index( - DQE::Variable(Selector::by_name("arr", false)).boxed(), + expr: Dqe::Field( + Dqe::Index( + Dqe::Slice( + Dqe::Slice( + Dqe::Index( + Dqe::Variable(Selector::by_name("arr", false)).boxed(), Literal::Int(0), ) .boxed(), @@ -603,12 +605,12 @@ mod test { }, TestCase { string: "map[\"key\"][-5][1.1][false][0x12]", - expr: DQE::Index( - DQE::Index( - DQE::Index( - DQE::Index( - DQE::Index( - DQE::Variable(Selector::by_name("map", false)).boxed(), + expr: Dqe::Index( + Dqe::Index( + Dqe::Index( + Dqe::Index( + Dqe::Index( + Dqe::Variable(Selector::by_name("map", false)).boxed(), Literal::String("key".to_string()), ) .boxed(), @@ -626,21 +628,21 @@ mod test { }, TestCase { string: "map[Some(true)]", - expr: DQE::Index( - DQE::Variable(Selector::by_name("map", false)).boxed(), + expr: Dqe::Index( + Dqe::Variable(Selector::by_name("map", false)).boxed(), Literal::EnumVariant("Some".to_string(), Some(Box::new(Literal::Bool(true)))), ), }, TestCase { string: "&a", - expr: DQE::Address(DQE::Variable(Selector::by_name("a", false)).boxed()), + expr: Dqe::Address(Dqe::Variable(Selector::by_name("a", false)).boxed()), }, TestCase { string: "&*a.b", - expr: DQE::Address( - DQE::Deref( - DQE::Field( - DQE::Variable(Selector::by_name("a", false)).boxed(), + expr: Dqe::Address( + Dqe::Deref( + Dqe::Field( + Dqe::Variable(Selector::by_name("a", false)).boxed(), "b".to_string(), ) .boxed(), @@ -650,8 +652,8 @@ mod test { }, TestCase { string: "&&(*i32)0x123", - expr: DQE::Address( - DQE::Address(DQE::PtrCast(0x123, "*i32".to_string()).boxed()).boxed(), + expr: Dqe::Address( + Dqe::Address(Dqe::PtrCast(PointerCast::new(0x123, "*i32")).boxed()).boxed(), ), }, ]; diff --git a/src/ui/command/parser/mod.rs b/src/ui/command/parser/mod.rs index 3053caf..9f387b3 100644 --- a/src/ui/command/parser/mod.rs +++ b/src/ui/command/parser/mod.rs @@ -4,7 +4,8 @@ use super::r#break::BreakpointIdentity; use super::{frame, memory, register, source_code, thread, watch, Command, CommandError}; use super::{r#break, CommandResult}; use crate::debugger::register::debug::BreakCondition; -use crate::debugger::variable::select::{Selector, DQE}; +use crate::debugger::variable::dqe::Dqe; +use crate::debugger::variable::dqe::Selector; use crate::ui::command::watch::WatchpointIdentity; use ariadne::{Color, Fmt, Label, Report, ReportKind, Source}; use chumsky::error::{Rich, RichPattern, RichReason}; @@ -246,7 +247,7 @@ impl Command { let print_local_vars = op_w_arg(VAR_COMMAND) .then(sub_op(VAR_LOCAL_KEY)) - .map(|_| Command::PrintVariables(DQE::Variable(Selector::Any))); + .map(|_| Command::PrintVariables(Dqe::Variable(Selector::Any))); let print_var = op_w_arg(VAR_COMMAND) .ignore_then(expression::parser()) .map(Command::PrintVariables); @@ -255,7 +256,7 @@ impl Command { let print_all_args = op_w_arg(ARG_COMMAND) .then(sub_op(ARG_ALL_KEY)) - .map(|_| Command::PrintArguments(DQE::Variable(Selector::Any))); + .map(|_| Command::PrintArguments(Dqe::Variable(Selector::Any))); let print_arg = op_w_arg(ARG_COMMAND) .ignore_then(expression::parser()) .map(Command::PrintArguments); @@ -542,7 +543,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::PrintVariables(DQE::Variable(Selector::Any)) + Command::PrintVariables(Dqe::Variable(Selector::Any)) )); }, }, @@ -551,7 +552,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::PrintVariables(DQE::Deref(_)) + Command::PrintVariables(Dqe::Deref(_)) )); }, }, @@ -560,7 +561,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::PrintVariables(DQE::Variable(Selector::Name { var_name, .. })) if var_name == "locals_var" + Command::PrintVariables(Dqe::Variable(Selector::Name { var_name, .. })) if var_name == "locals_var" )); }, }, @@ -589,7 +590,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::PrintArguments(DQE::Variable(Selector::Any)) + Command::PrintArguments(Dqe::Variable(Selector::Any)) )); }, }, @@ -598,7 +599,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::PrintArguments(DQE::Variable(Selector::Name { var_name, .. })) if var_name == "all_arg" + Command::PrintArguments(Dqe::Variable(Selector::Name { var_name, .. })) if var_name == "all_arg" )); }, }, @@ -790,7 +791,7 @@ fn test_parser() { assert!(matches!( result.unwrap(), Command::Watchpoint(watch::Command::Add( - WatchpointIdentity::DQE(source, DQE::Variable(Selector::Name {var_name, ..})), BreakCondition::DataWrites + WatchpointIdentity::DQE(source, Dqe::Variable(Selector::Name {var_name, ..})), BreakCondition::DataWrites )) if var_name == "var1" && source == "var1" )); }, @@ -801,7 +802,7 @@ fn test_parser() { assert!(matches!( result.unwrap(), Command::Watchpoint(watch::Command::Add( - WatchpointIdentity::DQE(source, DQE::Variable(Selector::Name {var_name, ..})), BreakCondition::DataReadsWrites + WatchpointIdentity::DQE(source, Dqe::Variable(Selector::Name {var_name, ..})), BreakCondition::DataReadsWrites )) if var_name == "var1" && source == "var1" )); }, @@ -816,7 +817,7 @@ fn test_parser() { assert!(matches!( result.unwrap(), Command::Watchpoint(watch::Command::Add( - WatchpointIdentity::DQE(source, DQE::Variable(Selector::Name {var_name, ..})), BreakCondition::DataWrites + WatchpointIdentity::DQE(source, Dqe::Variable(Selector::Name {var_name, ..})), BreakCondition::DataWrites )) if var_name == "ns1::ns2::var1" && source == "ns1::ns2::var1" )); }, @@ -844,7 +845,7 @@ fn test_parser() { command_matcher: |result| { assert!(matches!( result.unwrap(), - Command::Watchpoint(watch::Command::Remove(WatchpointIdentity::DQE(source, DQE::Variable(Selector::Name {var_name, ..})))) if var_name == "var1" && source == "var1" + Command::Watchpoint(watch::Command::Remove(WatchpointIdentity::DQE(source, Dqe::Variable(Selector::Name {var_name, ..})))) if var_name == "var1" && source == "var1" )); }, }, diff --git a/src/ui/command/variables.rs b/src/ui/command/variables.rs index c6ee968..8134c12 100644 --- a/src/ui/command/variables.rs +++ b/src/ui/command/variables.rs @@ -1,5 +1,5 @@ -use crate::debugger::variable::select::DQE; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::dqe::Dqe; +use crate::debugger::variable::execute::QueryResult; use crate::debugger::Debugger; use crate::ui::command; @@ -12,7 +12,7 @@ impl<'a> Handler<'a> { Self { dbg: debugger } } - pub fn handle(self, select_expression: DQE) -> command::CommandResult> { + pub fn handle(self, select_expression: Dqe) -> command::CommandResult>> { Ok(self.dbg.read_variable(select_expression)?) } } diff --git a/src/ui/command/watch.rs b/src/ui/command/watch.rs index fee50b5..3228221 100644 --- a/src/ui/command/watch.rs +++ b/src/ui/command/watch.rs @@ -1,13 +1,13 @@ use crate::debugger::address::RelocatedAddress; use crate::debugger::register::debug::{BreakCondition, BreakSize}; -use crate::debugger::variable::select::DQE; +use crate::debugger::variable::dqe::Dqe; use crate::debugger::Debugger; use crate::debugger::Error; use crate::debugger::WatchpointView; #[derive(Debug, Clone)] pub enum WatchpointIdentity { - DQE(String, DQE), + DQE(String, Dqe), Address(usize, u8), Number(u32), } diff --git a/src/ui/console/hook.rs b/src/ui/console/hook.rs index 028c5ad..207a0ff 100644 --- a/src/ui/console/hook.rs +++ b/src/ui/console/hook.rs @@ -1,12 +1,12 @@ use crate::debugger::address::RelocatedAddress; use crate::debugger::register::debug::BreakCondition; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::value::Value; use crate::debugger::PlaceDescriptor; use crate::debugger::{EventHook, FunctionDie}; use crate::ui::console::file::FileView; use crate::ui::console::print::style::{AddressView, FilePathView, FunctionNameView, KeywordView}; use crate::ui::console::print::ExternalPrinter; -use crate::ui::console::variable::render_variable; +use crate::ui::console::variable::render_value; use crate::version; use log::warn; use nix::sys::signal::Signal; @@ -73,18 +73,22 @@ impl EventHook for TerminalHook { num: u32, mb_place: Option, cond: BreakCondition, - old: Option<&VariableIR>, - new: Option<&VariableIR>, + dqe_string: Option<&str>, + old: Option<&Value>, + new: Option<&Value>, end_of_scope: bool, ) -> anyhow::Result<()> { + let source_dqe = dqe_string + .map(|dqe| format!(" (expr: {dqe})")) + .unwrap_or_default(); let msg = if end_of_scope { format!( - "Watchpoint {num} end of scope (and it will be removed)\n{}:", + "Watchpoint {num}{source_dqe} end of scope (and it will be removed)\n{}:", AddressView::from(pc) ) } else { format!( - "Hit watchpoint {num} ({cond}) at {}:", + "Hit watchpoint {num}{source_dqe} ({cond}) at {}:", AddressView::from(pc) ) }; @@ -101,16 +105,16 @@ impl EventHook for TerminalHook { if cond == BreakCondition::DataReadsWrites && old == new { if let Some(old) = old { - let val = render_variable(old)?; + let val = render_value(old); self.printer.println(format!("value: {val}")); } } else { if let Some(old) = old { - let old = render_variable(old)?; + let old = render_value(old); self.printer.println(format!("old value: {old}")); } if let Some(new) = new { - let new = render_variable(new)?; + let new = render_value(new); self.printer.println(format!("new value: {new}")); } } diff --git a/src/ui/console/mod.rs b/src/ui/console/mod.rs index 38715a9..68ee2d3 100644 --- a/src/ui/console/mod.rs +++ b/src/ui/console/mod.rs @@ -1,6 +1,6 @@ use crate::debugger; use crate::debugger::process::{Child, Installed}; -use crate::debugger::variable::select::{Selector, DQE}; +use crate::debugger::variable::dqe::{Dqe, Selector}; use crate::debugger::{Debugger, DebuggerBuilder}; use crate::ui::command::arguments::Handler as ArgumentsHandler; use crate::ui::command::backtrace::Handler as BacktraceHandler; @@ -362,10 +362,10 @@ impl AppLoop { fn update_completer_variables(&self) -> anyhow::Result<()> { let vars = self .debugger - .read_variable_names(DQE::Variable(Selector::Any))?; + .read_variable_names(Dqe::Variable(Selector::Any))?; let args = self .debugger - .read_argument_names(DQE::Variable(Selector::Any))?; + .read_argument_names(Dqe::Variable(Selector::Any))?; let mut completer = self.completer.lock().unwrap(); completer.replace_local_var_hints(vars); diff --git a/src/ui/console/variable.rs b/src/ui/console/variable.rs index 45a053c..ed5696e 100644 --- a/src/ui/console/variable.rs +++ b/src/ui/console/variable.rs @@ -1,21 +1,23 @@ use crate::debugger::address::RelocatedAddress; -use crate::debugger::variable::render::{RenderRepr, ValueLayout}; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::execute::{QueryResult, QueryResultKind}; +use crate::debugger::variable::render::{RenderValue, ValueLayout}; +use crate::debugger::variable::value::Value; use crate::ui::syntax; use crate::ui::syntax::StylizedLine; use syntect::util::as_24_bit_terminal_escaped; const TAB: &str = "\t"; -pub fn render_variable(var: &VariableIR) -> anyhow::Result { +pub fn render_variable(var: &QueryResult) -> anyhow::Result { let syntax_renderer = syntax::rust_syntax_renderer(); let mut line_renderer = syntax_renderer.line_renderer(); - let prefix = var - .name() - .map(|name| format!("{name} = ")) - .unwrap_or_default(); + let prefix = if var.kind() == QueryResultKind::Root && var.identity().name.is_some() { + format!("{} = ", var.identity()) + } else { + String::default() + }; - let var_as_string = format!("{prefix}{}", render_variable_ir(var, 0)); + let var_as_string = format!("{prefix}{}", render_value(var.value())); Ok(var_as_string .lines() .map(|l| -> anyhow::Result { @@ -32,29 +34,33 @@ pub fn render_variable(var: &VariableIR) -> anyhow::Result { .join("\n")) } -pub fn render_variable_ir(view: &VariableIR, depth: usize) -> String { - match view.value() { - Some(value) => match value { - ValueLayout::PreRendered(rendered_value) => match view { - VariableIR::CEnum(_) => format!("{}::{}", view.r#type().name_fmt(), rendered_value), - _ => format!("{}({})", view.r#type().name_fmt(), rendered_value), +pub fn render_value(value: &Value) -> String { + render_value_inner(value, 0) +} + +fn render_value_inner(value: &Value, depth: usize) -> String { + match value.value_layout() { + Some(layout) => match layout { + ValueLayout::PreRendered(rendered_value) => match value { + Value::CEnum(_) => format!("{}::{}", value.r#type().name_fmt(), rendered_value), + _ => format!("{}({})", value.r#type().name_fmt(), rendered_value), }, ValueLayout::Referential(addr) => { format!( "{} [{}]", - view.r#type().name_fmt(), + value.r#type().name_fmt(), RelocatedAddress::from(addr as usize) ) } ValueLayout::Wrapped(val) => { format!( "{}::{}", - view.r#type().name_fmt(), - render_variable_ir(val, depth) + value.r#type().name_fmt(), + render_value_inner(val, depth) ) } ValueLayout::Structure(members) => { - let mut render = format!("{} {{", view.r#type().name_fmt()); + let mut render = format!("{} {{", value.r#type().name_fmt()); let tabs = TAB.repeat(depth + 1); @@ -63,14 +69,14 @@ pub fn render_variable_ir(view: &VariableIR, depth: usize) -> String { render = format!( "{render}{tabs}{}: {}", member.field_name.as_deref().unwrap_or_default(), - render_variable_ir(&member.value, depth + 1) + render_value_inner(&member.value, depth + 1) ); } format!("{render}\n{}}}", TAB.repeat(depth)) } ValueLayout::Map(kv_children) => { - let mut render = format!("{} {{", view.r#type().name_fmt()); + let mut render = format!("{} {{", value.r#type().name_fmt()); let tabs = TAB.repeat(depth + 1); @@ -78,15 +84,15 @@ pub fn render_variable_ir(view: &VariableIR, depth: usize) -> String { render = format!("{render}\n"); render = format!( "{render}{tabs}{}: {}", - render_variable_ir(&kv.0, depth + 1), - render_variable_ir(&kv.1, depth + 1) + render_value_inner(&kv.0, depth + 1), + render_value_inner(&kv.1, depth + 1) ); } format!("{render}\n{}}}", TAB.repeat(depth)) } ValueLayout::IndexedList(items) => { - let mut render = format!("{} {{", view.r#type().name_fmt()); + let mut render = format!("{} {{", value.r#type().name_fmt()); let tabs = TAB.repeat(depth + 1); @@ -95,25 +101,25 @@ pub fn render_variable_ir(view: &VariableIR, depth: usize) -> String { render = format!( "{render}{tabs}{}: {}", item.index, - render_variable_ir(&item.value, depth + 1) + render_value_inner(&item.value, depth + 1) ); } format!("{render}\n{}}}", TAB.repeat(depth)) } ValueLayout::NonIndexedList(values) => { - let mut render = format!("{} {{", view.r#type().name_fmt()); + let mut render = format!("{} {{", value.r#type().name_fmt()); let tabs = TAB.repeat(depth + 1); for val in values { render = format!("{render}\n"); - render = format!("{render}{tabs}{}", render_variable_ir(val, depth + 1)); + render = format!("{render}{tabs}{}", render_value_inner(val, depth + 1)); } format!("{render}\n{}}}", TAB.repeat(depth)) } }, - None => format!("{}(unknown)", view.r#type().name_fmt()), + None => format!("{}(unknown)", value.r#type().name_fmt()), } } diff --git a/src/ui/tui/app/port.rs b/src/ui/tui/app/port.rs index 20b4bc7..2c10425 100644 --- a/src/ui/tui/app/port.rs +++ b/src/ui/tui/app/port.rs @@ -1,6 +1,6 @@ use crate::debugger::address::RelocatedAddress; use crate::debugger::register::debug::BreakCondition; -use crate::debugger::variable::VariableIR; +use crate::debugger::variable::value::Value; use crate::debugger::{EventHook, FunctionDie, PlaceDescriptor}; use crate::ui::tui::output::OutputLine; use crate::ui::tui::proto::ClientExchanger; @@ -14,7 +14,7 @@ use std::sync::{Arc, Mutex}; use tuirealm::listener::{ListenerResult, Poll}; use tuirealm::Event; -impl PartialOrd for VariableIR { +impl PartialOrd for Value { fn partial_cmp(&self, _: &Self) -> Option { None } @@ -36,8 +36,8 @@ pub enum UserEvent { file: Option, line: Option, cond: BreakCondition, - old_value: Option, - new_value: Option, + old_value: Option, + new_value: Option, end_of_scope: bool, }, Step { @@ -148,8 +148,9 @@ impl EventHook for TuiHook { num: u32, place: Option, cond: BreakCondition, - old: Option<&VariableIR>, - new: Option<&VariableIR>, + _: Option<&str>, + old: Option<&Value>, + new: Option<&Value>, end_of_scope: bool, ) -> anyhow::Result<()> { self.event_queue diff --git a/src/ui/tui/components/variables.rs b/src/ui/tui/components/variables.rs index aece35d..b1d0bf8 100644 --- a/src/ui/tui/components/variables.rs +++ b/src/ui/tui/components/variables.rs @@ -1,7 +1,7 @@ use crate::debugger::register::debug::BreakCondition; -use crate::debugger::variable::render::{RenderRepr, ValueLayout}; -use crate::debugger::variable::select::{Literal, Selector, DQE}; -use crate::debugger::variable::{select, VariableIR}; +use crate::debugger::variable::dqe::{Dqe, Selector}; +use crate::debugger::variable::execute::{QueryResult, QueryResultKind}; +use crate::debugger::variable::render::{RenderValue, ValueLayout}; use crate::ui; use crate::ui::syntax::StylizedLine; use crate::ui::tui::app::port::UserEvent; @@ -38,9 +38,7 @@ fn render_var_inner( let mut line_renderer = syntax_renderer.line_renderer(); let line = match (value, name) { - (None, None) => { - format!("{typ}") - } + (None, None) => typ.to_string(), (Some(val), None) => { format!("{typ}({val})") } @@ -72,197 +70,223 @@ fn render_var_def(name: Option<&str>, typ: &str) -> anyhow::Result render_var_inner(name, typ, None) } -impl Variables { - fn node_from_var( - &self, - recursion: u32, - node_name: &str, - name: Option<&str>, - val: &VariableIR, - select_path: Option, - ) -> Node> { - let typ = val.r#type().name_fmt(); - - // recursion guard - if recursion >= MAX_RECURSION { - return Node::new( - node_name.to_string(), - render_var(name, typ, "...").expect("should be rendered"), - ); - } +fn node_from_var2( + recursion: u32, + node_name: &str, + name: Option<&str>, + qr: QueryResult, +) -> Node> { + let ty = qr.value().r#type().name_fmt(); + + // recursion guard + if recursion >= MAX_RECURSION { + return Node::new( + node_name.to_string(), + render_var(None, ty, "...").expect("should be rendered"), + ); + } - match val.value() { - None => Node::new( + match qr.value().value_layout() { + None => Node::new( + node_name.to_string(), + render_var(None, ty, "???").expect("should be rendered"), + ), + Some(layout) => match layout { + ValueLayout::PreRendered(val) => Node::new( node_name.to_string(), - render_var(name, typ, "???").expect("should be rendered"), + render_var(name, ty, &val).expect("should be rendered"), ), - Some(layout) => match layout { - ValueLayout::PreRendered(val) => Node::new( + ValueLayout::Referential(addr) => { + let value = format!("{addr:p}"); + let mut node = Node::new( node_name.to_string(), - render_var(name, typ, &val).expect("should be rendered"), - ), - ValueLayout::Referential(addr) => { - let value = format!("{addr:p}"); - let mut node = Node::new( - node_name.to_string(), - render_var(name, typ, &value).expect("should be rendered"), - ); + render_var(name, ty, &value).expect("should be rendered"), + ); - if let Some(path) = select_path { - let deref_expr = DQE::Deref(Box::new(path)); - - let variables = { - let deref_expr = deref_expr.clone(); - self.exchanger - .request_sync(|dbg| { - let handler = command::variables::Handler::new(dbg); - handler.handle(deref_expr) - }) - .expect("messaging enabled") - }; - - if let Ok(variables) = variables { - if let Some(var) = variables.first() { - let deref_node = self.node_from_var( - recursion + 1, - format!("{node_name}_deref").as_str(), - Some("*"), - var, - Some(deref_expr), - ); - node.add_child(deref_node); - } - } - } + let qr = qr.modify_value(|ctx, val| val.deref(ctx)); - node - } - ValueLayout::Wrapped(other) => { - let mut node = Node::new( - node_name.to_string(), - render_var_def(name, typ).expect("should be rendered"), + if let Some(qr) = qr { + let deref_node = node_from_var2( + recursion + 1, + format!("{node_name}_deref").as_str(), + Some("*"), + qr, ); - node.add_child(self.node_from_var( + node.add_child(deref_node); + } + + node + } + ValueLayout::Wrapped(inner) => { + let mut node = Node::new( + node_name.to_string(), + render_var_def(name, ty).expect("should be rendered"), + ); + let qr = qr + .clone() + .modify_value(|_, _| Some(inner.clone())) + .expect("should be `Some`"); + + node.add_child(node_from_var2( + recursion + 1, + format!("{node_name}_1").as_str(), + None, + qr, + )); + + node + } + ValueLayout::Structure(members) => { + let mut node = Node::new( + node_name.to_string(), + render_var_def(name, ty).expect("should be rendered"), + ); + for (i, member) in members.iter().enumerate() { + let member_var = qr + .clone() + .modify_value(|_, _| Some(member.value.clone())) + .expect("should be `Some`"); + + node.add_child(node_from_var2( recursion + 1, - format!("{node_name}_1").as_str(), - None, - other, - select_path, + format!("{node_name}_{i}").as_str(), + member.field_name.as_deref(), + member_var, )); - node - } - ValueLayout::Structure(members) => { - let mut node = Node::new( - node_name.to_string(), - render_var_def(name, typ).expect("should be rendered"), - ); - for (i, member) in members.iter().enumerate() { - node.add_child(self.node_from_var( - recursion + 1, - format!("{node_name}_{i}").as_str(), - member.field_name.as_deref(), - &member.value, - select_path.clone().map(|expr| { - DQE::Field( - Box::new(expr), - member.field_name.as_deref().unwrap_or_default().to_string(), - ) - }), - )); - } - node } - ValueLayout::Map(kvs) => { - let mut node = Node::new( - node_name.to_string(), - render_var_def(name, typ).expect("should be rendered"), - ); - for (i, (key, val)) in kvs.iter().enumerate() { - let mut kv_pair = Node::new( - format!("{node_name}_kv_{i}"), - vec![TextSpan::new(format!("kv {i}"))], - ); - - kv_pair.add_child(self.node_from_var( - recursion + 1, - format!("{node_name}_kv_{i}_key").as_str(), - Some("key"), - key, - // currently no way to use expressions with keys - None, - )); - - kv_pair.add_child(self.node_from_var( - recursion + 1, - format!("{node_name}_kv_{i}_val").as_str(), - Some("value"), - val, - // TODO works only if key is a String or &str, - // need better support of field expr on maps - select_path.clone().map(|expr| { - DQE::Field(Box::new(expr), key.name().unwrap_or_default()) - }), - )); - node.add_child(kv_pair); - } - node + node + } + ValueLayout::IndexedList(items) => { + let mut node = Node::new( + node_name.to_string(), + render_var_def(name, ty).expect("should be rendered"), + ); + for (i, item) in items.iter().enumerate() { + let item_var = qr + .clone() + .modify_value(|_, _| Some(item.value.clone())) + .expect("should be `Some`"); + + node.add_child(node_from_var2( + recursion + 1, + format!("{node_name}_{i}").as_str(), + Some(&format!("{}", item.index)), + item_var, + )); } - ValueLayout::IndexedList(items) => { - let mut node = Node::new( - node_name.to_string(), - render_var_def(name, typ).expect("should be rendered"), - ); - for (i, item) in items.iter().enumerate() { - let el_path = select_path - .clone() - .map(|expr| DQE::Index(Box::new(expr), Literal::Int(item.index))); - - node.add_child(self.node_from_var( - recursion + 1, - format!("{node_name}_{i}").as_str(), - Some(&format!("{}", item.index)), - &item.value, - el_path, - )); - } - node + node + } + ValueLayout::NonIndexedList(items) => { + let mut node = Node::new( + node_name.to_string(), + render_var_def(name, ty).expect("should be rendered"), + ); + for (i, value) in items.iter().enumerate() { + let item_var = qr + .clone() + .modify_value(|_, _| Some(value.clone())) + .expect("should be `Some`"); + + node.add_child(node_from_var2( + recursion + 1, + format!("{node_name}_{i}").as_str(), + None, + item_var, + )); } - ValueLayout::NonIndexedList(values) => { - let mut node = Node::new( - node_name.to_string(), - render_var_def(name, typ).expect("should be rendered"), + node + } + ValueLayout::Map(kvs) => { + let mut node = Node::new( + node_name.to_string(), + render_var_def(name, ty).expect("should be rendered"), + ); + for (i, (key, _val)) in kvs.iter().enumerate() { + let mut kv_pair = Node::new( + format!("{node_name}_kv_{i}"), + vec![TextSpan::new(format!("kv {i}"))], ); - for (i, value) in values.iter().enumerate() { - node.add_child(self.node_from_var( - recursion + 1, - format!("{node_name}_{i}").as_str(), - None, - value, - None, - )); + + let key_var = qr + .clone() + .modify_value(|_, _| Some(key.clone())) + .expect("should be `Some`"); + let key_literal = key_var.value().as_literal(); + + kv_pair.add_child(node_from_var2( + recursion + 1, + format!("{node_name}_kv_{i}_key").as_str(), + Some("key"), + key_var, + )); + + if let Some(ref key_literal) = key_literal { + let value_var = qr.clone(); + let value_var = value_var.modify_value(|_, value| value.index(key_literal)); + + if let Some(value_var) = value_var { + kv_pair.add_child(node_from_var2( + recursion + 1, + format!("{node_name}_kv_{i}_val").as_str(), + Some("value"), + value_var, + )); + } } - node + node.add_child(kv_pair); } - }, - } + node + } + }, } +} +impl Variables { fn update(&mut self) { - let Ok(variables) = self.exchanger.request_sync(|dbg| { - let expr = select::DQE::Variable(Selector::Any); - let vars = command::variables::Handler::new(dbg) - .handle(expr) - .unwrap_or_default(); - vars + let Ok(vars_node) = self.exchanger.request_sync(|dbg| { + let expr = Dqe::Variable(Selector::Any); + let handler = command::variables::Handler::new(dbg); + let vars = handler.handle(expr).unwrap_or_default(); + + let mut vars_node = + Node::new("variables".to_string(), vec![TextSpan::new("variables")]); + + for (i, var) in vars.into_iter().enumerate() { + let node_name = format!("var_{i}"); + let name = if var.kind() == QueryResultKind::Root && var.identity().name.is_some() { + Some(var.identity().to_string()) + } else { + None + }; + + let var_node = node_from_var2(0, &node_name, name.as_deref(), var); + vars_node.add_child(var_node); + } + + vars_node }) else { return; }; - let Ok(arguments) = self.exchanger.request_sync(|dbg| { - let expr = select::DQE::Variable(Selector::Any); - let args = command::arguments::Handler::new(dbg) - .handle(expr) - .unwrap_or_default(); - args + + let Ok(args_node) = self.exchanger.request_sync(|dbg| { + let expr = Dqe::Variable(Selector::Any); + let handler = command::arguments::Handler::new(dbg); + let args = handler.handle(expr).unwrap_or_default(); + + let mut args_node = + Node::new("arguments".to_string(), vec![TextSpan::new("arguments")]); + for (i, arg) in args.into_iter().enumerate() { + let node_name = format!("arg_{i}"); + let name = if arg.kind() == QueryResultKind::Root && arg.identity().name.is_some() { + Some(arg.identity().to_string()) + } else { + None + }; + + let var_node = node_from_var2(0, &node_name, name.as_deref(), arg); + args_node.add_child(var_node); + } + args_node }) else { return; }; @@ -272,48 +296,20 @@ impl Variables { vec![TextSpan::new("arguments and variables")], ); - let mut args_node = Node::new("arguments".to_string(), vec![TextSpan::new("arguments")]); - for (i, arg) in arguments.iter().enumerate() { - let node_name = format!("arg_{i}"); - let var_node = self.node_from_var( - 0, - node_name.as_str(), - arg.name().as_deref(), - arg, - Some(DQE::Variable(Selector::by_name( - arg.name().unwrap_or_default(), - false, - ))), - ); - args_node.add_child(var_node); - } - root.add_child(args_node); + let vars_count = vars_node.children().len(); + let args_count = args_node.children().len(); - let mut vars_node = Node::new("variables".to_string(), vec![TextSpan::new("variables")]); - for (i, var) in variables.iter().enumerate() { - let node_name = format!("var_{i}"); - let var_node = self.node_from_var( - 0, - node_name.as_str(), - var.name().as_deref(), - var, - Some(DQE::Variable(Selector::by_name( - var.name().unwrap_or_default(), - true, - ))), - ); - vars_node.add_child(var_node); - } + root.add_child(args_node); root.add_child(vars_node); self.component.set_tree(Tree::new(root)); - if !variables.is_empty() { + if vars_count != 0 { self.component.attr( Attribute::Custom(TREE_INITIAL_NODE), AttrValue::String("var_0".to_string()), ); } - if !arguments.is_empty() { + if args_count != 0 { self.component.attr( Attribute::Custom(TREE_INITIAL_NODE), AttrValue::String("arg_0".to_string()), @@ -376,7 +372,7 @@ impl Variables { .inactive(Style::default().fg(Color::Gray)) .indent_size(3) .scroll_step(6) - .preserve_state(true) + .preserve_state(false) .title("Variables", Alignment::Center) .highlighted_color(Color::LightYellow) .highlight_symbol("â–¶"), diff --git a/tests/debugger/common/mod.rs b/tests/debugger/common/mod.rs index 9991717..35ec8d1 100644 --- a/tests/debugger/common/mod.rs +++ b/tests/debugger/common/mod.rs @@ -1,6 +1,6 @@ use bugstalker::debugger::address::RelocatedAddress; use bugstalker::debugger::register::debug::BreakCondition; -use bugstalker::debugger::variable::VariableIR; +use bugstalker::debugger::variable::value::Value; use bugstalker::debugger::{EventHook, FunctionDie, PlaceDescriptor}; use bugstalker::version::Version; use nix::sys::signal::Signal; @@ -15,8 +15,9 @@ pub struct TestInfo { pub addr: Arc>>, pub line: Arc>>, pub file: Arc>>, - pub old_value: Arc>>, - pub new_value: Arc>>, + pub wp_dqe_string: Arc>>, + pub old_value: Arc>>, + pub new_value: Arc>>, } #[derive(Default)] @@ -51,14 +52,18 @@ impl EventHook for TestHooks { _: u32, place: Option, _: BreakCondition, - old_value: Option<&VariableIR>, - new_value: Option<&VariableIR>, + dqe_string: Option<&str>, + old_value: Option<&Value>, + new_value: Option<&Value>, _: bool, ) -> anyhow::Result<()> { self.info.addr.set(Some(pc)); let file = &self.info.file; file.set(place.as_ref().map(|p| p.file.to_str().unwrap().to_string())); self.info.line.set(place.map(|p| p.line_number)); + self.info + .wp_dqe_string + .replace(dqe_string.map(ToString::to_string)); self.info.old_value.replace(old_value.cloned()); self.info.new_value.replace(new_value.cloned()); Ok(()) diff --git a/tests/debugger/io.rs b/tests/debugger/io.rs index b0903fb..5f5fff0 100644 --- a/tests/debugger/io.rs +++ b/tests/debugger/io.rs @@ -2,7 +2,7 @@ use crate::common::TestHooks; use crate::common::TestInfo; use crate::HW_APP; use crate::{assert_no_proc, prepare_debugee_process, CALC_APP}; -use bugstalker::debugger::variable::render::{RenderRepr, ValueLayout}; +use bugstalker::debugger::variable::render::{RenderValue, ValueLayout}; use bugstalker::debugger::DebuggerBuilder; use serial_test::serial; use std::borrow::Cow; @@ -77,12 +77,13 @@ fn test_read_value_u64() { let vars = debugger.read_local_variables().unwrap(); assert_eq!(vars.len(), 5); - assert_eq!(vars[4].name().unwrap(), "s"); - assert_eq!(vars[4].r#type().name_fmt(), "i64"); - let _six = "6".to_string(); + + let s = &vars[4]; + assert_eq!(s.identity().to_string(), "s"); + assert_eq!(s.value().r#type().name_fmt(), "i64"); assert!(matches!( - vars[4].value().unwrap(), - ValueLayout::PreRendered(Cow::Owned(_six)) + s.value().value_layout().unwrap(), + ValueLayout::PreRendered(Cow::Owned(str)) if str == "6" )); debugger.continue_debugee().unwrap(); diff --git a/tests/debugger/steps.rs b/tests/debugger/steps.rs index 6a49cd5..ec0ef7c 100644 --- a/tests/debugger/steps.rs +++ b/tests/debugger/steps.rs @@ -2,7 +2,7 @@ use crate::common::TestInfo; use crate::common::{rust_version, TestHooks}; use crate::CALC_APP; use crate::{assert_no_proc, prepare_debugee_process, HW_APP, RECURSION_APP, VARS_APP}; -use bugstalker::debugger::variable::{SupportedScalar, VariableIR}; +use bugstalker::debugger::variable::value::{SupportedScalar, Value}; use bugstalker::debugger::{Debugger, DebuggerBuilder}; use bugstalker::ui::command::parser::expression; use bugstalker::version_switch; @@ -64,7 +64,7 @@ fn test_step_into_recursion() { fn assert_arg(debugger: &Debugger, expected: u64) { let get_i_expr = expression::parser().parse("i").unwrap(); let i_arg = debugger.read_argument(get_i_expr).unwrap().pop().unwrap(); - let VariableIR::Scalar(scalar) = i_arg else { + let Value::Scalar(scalar) = i_arg.into_value() else { panic!("not a scalar"); }; assert_eq!(scalar.value, Some(SupportedScalar::U64(expected))); diff --git a/tests/debugger/variables.rs b/tests/debugger/variables.rs index 555ac01..1556714 100644 --- a/tests/debugger/variables.rs +++ b/tests/debugger/variables.rs @@ -2,591 +2,341 @@ use crate::common::TestHooks; use crate::common::{rust_version, TestInfo}; use crate::VARS_APP; use crate::{assert_no_proc, prepare_debugee_process}; -use bugstalker::debugger::variable::render::RenderRepr; -use bugstalker::debugger::variable::select::{Literal, LiteralOrWildcard, Selector, DQE}; -use bugstalker::debugger::variable::{Member, VariableIR}; -use bugstalker::debugger::{variable, Debugger, DebuggerBuilder}; -use bugstalker::ui::command::parser::expression; +use bugstalker::debugger::variable::dqe::{Dqe, Literal, LiteralOrWildcard, PointerCast, Selector}; +use bugstalker::debugger::variable::render::RenderValue; +use bugstalker::debugger::variable::value::{Member, SpecializedValue, SupportedScalar, Value}; +use bugstalker::debugger::DebuggerBuilder; use bugstalker::version::Version; -use bugstalker::{debugger, version_switch}; -use chumsky::Parser; -use debugger::variable::SupportedScalar; +use bugstalker::version_switch; use serial_test::serial; use std::collections::HashMap; -use variable::SpecializedVariableIR; - -pub fn assert_scalar_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - exp_val: Option, -) { - let VariableIR::Scalar(scalar) = var else { + +pub fn assert_scalar(value: &Value, exp_type: &str, exp_val: Option) { + let Value::Scalar(scalar) = value else { panic!("not a scalar"); }; - assert_eq!(var.name().as_deref(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); + assert_eq!(value.r#type().name_fmt(), exp_type); assert_eq!(scalar.value, exp_val); } -pub fn assert_scalar_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - exp_val: Option, -) { - assert_scalar_inner(var, Some(exp_name), exp_type, exp_val) -} - -pub fn assert_scalar(var: &VariableIR, exp_type: &str, exp_val: Option) { - assert_scalar_inner(var, None, exp_type, exp_val) -} - -fn assert_struct_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - for_each_member: impl Fn(usize, &Member), -) { - let VariableIR::Struct(structure) = var else { +fn assert_struct(val: &Value, exp_type: &str, for_each_member: impl Fn(usize, &Member)) { + let Value::Struct(structure) = val else { panic!("not a struct"); }; - assert_eq!(var.name().as_deref(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); for (i, member) in structure.members.iter().enumerate() { for_each_member(i, member) } } -fn assert_struct_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - for_each_member: impl Fn(usize, &Member), -) { - assert_struct_inner(var, Some(exp_name), exp_type, for_each_member) -} - -fn assert_struct(var: &VariableIR, exp_type: &str, for_each_member: impl Fn(usize, &Member)) { - assert_struct_inner(var, None, exp_type, for_each_member) -} - -fn assert_member(member: &Member, expected_field_name: &str, with_value: impl Fn(&VariableIR)) { +fn assert_member(member: &Member, expected_field_name: &str, with_value: impl Fn(&Value)) { assert_eq!(member.field_name.as_deref(), Some(expected_field_name)); with_value(&member.value); } -fn assert_array_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - for_each_item: impl Fn(usize, &VariableIR), -) { - let VariableIR::Array(array) = var else { - panic!("not a array"); +fn assert_array(val: &Value, exp_type: &str, for_each_item: impl Fn(usize, &Value)) { + let Value::Array(array) = val else { + panic!("not an array"); }; - assert_eq!(array.identity.name.as_deref(), exp_name); assert_eq!(array.type_ident.name_fmt(), exp_type); for (i, item) in array.items.as_ref().unwrap_or(&vec![]).iter().enumerate() { for_each_item(i, &item.value) } } -fn assert_array_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - for_each_item: impl Fn(usize, &VariableIR), -) { - assert_array_inner(var, Some(exp_name), exp_type, for_each_item); -} - -fn assert_array(var: &VariableIR, exp_type: &str, for_each_item: impl Fn(usize, &VariableIR)) { - assert_array_inner(var, None, exp_type, for_each_item); -} - -fn assert_c_enum_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - exp_value: Option, -) { - let VariableIR::CEnum(c_enum) = var else { +fn assert_c_enum(val: &Value, exp_type: &str, exp_value: Option) { + let Value::CEnum(c_enum) = val else { panic!("not a c_enum"); }; - assert_eq!(var.name().as_deref(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); assert_eq!(c_enum.value, exp_value); } -fn assert_c_enum_n(var: &VariableIR, exp_name: &str, exp_type: &str, exp_value: Option) { - assert_c_enum_inner(var, Some(exp_name), exp_type, exp_value) -} - -fn assert_c_enum(var: &VariableIR, exp_type: &str, exp_value: Option) { - assert_c_enum_inner(var, None, exp_type, exp_value) -} - -fn assert_rust_enum_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - with_value: impl FnOnce(&VariableIR), -) { - let VariableIR::RustEnum(rust_enum) = var else { +fn assert_rust_enum(val: &Value, exp_type: &str, with_value: impl FnOnce(&Value)) { + let Value::RustEnum(rust_enum) = val else { panic!("not a c_enum"); }; - assert_eq!(var.name().as_deref(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); with_value(&rust_enum.value.as_ref().unwrap().value); } -fn assert_rust_enum_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_value: impl FnOnce(&VariableIR), -) { - assert_rust_enum_inner(var, Some(exp_name), exp_type, with_value) -} - -fn assert_rust_enum(var: &VariableIR, exp_type: &str, with_value: impl FnOnce(&VariableIR)) { - assert_rust_enum_inner(var, None, exp_type, with_value) -} - -fn assert_pointer_inner(var: &VariableIR, exp_name: Option<&str>, exp_type: &str) { - let VariableIR::Pointer(ptr) = var else { +fn assert_pointer(val: &Value, exp_type: &str) { + let Value::Pointer(ptr) = val else { panic!("not a pointer"); }; - assert_eq!(var.name().as_deref(), exp_name); assert_eq!(ptr.type_ident.name_fmt(), exp_type); } -fn assert_pointer_n(var: &VariableIR, exp_name: &str, exp_type: &str) { - assert_pointer_inner(var, Some(exp_name), exp_type) -} - -fn assert_pointer(var: &VariableIR, exp_type: &str) { - assert_pointer_inner(var, None, exp_type) -} - -fn assert_vec_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - exp_cap: usize, - with_buf: impl FnOnce(&VariableIR), -) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::Vector(vector)), +fn assert_vec(val: &Value, exp_type: &str, exp_cap: usize, with_buf: impl FnOnce(&Value)) { + let Value::Specialized { + value: Some(SpecializedValue::Vector(vector)), .. - } = var + } = val else { panic!("not a vector"); }; - assert_eq!(var.name().as_deref(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); - let VariableIR::Scalar(capacity) = &vector.structure.members[1].value else { + assert_eq!(val.r#type().name_fmt(), exp_type); + let Value::Scalar(capacity) = &vector.structure.members[1].value else { panic!("no capacity"); }; assert_eq!(capacity.value, Some(SupportedScalar::Usize(exp_cap))); with_buf(&vector.structure.members[0].value); } -fn assert_vec_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - exp_cap: usize, - with_buf: impl FnOnce(&VariableIR), -) { - assert_vec_inner(var, Some(exp_name), exp_type, exp_cap, with_buf) -} - -fn assert_vec( - var: &VariableIR, - exp_type: &str, - exp_cap: usize, - with_buf: impl FnOnce(&VariableIR), -) { - assert_vec_inner(var, None, exp_type, exp_cap, with_buf) -} - -fn assert_string_inner(var: &VariableIR, exp_name: Option<&str>, exp_value: &str) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::String(string)), +fn assert_string(val: &Value, exp_value: &str) { + let Value::Specialized { + value: Some(SpecializedValue::String(string)), .. - } = var + } = val else { panic!("not a string"); }; - assert_eq!(var.name().as_deref(), exp_name); assert_eq!(string.value, exp_value); } -fn assert_string_n(var: &VariableIR, exp_name: &str, exp_value: &str) { - assert_string_inner(var, Some(exp_name), exp_value) -} - -fn assert_string(var: &VariableIR, exp_value: &str) { - assert_string_inner(var, None, exp_value) -} - -fn assert_str_inner(var: &VariableIR, exp_name: Option<&str>, exp_value: &str) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::Str(str)), +fn assert_str(val: &Value, exp_value: &str) { + let Value::Specialized { + value: Some(SpecializedValue::Str(str)), .. - } = var + } = val else { panic!("not a &str"); }; - assert_eq!(var.name().as_deref(), exp_name); assert_eq!(str.value, exp_value); } -fn assert_str_n(var: &VariableIR, exp_name: &str, exp_value: &str) { - assert_str_inner(var, Some(exp_name), exp_value) -} - -fn assert_str(var: &VariableIR, exp_value: &str) { - assert_str_inner(var, None, exp_value) -} - -fn assert_init_tls_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_var: impl FnOnce(&VariableIR), -) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::Tls(tls)), +fn assert_init_tls(val: &Value, exp_type: &str, with_inner: impl FnOnce(&Value)) { + let Value::Specialized { + value: Some(SpecializedValue::Tls(tls)), .. - } = var + } = val else { panic!("not a tls"); }; - assert_eq!(tls.identity.name.as_ref().unwrap(), exp_name); assert_eq!(tls.inner_type.name_fmt(), exp_type); - with_var(tls.inner_value.as_ref().unwrap()); + with_inner(tls.inner_value.as_ref().unwrap()); } -fn assert_uninit_tls(var: &VariableIR, exp_name: &str, exp_type: &str) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::Tls(tls)), +fn assert_uninit_tls(val: &Value, exp_type: &str) { + let Value::Specialized { + value: Some(SpecializedValue::Tls(tls)), .. - } = var + } = val else { panic!("not a tls"); }; - assert_eq!(tls.identity.name.as_ref().unwrap(), exp_name); assert_eq!(tls.inner_type.name_fmt(), exp_type); assert!(tls.inner_value.is_none()); } -fn assert_hashmap_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - with_kv_items: impl FnOnce(&Vec<(VariableIR, VariableIR)>), -) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::HashMap(map)), +fn assert_hashmap(val: &Value, exp_type: &str, with_kv_items: impl FnOnce(&Vec<(Value, Value)>)) { + let Value::Specialized { + value: Some(SpecializedValue::HashMap(map)), .. - } = var + } = val else { panic!("not a hashmap"); }; - assert_eq!(var.name().as_deref(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); let mut items = map.kv_items.clone(); items.sort_by(|v1, v2| { - let k1_render = format!("{:?}", v1.0.value()); - let k2_render = format!("{:?}", v2.0.value()); + let k1_render = format!("{:?}", v1.0.value_layout()); + let k2_render = format!("{:?}", v2.0.value_layout()); k1_render.cmp(&k2_render) }); with_kv_items(&items); } -fn assert_hashmap_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_kv_items: impl FnOnce(&Vec<(VariableIR, VariableIR)>), -) { - assert_hashmap_inner(var, Some(exp_name), exp_type, with_kv_items) -} - -fn assert_hashmap( - var: &VariableIR, - exp_type: &str, - with_kv_items: impl FnOnce(&Vec<(VariableIR, VariableIR)>), -) { - assert_hashmap_inner(var, None, exp_type, with_kv_items) -} - -fn assert_hashset( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_items: impl FnOnce(&Vec), -) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::HashSet(set)), +fn assert_hashset(val: &Value, exp_type: &str, with_items: impl FnOnce(&Vec)) { + let Value::Specialized { + value: Some(SpecializedValue::HashSet(set)), .. - } = var + } = val else { panic!("not a hashset"); }; - assert_eq!(set.identity.name.as_ref().unwrap(), exp_name); assert_eq!(set.type_ident.name_fmt(), exp_type); let mut items = set.items.clone(); items.sort_by(|v1, v2| { - let k1_render = format!("{:?}", v1.value()); - let k2_render = format!("{:?}", v2.value()); + let k1_render = format!("{:?}", v1.value_layout()); + let k2_render = format!("{:?}", v2.value_layout()); k1_render.cmp(&k2_render) }); with_items(&items); } -fn assert_btree_map_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - with_kv_items: impl FnOnce(&Vec<(VariableIR, VariableIR)>), -) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::BTreeMap(map)), +fn assert_btree_map(val: &Value, exp_type: &str, with_kv_items: impl FnOnce(&Vec<(Value, Value)>)) { + let Value::Specialized { + value: Some(SpecializedValue::BTreeMap(map)), .. - } = var + } = val else { panic!("not a BTreeMap"); }; - assert_eq!(map.identity.name.as_deref(), exp_name); assert_eq!(map.type_ident.name_fmt(), exp_type); with_kv_items(&map.kv_items); } -fn assert_btree_map_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_kv_items: impl FnOnce(&Vec<(VariableIR, VariableIR)>), -) { - assert_btree_map_inner(var, Some(exp_name), exp_type, with_kv_items) -} - -fn assert_btree_map( - var: &VariableIR, - exp_type: &str, - with_kv_items: impl FnOnce(&Vec<(VariableIR, VariableIR)>), -) { - assert_btree_map_inner(var, None, exp_type, with_kv_items) -} - -fn assert_btree_set( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_items: impl FnOnce(&Vec), -) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::BTreeSet(set)), +fn assert_btree_set(val: &Value, exp_type: &str, with_items: impl FnOnce(&Vec)) { + let Value::Specialized { + value: Some(SpecializedValue::BTreeSet(set)), .. - } = var + } = val else { panic!("not a BTreeSet"); }; - assert_eq!(set.identity.name.as_ref().unwrap(), exp_name); assert_eq!(set.type_ident.name_fmt(), exp_type); with_items(&set.items); } -fn assert_vec_deque_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - exp_cap: usize, - with_buf: impl FnOnce(&VariableIR), -) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::VecDeque(vector)), +fn assert_vec_deque(val: &Value, exp_type: &str, exp_cap: usize, with_buf: impl FnOnce(&Value)) { + let Value::Specialized { + value: Some(SpecializedValue::VecDeque(vector)), .. - } = var + } = val else { panic!("not a VecDeque"); }; - assert_eq!(vector.structure.identity.name.as_deref(), exp_name); assert_eq!(vector.structure.type_ident.name_fmt(), exp_type); - let VariableIR::Scalar(capacity) = &vector.structure.members[1].value else { + let Value::Scalar(capacity) = &vector.structure.members[1].value else { panic!("no capacity"); }; assert_eq!(capacity.value, Some(SupportedScalar::Usize(exp_cap))); with_buf(&vector.structure.members[0].value); } -fn assert_vec_deque_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - exp_cap: usize, - with_buf: impl FnOnce(&VariableIR), -) { - assert_vec_deque_inner(var, Some(exp_name), exp_type, exp_cap, with_buf) -} - -fn assert_vec_deque( - var: &VariableIR, - exp_type: &str, - exp_cap: usize, - with_buf: impl FnOnce(&VariableIR), -) { - assert_vec_deque_inner(var, None, exp_type, exp_cap, with_buf) -} - -fn assert_cell_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - with_value: impl FnOnce(&VariableIR), -) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::Cell(value)), + +fn assert_cell(val: &Value, exp_type: &str, with_value: impl FnOnce(&Value)) { + let Value::Specialized { + value: Some(SpecializedValue::Cell(value)), .. - } = var + } = val else { panic!("not a Cell"); }; - assert_eq!(var.name().as_deref(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); with_value(value.as_ref()); } -fn assert_cell_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - with_value: impl FnOnce(&VariableIR), -) { - assert_cell_inner(var, Some(exp_name), exp_type, with_value) -} - -fn assert_cell(var: &VariableIR, exp_type: &str, with_value: impl FnOnce(&VariableIR)) { - assert_cell_inner(var, None, exp_type, with_value) -} - -fn assert_refcell_inner( - var: &VariableIR, - exp_name: Option<&str>, - exp_type: &str, - exp_borrow: isize, - with_value: impl FnOnce(&VariableIR), -) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::RefCell(value)), +fn assert_refcell(val: &Value, exp_type: &str, exp_borrow: isize, with_inner: impl FnOnce(&Value)) { + let Value::Specialized { + value: Some(SpecializedValue::RefCell(value)), .. - } = var + } = val else { panic!("not a Cell"); }; - assert_eq!(var.name().as_deref(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); - let VariableIR::Struct(as_struct) = value.as_ref() else { + assert_eq!(val.r#type().name_fmt(), exp_type); + let Value::Struct(as_struct) = value.as_ref() else { panic!("not a struct") }; - let VariableIR::Scalar(borrow) = &as_struct.members[0].value else { + let Value::Scalar(borrow) = &as_struct.members[0].value else { panic!("no borrow flag"); }; assert_eq!( borrow.value.as_ref().unwrap(), &SupportedScalar::Isize(exp_borrow) ); - with_value(&as_struct.members[1].value); -} - -fn assert_refcell_n( - var: &VariableIR, - exp_name: &str, - exp_type: &str, - exp_borrow: isize, - with_inner: impl FnOnce(&VariableIR), -) { - assert_refcell_inner(var, Some(exp_name), exp_type, exp_borrow, with_inner) -} - -fn assert_refcell( - var: &VariableIR, - exp_type: &str, - exp_borrow: isize, - with_inner: impl FnOnce(&VariableIR), -) { - assert_refcell_inner(var, None, exp_type, exp_borrow, with_inner) + with_inner(&as_struct.members[1].value); } -fn assert_rc_inner(var: &VariableIR, exp_name: Option<&str>, exp_type: &str) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::Rc(_)), +fn assert_rc(val: &Value, exp_type: &str) { + let Value::Specialized { + value: Some(SpecializedValue::Rc(_)), .. - } = var + } = val else { panic!("not an rc"); }; - assert_eq!(var.name().as_deref(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); -} - -fn assert_rc_n(var: &VariableIR, exp_name: &str, exp_type: &str) { - assert_rc_inner(var, Some(exp_name), exp_type) + assert_eq!(val.r#type().name_fmt(), exp_type); } -fn assert_rc(var: &VariableIR, exp_type: &str) { - assert_rc_inner(var, None, exp_type) -} - -fn assert_arc_n(var: &VariableIR, exp_name: &str, exp_type: &str) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::Arc(_)), +fn assert_arc(val: &Value, exp_type: &str) { + let Value::Specialized { + value: Some(SpecializedValue::Arc(_)), .. - } = var + } = val else { panic!("not an arc"); }; - assert_eq!(var.name().unwrap(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); } -fn assert_uuid_n(var: &VariableIR, exp_name: &str, exp_type: &str) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::Uuid(_)), +fn assert_uuid(val: &Value, exp_type: &str) { + let Value::Specialized { + value: Some(SpecializedValue::Uuid(_)), .. - } = var + } = val else { panic!("not an uuid"); }; - assert_eq!(var.name().unwrap(), exp_name); - assert_eq!(var.r#type().name_fmt(), exp_type); + assert_eq!(val.r#type().name_fmt(), exp_type); } -fn assert_system_time_n(var: &VariableIR, exp_name: &str, exp_value: (i64, u32)) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::SystemTime(value)), +fn assert_system_time(val: &Value, exp_value: (i64, u32)) { + let Value::Specialized { + value: Some(SpecializedValue::SystemTime(value)), .. - } = var + } = val else { panic!("not a SystemTime"); }; - assert_eq!(var.name().unwrap(), exp_name); assert_eq!(*value, exp_value); } -fn assert_instant_n(var: &VariableIR, exp_name: &str) { - let VariableIR::Specialized { - value: Some(SpecializedVariableIR::Instant(_)), +fn assert_instant(val: &Value) { + let Value::Specialized { + value: Some(SpecializedValue::Instant(_)), .. - } = var + } = val else { panic!("not an Instant"); }; - assert_eq!(var.name().unwrap(), exp_name); +} + +macro_rules! read_locals { + ($debugger: expr => $($var: ident),*) => { + let vars = $debugger.read_local_variables().unwrap(); + let &[$($var),*] = &vars.as_slice() else { + panic!("Invalid variables count") + }; + }; +} + +macro_rules! read_var_dqe { + ($debugger: expr, $dqe: expr => $($var: ident),*) => { + let vars = $debugger.read_variable($dqe).unwrap(); + let &[$($var),*] = &vars.as_slice() else { + panic!("Invalid variables count") + }; + }; +} + +macro_rules! read_arg_dqe { + ($debugger: expr, $dqe: expr => $($var: ident),*) => { + let args = $debugger.read_argument($dqe).unwrap(); + let &[$($var),*] = &args.as_slice() else { + panic!("Invalid variables count") + }; + }; +} + +macro_rules! read_var_dqe_type_order { + ($debugger: expr, $dqe: expr => $($var: ident),*) => { + let mut vars = $debugger.read_variable($dqe).unwrap(); + vars.sort_by(|v1, v2| v1.value().r#type().cmp(v2.value().r#type())); + let &[$($var),*] = &vars.as_slice() else { + panic!("Invalid variables count") + }; + }; +} + +macro_rules! assert_idents { + ($($var: ident => $name: literal),*) => { + $( + assert_eq!($var.identity().to_string(), $name); + )* + }; } #[test] @@ -603,46 +353,33 @@ fn test_read_scalar_variables() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(30)); - let vars = debugger.read_local_variables().unwrap(); - assert_scalar_n(&vars[0], "int8", "i8", Some(SupportedScalar::I8(1))); - assert_scalar_n(&vars[1], "int16", "i16", Some(SupportedScalar::I16(-1))); - assert_scalar_n(&vars[2], "int32", "i32", Some(SupportedScalar::I32(2))); - assert_scalar_n(&vars[3], "int64", "i64", Some(SupportedScalar::I64(-2))); - assert_scalar_n(&vars[4], "int128", "i128", Some(SupportedScalar::I128(3))); - assert_scalar_n(&vars[5], "isize", "isize", Some(SupportedScalar::Isize(-3))); - assert_scalar_n(&vars[6], "uint8", "u8", Some(SupportedScalar::U8(1))); - assert_scalar_n(&vars[7], "uint16", "u16", Some(SupportedScalar::U16(2))); - assert_scalar_n(&vars[8], "uint32", "u32", Some(SupportedScalar::U32(3))); - assert_scalar_n(&vars[9], "uint64", "u64", Some(SupportedScalar::U64(4))); - assert_scalar_n(&vars[10], "uint128", "u128", Some(SupportedScalar::U128(5))); - assert_scalar_n(&vars[11], "usize", "usize", Some(SupportedScalar::Usize(6))); - assert_scalar_n(&vars[12], "f32", "f32", Some(SupportedScalar::F32(1.1))); - assert_scalar_n(&vars[13], "f64", "f64", Some(SupportedScalar::F64(1.2))); - assert_scalar_n( - &vars[14], - "boolean_true", - "bool", - Some(SupportedScalar::Bool(true)), - ); - assert_scalar_n( - &vars[15], - "boolean_false", - "bool", - Some(SupportedScalar::Bool(false)), - ); - assert_scalar_n( - &vars[16], - "char_ascii", - "char", - Some(SupportedScalar::Char('a')), - ); - assert_scalar_n( - &vars[17], - "char_non_ascii", - "char", - Some(SupportedScalar::Char('😊')), + read_locals!(debugger => int8, int16, int32, int64, int128, isize, uint8, uint16, uint32, uint64, uint128, usize, f32, f64, b_true, b_false, ch_ascii, c_n_ascii); + assert_idents!( + int8 => "int8", int16 => "int16", int32 => "int32", int64 => "int64", int128 => "int128", + isize => "isize", uint8 => "uint8", uint16 => "uint16", uint32 => "uint32", uint64 => "uint64", + uint128 => "uint128", usize => "usize", f32 => "f32", f64 => "f64", b_true => "boolean_true", + b_false => "boolean_false", ch_ascii => "char_ascii", c_n_ascii => "char_non_ascii" ); + assert_scalar(int8.value(), "i8", Some(SupportedScalar::I8(1))); + assert_scalar(int16.value(), "i16", Some(SupportedScalar::I16(-1))); + assert_scalar(int32.value(), "i32", Some(SupportedScalar::I32(2))); + assert_scalar(int64.value(), "i64", Some(SupportedScalar::I64(-2))); + assert_scalar(int128.value(), "i128", Some(SupportedScalar::I128(3))); + assert_scalar(isize.value(), "isize", Some(SupportedScalar::Isize(-3))); + assert_scalar(uint8.value(), "u8", Some(SupportedScalar::U8(1))); + assert_scalar(uint16.value(), "u16", Some(SupportedScalar::U16(2))); + assert_scalar(uint32.value(), "u32", Some(SupportedScalar::U32(3))); + assert_scalar(uint64.value(), "u64", Some(SupportedScalar::U64(4))); + assert_scalar(uint128.value(), "u128", Some(SupportedScalar::U128(5))); + assert_scalar(usize.value(), "usize", Some(SupportedScalar::Usize(6))); + assert_scalar(f32.value(), "f32", Some(SupportedScalar::F32(1.1))); + assert_scalar(f64.value(), "f64", Some(SupportedScalar::F64(1.2))); + assert_scalar(b_true.value(), "bool", Some(SupportedScalar::Bool(true))); + assert_scalar(b_false.value(), "bool", Some(SupportedScalar::Bool(false))); + assert_scalar(ch_ascii.value(), "char", Some(SupportedScalar::Char('a'))); + assert_scalar(c_n_ascii.value(), "char", Some(SupportedScalar::Char('😊'))); + debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); } @@ -685,9 +422,11 @@ fn test_read_struct() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(53)); - let vars = debugger.read_local_variables().unwrap(); - assert_scalar_n(&vars[0], "tuple_0", "()", Some(SupportedScalar::Empty())); - assert_struct_n(&vars[1], "tuple_1", "(f64, f64)", |i, member| match i { + read_locals!(debugger => tuple_0, tuple_1, tuple_2, foo, foo2); + assert_idents!(tuple_0 => "tuple_0", tuple_1 => "tuple_1", tuple_2 => "tuple_2", foo => "foo", foo2 => "foo2"); + + assert_scalar(tuple_0.value(), "()", Some(SupportedScalar::Empty())); + assert_struct(tuple_1.value(), "(f64, f64)", |i, member| match i { 0 => assert_member(member, "__0", |val| { assert_scalar(val, "f64", Some(SupportedScalar::F64(0f64))) }), @@ -696,9 +435,8 @@ fn test_read_struct() { }), _ => panic!("2 members expected"), }); - assert_struct_n( - &vars[2], - "tuple_2", + assert_struct( + tuple_2.value(), "(u64, i64, char, bool)", |i, member| match i { 0 => assert_member(member, "__0", |val| { @@ -716,7 +454,7 @@ fn test_read_struct() { _ => panic!("4 members expected"), }, ); - assert_struct_n(&vars[3], "foo", "Foo", |i, member| match i { + assert_struct(foo.value(), "Foo", |i, member| match i { 0 => assert_member(member, "bar", |val| { assert_scalar(val, "i32", Some(SupportedScalar::I32(100))) }), @@ -725,7 +463,7 @@ fn test_read_struct() { }), _ => panic!("2 members expected"), }); - assert_struct_n(&vars[4], "foo2", "Foo2", |i, member| match i { + assert_struct(foo2.value(), "Foo2", |i, member| match i { 0 => assert_member(member, "foo", |val| { assert_struct(val, "Foo", |i, member| match i { 0 => assert_member(member, "bar", |val| { @@ -761,8 +499,10 @@ fn test_read_array() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(61)); - let vars = debugger.read_local_variables().unwrap(); - assert_array_n(&vars[0], "arr_1", "[i32]", |i, item| match i { + read_locals!(debugger => arr_1, arr_2); + assert_idents!(arr_1 => "arr_1", arr_2 => "arr_2"); + + assert_array(arr_1.value(), "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), @@ -770,7 +510,7 @@ fn test_read_array() { 4 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("5 items expected"), }); - assert_array_n(&vars[1], "arr_2", "[[i32]]", |i, item| match i { + assert_array(arr_2.value(), "[[i32]]", |i, item| match i { 0 => assert_array(item, "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), @@ -816,16 +556,21 @@ fn test_read_enum() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(93)); - let vars = debugger.read_local_variables().unwrap(); - assert_c_enum_n(&vars[0], "enum_1", "EnumA", Some("B".to_string())); - assert_rust_enum_n(&vars[1], "enum_2", "EnumC", |enum_val| { + read_locals!(debugger => enum_1, enum_2, enum_3, enum_4, enum_5, enum_6, enum_7); + assert_idents!( + enum_1 => "enum_1", enum_2 => "enum_2", enum_3 => "enum_3", enum_4 => "enum_4", + enum_5 => "enum_5", enum_6 => "enum_6", enum_7 => "enum_7" + ); + + assert_c_enum(enum_1.value(), "EnumA", Some("B".to_string())); + assert_rust_enum(enum_2.value(), "EnumC", |enum_val| { assert_struct(enum_val, "C", |_, member| { assert_member(member, "__0", |val| { assert_scalar(val, "char", Some(SupportedScalar::Char('b'))) }) }); }); - assert_rust_enum_n(&vars[2], "enum_3", "EnumC", |enum_val| { + assert_rust_enum(enum_3.value(), "EnumC", |enum_val| { assert_struct(enum_val, "D", |i, member| { match i { 0 => assert_member(member, "__0", |val| { @@ -838,12 +583,12 @@ fn test_read_enum() { }; }); }); - assert_rust_enum_n(&vars[3], "enum_4", "EnumC", |enum_val| { + assert_rust_enum(enum_4.value(), "EnumC", |enum_val| { assert_struct(enum_val, "E", |_, _| { panic!("expected empty struct"); }); }); - assert_rust_enum_n(&vars[4], "enum_5", "EnumF", |enum_val| { + assert_rust_enum(enum_5.value(), "EnumF", |enum_val| { assert_struct(enum_val, "F", |i, member| { match i { 0 => assert_member(member, "__0", |val| { @@ -859,7 +604,7 @@ fn test_read_enum() { }; }); }); - assert_rust_enum_n(&vars[5], "enum_6", "EnumF", |enum_val| { + assert_rust_enum(enum_6.value(), "EnumF", |enum_val| { assert_struct(enum_val, "G", |i, member| { match i { 0 => assert_member(member, "__0", |val| { @@ -877,7 +622,7 @@ fn test_read_enum() { }; }); }); - assert_rust_enum_n(&vars[6], "enum_7", "EnumF", |enum_val| { + assert_rust_enum(enum_7.value(), "EnumF", |enum_val| { assert_struct(enum_val, "J", |i, member| { match i { 0 => assert_member(member, "__0", |val| { @@ -892,26 +637,6 @@ fn test_read_enum() { assert_no_proc!(debugee_pid); } -fn make_select_plan(expr: &str) -> DQE { - expression::parser().parse(expr).unwrap() -} - -fn read_single_var(debugger: &Debugger, expr: &str) -> VariableIR { - debugger - .read_variable(make_select_plan(expr)) - .unwrap() - .pop() - .unwrap() -} - -fn read_single_arg(debugger: &Debugger, expr: &str) -> VariableIR { - debugger - .read_argument(make_select_plan(expr)) - .unwrap() - .pop() - .unwrap() -} - #[test] #[serial] fn test_read_pointers() { @@ -926,44 +651,41 @@ fn test_read_pointers() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(119)); - let vars = debugger.read_local_variables().unwrap(); - assert_scalar_n(&vars[0], "a", "i32", Some(SupportedScalar::I32(2))); - - assert_pointer_n(&vars[1], "ref_a", "&i32"); - let deref = read_single_var(&debugger, "*ref_a"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); - - assert_pointer_n(&vars[2], "ptr_a", "*const i32"); - let deref = read_single_var(&debugger, "*ptr_a"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); - - assert_pointer_n(&vars[3], "ptr_ptr_a", "*const *const i32"); - let deref = read_single_var(&debugger, "*ptr_ptr_a"); - assert_pointer(&deref, "*const i32"); - let deref = read_single_var(&debugger, "**ptr_ptr_a"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); - - assert_scalar_n(&vars[4], "b", "i32", Some(SupportedScalar::I32(2))); - - assert_pointer_n(&vars[5], "mut_ref_b", "&mut i32"); - let deref = read_single_var(&debugger, "*mut_ref_b"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); - - assert_scalar_n(&vars[6], "c", "i32", Some(SupportedScalar::I32(2))); - - assert_pointer_n(&vars[7], "mut_ptr_c", "*mut i32"); - let deref = read_single_var(&debugger, "*mut_ptr_c"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); + read_locals!(debugger => a, ref_a, ptr_a, ptr_ptr_a, b, mut_ref_b, c, mut_ptr_c, box_d, f, ref_f); + assert_idents!( + a => "a", ref_a => "ref_a", ptr_a => "ptr_a", ptr_ptr_a => "ptr_ptr_a", b => "b", + mut_ref_b => "mut_ref_b",c => "c", mut_ptr_c => "mut_ptr_c", box_d => "box_d", f => "f", ref_f => "ref_f" + ); - assert_pointer_n( - &vars[8], - "box_d", + assert_scalar(a.value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer(ref_a.value(), "&i32"); + let deref = ref_a.clone().modify_value(|ctx, val| val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer(ptr_a.value(), "*const i32"); + let deref = ptr_a.clone().modify_value(|ctx, val| val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer(ptr_ptr_a.value(), "*const *const i32"); + let deref = ptr_ptr_a.clone().modify_value(|ctx, val| val.deref(ctx)); + assert_pointer(deref.unwrap().value(), "*const i32"); + let deref = ptr_ptr_a + .clone() + .modify_value(|ctx, v| v.deref(ctx).and_then(|v| v.deref(ctx))); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_scalar(b.value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer(mut_ref_b.value(), "&mut i32"); + let deref = mut_ref_b.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_scalar(c.value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer(mut_ptr_c.value(), "*mut i32"); + let deref = mut_ptr_c.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_pointer( + box_d.value(), "alloc::boxed::Box", ); - let deref = read_single_var(&debugger, "*box_d"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); - - assert_struct_n(&vars[9], "f", "Foo", |i, member| match i { + let deref = box_d.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + assert_struct(f.value(), "Foo", |i, member| match i { 0 => assert_member(member, "bar", |val| { assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) }), @@ -976,15 +698,15 @@ fn test_read_pointers() { }), 2 => { assert_member(member, "foo", |val| assert_pointer(val, "&i32")); - let deref = read_single_var(&debugger, "*f.foo"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); + let foo_val = member.value.clone(); + let deref = f.clone().modify_value(|ctx, _| foo_val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); } _ => panic!("3 members expected"), }); - - assert_pointer_n(&vars[10], "ref_f", "&vars::references::Foo"); - let deref = read_single_var(&debugger, "*ref_f"); - assert_struct(&deref, "Foo", |i, member| match i { + assert_pointer(ref_f.value(), "&vars::references::Foo"); + let deref = ref_f.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct(deref.unwrap().value(), "Foo", |i, member| match i { 0 => assert_member(member, "bar", |val| { assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) }), @@ -997,8 +719,9 @@ fn test_read_pointers() { }), 2 => { assert_member(member, "foo", |val| assert_pointer(val, "&i32")); - let deref = read_single_var(&debugger, "*(*ref_f).foo"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); + let foo_val = member.value.clone(); + let deref = ref_f.clone().modify_value(|ctx, _| foo_val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); } _ => panic!("3 members expected"), }); @@ -1017,12 +740,12 @@ fn test_read_type_alias() { let mut debugger = builder.build(process).unwrap(); debugger.set_breakpoint_at_line("vars.rs", 126).unwrap(); - debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(126)); - let vars = debugger.read_local_variables().unwrap(); - assert_scalar_n(&vars[0], "a_alias", "i32", Some(SupportedScalar::I32(1))); + read_locals!(debugger => a_alias); + assert_idents!(a_alias => "a_alias"); + assert_scalar(a_alias.value(), "i32", Some(SupportedScalar::I32(1))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1042,8 +765,9 @@ fn test_type_parameters() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(135)); - let vars = debugger.read_local_variables().unwrap(); - assert_struct_n(&vars[0], "a", "Foo", |i, member| match i { + read_locals!(debugger => a); + assert_idents!(a => "a"); + assert_struct(a.value(), "Foo", |i, member| match i { 0 => assert_member(member, "bar", |val| { assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) }), @@ -1068,24 +792,20 @@ fn test_read_vec_and_slice() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(151)); - let vars = debugger.read_local_variables().unwrap(); - assert_vec_n( - &vars[0], - "vec1", - "Vec", - 3, - |buf| { - assert_array(buf, "[i32]", |i, item| match i { - 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), - 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), - 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), - _ => panic!("3 items expected"), - }) - }, - ); - assert_vec_n( - &vars[1], - "vec2", + read_locals!(debugger => vec1, vec2, vec3, slice1, slice2); + assert_idents!(vec1 => "vec1", vec2 => "vec2", vec3 => "vec3", slice1 => "slice1", slice2 => "slice2"); + + assert_vec(vec1.value(), "Vec", 3, |buf| { + assert_array(buf, "[i32]", |i, item| match i { + 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), + 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), + 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), + _ => panic!("3 items expected"), + }) + }); + + assert_vec( + vec2.value(), "Vec", 2, |buf| { @@ -1106,9 +826,9 @@ fn test_read_vec_and_slice() { }) }, ); - assert_vec_n( - &vars[2], - "vec3", + + assert_vec( + vec3.value(), "Vec, alloc::alloc::Global>", 2, |buf| { @@ -1134,22 +854,23 @@ fn test_read_vec_and_slice() { }, ); - assert_pointer_n(&vars[3], "slice1", "&[i32; 3]"); - let deref = read_single_var(&debugger, "*slice1"); - assert_array(&deref, "[i32]", |i, item| match i { + assert_pointer(slice1.value(), "&[i32; 3]"); + let deref = slice1.clone().modify_value(|ctx, val| val.deref(ctx)); + assert_array(deref.unwrap().value(), "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("3 items expected"), }); - assert_pointer_n(&vars[4], "slice2", "&[&[i32; 3]; 2]"); - let deref = read_single_var(&debugger, "*slice2"); - assert_array(&deref, "[&[i32; 3]]", |i, item| match i { + assert_pointer(slice2.value(), "&[&[i32; 3]; 2]"); + let deref = slice2.clone().modify_value(|ctx, val| val.deref(ctx)); + assert_array(deref.unwrap().value(), "[&[i32; 3]]", |i, item| match i { 0 => { assert_pointer(item, "&[i32; 3]"); - let deref = read_single_var(&debugger, "*(*slice2)[0]"); - assert_array(&deref, "[i32]", |i, item| match i { + let item_val = item.clone(); + let deref = slice2.clone().modify_value(|ctx, _| item_val.deref(ctx)); + assert_array(deref.unwrap().value(), "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), @@ -1158,8 +879,9 @@ fn test_read_vec_and_slice() { } 1 => { assert_pointer(item, "&[i32; 3]"); - let deref = read_single_var(&debugger, "*(*slice2)[1]"); - assert_array(&deref, "[i32]", |i, item| match i { + let item_val = item.clone(); + let deref = slice2.clone().modify_value(|ctx, _| item_val.deref(ctx)); + assert_array(deref.unwrap().value(), "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), @@ -1187,10 +909,12 @@ fn test_read_strings() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(159)); - let vars = debugger.read_local_variables().unwrap(); - assert_string_n(&vars[0], "s1", "hello world"); - assert_str_n(&vars[1], "s2", "hello world"); - assert_str_n(&vars[2], "s3", "hello world"); + read_locals!(debugger => s1, s2, s3); + assert_idents!(s1 => "s1", s2 => "s2", s3 => "s3"); + + assert_string(s1.value(), "hello world"); + assert_str(s2.value(), "hello world"); + assert_str(s3.value(), "hello world"); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1210,22 +934,13 @@ fn test_read_static_variables() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(168)); - let vars = debugger - .read_variable(DQE::Variable(Selector::by_name("GLOB_1", false))) - .unwrap(); - assert_eq!(vars.len(), 1); - assert_str_n(&vars[0], "vars::GLOB_1", "glob_1"); + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name("GLOB_1", false)) => glob_1); + assert_idents!(glob_1 => "vars::GLOB_1"); + assert_str(glob_1.value(), "glob_1"); - let vars = debugger - .read_variable(DQE::Variable(Selector::by_name("GLOB_2", false))) - .unwrap(); - assert_eq!(vars.len(), 1); - assert_scalar_n( - &vars[0], - "vars::GLOB_2", - "i32", - Some(SupportedScalar::I32(2)), - ); + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name("GLOB_2", false)) => glob_2); + assert_idents!(glob_2 => "vars::GLOB_2"); + assert_scalar(glob_2.value(), "i32", Some(SupportedScalar::I32(2))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1246,7 +961,7 @@ fn test_read_only_local_variables() { assert_eq!(info.line.take(), Some(168)); let vars = debugger - .read_variable(DQE::Variable(Selector::by_name("GLOB_1", true))) + .read_variable(Dqe::Variable(Selector::by_name("GLOB_1", true))) .unwrap(); assert_eq!(vars.len(), 0); @@ -1268,19 +983,12 @@ fn test_read_static_variables_different_modules() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(179)); - let mut vars = debugger - .read_variable(DQE::Variable(Selector::by_name("GLOB_3", false))) - .unwrap(); - assert_eq!(vars.len(), 2); - vars.sort_by(|v1, v2| v1.r#type().cmp(v2.r#type())); - - assert_str_n(&vars[0], "vars::ns_1::GLOB_3", "glob_3"); - assert_scalar_n( - &vars[1], - "vars::GLOB_3", - "i32", - Some(SupportedScalar::I32(3)), - ); + read_var_dqe_type_order!(debugger, Dqe::Variable(Selector::by_name("GLOB_3", false)) => glob_3_1, glob_3_2); + assert_idents!(glob_3_1 => "vars::ns_1::GLOB_3"); + assert_str(glob_3_1.value(), "glob_3"); + + assert_idents!(glob_3_2 => "vars::GLOB_3"); + assert_scalar(glob_3_2.value(), "i32", Some(SupportedScalar::I32(3))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1294,31 +1002,47 @@ fn test_read_tls_variables() { let info = TestInfo::default(); let builder = DebuggerBuilder::new().with_hooks(TestHooks::new(info.clone())); let mut debugger = builder.build(process).unwrap(); + let rust_version = rust_version(VARS_APP).unwrap(); debugger.set_breakpoint_at_line("vars.rs", 194).unwrap(); debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(194)); - let vars = debugger - .read_variable(DQE::Variable(Selector::by_name( + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name( "THREAD_LOCAL_VAR_1", false, - ))) - .unwrap(); - assert_init_tls_n(&vars[0], "THREAD_LOCAL_VAR_1", "Cell", |inner| { + )) => tls_var_1); + + version_switch!( + rust_version, + (1, 0, 0) ..= (1, 79, u32::MAX) => { + assert_idents!(tls_var_1 => "vars::THREAD_LOCAL_VAR_1::__getit::__KEY"); + }, + (1, 80, 0) ..= (1, u32::MAX, u32::MAX) => { + assert_idents!(tls_var_1 => "vars::THREAD_LOCAL_VAR_1::{constant#0}::{closure#1}::VAL"); + } + ); + assert_init_tls(tls_var_1.value(), "Cell", |inner| { assert_cell(inner, "Cell", |value| { assert_scalar(value, "i32", Some(SupportedScalar::I32(2))) }) }); - let vars = debugger - .read_variable(DQE::Variable(Selector::by_name( + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name( "THREAD_LOCAL_VAR_2", false, - ))) - .unwrap(); - assert_init_tls_n(&vars[0], "THREAD_LOCAL_VAR_2", "Cell<&str>", |inner| { + )) => tls_var_2); + version_switch!( + rust_version, + (1, 0, 0) ..= (1, 79, u32::MAX) => { + assert_idents!(tls_var_2 => "vars::THREAD_LOCAL_VAR_2::__getit::__KEY"); + }, + (1, 80, 0) ..= (1, u32::MAX, u32::MAX) => { + assert_idents!(tls_var_2 => "vars::THREAD_LOCAL_VAR_2::{constant#0}::{closure#1}::VAL"); + } + ); + assert_init_tls(tls_var_2.value(), "Cell<&str>", |inner| { assert_cell(inner, "Cell<&str>", |value| assert_str(value, "2")) }); @@ -1327,19 +1051,21 @@ fn test_read_tls_variables() { debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(199)); - let vars = debugger - .read_variable(DQE::Variable(Selector::by_name( - "THREAD_LOCAL_VAR_1", - false, - ))) - .unwrap(); - let rust_version = rust_version(VARS_APP).unwrap(); version_switch!( rust_version, (1, 0, 0) ..= (1, 79, u32::MAX) => { - assert_uninit_tls(&vars[0], "THREAD_LOCAL_VAR_1", "Cell"); + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name( + "THREAD_LOCAL_VAR_1", + false, + )) => tls_var_1); + assert_idents!(tls_var_1 => "vars::THREAD_LOCAL_VAR_1::__getit::__KEY"); + assert_uninit_tls(tls_var_1.value(), "Cell"); }, (1, 80, 0) ..= (1, u32::MAX, u32::MAX) => { + let vars = debugger.read_variable(Dqe::Variable(Selector::by_name( + "THREAD_LOCAL_VAR_1", + false, + ))).unwrap(); assert!(vars.is_empty()); }, ); @@ -1349,13 +1075,11 @@ fn test_read_tls_variables() { debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(203)); - let vars = debugger - .read_variable(DQE::Variable(Selector::by_name( + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name( "THREAD_LOCAL_VAR_1", false, - ))) - .unwrap(); - assert_init_tls_n(&vars[0], "THREAD_LOCAL_VAR_1", "Cell", |inner| { + )) => tls_var_1); + assert_init_tls(tls_var_1.value(), "Cell", |inner| { assert_cell(inner, "Cell", |value| { assert_scalar(value, "i32", Some(SupportedScalar::I32(1))) }) @@ -1368,6 +1092,11 @@ fn test_read_tls_variables() { #[test] #[serial] fn test_read_tls_const_variables() { + let rust_version = rust_version(VARS_APP).unwrap(); + if rust_version < Version((1, 79, 0)) { + return; + } + let process = prepare_debugee_process(VARS_APP, &[]); let debugee_pid = process.pid(); let info = TestInfo::default(); @@ -1379,13 +1108,20 @@ fn test_read_tls_const_variables() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(538)); - let vars = debugger - .read_variable(DQE::Variable(Selector::by_name( + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name( "CONSTANT_THREAD_LOCAL", false, - ))) - .unwrap(); - assert_init_tls_n(&vars[0], "CONSTANT_THREAD_LOCAL", "i32", |value| { + )) => const_tls); + version_switch!( + rust_version, + (1, 0, 0) ..= (1, 79, u32::MAX) => { + assert_idents!(const_tls => "vars::thread_local_const_init::CONSTANT_THREAD_LOCAL::__getit::VAL"); + }, + (1, 80, 0) ..= (1, u32::MAX, u32::MAX) => { + assert_idents!(const_tls => "vars::thread_local_const_init::CONSTANT_THREAD_LOCAL::{constant#0}::{closure#0}::VAL"); + } + ); + assert_init_tls(const_tls.value(), "i32", |value| { assert_scalar(value, "i32", Some(SupportedScalar::I32(1337))) }); @@ -1407,29 +1143,36 @@ fn test_read_closures() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(223)); - let vars = debugger.read_local_variables().unwrap(); - assert_struct_n(&vars[0], "inc", "{closure_env#0}", |_, _| { + read_locals!(debugger => inc, inc_mut, _outer, closure, _a, _b, _c, trait_once, trait_mut, trait_fn, fn_ptr); + assert_idents!( + inc => "inc", inc_mut => "inc_mut", closure => "closure", trait_once => "trait_once", + trait_mut => "trait_mut", trait_fn => "trait_fn", fn_ptr => "fn_ptr" + ); + + assert_struct(inc.value(), "{closure_env#0}", |_, _| { panic!("no members expected") }); - assert_struct_n(&vars[1], "inc_mut", "{closure_env#1}", |_, _| { + assert_struct(inc_mut.value(), "{closure_env#1}", |_, _| { panic!("no members expected") }); - assert_struct_n(&vars[3], "closure", "{closure_env#2}", |_, member| { + assert_struct(closure.value(), "{closure_env#2}", |_, member| { assert_member(member, "outer", |val| assert_string(val, "outer val")) }); let rust_version = rust_version(VARS_APP).unwrap(); - assert_struct_n( - &vars[7], - "trait_once", + assert_struct( + trait_once.value(), "alloc::boxed::Box, alloc::alloc::Global>", |i, member| match i { 0 => { assert_member(member, "pointer", |val| { assert_pointer(val, "*dyn core::ops::function::FnOnce<(), Output=()>") }); - let deref = read_single_var(&debugger, "*trait_once.pointer"); + let member_val = member.value.clone(); + let deref = trait_once + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); assert_struct( - &deref, + deref.unwrap().value(), "dyn core::ops::function::FnOnce<(), Output=()>", |_, _| {}, ); @@ -1441,24 +1184,29 @@ fn test_read_closures() { "&[usize; 3]" }; assert_member(member, "vtable", |val| assert_pointer(val, exp_type)); - let deref = read_single_var(&debugger, "*trait_once.vtable"); - assert_array(&deref, "[usize]", |i, _| |_, _| {}); + let member_val = member.value.clone(); + let deref = trait_once + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); + assert_array(deref.unwrap().value(), "[usize]", |_, _| {}); } _ => panic!("2 members expected"), }, ); - assert_struct_n( - &vars[8], - "trait_mut", + assert_struct( + trait_mut.value(), "alloc::boxed::Box, alloc::alloc::Global>", |i, member| match i { 0 => { assert_member(member, "pointer", |val| { assert_pointer(val, "*dyn core::ops::function::FnMut<(), Output=()>") }); - let deref = read_single_var(&debugger, "*trait_mut.pointer"); + let member_val = member.value.clone(); + let deref = trait_mut + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); assert_struct( - &deref, + deref.unwrap().value(), "dyn core::ops::function::FnMut<(), Output=()>", |_, _| {}, ); @@ -1470,24 +1218,29 @@ fn test_read_closures() { "&[usize; 3]" }; assert_member(member, "vtable", |val| assert_pointer(val, exp_type)); - let deref = read_single_var(&debugger, "*trait_mut.vtable"); - assert_array(&deref, "[usize]", |_, _| {}); + let member_val = member.value.clone(); + let deref = trait_mut + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); + assert_array(deref.unwrap().value(), "[usize]", |_, _| {}); } _ => panic!("2 members expected"), }, ); - assert_struct_n( - &vars[9], - "trait_fn", + assert_struct( + trait_fn.value(), "alloc::boxed::Box, alloc::alloc::Global>", |i, member| match i { 0 => { assert_member(member, "pointer", |val| { assert_pointer(val, "*dyn core::ops::function::Fn<(), Output=()>") }); - let deref = read_single_var(&debugger, "*trait_fn.pointer"); + let member_val = member.value.clone(); + let deref = trait_fn + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); assert_struct( - &deref, + deref.unwrap().value(), "dyn core::ops::function::Fn<(), Output=()>", |_, _| {}, ); @@ -1498,14 +1251,17 @@ fn test_read_closures() { } else { "&[usize; 3]" }; - assert_member(member, "vtable", |val| assert_pointer(val, "&[usize; 3]")); - let deref = read_single_var(&debugger, "*trait_fn.vtable"); - assert_array(&deref, "[usize]", |_, _| {}); + assert_member(member, "vtable", |val| assert_pointer(val, exp_type)); + let member_val = member.value.clone(); + let deref = trait_fn + .clone() + .modify_value(|ctx, _| member_val.deref(ctx)); + assert_array(deref.unwrap().value(), "[usize]", |_, _| {}); } _ => panic!("2 members expected"), }, ); - assert_pointer_n(&vars[10], "fn_ptr", "fn() -> u8"); + assert_pointer(fn_ptr.value(), "fn() -> u8"); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1525,15 +1281,16 @@ fn test_arguments() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(232)); - let args = debugger - .read_argument(DQE::Variable(Selector::Any)) - .unwrap(); - assert_scalar_n(&args[0], "by_val", "i32", Some(SupportedScalar::I32(1))); - assert_pointer_n(&args[1], "by_ref", "&i32"); - let deref = read_single_arg(&debugger, "*by_ref"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); + read_arg_dqe!(debugger, Dqe::Variable(Selector::Any) => by_val, by_ref, vec, box_arr); + assert_idents!(by_val => "by_val", by_ref => "by_ref", vec => "vec", box_arr => "box_arr"); + + assert_scalar(by_val.value(), "i32", Some(SupportedScalar::I32(1))); - assert_vec_n(&args[2], "vec", "Vec", 3, |buf| { + assert_pointer(by_ref.value(), "&i32"); + let deref = by_ref.clone().modify_value(|ctx, value| value.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); + + assert_vec(vec.value(), "Vec", 3, |buf| { assert_array(buf, "[u8]", |i, item| match i { 0 => assert_scalar(item, "u8", Some(SupportedScalar::U8(3))), 1 => assert_scalar(item, "u8", Some(SupportedScalar::U8(4))), @@ -1541,15 +1298,18 @@ fn test_arguments() { _ => panic!("3 items expected"), }) }); - assert_struct_n( - &args[3], - "box_arr", + + assert_struct( + box_arr.value(), "alloc::boxed::Box<[u8], alloc::alloc::Global>", |i, member| match i { 0 => { assert_member(member, "data_ptr", |val| assert_pointer(val, "*u8")); - let deref = read_single_arg(&debugger, "*box_arr.data_ptr"); - assert_scalar(&deref, "u8", Some(SupportedScalar::U8(6))); + let data_ptr_val = member.value.clone(); + let deref = box_arr + .clone() + .modify_value(|ctx, _| data_ptr_val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "u8", Some(SupportedScalar::U8(6))); } 1 => assert_member(member, "length", |val| { assert_scalar(val, "usize", Some(SupportedScalar::Usize(3))) @@ -1576,8 +1336,9 @@ fn test_read_union() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(244)); - let vars = debugger.read_local_variables().unwrap(); - assert_struct_n(&vars[0], "union", "Union1", |i, member| match i { + read_locals!(debugger => union); + assert_idents!(union => "union"); + assert_struct(union.value(), "Union1", |i, member| match i { 0 => assert_member(member, "f1", |val| { assert_scalar(val, "f32", Some(SupportedScalar::F32(1.1))) }), @@ -1611,8 +1372,10 @@ fn test_read_hashmap() { (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap", ).unwrap(); - let vars = debugger.read_local_variables().unwrap(); - assert_hashmap_n(&vars[0], "hm1", hash_map_type, |items| { + read_locals!(debugger => hm1, hm2, hm3, hm4, _a, b, _hm5, _hm6); + assert_idents!(hm1 => "hm1", hm2 => "hm2", hm3 => "hm3", hm4 => "hm4"); + + assert_hashmap(hm1.value(), hash_map_type, |items| { assert_eq!(items.len(), 2); assert_scalar(&items[0].0, "bool", Some(SupportedScalar::Bool(false))); assert_scalar(&items[0].1, "i64", Some(SupportedScalar::I64(5))); @@ -1625,7 +1388,7 @@ fn test_read_hashmap() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap<&str, alloc::vec::Vec, std::collections::hash::map::RandomState>", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap<&str, alloc::vec::Vec, std::hash::random::RandomState>", ).unwrap(); - assert_hashmap_n(&vars[1], "hm2", hash_map_type, |items| { + assert_hashmap(hm2.value(), hash_map_type, |items| { assert_eq!(items.len(), 2); assert_str(&items[0].0, "abc"); assert_vec(&items[0].1, "Vec", 3, |buf| { @@ -1652,7 +1415,7 @@ fn test_read_hashmap() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap", ).unwrap(); - assert_hashmap_n(&vars[2], "hm3", hash_map_type, |items| { + assert_hashmap(hm3.value(), hash_map_type, |items| { assert_eq!(items.len(), 100); let mut exp_items = (0..100).collect::>(); @@ -1676,8 +1439,7 @@ fn test_read_hashmap() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap", ).unwrap(); - - assert_hashmap_n(&vars[3], "hm4", hash_map_type, |items| { + assert_hashmap(hm4.value(), hash_map_type, |items| { assert_eq!(items.len(), 2); assert_string(&items[0].0, "1"); assert_hashmap(&items[0].1, inner_hash_map_type, |items| { @@ -1699,22 +1461,18 @@ fn test_read_hashmap() { }); let make_idx_dqe = |var: &str, literal| { - DQE::Index(DQE::Variable(Selector::by_name(var, true)).boxed(), literal) + Dqe::Index(Dqe::Variable(Selector::by_name(var, true)).boxed(), literal) }; // get by bool key let dqe = make_idx_dqe("hm1", Literal::Bool(true)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "i64", Some(SupportedScalar::I64(3))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i64", Some(SupportedScalar::I64(3))); // get by string key let dqe = make_idx_dqe("hm2", Literal::String("efg".to_string())); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_vec(val, "Vec", 3, |buf| { + read_var_dqe!(debugger, dqe => val); + assert_vec(val.value(), "Vec", 3, |buf| { assert_array(buf, "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(11))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(12))), @@ -1725,26 +1483,18 @@ fn test_read_hashmap() { // get by int key let dqe = make_idx_dqe("hm3", Literal::Int(99)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "i32", Some(SupportedScalar::I32(99))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i32", Some(SupportedScalar::I32(99))); // get by pointer key - let key = debugger - .read_variable(DQE::Variable(Selector::by_name("b", true))) - .unwrap(); - assert_eq!(key.len(), 1); - let VariableIR::Pointer(ptr) = &key[0] else { + let Value::Pointer(ptr) = &b.value() else { panic!("not a pointer") }; let ptr_val = ptr.value.unwrap() as usize; let dqe = make_idx_dqe("hm5", Literal::Address(ptr_val)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_str(val, "b"); + read_var_dqe!(debugger, dqe => val); + assert_str(val.value(), "b"); // get by complex object let dqe = make_idx_dqe( @@ -1772,10 +1522,8 @@ fn test_read_hashmap() { ), ])), ); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "i32", Some(SupportedScalar::I32(1))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i32", Some(SupportedScalar::I32(1))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1802,15 +1550,17 @@ fn test_read_hashset() { (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashSet", ).unwrap(); - let vars = debugger.read_local_variables().unwrap(); - assert_hashset(&vars[0], "hs1", hashset_type, |items| { + read_locals!(debugger => hs1, hs2, hs3, _a, b, _hs4); + assert_idents!(hs1 => "hs1", hs2 => "hs2", hs3 => "hs3"); + + assert_hashset(hs1.value(), hashset_type, |items| { assert_eq!(items.len(), 4); assert_scalar(&items[0], "i32", Some(SupportedScalar::I32(1))); assert_scalar(&items[1], "i32", Some(SupportedScalar::I32(2))); assert_scalar(&items[2], "i32", Some(SupportedScalar::I32(3))); assert_scalar(&items[3], "i32", Some(SupportedScalar::I32(4))); }); - assert_hashset(&vars[1], "hs2", hashset_type, |items| { + assert_hashset(hs2.value(), hashset_type, |items| { assert_eq!(items.len(), 100); let mut exp_items = (0..100).collect::>(); exp_items.sort_by_key(|i1| i1.to_string()); @@ -1825,7 +1575,7 @@ fn test_read_hashset() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashSet, std::collections::hash::map::RandomState>", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashSet, std::hash::random::RandomState>", ).unwrap(); - assert_hashset(&vars[2], "hs3", hashset_type, |items| { + assert_hashset(hs3.value(), hashset_type, |items| { assert_eq!(items.len(), 1); assert_vec(&items[0], "Vec", 2, |buf| { assert_array(buf, "[i32]", |i, item| match i { @@ -1837,43 +1587,31 @@ fn test_read_hashset() { }); let make_idx_dqe = |var: &str, literal| { - DQE::Index(DQE::Variable(Selector::by_name(var, true)).boxed(), literal) + Dqe::Index(Dqe::Variable(Selector::by_name(var, true)).boxed(), literal) }; // get by int key let dqe = make_idx_dqe("hs1", Literal::Int(2)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "bool", Some(SupportedScalar::Bool(true))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(true))); let dqe = make_idx_dqe("hs1", Literal::Int(5)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "bool", Some(SupportedScalar::Bool(false))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(false))); // get by pointer key - let key = debugger - .read_variable(DQE::Variable(Selector::by_name("b", true))) - .unwrap(); - assert_eq!(key.len(), 1); - let VariableIR::Pointer(ptr) = &key[0] else { + let Value::Pointer(ptr) = &b.value() else { panic!("not a pointer") }; let ptr_val = ptr.value.unwrap() as usize; let dqe = make_idx_dqe("hs4", Literal::Address(ptr_val)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "bool", Some(SupportedScalar::Bool(true))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(true))); let dqe = make_idx_dqe("hs4", Literal::Address(0)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "bool", Some(SupportedScalar::Bool(false))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(false))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1893,39 +1631,42 @@ fn test_circular_ref_types() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(334)); - let vars = debugger.read_local_variables().unwrap(); - assert_rc_n( - &vars[0], - "a_circ", + read_locals!(debugger => a_circ, b_circ); + assert_idents!(a_circ => "a_circ", b_circ => "b_circ"); + + assert_rc( + a_circ.value(), "Rc", ); - assert_rc_n( - &vars[1], - "b_circ", + assert_rc( + b_circ.value(), "Rc", ); - let deref = read_single_var(&debugger, "*a_circ"); - assert_struct(&deref, "RcBox", |i, member| match i { - 0 => assert_member(member, "strong", |val| { - assert_cell(val, "Cell", |inner| { - assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) - }) - }), - 1 => assert_member(member, "weak", |val| { - assert_cell(val, "Cell", |inner| { - assert_scalar(inner, "usize", Some(SupportedScalar::Usize(1))) - }) - }), - 2 => { - assert_member(member, "value", |val| { - assert_rust_enum(val, "List", |enum_member| { - assert_struct(enum_member, "Cons", |i, cons_member| match i { - 0 => assert_member(cons_member, "__0", |val| { - assert_scalar(val, "i32", Some(SupportedScalar::I32(5))) - }), - 1 => assert_member(cons_member, "__1", |val| { - assert_refcell( + let deref = a_circ.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct( + deref.unwrap().value(), + "RcBox", + |i, member| match i { + 0 => assert_member(member, "strong", |val| { + assert_cell(val, "Cell", |inner| { + assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) + }) + }), + 1 => assert_member(member, "weak", |val| { + assert_cell(val, "Cell", |inner| { + assert_scalar(inner, "usize", Some(SupportedScalar::Usize(1))) + }) + }), + 2 => { + assert_member(member, "value", |val| { + assert_rust_enum(val, "List", |enum_member| { + assert_struct(enum_member, "Cons", |i, cons_member| match i { + 0 => assert_member(cons_member, "__0", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(5))) + }), + 1 => assert_member(cons_member, "__1", |val| { + assert_refcell( val, "RefCell>", 0, @@ -1936,14 +1677,15 @@ fn test_circular_ref_types() { ) }, ) - }), - _ => panic!("2 members expected"), - }); - }) - }); - } - _ => panic!("3 members expected"), - }); + }), + _ => panic!("2 members expected"), + }); + }) + }); + } + _ => panic!("3 members expected"), + }, + ); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -1962,40 +1704,32 @@ fn test_lexical_blocks() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(340)); - let vars = debugger.read_local_variables().unwrap(); + read_locals!(debugger => alpha, _beta); // WAITFORFIX: https://github.com/rust-lang/rust/issues/113819 // expected: assert_eq!(vars.len(), 1); // through this bug there is uninitialized variable here - assert_eq!(vars.len(), 2); - assert_eq!(vars[0].name().unwrap(), "alpha"); + assert_idents!(alpha => "alpha"); debugger.set_breakpoint_at_line("vars.rs", 342).unwrap(); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(342)); - let vars = debugger.read_local_variables().unwrap(); - assert_eq!(vars.len(), 2); - assert_eq!(vars[0].name().unwrap(), "alpha"); - assert_eq!(vars[1].name().unwrap(), "beta"); + read_locals!(debugger => alpha, beta); + assert_idents!(alpha => "alpha", beta => "beta"); debugger.set_breakpoint_at_line("vars.rs", 343).unwrap(); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(343)); - let vars = debugger.read_local_variables().unwrap(); - assert_eq!(vars.len(), 3); - assert_eq!(vars[0].name().unwrap(), "alpha"); - assert_eq!(vars[1].name().unwrap(), "beta"); - assert_eq!(vars[2].name().unwrap(), "gama"); + read_locals!(debugger => alpha, beta, gama); + assert_idents!(alpha => "alpha", beta => "beta", gama => "gama"); debugger.set_breakpoint_at_line("vars.rs", 349).unwrap(); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(349)); - let vars = debugger.read_local_variables().unwrap(); - assert_eq!(vars.len(), 2); - assert_eq!(vars[0].name().unwrap(), "alpha"); - assert_eq!(vars[1].name().unwrap(), "delta"); + read_locals!(debugger => alpha, delta); + assert_idents!(alpha => "alpha", delta => "delta"); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2014,10 +1748,11 @@ fn test_btree_map() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(396)); - let vars = debugger.read_local_variables().unwrap(); - assert_btree_map_n( - &vars[0], - "hm1", + read_locals!(debugger => hm1, hm2, hm3, hm4, _a, b, _hm5, _hm6); + assert_idents!(hm1 => "hm1", hm2 => "hm2", hm3 => "hm3", hm4 => "hm4"); + + assert_btree_map( + hm1.value(), "BTreeMap", |items| { assert_eq!(items.len(), 2); @@ -2027,9 +1762,9 @@ fn test_btree_map() { assert_scalar(&items[1].1, "i64", Some(SupportedScalar::I64(3))); }, ); - assert_btree_map_n( - &vars[1], - "hm2", + + assert_btree_map( + hm2.value(), "BTreeMap<&str, alloc::vec::Vec, alloc::alloc::Global>", |items| { assert_eq!(items.len(), 2); @@ -2053,9 +1788,9 @@ fn test_btree_map() { }); }, ); - assert_btree_map_n( - &vars[2], - "hm3", + + assert_btree_map( + hm3.value(), "BTreeMap", |items| { assert_eq!(items.len(), 100); @@ -2070,9 +1805,9 @@ fn test_btree_map() { } }, ); - assert_btree_map_n( - &vars[3], - "hm4", + + assert_btree_map( + hm4.value(), "BTreeMap, alloc::alloc::Global>", |items| { assert_eq!(items.len(), 2); @@ -2107,22 +1842,18 @@ fn test_btree_map() { }); let make_idx_dqe = |var: &str, literal| { - DQE::Index(DQE::Variable(Selector::by_name(var, true)).boxed(), literal) + Dqe::Index(Dqe::Variable(Selector::by_name(var, true)).boxed(), literal) }; // get by bool key let dqe = make_idx_dqe("hm1", Literal::Bool(true)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "i64", Some(SupportedScalar::I64(3))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i64", Some(SupportedScalar::I64(3))); // get by string key let dqe = make_idx_dqe("hm2", Literal::String("efg".to_string())); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_vec(val, "Vec", 3, |buf| { + read_var_dqe!(debugger, dqe => val); + assert_vec(val.value(), "Vec", 3, |buf| { assert_array(buf, "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(11))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(12))), @@ -2133,26 +1864,18 @@ fn test_btree_map() { // get by int key let dqe = make_idx_dqe("hm3", Literal::Int(99)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "i32", Some(SupportedScalar::I32(99))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i32", Some(SupportedScalar::I32(99))); // get by pointer key - let key = debugger - .read_variable(DQE::Variable(Selector::by_name("b", true))) - .unwrap(); - assert_eq!(key.len(), 1); - let VariableIR::Pointer(ptr) = &key[0] else { + let Value::Pointer(ptr) = b.value() else { panic!("not a pointer") }; let ptr_val = ptr.value.unwrap() as usize; let dqe = make_idx_dqe("hm5", Literal::Address(ptr_val)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_str(val, "b"); + read_var_dqe!(debugger, dqe => val); + assert_str(val.value(), "b"); // get by complex object let dqe = make_idx_dqe( @@ -2173,10 +1896,8 @@ fn test_btree_map() { ), ])), ); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "i32", Some(SupportedScalar::I32(2))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "i32", Some(SupportedScalar::I32(2))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2196,10 +1917,11 @@ fn test_read_btree_set() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(413)); - let vars = debugger.read_local_variables().unwrap(); + read_locals!(debugger => hs1, hs2, hs3, _a, b, _hs4); + assert_idents!(hs1 => "hs1", hs2 => "hs2", hs3 => "hs3"); + assert_btree_set( - &vars[0], - "hs1", + hs1.value(), "BTreeSet", |items| { assert_eq!(items.len(), 4); @@ -2209,9 +1931,9 @@ fn test_read_btree_set() { assert_scalar(&items[3], "i32", Some(SupportedScalar::I32(4))); }, ); + assert_btree_set( - &vars[1], - "hs2", + hs2.value(), "BTreeSet", |items| { assert_eq!(items.len(), 100); @@ -2222,9 +1944,9 @@ fn test_read_btree_set() { } }, ); + assert_btree_set( - &vars[2], - "hs3", + hs3.value(), "BTreeSet, alloc::alloc::Global>", |items| { assert_eq!(items.len(), 1); @@ -2239,43 +1961,31 @@ fn test_read_btree_set() { ); let make_idx_dqe = |var: &str, literal| { - DQE::Index(DQE::Variable(Selector::by_name(var, true)).boxed(), literal) + Dqe::Index(Dqe::Variable(Selector::by_name(var, true)).boxed(), literal) }; // get by int key let dqe = make_idx_dqe("hs1", Literal::Int(2)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "bool", Some(SupportedScalar::Bool(true))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(true))); let dqe = make_idx_dqe("hs1", Literal::Int(5)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "bool", Some(SupportedScalar::Bool(false))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(false))); // get by pointer key - let key = debugger - .read_variable(DQE::Variable(Selector::by_name("b", true))) - .unwrap(); - assert_eq!(key.len(), 1); - let VariableIR::Pointer(ptr) = &key[0] else { + let Value::Pointer(ptr) = b.value() else { panic!("not a pointer") }; let ptr_val = ptr.value.unwrap() as usize; let dqe = make_idx_dqe("hs4", Literal::Address(ptr_val)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "bool", Some(SupportedScalar::Bool(true))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(true))); let dqe = make_idx_dqe("hs4", Literal::Address(0)); - let val = debugger.read_variable(dqe).unwrap(); - assert_eq!(val.len(), 1); - let val = &val[0]; - assert_scalar(val, "bool", Some(SupportedScalar::Bool(false))); + read_var_dqe!(debugger, dqe => val); + assert_scalar(val.value(), "bool", Some(SupportedScalar::Bool(false))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2295,10 +2005,11 @@ fn test_read_vec_deque() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(431)); - let vars = debugger.read_local_variables().unwrap(); - assert_vec_deque_n( - &vars[0], - "vd1", + read_locals!(debugger => vd1, vd2); + assert_idents!(vd1 => "vd1", vd2 => "vd2"); + + assert_vec_deque( + vd1.value(), "VecDeque", 8, |buf| { @@ -2313,9 +2024,8 @@ fn test_read_vec_deque() { }, ); - assert_vec_deque_n( - &vars[1], - "vd2", + assert_vec_deque( + vd2.value(), "VecDeque, alloc::alloc::Global>", 4, |buf| { @@ -2366,8 +2076,10 @@ fn test_read_atomic() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(441)); - let vars = debugger.read_local_variables().unwrap(); - assert_struct_n(&vars[0], "int32_atomic", "AtomicI32", |i, member| match i { + read_locals!(debugger => int32_atomic, _int32, int32_atomic_ptr); + assert_idents!(int32_atomic => "int32_atomic", int32_atomic_ptr => "int32_atomic_ptr"); + + assert_struct(int32_atomic.value(), "AtomicI32", |i, member| match i { 0 => assert_member(member, "v", |val| { assert_struct(val, "UnsafeCell", |i, member| match i { 0 => assert_member(member, "value", |val| { @@ -2379,9 +2091,8 @@ fn test_read_atomic() { _ => panic!("1 members expected"), }); - assert_struct_n( - &vars[2], - "int32_atomic_ptr", + assert_struct( + int32_atomic_ptr.value(), "AtomicPtr", |i, member| match i { 0 => assert_member(member, "p", |val| { @@ -2394,8 +2105,10 @@ fn test_read_atomic() { }, ); - let deref = read_single_var(&debugger, "*int32_atomic_ptr.p.value"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); + let deref = int32_atomic_ptr + .clone() + .modify_value(|ctx, v| v.field("p").unwrap().field("value").unwrap().deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2415,14 +2128,15 @@ fn test_cell() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(453)); - let vars = debugger.read_local_variables().unwrap(); - assert_cell_n(&vars[0], "a_cell", "Cell", |value| { + read_locals!(debugger => a_cell, b_refcell, _b_refcell_borrow_1, _b_refcell_borrow_2); + assert_idents!(a_cell => "a_cell", b_refcell => "b_refcell"); + + assert_cell(a_cell.value(), "Cell", |value| { assert_scalar(value, "i32", Some(SupportedScalar::I32(1))) }); - assert_refcell_n( - &vars[1], - "b_refcell", + assert_refcell( + b_refcell.value(), "RefCell>", 2, |value| { @@ -2455,10 +2169,14 @@ fn test_shared_ptr() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(475)); - let vars = debugger.read_local_variables().unwrap(); - assert_rc_n(&vars[0], "rc0", "Rc"); - let deref = read_single_var(&debugger, "*rc0"); - assert_struct(&deref, "RcBox", |i, member| match i { + read_locals!(debugger => rc0, rc1, weak_rc2, arc0, arc1, weak_arc2); + assert_idents!( + rc0 => "rc0", rc1 => "rc1", weak_rc2 => "weak_rc2", arc0 => "arc0", arc1 => "arc1", weak_arc2 => "weak_arc2" + ); + + assert_rc(rc0.value(), "Rc"); + let deref = rc0.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct(deref.unwrap().value(), "RcBox", |i, member| match i { 0 => assert_member(member, "strong", |val| { assert_cell(val, "Cell", |inner| { assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) @@ -2474,9 +2192,10 @@ fn test_shared_ptr() { }), _ => panic!("3 members expected"), }); - assert_rc_n(&vars[1], "rc1", "Rc"); - let deref = read_single_var(&debugger, "*rc1"); - assert_struct(&deref, "RcBox", |i, member| match i { + + assert_rc(rc1.value(), "Rc"); + let deref = rc1.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct(deref.unwrap().value(), "RcBox", |i, member| match i { 0 => assert_member(member, "strong", |val| { assert_cell(val, "Cell", |inner| { assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) @@ -2492,9 +2211,10 @@ fn test_shared_ptr() { }), _ => panic!("3 members expected"), }); - assert_rc_n(&vars[2], "weak_rc2", "Weak"); - let deref = read_single_var(&debugger, "*weak_rc2"); - assert_struct(&deref, "RcBox", |i, member| match i { + + assert_rc(weak_rc2.value(), "Weak"); + let deref = weak_rc2.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct(deref.unwrap().value(), "RcBox", |i, member| match i { 0 => assert_member(member, "strong", |val| { assert_cell(val, "Cell", |inner| { assert_scalar(inner, "usize", Some(SupportedScalar::Usize(2))) @@ -2511,73 +2231,86 @@ fn test_shared_ptr() { _ => panic!("3 members expected"), }); - assert_arc_n(&vars[3], "arc0", "Arc"); - let deref = read_single_var(&debugger, "*arc0"); - assert_struct(&deref, "ArcInner", |i, member| match i { - 0 => assert_member(member, "strong", |val| { - assert_struct(val, "AtomicUsize", |i, member| match i { - 0 => assert_member(member, "v", |val| { - assert_struct(val, "UnsafeCell", |_, member| { - assert_member(member, "value", |val| { - assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + assert_arc(arc0.value(), "Arc"); + let deref = arc0.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct( + deref.unwrap().value(), + "ArcInner", + |i, member| match i { + 0 => assert_member(member, "strong", |val| { + assert_struct(val, "AtomicUsize", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |_, member| { + assert_member(member, "value", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }) }) - }) - }), - _ => panic!("1 member expected"), - }) - }), - 1 => assert_member(member, "weak", |val| { - assert_struct(val, "AtomicUsize", |i, member| match i { - 0 => assert_member(member, "v", |val| { - assert_struct(val, "UnsafeCell", |_, member| { - assert_member(member, "value", |val| { - assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }), + _ => panic!("1 member expected"), + }) + }), + 1 => assert_member(member, "weak", |val| { + assert_struct(val, "AtomicUsize", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |_, member| { + assert_member(member, "value", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }) }) - }) - }), - _ => panic!("1 member expected"), - }) - }), - 2 => assert_member(member, "data", |val| { - assert_scalar(val, "i32", Some(SupportedScalar::I32(2))) - }), - _ => panic!("3 members expected"), - }); - assert_arc_n(&vars[4], "arc1", "Arc"); - let deref = read_single_var(&debugger, "*arc1"); - assert_struct(&deref, "ArcInner", |i, member| match i { - 0 => assert_member(member, "strong", |val| { - assert_struct(val, "AtomicUsize", |i, member| match i { - 0 => assert_member(member, "v", |val| { - assert_struct(val, "UnsafeCell", |_, member| { - assert_member(member, "value", |val| { - assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }), + _ => panic!("1 member expected"), + }) + }), + 2 => assert_member(member, "data", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(2))) + }), + _ => panic!("3 members expected"), + }, + ); + + assert_arc(arc1.value(), "Arc"); + let deref = arc1.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_struct( + deref.unwrap().value(), + "ArcInner", + |i, member| match i { + 0 => assert_member(member, "strong", |val| { + assert_struct(val, "AtomicUsize", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |_, member| { + assert_member(member, "value", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }) }) - }) - }), - _ => panic!("1 member expected"), - }) - }), - 1 => assert_member(member, "weak", |val| { - assert_struct(val, "AtomicUsize", |i, member| match i { - 0 => assert_member(member, "v", |val| { - assert_struct(val, "UnsafeCell", |_, member| { - assert_member(member, "value", |val| { - assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }), + _ => panic!("1 member expected"), + }) + }), + 1 => assert_member(member, "weak", |val| { + assert_struct(val, "AtomicUsize", |i, member| match i { + 0 => assert_member(member, "v", |val| { + assert_struct(val, "UnsafeCell", |_, member| { + assert_member(member, "value", |val| { + assert_scalar(val, "usize", Some(SupportedScalar::Usize(2))) + }) }) - }) - }), - _ => panic!("1 member expected"), - }) - }), - 2 => assert_member(member, "data", |val| { - assert_scalar(val, "i32", Some(SupportedScalar::I32(2))) - }), - _ => panic!("3 members expected"), - }); - assert_arc_n(&vars[5], "weak_arc2", "Weak"); - let deref = read_single_var(&debugger, "*weak_arc2"); - assert_struct(&deref, "ArcInner", |i, member| match i { + }), + _ => panic!("1 member expected"), + }) + }), + 2 => assert_member(member, "data", |val| { + assert_scalar(val, "i32", Some(SupportedScalar::I32(2))) + }), + _ => panic!("3 members expected"), + }, + ); + + assert_arc(weak_arc2.value(), "Weak"); + let deref = weak_arc2 + .clone() + .modify_value(|ctx, v| v.deref(ctx)) + .unwrap(); + assert_struct(deref.value(), "ArcInner", |i, member| match i { 0 => assert_member(member, "strong", |val| { assert_struct(val, "AtomicUsize", |i, member| match i { 0 => assert_member(member, "v", |val| { @@ -2626,51 +2359,43 @@ fn test_zst_types() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(496)); - let vars = debugger.read_local_variables().unwrap(); + read_locals!( + debugger => ptr_zst, array_zst, vec_zst, slice_zst, struct_zst, enum_zst, vecdeque_zst, + hash_map_zst_key, hash_map_zst_val, hash_map_zst, hash_set_zst, btree_map_zst_key, + btree_map_zst_val, btree_map_zst, btree_set_zst + ); + assert_idents!( + ptr_zst => "ptr_zst", array_zst => "array_zst", vec_zst => "vec_zst", + slice_zst => "slice_zst", struct_zst => "struct_zst", enum_zst => "enum_zst", + vecdeque_zst => "vecdeque_zst", hash_map_zst_key => "hash_map_zst_key", + hash_map_zst_val => "hash_map_zst_val", hash_map_zst => "hash_map_zst", + hash_set_zst => "hash_set_zst", btree_map_zst_key => "btree_map_zst_key", + btree_map_zst_val => "btree_map_zst_val", btree_map_zst => "btree_map_zst", + btree_set_zst => "btree_set_zst" + ); - assert_pointer_n(&vars[0], "ptr_zst", "&()"); - let deref = read_single_var(&debugger, "*ptr_zst"); - assert_scalar(&deref, "()", Some(SupportedScalar::Empty())); + assert_pointer(ptr_zst.value(), "&()"); + let deref = ptr_zst.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_scalar(deref.unwrap().value(), "()", Some(SupportedScalar::Empty())); - assert_array_n(&vars[1], "array_zst", "[()]", |i, item| match i { + assert_array(array_zst.value(), "[()]", |i, item| match i { 0 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), 1 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), _ => panic!("2 members expected"), }); - assert_vec_n( - &vars[2], - "vec_zst", - "Vec<(), alloc::alloc::Global>", - 0, - |buf| { - assert_array(buf, "[()]", |i, item| match i { - 0 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), - 1 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), - 2 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), - _ => panic!("3 members expected"), - }) - }, - ); - - assert_vec_n( - &vars[2], - "vec_zst", - "Vec<(), alloc::alloc::Global>", - 0, - |buf| { - assert_array(buf, "[()]", |i, item| match i { - 0 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), - 1 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), - 2 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), - _ => panic!("3 members expected"), - }) - }, - ); + assert_vec(vec_zst.value(), "Vec<(), alloc::alloc::Global>", 0, |buf| { + assert_array(buf, "[()]", |i, item| match i { + 0 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 1 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + 2 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), + _ => panic!("3 members expected"), + }) + }); - assert_pointer_n(&vars[3], "slice_zst", "&[(); 4]"); - let deref = read_single_var(&debugger, "*slice_zst"); - assert_array(&deref, "[()]", |i, item| match i { + assert_pointer(slice_zst.value(), "&[(); 4]"); + let deref = slice_zst.clone().modify_value(|ctx, v| v.deref(ctx)); + assert_array(deref.unwrap().value(), "[()]", |i, item| match i { 0 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), 1 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), 2 => assert_scalar(item, "()", Some(SupportedScalar::Empty())), @@ -2678,14 +2403,14 @@ fn test_zst_types() { _ => panic!("4 members expected"), }); - assert_struct_n(&vars[4], "struct_zst", "StructZst", |i, member| match i { + assert_struct(struct_zst.value(), "StructZst", |i, member| match i { 0 => assert_member(member, "__0", |val| { assert_scalar(val, "()", Some(SupportedScalar::Empty())) }), _ => panic!("1 member expected"), }); - assert_rust_enum_n(&vars[5], "enum_zst", "Option<()>", |member| { + assert_rust_enum(enum_zst.value(), "Option<()>", |member| { assert_struct(member, "Some", |i, member| match i { 0 => assert_member(member, "__0", |val| { assert_scalar(val, "()", Some(SupportedScalar::Empty())) @@ -2694,9 +2419,8 @@ fn test_zst_types() { }) }); - assert_vec_deque_n( - &vars[6], - "vecdeque_zst", + assert_vec_deque( + vecdeque_zst.value(), "VecDeque<(), alloc::alloc::Global>", 0, |buf| { @@ -2717,7 +2441,7 @@ fn test_zst_types() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap<(), i32, std::collections::hash::map::RandomState>", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap<(), i32, std::hash::random::RandomState>", ).unwrap(); - assert_hashmap_n(&vars[7], "hash_map_zst_key", hashmap_type, |items| { + assert_hashmap(hash_map_zst_key.value(), hashmap_type, |items| { assert_eq!(items.len(), 1); assert_scalar(&items[0].0, "()", Some(SupportedScalar::Empty())); assert_scalar(&items[0].1, "i32", Some(SupportedScalar::I32(1))); @@ -2728,7 +2452,7 @@ fn test_zst_types() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap", ).unwrap(); - assert_hashmap_n(&vars[8], "hash_map_zst_val", hashmap_type, |items| { + assert_hashmap(hash_map_zst_val.value(), hashmap_type, |items| { assert_eq!(items.len(), 1); assert_scalar(&items[0].0, "i32", Some(SupportedScalar::I32(1))); assert_scalar(&items[0].1, "()", Some(SupportedScalar::Empty())); @@ -2739,7 +2463,7 @@ fn test_zst_types() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashMap<(), (), std::collections::hash::map::RandomState>", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashMap<(), (), std::hash::random::RandomState>", ).unwrap(); - assert_hashmap_n(&vars[9], "hash_map_zst", hashmap_type, |items| { + assert_hashmap(hash_map_zst.value(), hashmap_type, |items| { assert_eq!(items.len(), 1); assert_scalar(&items[0].0, "()", Some(SupportedScalar::Empty())); assert_scalar(&items[0].1, "()", Some(SupportedScalar::Empty())); @@ -2750,14 +2474,13 @@ fn test_zst_types() { (1, 0, 0) ..= (1, 75, u32::MAX) => "HashSet<(), std::collections::hash::map::RandomState>", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "HashSet<(), std::hash::random::RandomState>", ).unwrap(); - assert_hashset(&vars[10], "hash_set_zst", hashset_type, |items| { + assert_hashset(hash_set_zst.value(), hashset_type, |items| { assert_eq!(items.len(), 1); assert_scalar(&items[0], "()", Some(SupportedScalar::Empty())); }); - assert_btree_map_n( - &vars[11], - "btree_map_zst_key", + assert_btree_map( + btree_map_zst_key.value(), "BTreeMap<(), i32, alloc::alloc::Global>", |items| { assert_eq!(items.len(), 1); @@ -2765,9 +2488,9 @@ fn test_zst_types() { assert_scalar(&items[0].1, "i32", Some(SupportedScalar::I32(1))); }, ); - assert_btree_map_n( - &vars[12], - "btree_map_zst_val", + + assert_btree_map( + btree_map_zst_val.value(), "BTreeMap", |items| { assert_eq!(items.len(), 2); @@ -2777,9 +2500,9 @@ fn test_zst_types() { assert_scalar(&items[1].1, "()", Some(SupportedScalar::Empty())); }, ); - assert_btree_map_n( - &vars[13], - "btree_map_zst", + + assert_btree_map( + btree_map_zst.value(), "BTreeMap<(), (), alloc::alloc::Global>", |items| { assert_eq!(items.len(), 1); @@ -2787,9 +2510,9 @@ fn test_zst_types() { assert_scalar(&items[0].1, "()", Some(SupportedScalar::Empty())); }, ); + assert_btree_set( - &vars[14], - "btree_set_zst", + btree_set_zst.value(), "BTreeSet<(), alloc::alloc::Global>", |items| { assert_eq!(items.len(), 1); @@ -2818,28 +2541,16 @@ fn test_read_static_in_fn_variable() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(504)); - let vars = debugger - .read_variable(DQE::Variable(Selector::by_name("INNER_STATIC", false))) - .unwrap(); - assert_scalar_n( - &vars[0], - "vars::inner_static::INNER_STATIC", - "u32", - Some(SupportedScalar::U32(1)), - ); + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name("INNER_STATIC", false)) => inner_static); + assert_idents!(inner_static => "vars::inner_static::INNER_STATIC"); + assert_scalar(inner_static.value(), "u32", Some(SupportedScalar::U32(1))); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(570)); - let vars = debugger - .read_variable(DQE::Variable(Selector::by_name("INNER_STATIC", false))) - .unwrap(); - assert_scalar_n( - &vars[0], - "vars::inner_static::INNER_STATIC", - "u32", - Some(SupportedScalar::U32(1)), - ); + read_var_dqe!(debugger, Dqe::Variable(Selector::by_name("INNER_STATIC", false)) => inner_static); + assert_idents!(inner_static => "vars::inner_static::INNER_STATIC"); + assert_scalar(inner_static.value(), "u32", Some(SupportedScalar::U32(1))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2859,14 +2570,13 @@ fn test_slice_operator() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(61)); - let vars = debugger - .read_variable(DQE::Slice( - DQE::Variable(Selector::by_name("arr_1", true)).boxed(), + read_var_dqe!(debugger, Dqe::Slice( + Dqe::Variable(Selector::by_name("arr_1", true)).boxed(), None, None, - )) - .unwrap(); - assert_array_n(&vars[0], "arr_1", "[i32]", |i, item| match i { + ) => arr_1); + assert_idents!(arr_1 => "arr_1"); + assert_array(arr_1.value(), "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), @@ -2875,40 +2585,37 @@ fn test_slice_operator() { _ => panic!("5 items expected"), }); - let vars = debugger - .read_variable(DQE::Slice( - DQE::Variable(Selector::by_name("arr_1", true)).boxed(), + read_var_dqe!(debugger, Dqe::Slice( + Dqe::Variable(Selector::by_name("arr_1", true)).boxed(), Some(3), None, - )) - .unwrap(); - assert_array_n(&vars[0], "arr_1", "[i32]", |i, item| match i { + ) => arr_1); + assert_idents!(arr_1 => "arr_1"); + assert_array(arr_1.value(), "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-2))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(3))), _ => panic!("2 items expected"), }); - let vars = debugger - .read_variable(DQE::Slice( - DQE::Variable(Selector::by_name("arr_1", true)).boxed(), + read_var_dqe!(debugger, Dqe::Slice( + Dqe::Variable(Selector::by_name("arr_1", true)).boxed(), None, Some(2), - )) - .unwrap(); - assert_array_n(&vars[0], "arr_1", "[i32]", |i, item| match i { + ) => arr_1); + assert_idents!(arr_1 => "arr_1"); + assert_array(arr_1.value(), "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(1))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), _ => panic!("2 items expected"), }); - let vars = debugger - .read_variable(DQE::Slice( - DQE::Variable(Selector::by_name("arr_1", true)).boxed(), + read_var_dqe!(debugger, Dqe::Slice( + Dqe::Variable(Selector::by_name("arr_1", true)).boxed(), Some(1), Some(4), - )) - .unwrap(); - assert_array_n(&vars[0], "arr_1", "[i32]", |i, item| match i { + ) => arr_1); + assert_idents!(arr_1 => "arr_1"); + assert_array(arr_1.value(), "[i32]", |i, item| match i { 0 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-1))), 1 => assert_scalar(item, "i32", Some(SupportedScalar::I32(2))), 2 => assert_scalar(item, "i32", Some(SupportedScalar::I32(-2))), @@ -2933,21 +2640,19 @@ fn test_cast_pointers() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(119)); - let vars = debugger.read_local_variables().unwrap(); - assert_scalar_n(&vars[0], "a", "i32", Some(SupportedScalar::I32(2))); + read_locals!(debugger => a, ref_a, _ptr_a, _ptr_ptr_a, _b, _mut_ref_b, _c, _mut_ptr_c, _box_d, _f, _ref_f); - let VariableIR::Pointer(pointer) = &vars[1] else { + assert_scalar(a.value(), "i32", Some(SupportedScalar::I32(2))); + let Value::Pointer(pointer) = ref_a.value() else { panic!("expect a pointer"); }; let raw_ptr = pointer.value.unwrap(); - let var = debugger - .read_variable(DQE::Deref( - DQE::PtrCast(raw_ptr as usize, "*const i32".to_string()).boxed(), - )) - .unwrap(); - assert_scalar(&var[0], "i32", Some(SupportedScalar::I32(2))); + read_var_dqe!(debugger, Dqe::Deref( + Dqe::PtrCast(PointerCast::new(raw_ptr as usize, "*const i32")).boxed(), + ) => val); + assert_scalar(val.value(), "i32", Some(SupportedScalar::I32(2))); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2967,9 +2672,10 @@ fn test_read_uuid() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(519)); - let vars = debugger.read_local_variables().unwrap(); - assert_uuid_n(&vars[0], "uuid_v4", "Uuid"); - assert_uuid_n(&vars[1], "uuid_v7", "Uuid"); + read_locals!(debugger => uuid_v4, uuid_v7); + assert_idents!(uuid_v4 => "uuid_v4", uuid_v7 => "uuid_v7"); + assert_uuid(uuid_v4.value(), "Uuid"); + assert_uuid(uuid_v7.value(), "Uuid"); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -2988,22 +2694,22 @@ fn test_address_operator() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(119)); - fn addr_of(name: &str, loc: bool) -> DQE { - DQE::Address(DQE::Variable(Selector::by_name(name, loc)).boxed()) + fn addr_of(name: &str, loc: bool) -> Dqe { + Dqe::Address(Dqe::Variable(Selector::by_name(name, loc)).boxed()) } - fn addr_of_index(name: &str, index: i32) -> DQE { - DQE::Address( - DQE::Index( - DQE::Variable(Selector::by_name(name, true)).boxed(), + fn addr_of_index(name: &str, index: i32) -> Dqe { + Dqe::Address( + Dqe::Index( + Dqe::Variable(Selector::by_name(name, true)).boxed(), Literal::Int(index as i64), ) .boxed(), ) } - fn addr_of_field(name: &str, field: &str) -> DQE { - DQE::Address( - DQE::Field( - DQE::Variable(Selector::by_name(name, true)).boxed(), + fn addr_of_field(name: &str, field: &str) -> Dqe { + Dqe::Address( + Dqe::Field( + Dqe::Variable(Selector::by_name(name, true)).boxed(), field.to_string(), ) .boxed(), @@ -3012,29 +2718,23 @@ fn test_address_operator() { // get address of scalar variable and deref it let addr_a_dqe = addr_of("a", true); - let addr_a = debugger.read_variable(addr_a_dqe.clone()).unwrap(); - assert_pointer(&addr_a[0], "&i32"); - let a = debugger - .read_variable(DQE::Deref(addr_a_dqe.boxed())) - .unwrap(); - assert_scalar(&a[0], "i32", Some(SupportedScalar::I32(2))); - - let addr_ptr_a = debugger.read_variable(addr_of("ref_a", true)).unwrap(); - assert_pointer(&addr_ptr_a[0], "&&i32"); - let a = debugger - .read_variable(DQE::Deref( - DQE::Deref(addr_of("ref_a", true).boxed()).boxed(), - )) - .unwrap(); - assert_scalar(&a[0], "i32", Some(SupportedScalar::I32(2))); + read_var_dqe!(debugger, addr_a_dqe.clone() => a); + assert_pointer(a.value(), "&i32"); + read_var_dqe!(debugger, Dqe::Deref(addr_a_dqe.boxed()) => a); + assert_scalar(a.value(), "i32", Some(SupportedScalar::I32(2))); + + read_var_dqe!(debugger, addr_of("ref_a", true) => addr_ptr_a); + assert_pointer(addr_ptr_a.value(), "&&i32"); + read_var_dqe!(debugger, Dqe::Deref( + Dqe::Deref(addr_of("ref_a", true).boxed()).boxed(), + ) => a); + assert_scalar(a.value(), "i32", Some(SupportedScalar::I32(2))); // get address of structure field and deref it - let addr_f = debugger.read_variable(addr_of("f", true)).unwrap(); - assert_pointer(&addr_f[0], "&Foo"); - let f = debugger - .read_variable(DQE::Deref(addr_of("f", true).boxed())) - .unwrap(); - assert_struct(&f[0], "Foo", |i, member| match i { + read_var_dqe!(debugger, addr_of("f", true) => addr_f); + assert_pointer(addr_f.value(), "&Foo"); + read_var_dqe!(debugger, Dqe::Deref(addr_of("f", true).boxed()) => f); + assert_struct(f.value(), "Foo", |i, member| match i { 0 => assert_member(member, "bar", |val| { assert_scalar(val, "i32", Some(SupportedScalar::I32(1))) }), @@ -3047,60 +2747,53 @@ fn test_address_operator() { }), 2 => { assert_member(member, "foo", |val| assert_pointer(val, "&i32")); - let deref = read_single_var(&debugger, "*f.foo"); - assert_scalar(&deref, "i32", Some(SupportedScalar::I32(2))); + let member_val = member.value.clone(); + let deref = f.clone().modify_value(|ctx, _| member_val.deref(ctx)); + assert_scalar(deref.unwrap().value(), "i32", Some(SupportedScalar::I32(2))); } _ => panic!("3 members expected"), }); - let addr_f_bar = debugger.read_variable(addr_of_field("f", "bar")).unwrap(); - assert_pointer(&addr_f_bar[0], "&i32"); - let f_bar = debugger - .read_variable(DQE::Deref(addr_of_field("f", "bar").boxed())) - .unwrap(); - assert_scalar(&f_bar[0], "i32", Some(SupportedScalar::I32(1))); + + read_var_dqe!(debugger, addr_of_field("f", "bar") => addr_f_bar); + assert_pointer(addr_f_bar.value(), "&i32"); + read_var_dqe!(debugger, Dqe::Deref(addr_of_field("f", "bar").boxed()) => f_bar); + assert_scalar(f_bar.value(), "i32", Some(SupportedScalar::I32(1))); // get address of an array element and deref it debugger.set_breakpoint_at_line("vars.rs", 151).unwrap(); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(151)); - let addr_vec1 = debugger.read_variable(addr_of("vec1", true)).unwrap(); - assert_pointer(&addr_vec1[0], "&Vec"); - - let addr_el_1 = debugger.read_variable(addr_of_index("vec1", 1)).unwrap(); - assert_pointer(&addr_el_1[0], "&i32"); - let el_1 = debugger - .read_variable(DQE::Deref(addr_of_index("vec1", 1).boxed())) - .unwrap(); - assert_scalar(&el_1[0], "i32", Some(SupportedScalar::I32(2))); + read_var_dqe!(debugger, addr_of("vec1", true) => addr_vec1); + assert_pointer(addr_vec1.value(), "&Vec"); + read_var_dqe!(debugger, addr_of_index("vec1", 1) => addr_el_1); + assert_pointer(addr_el_1.value(), "&i32"); + read_var_dqe!(debugger, Dqe::Deref(addr_of_index("vec1", 1).boxed()) => el_1); + assert_scalar(el_1.value(), "i32", Some(SupportedScalar::I32(2))); // get an address of a hashmap element and deref it debugger.set_breakpoint_at_line("vars.rs", 290).unwrap(); debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(290)); - let addr_hm3 = debugger.read_variable(addr_of("hm3", true)).unwrap(); + read_var_dqe!(debugger, addr_of("hm3", true) => addr_hm3); let inner_hash_map_type = version_switch!( rust_version(VARS_APP).unwrap(), (1, 0, 0) ..= (1, 75, u32::MAX) => "&HashMap", (1, 76, 0) ..= (1, u32::MAX, u32::MAX) => "&HashMap", ).unwrap(); - assert_pointer(&addr_hm3[0], inner_hash_map_type); + assert_pointer(addr_hm3.value(), inner_hash_map_type); - let addr_el_11 = debugger.read_variable(addr_of_index("hm3", 11)).unwrap(); - assert_pointer(&addr_el_11[0], "&i32"); - let el_11 = debugger - .read_variable(DQE::Deref(addr_of_index("hm3", 11).boxed())) - .unwrap(); - assert_scalar(&el_11[0], "i32", Some(SupportedScalar::I32(11))); + read_var_dqe!(debugger, addr_of_index("hm3", 11) => addr_el_11); + assert_pointer(addr_el_11.value(), "&i32"); + read_var_dqe!(debugger, Dqe::Deref(addr_of_index("hm3", 11).boxed()) => el_11); + assert_scalar(el_11.value(), "i32", Some(SupportedScalar::I32(11))); // get address of global variable and deref it - let addr_glob_1 = debugger.read_variable(addr_of("GLOB_1", false)).unwrap(); - assert_pointer(&addr_glob_1[0], "&&str"); - let glob_1 = debugger - .read_variable(DQE::Deref(addr_of("GLOB_1", false).boxed())) - .unwrap(); - assert_str(&glob_1[0], "glob_1"); + read_var_dqe!(debugger, addr_of("GLOB_1", false) => addr_glob_1); + assert_pointer(addr_glob_1.value(), "&&str"); + read_var_dqe!(debugger, Dqe::Deref(addr_of("GLOB_1", false).boxed()) => glob_1); + assert_str(glob_1.value(), "glob_1"); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); @@ -3120,9 +2813,10 @@ fn test_read_time() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(529)); - let vars = debugger.read_local_variables().unwrap(); - assert_system_time_n(&vars[0], "system_time", (0, 0)); - assert_instant_n(&vars[1], "instant"); + read_locals!(debugger => system_time, instant); + assert_idents!(system_time => "system_time", instant => "instant"); + assert_system_time(system_time.value(), (0, 0)); + assert_instant(instant.value()); debugger.continue_debugee().unwrap(); assert_no_proc!(debugee_pid); diff --git a/tests/debugger/watchpoint.rs b/tests/debugger/watchpoint.rs index 47ff581..3556eba 100644 --- a/tests/debugger/watchpoint.rs +++ b/tests/debugger/watchpoint.rs @@ -1,36 +1,56 @@ use crate::common::{TestHooks, TestInfo}; -use crate::variables::assert_scalar_n; +use crate::variables::assert_scalar; use crate::{assert_no_proc, VARS_APP}; use crate::{prepare_debugee_process, CALCULATIONS_APP}; use bugstalker::debugger::address::RelocatedAddress; use bugstalker::debugger::register::debug::{BreakCondition, BreakSize}; -use bugstalker::debugger::variable::select::{Selector, DQE}; -use bugstalker::debugger::variable::{PointerVariable, SupportedScalar, VariableIR}; +use bugstalker::debugger::variable::dqe::{Dqe, Selector}; +use bugstalker::debugger::variable::value::{PointerValue, SupportedScalar, Value}; use bugstalker::debugger::{Debugger, DebuggerBuilder}; use serial_test::serial; use BreakCondition::DataWrites; use BreakSize::Bytes8; -fn assert_old_new( +fn assert_old_new_inner( info: &TestInfo, - name: &str, + source_dqe: Option<&str>, r#type: &str, old: SupportedScalar, mb_new: Option, ) { let old_val = &info.old_value.take().unwrap(); - assert_scalar_n(old_val, name, r#type, Some(old)); + assert_eq!(info.wp_dqe_string.take().as_deref(), source_dqe); + assert_scalar(old_val, r#type, Some(old)); match mb_new { None => { assert!(&info.new_value.take().is_none()) } Some(new) => { let new_val = &info.new_value.take().unwrap(); - assert_scalar_n(new_val, name, r#type, Some(new)); + assert_scalar(new_val, r#type, Some(new)); } } } +fn assert_old_new( + info: &TestInfo, + source_dqe: &str, + r#type: &str, + old: SupportedScalar, + mb_new: Option, +) { + assert_old_new_inner(info, Some(source_dqe), r#type, old, mb_new) +} + +fn assert_old_new_from_addr( + info: &TestInfo, + r#type: &str, + old: SupportedScalar, + mb_new: Option, +) { + assert_old_new_inner(info, None, r#type, old, mb_new) +} + #[test] #[serial] fn test_watchpoint_works() { @@ -43,7 +63,7 @@ fn test_watchpoint_works() { debugger.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(7)); - let wp_dqe = DQE::Variable(Selector::by_name("int8", true)); + let wp_dqe = Dqe::Variable(Selector::by_name("int8", true)); debugger .set_watchpoint_on_expr("int8", wp_dqe, DataWrites) .unwrap(); @@ -51,7 +71,7 @@ fn test_watchpoint_works() { debugger.continue_debugee().unwrap(); assert_eq!(info.line.take(), Some(8)); let var_val = &info.new_value.take().unwrap(); - assert_scalar_n(var_val, "int8", "i8", Some(SupportedScalar::I8(1))); + assert_scalar(var_val, "i8", Some(SupportedScalar::I8(1))); assert!(info.file.take().unwrap().contains("vars.rs")); debugger.continue_debugee().unwrap(); @@ -76,7 +96,7 @@ fn test_watchpoint_works_2() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(8)); - let wp_dqe = DQE::Variable(Selector::by_name("int8", true)); + let wp_dqe = Dqe::Variable(Selector::by_name("int8", true)); dbg.set_watchpoint_on_expr("int8", wp_dqe, DataWrites) .unwrap(); @@ -113,19 +133,19 @@ fn test_watchpoint_global_var() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(108)); - let wp_dqe = DQE::Variable(Selector::by_name("GLOBAL_1", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("GLOBAL_1", false)); dbg.set_watchpoint_on_expr("GLOBAL_1", wp_dqe, DataWrites) .unwrap(); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::I64(1), Some(SupportedScalar::I64(0))); - assert_old_new(&info, "calculations::GLOBAL_1", "i64", old, new); + assert_old_new(&info, "GLOBAL_1", "i64", old, new); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::I64(0), Some(SupportedScalar::I64(3))); - assert_old_new(&info, "calculations::GLOBAL_1", "i64", old, new); + assert_old_new(&info, "GLOBAL_1", "i64", old, new); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::I64(3), Some(SupportedScalar::I64(1))); - assert_old_new(&info, "calculations::GLOBAL_1", "i64", old, new); + assert_old_new(&info, "GLOBAL_1", "i64", old, new); dbg.continue_debugee().unwrap(); // watchpoint at global variables never removed automatically @@ -146,13 +166,13 @@ fn test_max_watchpoint_count() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(22)); - let wp_dqe = DQE::Variable(Selector::by_name("a", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("a", false)); dbg.set_watchpoint_on_expr("a", wp_dqe, DataWrites).unwrap(); - let wp_dqe = DQE::Variable(Selector::by_name("b", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("b", false)); dbg.set_watchpoint_on_expr("b", wp_dqe, DataWrites).unwrap(); - let wp_dqe = DQE::Variable(Selector::by_name("c", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("c", false)); dbg.set_watchpoint_on_expr("c", wp_dqe, DataWrites).unwrap(); - let wp_dqe = DQE::Variable(Selector::by_name("d", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("d", false)); dbg.set_watchpoint_on_expr("d", wp_dqe, DataWrites).unwrap(); dbg.continue_debugee().unwrap(); @@ -193,10 +213,10 @@ fn test_watchpoint_remove_and_continue() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(22)); - let a_wp_dqe = DQE::Variable(Selector::by_name("a", false)); + let a_wp_dqe = Dqe::Variable(Selector::by_name("a", false)); dbg.set_watchpoint_on_expr("a", a_wp_dqe.clone(), DataWrites) .unwrap(); - let d_wp_dqe = DQE::Variable(Selector::by_name("d", false)); + let d_wp_dqe = Dqe::Variable(Selector::by_name("d", false)); dbg.set_watchpoint_on_expr("d", d_wp_dqe, DataWrites) .unwrap(); @@ -229,9 +249,9 @@ fn test_watchpoint_global_var_multithread() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(56)); - let wp_dqe = DQE::Field( - DQE::Field( - DQE::Variable(Selector::by_name("GLOBAL_2", false)).boxed(), + let wp_dqe = Dqe::Field( + Dqe::Field( + Dqe::Variable(Selector::by_name("GLOBAL_2", false)).boxed(), "data".to_string(), ) .boxed(), @@ -272,7 +292,7 @@ fn test_watchpoint_local_var_multithread() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(67)); - let wp_dqe = DQE::Variable(Selector::by_name("a", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("a", false)); dbg.set_watchpoint_on_expr("a", wp_dqe, DataWrites).unwrap(); dbg.continue_debugee().unwrap(); @@ -304,12 +324,12 @@ fn test_max_watchpoint_count_at_address() { assert_eq!(info.line.take(), Some(22)); fn get_ptr_of(dbg: &Debugger, var: &str) -> RelocatedAddress { - let addr_dqe = DQE::Address(DQE::Variable(Selector::by_name(var, true)).boxed()); + let addr_dqe = Dqe::Address(Dqe::Variable(Selector::by_name(var, true)).boxed()); let var = dbg.read_variable(addr_dqe).unwrap(); - let VariableIR::Pointer(PointerVariable { value: Some(p), .. }) = var[0] else { + let Value::Pointer(PointerValue { value: Some(p), .. }) = var[0].value() else { panic!("not a pointer") }; - RelocatedAddress::from(p as usize) + RelocatedAddress::from(*p as usize) } let ptr_a = get_ptr_of(&dbg, "a"); @@ -325,16 +345,16 @@ fn test_max_watchpoint_count_at_address() { dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::U64(1), Some(SupportedScalar::U64(6))); - assert_old_new(&info, "data", "u64", old, new); + assert_old_new_from_addr(&info, "u64", old, new); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::U64(2), Some(SupportedScalar::U64(3))); - assert_old_new(&info, "data", "u64", old, new); + assert_old_new_from_addr(&info, "u64", old, new); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::U64(3), Some(SupportedScalar::U64(1))); - assert_old_new(&info, "data", "u64", old, new); + assert_old_new_from_addr(&info, "u64", old, new); dbg.continue_debugee().unwrap(); let (old, new) = (SupportedScalar::U64(4), Some(SupportedScalar::U64(3))); - assert_old_new(&info, "data", "u64", old, new); + assert_old_new_from_addr(&info, "u64", old, new); dbg.remove_watchpoint_by_addr(ptr_a).unwrap(); dbg.remove_watchpoint_by_addr(ptr_b).unwrap(); @@ -358,7 +378,7 @@ fn test_watchpoint_argument() { dbg.start_debugee().unwrap(); assert_eq!(info.line.take(), Some(101)); - let wp_dqe = DQE::Variable(Selector::by_name("arg", false)); + let wp_dqe = Dqe::Variable(Selector::by_name("arg", false)); dbg.set_watchpoint_on_expr("arg", wp_dqe, DataWrites) .unwrap(); diff --git a/tests/integration/test_variables.py b/tests/integration/test_variables.py index 3b86b89..92a76ed 100644 --- a/tests/integration/test_variables.py +++ b/tests/integration/test_variables.py @@ -353,14 +353,14 @@ def test_custom_select(self): self.debugger.cmd('var arr_2[0][2]', 'i32(2)') self.debugger.cmd( 'var arr_1[2..4]', - 'arr_1 = [i32] {', + '[i32] {', '2: i32(2)', '3: i32(-2)', '}', ) self.debugger.cmd( 'var arr_1[..]', - 'arr_1 = [i32] {', + '[i32] {', '0: i32(1)', '1: i32(-1)', '2: i32(2)', @@ -370,27 +370,27 @@ def test_custom_select(self): ) self.debugger.cmd( 'var arr_1[..2]', - 'arr_1 = [i32] {', + '[i32] {', '0: i32(1)', '1: i32(-1)', '}', ) self.debugger.cmd( 'var arr_1[3..]', - 'arr_1 = [i32] {', + '[i32] {', '3: i32(-2)', '4: i32(3)', '}', ) self.debugger.cmd( 'var arr_1[4..6]', - 'arr_1 = [i32] {', + '[i32] {', '4: i32(3)', '}', ) self.debugger.cmd( 'var arr_1[2..4][1..]', - 'arr_1 = [i32] {', + '[i32] {', '3: i32(-2)', '}', ) diff --git a/tests/integration/test_watchpoint.py b/tests/integration/test_watchpoint.py index 0c8878e..163ff07 100644 --- a/tests/integration/test_watchpoint.py +++ b/tests/integration/test_watchpoint.py @@ -15,12 +15,12 @@ def test_watchpoint(self): self.debugger.cmd('watch c', 'New watchpoint') self.debugger.cmd( 'continue', - 'Hit watchpoint', - 'old value: c = u64(3)', - 'new value: c = u64(1)', + 'Hit watchpoint 1 (expr: c) (w)', + 'old value: u64(3)', + 'new value: u64(1)', ) - self.debugger.cmd('continue', 'Watchpoint 1 end of scope', 'old value: c = u64(1)') + self.debugger.cmd('continue', 'Watchpoint 1 (expr: c) end of scope', 'old value: u64(1)') def test_watchpoint_at_field(self): """Add a new watchpoint for structure field or value in vector and check it works""" @@ -30,19 +30,19 @@ def test_watchpoint_at_field(self): self.debugger.cmd('watch vector[2]', 'New watchpoint') self.debugger.cmd( 'continue', - 'Hit watchpoint', - 'old value: vector[2] = i32(3)', - 'new value: vector[2] = i32(4)', + 'Hit watchpoint 1 (expr: vector[2]) (w)', + 'old value: i32(3)', + 'new value: i32(4)', ) self.debugger.cmd('continue', 'Hit breakpoint 2') self.debugger.cmd('watch s.b', 'New watchpoint') - self.debugger.cmd('continue', 'old value: s.b = f64(1)', 'new value: s.b = f64(2)') + self.debugger.cmd('continue', 'old value: f64(1)', 'new value: f64(2)') self.debugger.cmd( 'continue', - 'Watchpoint 1 end of scope', - 'old value: vector[2] = i32(4)', - 'Watchpoint 2 end of scope', - 'old value: s.b = f64(2)', + 'Watchpoint 1 (expr: vector[2]) end of scope', + 'old value: i32(4)', + 'Watchpoint 2 (expr: s.b) end of scope', + 'old value: f64(2)', ) def test_watchpoint_at_address(self): @@ -57,8 +57,8 @@ def test_watchpoint_at_address(self): self.debugger.cmd( 'continue', 'Hit watchpoint', - 'old value: data = u64(1)', - 'new value: data = u64(6)', + 'old value: u64(1)', + 'new value: u64(6)', ) self.debugger.cmd('q') @@ -67,7 +67,7 @@ def test_watchpoint_with_stepping(self): self.debugger.cmd('break calculations.rs:12', 'New breakpoint') self.debugger.cmd('run', 'Hit breakpoint 1') self.debugger.cmd('watch int8', 'New watchpoint') - self.debugger.cmd('next', 'Hit watchpoint') + self.debugger.cmd('next', 'Hit watchpoint 1 (expr: int8)') self.debugger.cmd('next', '13 println!("{int8}");') self.debugger.cmd('next') self.debugger.cmd('next') @@ -127,15 +127,15 @@ def test_watchpoint_rw(self): self.debugger.cmd('break calculations.rs:20', 'New breakpoint') self.debugger.cmd('run', 'Hit breakpoint 1') self.debugger.cmd('watch +rw a', 'New watchpoint') - self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: a = u64(1)') + self.debugger.cmd('continue', 'Hit watchpoint 1 (expr: a) (rw)', 'value: u64(1)') self.debugger.cmd( 'continue', - 'Hit watchpoint 1 (rw)', - 'old value: a = u64(1)', - 'new value: a = u64(6)', + 'Hit watchpoint 1 (expr: a) (rw)', + 'old value: u64(1)', + 'new value: u64(6)', ) - self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: a = u64(6)') - self.debugger.cmd('continue', 'Watchpoint 1 end of scope', 'old value: a = u64(6)') + self.debugger.cmd('continue', 'Hit watchpoint 1 (expr: a) (rw)', 'value: u64(6)') + self.debugger.cmd('continue', 'Watchpoint 1 (expr: a) end of scope', 'old value: u64(6)') def test_watchpoint_at_addr_rw(self): """Add a new watchpoint with read-write condition at address and check it works""" @@ -145,14 +145,14 @@ def test_watchpoint_at_addr_rw(self): addr = self.debugger.search_in_output(r'&u64 \[0x(.*)\]') addr = "0x" + addr[:14] self.debugger.cmd(f'watch +rw {addr}:8', 'New watchpoint') - self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: data = u64(1)') + self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: u64(1)') self.debugger.cmd( 'continue', 'Hit watchpoint 1 (rw)', - 'old value: data = u64(1)', - 'new value: data = u64(6)', + 'old value: u64(1)', + 'new value: u64(6)', ) - self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: data = u64(6)') + self.debugger.cmd('continue', 'Hit watchpoint 1 (rw)', 'value: u64(6)') def test_watchpoint_at_complex_data_types(self): """Add watchpoints for vector attribute""" @@ -161,20 +161,20 @@ def test_watchpoint_at_complex_data_types(self): self.debugger.cmd('watch (~vector2).len', 'New watchpoint') self.debugger.cmd( 'continue', - 'Hit watchpoint 1 (w)', - 'old value: (~vector2).len = usize(2)', - 'new value: (~vector2).len = usize(3)', + 'Hit watchpoint 1 (expr: (~vector2).len) (w)', + 'old value: usize(2)', + 'new value: usize(3)', ) self.debugger.cmd( 'continue', - 'Hit watchpoint 1 (w)', - 'old value: (~vector2).len = usize(3)', - 'new value: (~vector2).len = usize(4)', + 'Hit watchpoint 1 (expr: (~vector2).len) (w)', + 'old value: usize(3)', + 'new value: usize(4)', ) self.debugger.cmd( 'continue', - 'Watchpoint 1 end of scope', - 'old value: (~vector2).len = usize(4)', + 'Watchpoint 1 (expr: (~vector2).len) end of scope', + 'old value: usize(4)', ) def test_watchpoint_at_complex_data_types2(self): @@ -184,12 +184,12 @@ def test_watchpoint_at_complex_data_types2(self): self.debugger.cmd('watch (~(~string).vec).len', 'New watchpoint') self.debugger.cmd( 'continue', - 'Hit watchpoint 1 (w)', - 'old value: (~(~string).vec).len = usize(3)', - 'new value: (~(~string).vec).len = usize(7)', + 'Hit watchpoint 1 (expr: (~(~string).vec).len) (w)', + 'old value: usize(3)', + 'new value: usize(7)', ) self.debugger.cmd( 'continue', - 'Watchpoint 1 end of scope', - 'old value: (~(~string).vec).len = usize(7)', + 'Watchpoint 1 (expr: (~(~string).vec).len) end of scope', + 'old value: usize(7)', )