diff --git a/src/debugger/state/call_stack.rs b/src/debugger/state/call_stack.rs index ac2b6aa..ca4f6bb 100644 --- a/src/debugger/state/call_stack.rs +++ b/src/debugger/state/call_stack.rs @@ -224,104 +224,73 @@ impl CallStack { fn cairo_value_to_variable(&mut self, name: String, value: CairoValue) -> Variable { match value { - CairoValue::Bool(value) => Variable { - name, - value: value.to_string(), - variables_reference: 0, - ..Default::default() - }, - CairoValue::FeltLike(value) => Variable { - name, - value: value.to_string(), - variables_reference: 0, - ..Default::default() - }, + CairoValue::Bool(v) => leaf_variable(name, v.to_string()), + CairoValue::FeltLike(v) => leaf_variable(name, v.to_string()), + CairoValue::Other(v) => leaf_variable(name, v), CairoValue::Struct { type_name, fields } => { if fields.is_empty() { - Variable { - name, - value: "()".to_string(), - variables_reference: 0, - ..Default::default() - } + leaf_variable(name, "()".to_string()) } else { - let ref_id = self.register_children(fields); - Variable { - name, - value: type_name, - variables_reference: ref_id, - ..Default::default() - } + self.expandable_variable(name, type_name, fields) } } CairoValue::Tuple(fields) => { if fields.is_empty() { - Variable { - name, - value: "()".to_string(), - variables_reference: 0, - ..Default::default() - } + leaf_variable(name, "()".to_string()) } else { let children = fields.into_iter().enumerate().map(|(i, v)| (format!(".{i}"), v)).collect(); - let ref_id = self.register_children(children); - Variable { - name, - value: "(...)".to_string(), - variables_reference: ref_id, - ..Default::default() - } + self.expandable_variable(name, "(...)".to_string(), children) } } CairoValue::Enum { type_name, variant_name, variant_value } => { - let display_name = format!("{type_name}::{variant_name}"); - let ref_id = self.register_children(vec![("value".to_string(), *variant_value)]); - Variable { - name, - value: display_name, - variables_reference: ref_id, - ..Default::default() + let display = format!("{type_name}::{variant_name}"); + if variant_value.is_like_unit_type() { + leaf_variable(name, display) + } else { + self.expandable_variable( + name, + display, + vec![("value".to_string(), *variant_value)], + ) } } CairoValue::Array { element_type, elements } => { let type_display = format!("Array<{element_type}>"); if elements.is_empty() { - Variable { - name, - value: type_display, - variables_reference: 0, - ..Default::default() - } + leaf_variable(name, type_display) } else { let children = elements .into_iter() .enumerate() .map(|(i, v)| (format!("[{i}]"), v)) .collect(); - let ref_id = self.register_children(children); - Variable { - name, - value: type_display, - variables_reference: ref_id, - ..Default::default() - } + self.expandable_variable(name, type_display, children) } } - CairoValue::Other(value) => { - Variable { name, value, variables_reference: 0, ..Default::default() } + CairoValue::Snapshot(v) => { + let mut var = self.cairo_value_to_variable(name, *v); + var.value = format!("@{}", var.value); + var } - CairoValue::Snapshot(value) => { - let variable = self.cairo_value_to_variable(name, *value); - Variable { value: format!("@{}", variable.value), ..variable } - } - CairoValue::NonZero(value) => { - let variable = self.cairo_value_to_variable(name, *value); - Variable { value: format!("NonZero({})", variable.value), ..variable } + CairoValue::NonZero(v) => { + let mut var = self.cairo_value_to_variable(name, *v); + var.value = format!("NonZero({})", var.value); + var } } } + fn expandable_variable( + &mut self, + name: String, + value: String, + children: Vec<(String, CairoValue)>, + ) -> Variable { + let ref_id = self.register_children(children); + Variable { name, value, variables_reference: ref_id, ..Default::default() } + } + fn register_children(&mut self, children: Vec<(String, CairoValue)>) -> i64 { let ref_id = self.next_nested_ref; self.nested_var_registry.insert(ref_id, children); @@ -520,6 +489,10 @@ impl SierraFunctionContext { } } +fn leaf_variable(name: String, value: String) -> Variable { + Variable { name, value, variables_reference: 0, ..Default::default() } +} + enum Action { Push(SubStack), Pop, diff --git a/src/debugger/state/call_stack/variables.rs b/src/debugger/state/call_stack/variables.rs index 7445bb0..bd85fcd 100644 --- a/src/debugger/state/call_stack/variables.rs +++ b/src/debugger/state/call_stack/variables.rs @@ -1,26 +1,26 @@ use cairo_annotations::annotations::coverage::SourceCodeSpan; -use cairo_lang_casm::cell_expression::{CellExpression, CellOperator}; -use cairo_lang_casm::operand::{CellRef, DerefOrImmediate}; +use cairo_lang_casm::cell_expression::CellExpression; use cairo_lang_sierra::extensions::core::CoreTypeConcrete; use cairo_lang_sierra::extensions::modules::starknet::StarknetTypeConcrete; use cairo_lang_sierra::ids::ConcreteTypeId; -use cairo_lang_sierra::program::{ConcreteTypeLongId, GenericArg}; -use cairo_vm::Felt252; use cairo_vm::types::relocatable::{MaybeRelocatable, Relocatable}; use cairo_vm::vm::vm_core::VirtualMachine; use indexmap::IndexMap; -use starknet_types_core::felt::{Felt, NonZeroFelt}; -use tracing::{error, warn}; +use tracing::warn; use crate::debugger::context::{CairoVarId, CairoVarReference, Context}; -use crate::debugger::state::call_stack::{ - FunctionVariables, PostStatementsRegisters, RegistersValues, -}; +use crate::debugger::state::call_stack::{FunctionVariables, PostStatementsRegisters}; + +mod type_name; +mod vm_reader; + +use type_name::{extract_short_type_name, format_type_name, is_bool, is_panic_result, is_tuple}; +use vm_reader::VmReader; #[derive(Clone, Debug, PartialEq)] pub enum CairoValue { Bool(bool), - FeltLike(Felt252), + FeltLike(cairo_vm::Felt252), Struct { type_name: String, fields: Vec<(String, CairoValue)> }, Enum { type_name: String, variant_name: String, variant_value: Box }, Tuple(Vec), @@ -30,6 +30,18 @@ pub enum CairoValue { Other(String), } +impl CairoValue { + /// Returns true if this value carries no data — an empty struct or empty tuple. + /// Used to decide whether an enum variant should be displayed as a leaf node. + pub fn is_like_unit_type(&self) -> bool { + match self { + Self::Struct { fields, .. } => fields.is_empty(), + Self::Tuple(elems) => elems.is_empty(), + _ => false, + } + } +} + pub fn get_values_of_variables( ctx: &Context, vm: &VirtualMachine, @@ -50,6 +62,8 @@ pub fn get_values_of_variables( "return statement not expected - or we wouldn't be in this function frame already", ); + let reader = VmReader::new(vm, registers_values); + for ( CairoVarId { name, definition_span: span }, CairoVarReference { sierra_id: var_id, ref_expr }, @@ -69,9 +83,7 @@ pub fn get_values_of_variables( continue; } - let Some(value) = - extract_var_value(&ref_expr.cells, &type_id, registers_values, vm, ctx) - else { + let Some(value) = extract_var_value(&ref_expr.cells, &type_id, &reader, ctx) else { continue; }; @@ -108,22 +120,18 @@ pub fn get_values_of_variables( fn extract_var_value( cells: &[CellExpression], type_id: &ConcreteTypeId, - registers_values: &RegistersValues, - vm: &VirtualMachine, + reader: &VmReader<'_>, ctx: &Context, ) -> Option { - let values = cells - .iter() - .map(|cell| maybe_extract_maybe_relocatable_from_cell(cell, registers_values, vm)) - .collect::>>()?; + let values = cells.iter().map(|cell| reader.read_cell(cell)).collect::>>()?; - maybe_relocatables_to_cairo_value(&values, type_id, vm, ctx) + maybe_relocatables_to_cairo_value(&values, type_id, reader, ctx) } fn maybe_relocatables_to_cairo_value( values: &[MaybeRelocatable], type_id: &ConcreteTypeId, - vm: &VirtualMachine, + reader: &VmReader<'_>, ctx: &Context, ) -> Option { let Some(concrete_type) = ctx.get_concrete_type(type_id) else { @@ -133,33 +141,25 @@ fn maybe_relocatables_to_cairo_value( match concrete_type { CoreTypeConcrete::Array(info) | CoreTypeConcrete::Span(info) => { let (start_ptr, end_ptr) = extract_array_pointers(values)?; - extract_array_from_pointers(start_ptr, end_ptr, &info.ty, vm, ctx) + extract_array_from_pointers(start_ptr, end_ptr, &info.ty, reader, ctx) } CoreTypeConcrete::Snapshot(inner) => { - maybe_relocatables_to_cairo_value(values, &inner.ty, vm, ctx) + maybe_relocatables_to_cairo_value(values, &inner.ty, reader, ctx) .map(|v| CairoValue::Snapshot(Box::new(v))) } CoreTypeConcrete::NonZero(inner) => { - maybe_relocatables_to_cairo_value(values, &inner.ty, vm, ctx) + maybe_relocatables_to_cairo_value(values, &inner.ty, reader, ctx) .map(|v| CairoValue::NonZero(Box::new(v))) } CoreTypeConcrete::Struct(struct_type) => { let type_long_id = &ctx.var_type_info(type_id).long_id; + let slices = member_slices(&struct_type.members, values, ctx)?; if is_tuple(type_long_id) { - let mut offset = 0; - let mut elements = Vec::with_capacity(struct_type.members.len()); - for member_type_id in &struct_type.members { - let size = ctx.type_size(member_type_id); - let member_values = &values[offset..offset + size]; - offset += size; - elements.push(maybe_relocatables_to_cairo_value( - member_values, - member_type_id, - vm, - ctx, - )?); - } + let elements = slices + .into_iter() + .map(|(ty, vals)| maybe_relocatables_to_cairo_value(vals, ty, reader, ctx)) + .collect::>>()?; return Some(CairoValue::Tuple(elements)); } @@ -167,20 +167,18 @@ fn maybe_relocatables_to_cairo_value( let type_name = struct_info .map(|info| info.name.clone()) .unwrap_or_else(|| extract_short_type_name(type_id)); - let mut offset = 0; - let mut fields = Vec::with_capacity(struct_type.members.len()); - for (i, member_type_id) in struct_type.members.iter().enumerate() { - let size = ctx.type_size(member_type_id); - let member_values = &values[offset..offset + size]; - offset += size; - let name = struct_info - .and_then(|info| info.members.get(i)) - .cloned() - .unwrap_or_else(|| format!(".{i}")); - let value = - maybe_relocatables_to_cairo_value(member_values, member_type_id, vm, ctx)?; - fields.push((name, value)); - } + let fields = slices + .into_iter() + .enumerate() + .map(|(i, (ty, vals))| { + let name = struct_info + .and_then(|info| info.members.get(i)) + .cloned() + .unwrap_or_else(|| format!(".{i}")); + let value = maybe_relocatables_to_cairo_value(vals, ty, reader, ctx)?; + Some((name, value)) + }) + .collect::>>()?; Some(CairoValue::Struct { type_name, fields }) } CoreTypeConcrete::Enum(enum_type) => { @@ -220,7 +218,7 @@ fn maybe_relocatables_to_cairo_value( let payload = &values[1..]; let variant_values = &payload[payload.len() - variant_size..]; let variant_value = - maybe_relocatables_to_cairo_value(variant_values, variant_id, vm, ctx)?; + maybe_relocatables_to_cairo_value(variant_values, variant_id, reader, ctx)?; let enum_info = ctx.enum_info(type_id); let type_name = enum_info .map(|info| info.name.clone()) @@ -273,138 +271,6 @@ fn fallback_value(values: &[MaybeRelocatable], type_id: &ConcreteTypeId) -> Opti } } -fn extract_short_type_name(type_id: &ConcreteTypeId) -> String { - if let Some(debug_name) = &type_id.debug_name { - // Strip generic parameters and take the last path segment. - let base = debug_name.split('<').next().unwrap_or(debug_name.as_str()); - return base.split("::").last().unwrap_or(base).to_string(); - } - format!("type_{}", type_id.id) -} - -fn format_type_name(type_id: &ConcreteTypeId, ctx: &Context) -> String { - let Some(concrete_type) = ctx.get_concrete_type(type_id) else { - return extract_short_type_name(type_id); - }; - match concrete_type { - CoreTypeConcrete::Array(info) => format!("Array<{}>", format_type_name(&info.ty, ctx)), - CoreTypeConcrete::Span(info) => format!("Span<{}>", format_type_name(&info.ty, ctx)), - CoreTypeConcrete::Snapshot(inner) => format!("@{}", format_type_name(&inner.ty, ctx)), - CoreTypeConcrete::NonZero(inner) => { - format!("NonZero<{}>", format_type_name(&inner.ty, ctx)) - } - CoreTypeConcrete::Nullable(inner) => { - format!("Nullable<{}>", format_type_name(&inner.ty, ctx)) - } - CoreTypeConcrete::Felt252(_) => "felt252".to_string(), - CoreTypeConcrete::Uint8(_) => "u8".to_string(), - CoreTypeConcrete::Uint16(_) => "u16".to_string(), - CoreTypeConcrete::Uint32(_) => "u32".to_string(), - CoreTypeConcrete::Uint64(_) => "u64".to_string(), - CoreTypeConcrete::Uint128(_) => "u128".to_string(), - CoreTypeConcrete::Sint8(_) => "i8".to_string(), - CoreTypeConcrete::Sint16(_) => "i16".to_string(), - CoreTypeConcrete::Sint32(_) => "i32".to_string(), - CoreTypeConcrete::Sint64(_) => "i64".to_string(), - CoreTypeConcrete::Sint128(_) => "i128".to_string(), - CoreTypeConcrete::Bytes31(_) => "bytes31".to_string(), - CoreTypeConcrete::Struct(_) => { - let type_long_id = &ctx.var_type_info(type_id).long_id; - if is_tuple(type_long_id) { - return "Tuple".to_string(); - } - ctx.struct_info(type_id) - .map(|info| info.name.clone()) - .unwrap_or_else(|| extract_short_type_name(type_id)) - } - CoreTypeConcrete::Enum(_) => { - let type_long_id = &ctx.var_type_info(type_id).long_id; - if is_bool(type_long_id) { - return "bool".to_string(); - } - ctx.enum_info(type_id) - .map(|info| info.name.clone()) - .unwrap_or_else(|| extract_short_type_name(type_id)) - } - CoreTypeConcrete::Starknet(starknet_type) => match starknet_type { - StarknetTypeConcrete::ContractAddress(_) => "ContractAddress".to_string(), - StarknetTypeConcrete::ClassHash(_) => "ClassHash".to_string(), - StarknetTypeConcrete::StorageAddress(_) => "StorageAddress".to_string(), - StarknetTypeConcrete::StorageBaseAddress(_) => "StorageBaseAddress".to_string(), - _ => extract_short_type_name(type_id), - }, - _ => extract_short_type_name(type_id), - } -} - -fn is_tuple(type_long_id: &ConcreteTypeLongId) -> bool { - type_long_id.generic_id.0 == "Struct" - && matches!( - type_long_id.generic_args.first(), - Some(GenericArg::UserType(user_type)) - if user_type.debug_name.as_ref().is_some_and(|n| n.as_str() == "Tuple") - ) -} - -fn is_bool(type_long_id: &ConcreteTypeLongId) -> bool { - type_long_id.generic_id.0 == "Enum" - && matches!( - type_long_id.generic_args.first(), - Some(GenericArg::UserType(user_type)) - if user_type.debug_name.as_ref().is_some_and(|n| n.as_str() == "core::bool") - ) -} - -fn is_panic_result(type_long_id: &ConcreteTypeLongId) -> bool { - type_long_id.generic_id.0 == "Enum" - && matches!( - type_long_id.generic_args.first(), - Some(GenericArg::UserType(user_type)) - if user_type.debug_name.as_ref().is_some_and(|n| n.starts_with("core::panics::PanicResult")) - ) -} - -fn maybe_extract_maybe_relocatable_from_cell( - cell: &CellExpression, - registers_values: &RegistersValues, - vm: &VirtualMachine, -) -> Option { - match cell { - CellExpression::Deref(cell_ref) => { - let addr = registers_values.relocatable_from_cell_ref(cell_ref); - maybe_get_maybe_relocatable(addr, vm) - } - CellExpression::DoubleDeref(cell_ref, offset) => { - let addr = registers_values.relocatable_from_cell_ref(cell_ref); - let mut inner = match vm.segments.memory.get_relocatable(addr) { - Ok(value) => value, - Err(err) => { - error!("error when extracting relocatable from VM: {err:?}"); - return None; - } - }; - inner.offset = (inner.offset as isize + *offset as isize) as usize; - maybe_get_maybe_relocatable(inner, vm) - } - CellExpression::Immediate(value) => Some(MaybeRelocatable::Int(Felt252::from(value))), - CellExpression::BinOp { op, a, b } => { - let a_felt = maybe_get_felt_from_cell_ref(a, registers_values, vm)?; - let b_felt = match b { - DerefOrImmediate::Deref(cell_ref) => { - maybe_get_felt_from_cell_ref(cell_ref, registers_values, vm) - } - DerefOrImmediate::Immediate(value) => Some(Felt::from(value.value.clone())), - }?; - Some(MaybeRelocatable::Int(match op { - CellOperator::Add => a_felt + b_felt, - CellOperator::Sub => a_felt - b_felt, - CellOperator::Mul => a_felt * b_felt, - CellOperator::Div => a_felt.field_div(&NonZeroFelt::try_from(b_felt).unwrap()), - })) - } - } -} - fn extract_array_pointers(values: &[MaybeRelocatable]) -> Option<(Relocatable, Relocatable)> { if values.len() != 2 { warn!("expected 2 values for array/span, got {}", values.len()); @@ -425,7 +291,7 @@ fn extract_array_from_pointers( start_ptr: Relocatable, end_ptr: Relocatable, element_type_id: &ConcreteTypeId, - vm: &VirtualMachine, + reader: &VmReader<'_>, ctx: &Context, ) -> Option { if start_ptr.segment_index != end_ptr.segment_index { @@ -451,19 +317,16 @@ fn extract_array_from_pointers( for i in 0..num_elements { let element_values = (0..element_size) .map(|j| { - maybe_get_maybe_relocatable( - Relocatable { - segment_index: start_ptr.segment_index, - offset: start_ptr.offset + i * element_size + j, - }, - vm, - ) + reader.read_relocatable(Relocatable { + segment_index: start_ptr.segment_index, + offset: start_ptr.offset + i * element_size + j, + }) }) .collect::>>()?; elements.push(maybe_relocatables_to_cairo_value( &element_values, element_type_id, - vm, + reader, ctx, )?); } @@ -471,27 +334,23 @@ fn extract_array_from_pointers( Some(CairoValue::Array { element_type, elements }) } -fn maybe_get_felt_from_cell_ref( - cell_ref: &CellRef, - registers_values: &RegistersValues, - vm: &VirtualMachine, -) -> Option { - let relocatable = registers_values.relocatable_from_cell_ref(cell_ref); - match vm.segments.memory.get_integer(relocatable) { - Ok(value) => Some(*value), - Err(err) => { - error!("error when extracting felt from VM: {err:?}"); - None - } - } -} - -fn maybe_get_maybe_relocatable(addr: Relocatable, vm: &VirtualMachine) -> Option { - match vm.segments.memory.get_maybe_relocatable(addr) { - Ok(value) => Some(value), - Err(err) => { - error!("error when reading memory at {addr}: {err:?}"); - None - } - } +/// Splits array of [`MaybeRelocatable`]s that contain data corresponding to the entire struct +/// to subarrays so that each subarray contains data corresponding to a consecutive field of that +/// struct. +fn member_slices<'m, 'v>( + members: &'m [ConcreteTypeId], + values: &'v [MaybeRelocatable], + ctx: &Context, +) -> Option> { + let mut offset = 0; + members + .iter() + .map(|ty| { + let size = ctx.type_size(ty); + let end = offset + size; + let slice = values.get(offset..end)?; + offset = end; + Some((ty, slice)) + }) + .collect() } diff --git a/src/debugger/state/call_stack/variables/type_name.rs b/src/debugger/state/call_stack/variables/type_name.rs new file mode 100644 index 0000000..e18ae25 --- /dev/null +++ b/src/debugger/state/call_stack/variables/type_name.rs @@ -0,0 +1,95 @@ +use cairo_lang_sierra::extensions::core::CoreTypeConcrete; +use cairo_lang_sierra::extensions::modules::starknet::StarknetTypeConcrete; +use cairo_lang_sierra::ids::ConcreteTypeId; +use cairo_lang_sierra::program::{ConcreteTypeLongId, GenericArg}; + +use crate::debugger::context::Context; + +pub fn extract_short_type_name(type_id: &ConcreteTypeId) -> String { + if let Some(debug_name) = &type_id.debug_name { + // Strip generic parameters and take the last path segment. + let base = debug_name.split('<').next().unwrap_or(debug_name.as_str()); + return base.split("::").last().unwrap_or(base).to_string(); + } + format!("type_{}", type_id.id) +} + +pub fn format_type_name(type_id: &ConcreteTypeId, ctx: &Context) -> String { + let Some(concrete_type) = ctx.get_concrete_type(type_id) else { + return extract_short_type_name(type_id); + }; + match concrete_type { + CoreTypeConcrete::Array(info) => format!("Array<{}>", format_type_name(&info.ty, ctx)), + CoreTypeConcrete::Span(info) => format!("Span<{}>", format_type_name(&info.ty, ctx)), + CoreTypeConcrete::Snapshot(inner) => format!("@{}", format_type_name(&inner.ty, ctx)), + CoreTypeConcrete::NonZero(inner) => { + format!("NonZero<{}>", format_type_name(&inner.ty, ctx)) + } + CoreTypeConcrete::Nullable(inner) => { + format!("Nullable<{}>", format_type_name(&inner.ty, ctx)) + } + CoreTypeConcrete::Felt252(_) => "felt252".to_string(), + CoreTypeConcrete::Uint8(_) => "u8".to_string(), + CoreTypeConcrete::Uint16(_) => "u16".to_string(), + CoreTypeConcrete::Uint32(_) => "u32".to_string(), + CoreTypeConcrete::Uint64(_) => "u64".to_string(), + CoreTypeConcrete::Uint128(_) => "u128".to_string(), + CoreTypeConcrete::Sint8(_) => "i8".to_string(), + CoreTypeConcrete::Sint16(_) => "i16".to_string(), + CoreTypeConcrete::Sint32(_) => "i32".to_string(), + CoreTypeConcrete::Sint64(_) => "i64".to_string(), + CoreTypeConcrete::Sint128(_) => "i128".to_string(), + CoreTypeConcrete::Bytes31(_) => "bytes31".to_string(), + CoreTypeConcrete::Struct(_) => { + let type_long_id = &ctx.var_type_info(type_id).long_id; + if is_tuple(type_long_id) { + return "Tuple".to_string(); + } + ctx.struct_info(type_id) + .map(|info| info.name.clone()) + .unwrap_or_else(|| extract_short_type_name(type_id)) + } + CoreTypeConcrete::Enum(_) => { + let type_long_id = &ctx.var_type_info(type_id).long_id; + if is_bool(type_long_id) { + return "bool".to_string(); + } + ctx.enum_info(type_id) + .map(|info| info.name.clone()) + .unwrap_or_else(|| extract_short_type_name(type_id)) + } + CoreTypeConcrete::Starknet(starknet_type) => match starknet_type { + StarknetTypeConcrete::ContractAddress(_) => "ContractAddress".to_string(), + StarknetTypeConcrete::ClassHash(_) => "ClassHash".to_string(), + StarknetTypeConcrete::StorageAddress(_) => "StorageAddress".to_string(), + StarknetTypeConcrete::StorageBaseAddress(_) => "StorageBaseAddress".to_string(), + _ => extract_short_type_name(type_id), + }, + _ => extract_short_type_name(type_id), + } +} + +pub fn is_tuple(type_long_id: &ConcreteTypeLongId) -> bool { + matches_user_type(type_long_id, "Struct", |n| n == "Tuple") +} + +pub fn is_bool(type_long_id: &ConcreteTypeLongId) -> bool { + matches_user_type(type_long_id, "Enum", |n| n == "core::bool") +} + +pub fn is_panic_result(type_long_id: &ConcreteTypeLongId) -> bool { + matches_user_type(type_long_id, "Enum", |n| n.starts_with("core::panics::PanicResult")) +} + +fn matches_user_type( + type_long_id: &ConcreteTypeLongId, + generic_kind: &str, + name_matches: impl Fn(&str) -> bool, +) -> bool { + type_long_id.generic_id.0.as_str() == generic_kind + && matches!( + type_long_id.generic_args.first(), + Some(GenericArg::UserType(user_type)) + if user_type.debug_name.as_ref().is_some_and(|n| name_matches(n.as_str())) + ) +} diff --git a/src/debugger/state/call_stack/variables/vm_reader.rs b/src/debugger/state/call_stack/variables/vm_reader.rs new file mode 100644 index 0000000..21c2d85 --- /dev/null +++ b/src/debugger/state/call_stack/variables/vm_reader.rs @@ -0,0 +1,77 @@ +use cairo_lang_casm::cell_expression::{CellExpression, CellOperator}; +use cairo_lang_casm::operand::{CellRef, DerefOrImmediate}; +use cairo_vm::Felt252; +use cairo_vm::types::relocatable::{MaybeRelocatable, Relocatable}; +use cairo_vm::vm::vm_core::VirtualMachine; +use starknet_types_core::felt::{Felt, NonZeroFelt}; +use tracing::error; + +use crate::debugger::state::call_stack::RegistersValues; + +/// Bundles a VM snapshot with the register values for a single statement, +/// providing typed read methods. +pub struct VmReader<'a> { + vm: &'a VirtualMachine, + registers: &'a RegistersValues, +} + +impl<'a> VmReader<'a> { + pub fn new(vm: &'a VirtualMachine, registers: &'a RegistersValues) -> Self { + Self { vm, registers } + } + + pub fn read_relocatable(&self, addr: Relocatable) -> Option { + match self.vm.segments.memory.get_maybe_relocatable(addr) { + Ok(value) => Some(value), + Err(err) => { + error!("error when reading memory at {addr}: {err:?}"); + None + } + } + } + + pub fn read_cell(&self, cell: &CellExpression) -> Option { + match cell { + CellExpression::Deref(cell_ref) => { + self.read_relocatable(self.registers.relocatable_from_cell_ref(cell_ref)) + } + CellExpression::DoubleDeref(cell_ref, offset) => { + let addr = self.registers.relocatable_from_cell_ref(cell_ref); + let mut inner = match self.vm.segments.memory.get_relocatable(addr) { + Ok(value) => value, + Err(err) => { + error!("error when extracting relocatable from VM: {err:?}"); + return None; + } + }; + inner.offset = (inner.offset as isize + *offset as isize) as usize; + self.read_relocatable(inner) + } + CellExpression::Immediate(value) => Some(MaybeRelocatable::Int(Felt252::from(value))), + CellExpression::BinOp { op, a, b } => { + let a_felt = self.extract_felt(a)?; + let b_felt = match b { + DerefOrImmediate::Deref(cell_ref) => self.extract_felt(cell_ref), + DerefOrImmediate::Immediate(value) => Some(Felt::from(value.value.clone())), + }?; + Some(MaybeRelocatable::Int(match op { + CellOperator::Add => a_felt + b_felt, + CellOperator::Sub => a_felt - b_felt, + CellOperator::Mul => a_felt * b_felt, + CellOperator::Div => a_felt.field_div(&NonZeroFelt::try_from(b_felt).unwrap()), + })) + } + } + } + + fn extract_felt(&self, cell_ref: &CellRef) -> Option { + let addr = self.registers.relocatable_from_cell_ref(cell_ref); + match self.vm.segments.memory.get_integer(addr) { + Ok(value) => Some(*value), + Err(err) => { + error!("error when extracting felt from VM: {err:?}"); + None + } + } + } +}