diff --git a/Cargo.lock b/Cargo.lock index de75616..d4c3bb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,17 +151,17 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cairo-annotations" -version = "0.8.0" -source = "git+https://github.com/software-mansion/cairo-annotations?rev=ff307d9f0ab4c6514b90698e3ce29d91f8e0ce56#ff307d9f0ab4c6514b90698e3ce29d91f8e0ce56" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e49e38b0075676cba568f2f18758d789e42c6205345be9a834a3ec1699723b7" dependencies = [ "cairo-lang-sierra", "cairo-lang-sierra-to-casm", - "cairo-lang-sierra-type-size", "camino", "derive_more", "serde", "serde_json", - "starknet-types-core 1.0.0", + "starknet-types-core", "strum", "strum_macros", "thiserror 2.0.18", @@ -182,7 +182,7 @@ dependencies = [ "indexmap", "scarb-metadata", "serde_json", - "starknet-types-core 0.2.4", + "starknet-types-core", "tracing", ] @@ -232,7 +232,7 @@ dependencies = [ "serde_json", "sha3", "smol_str", - "starknet-types-core 0.2.4", + "starknet-types-core", "thiserror 2.0.18", ] @@ -285,7 +285,7 @@ dependencies = [ "itertools", "num-bigint", "num-traits", - "starknet-types-core 0.2.4", + "starknet-types-core", "thiserror 2.0.18", ] @@ -345,7 +345,7 @@ dependencies = [ "sha2", "sha3", "starknet-crypto", - "starknet-types-core 0.2.4", + "starknet-types-core", "thiserror 2.0.18", "tracing", "zip", @@ -1680,7 +1680,7 @@ dependencies = [ "rfc6979", "sha2", "starknet-curve", - "starknet-types-core 0.2.4", + "starknet-types-core", "zeroize", ] @@ -1690,7 +1690,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22c898ae81b6409532374cf237f1bd752d068b96c6ad500af9ebbd0d9bb712f6" dependencies = [ - "starknet-types-core 0.2.4", + "starknet-types-core", ] [[package]] @@ -1713,23 +1713,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "starknet-types-core" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a12690813e587969cb4a9e7d8ebdb069d4bb7ec8d03275c5f719310c8e1f07c" -dependencies = [ - "generic-array", - "lambdaworks-crypto", - "lambdaworks-math", - "num-bigint", - "num-integer", - "num-traits", - "rand 0.9.2", - "serde", - "zeroize", -] - [[package]] name = "string_cache" version = "0.8.9" diff --git a/Cargo.toml b/Cargo.toml index 663a606..b45cd05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] anyhow = "1.0" -cairo-annotations = { git = "https://github.com/software-mansion/cairo-annotations", rev = "ff307d9f0ab4c6514b90698e3ce29d91f8e0ce56", features = ["cairo-lang"] } +cairo-annotations = { version = "1", features = ["cairo-lang"] } cairo-lang-casm = "2.17.0" cairo-lang-sierra = "2.17.0" cairo-lang-sierra-to-casm = "2.17.0" diff --git a/src/debugger/context.rs b/src/debugger/context.rs index 2d63d64..372b024 100644 --- a/src/debugger/context.rs +++ b/src/debugger/context.rs @@ -19,7 +19,7 @@ use cairo_lang_sierra::extensions::core::{CoreConcreteLibfunc, CoreTypeConcrete} use cairo_lang_sierra::extensions::lib_func::BranchSignature; use cairo_lang_sierra::extensions::types::TypeInfo; use cairo_lang_sierra::extensions::{ConcreteLibfunc, ConcreteType}; -use cairo_lang_sierra::ids::{ConcreteTypeId, VarId}; +use cairo_lang_sierra::ids::{ConcreteTypeId, FunctionId, VarId}; use cairo_lang_sierra::program::{ Function, GenBranchTarget, GenInvocation, Program, ProgramArtifact, Statement, StatementIdx, }; @@ -29,7 +29,9 @@ use cairo_lang_sierra_type_size::ProgramRegistryInfo; use scarb_metadata::MetadataCommand; use crate::debugger::context::file_locations::{FileCodeLocationsData, build_file_locations_map}; -use crate::debugger::context::variables::build_cairo_var_to_casm_map; +use crate::debugger::context::variables::{ + build_cairo_var_to_casm_map, build_function_to_param_vars_map, +}; mod file_locations; #[cfg(feature = "dev")] @@ -46,6 +48,7 @@ pub struct Context { casm_debug_info: CairoProgramDebugInfo, files_data: HashMap, pub cairo_var_map: HashMap, + pub function_param_var_map: HashMap>, #[cfg(feature = "dev")] labels: HashMap, } @@ -85,7 +88,12 @@ impl Context { let casm_debug_info = compile_sierra_to_get_casm_debug_info(&program, &program_registry_info)?; let cairo_var_map = - build_cairo_var_to_casm_map(&program, &casm_debug_info, functions_debug_info); + build_cairo_var_to_casm_map(&program, &casm_debug_info, &functions_debug_info); + let function_param_var_map = build_function_to_param_vars_map( + &program, + &program_registry_info.type_sizes, + &functions_debug_info, + ); let files_data = build_file_locations_map(&casm_debug_info, &code_locations); @@ -109,6 +117,7 @@ impl Context { casm_debug_info, files_data, cairo_var_map, + function_param_var_map, }) } diff --git a/src/debugger/context/variables.rs b/src/debugger/context/variables.rs index e15cf90..38bdb8e 100644 --- a/src/debugger/context/variables.rs +++ b/src/debugger/context/variables.rs @@ -5,10 +5,11 @@ use cairo_annotations::annotations::coverage::SourceCodeSpan; use cairo_annotations::annotations::debugger::{ DebuggerAnnotationsV1 as FunctionsDebugInfo, FunctionDebugInfo, SierraFunctionId, SierraVarId, }; -use cairo_lang_sierra::ids::VarId; +use cairo_lang_sierra::ids::{FunctionId, VarId}; use cairo_lang_sierra::program::{GenBranchTarget, Program, Statement, StatementIdx}; use cairo_lang_sierra_to_casm::compiler::{CairoProgramDebugInfo, StatementKindDebugInfo}; -use cairo_lang_sierra_to_casm::references::ReferenceExpression; +use cairo_lang_sierra_to_casm::references::{ReferenceExpression, build_function_parameters_refs}; +use cairo_lang_sierra_type_size::TypeSizeMap; use crate::debugger::context::sierra_function_for_statement; @@ -61,7 +62,7 @@ impl From> for GenBranchTargetHashable { pub fn build_cairo_var_to_casm_map( program: &Program, casm_debug_info: &CairoProgramDebugInfo, - functions_debug_info: FunctionsDebugInfo, + functions_debug_info: &FunctionsDebugInfo, ) -> HashMap { let mut result = HashMap::new(); for (idx, statement_debug_info) in casm_debug_info.sierra_statement_info.iter().enumerate() { @@ -141,6 +142,94 @@ pub fn build_cairo_var_to_casm_map( result } +/// Build the per-function map of param name -> FP-relative reference. +/// +/// The references retrieved in this function are FP-only. +/// They are independent of further usage of corresponding Sierra variables in the function body - +/// if a param is used in the function body, it is either consumed or copied to a new, AP-based cell, effectively becoming a local variable. +/// We don't handle the second case here, since we have a dedicated piece of logic for local variables that can shadow the references returned from here. +/// +/// # Contract +/// We rely on CairoVM's calling convention to get the initial, FP-relative offsets of function params +/// and return references via FP. +pub fn build_function_to_param_vars_map( + program: &Program, + type_sizes: &TypeSizeMap, + functions_debug_info: &FunctionsDebugInfo, +) -> HashMap> { + program + .funcs + .iter() + .map(|function| { + let debug_info = + &functions_debug_info.functions_info[&SierraFunctionId(function.id.id)]; + let param_refs = build_function_parameters_refs(function, type_sizes) + .expect("function param refs construction should not fail"); + + let cairo_var_map = if let Some(parameters) = &debug_info.parameters { + parameters + .iter() + .filter_map(|(id, var_definition)| { + let sierra_id = VarId::new(id.0); + let ref_expr = param_refs.get(&sierra_id)?.expression.clone(); + let cairo_var_ref = CairoVarReference { sierra_id, ref_expr }; + let cairo_var_id = CairoVarId { + name: var_definition.name.clone(), + definition_span: var_definition.span.clone(), + }; + Some((cairo_var_id, cairo_var_ref)) + }) + .collect() + } else { + // Fallback when `parameters` field is not present in debug info (Scarb < 2.20.0). + // We still know the `CairoVarReference` of the parameters - we try to find their + // `CairoVarId` in `sierra_to_cairo_variable` by their sierra var id. + // + // Note that this fallback works just as good as using the `parameters` field + // in the vast majority of cases. `parameters` works better for cases where + // sierra var ids are reused between params and local variables, such as: + // + // fn foo(x: felt252) { + // assert(true, ''); + // // `x` has var id `0` - since it is a first param + // // The line below compiles to `store_temp([0]) -> ([0])` + // // so `y` has var id `0` as well. + // // This will cause `0: y` entry to override `0: x` entry in + // // `sierra_to_cairo_variable`. + // let mut y = x; + // y += 1; + // } + // + // This may lead to wrong variable names being shown for a while, since + // the parameter name we get from `sierra_to_cairo_variable` is in fact the local + // variable name. In the example above in mappings we will have sierra var id + // 0 mapped to `y`, so we will wrongly assume that the `x` param is named `y`. + // Assuming the function was called via `foo(5)`: + // + // fn foo(x: felt252) { + // assert(true, ''); // here `y = 5` will be shown instead of `x = 5` + // let mut y = x; + // y += 1; + // } + // + // We are fine with such errors - we prefer being wrong sometimes over not showing + // param values until their usage (after usage they become local vars and are shown + // anyways) for older Scarb version. + let param_references = param_refs + .into_iter() + .map(|(sierra_id, ref_value)| CairoVarReference { + sierra_id, + ref_expr: ref_value.expression, + }) + .collect(); + extract_cairo_var_map(param_references, debug_info) + }; + + (function.id.clone(), cairo_var_map) + }) + .collect() +} + /// For each var reference use its sierra var id to get the Cairo variable it corresponds to. fn extract_cairo_var_map( var_refs: Vec, diff --git a/src/debugger/state/call_stack.rs b/src/debugger/state/call_stack.rs index ca4f6bb..5f3d236 100644 --- a/src/debugger/state/call_stack.rs +++ b/src/debugger/state/call_stack.rs @@ -14,7 +14,9 @@ use indexmap::IndexMap; use crate::debugger::MIN_OBJECT_REFERENCE; use crate::debugger::context::Context; -use crate::debugger::state::call_stack::variables::get_values_of_variables; +use crate::debugger::state::call_stack::variables::{ + get_values_of_function_params, get_values_of_variables, +}; mod variables; @@ -181,11 +183,30 @@ impl CallStack { ctx: &Context, vm: &VirtualMachine, ) -> FunctionVariables { - get_values_of_variables( + let mut current_function_params = { + let last_executed_statement = self + .current_sierra_function_context + .last_executed_statement + .expect("last executed statement for current function should be set here - we are after pre-step update"); + // Access to function params in VM's memory is based only on FP, + // which is fixed for each function execution (frame). + let registers_values = RegistersValues { ap: 0, fp: vm.get_fp().offset }; + + get_values_of_function_params(ctx, vm, last_executed_statement, ®isters_values) + .unwrap_or_default() + }; + + let current_function_variables = get_values_of_variables( ctx, vm, &self.current_sierra_function_context.post_statements_registers, - ) + ); + + // Extend params with local variables so that local variables override params if there is + // a repetition (e.g. because a param variable changed its value). + current_function_params.names_to_values.extend(current_function_variables.names_to_values); + + current_function_params } pub fn get_variables( diff --git a/src/debugger/state/call_stack/variables.rs b/src/debugger/state/call_stack/variables.rs index bd85fcd..2e412ac 100644 --- a/src/debugger/state/call_stack/variables.rs +++ b/src/debugger/state/call_stack/variables.rs @@ -3,13 +3,16 @@ 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::StatementIdx; use cairo_vm::types::relocatable::{MaybeRelocatable, Relocatable}; use cairo_vm::vm::vm_core::VirtualMachine; use indexmap::IndexMap; use tracing::warn; use crate::debugger::context::{CairoVarId, CairoVarReference, Context}; -use crate::debugger::state::call_stack::{FunctionVariables, PostStatementsRegisters}; +use crate::debugger::state::call_stack::{ + FunctionVariables, PostStatementsRegisters, RegistersValues, +}; mod type_name; mod vm_reader; @@ -117,6 +120,27 @@ pub fn get_values_of_variables( FunctionVariables { names_to_values } } +pub fn get_values_of_function_params( + ctx: &Context, + vm: &VirtualMachine, + last_executed_statement: StatementIdx, + registers_values: &RegistersValues, +) -> Option { + let function = ctx.sierra_function_for_statement(last_executed_statement); + let params = ctx.function_param_var_map.get(&function.id)?; + + let reader = VmReader::new(vm, registers_values); + let names_to_values: IndexMap<_, _> = params + .iter() + .filter_map(|(CairoVarId { name, .. }, CairoVarReference { sierra_id, ref_expr })| { + let type_id = &function.params.iter().find(|param| ¶m.id == sierra_id).unwrap().ty; + Some((name.to_string(), extract_var_value(&ref_expr.cells, type_id, &reader, ctx)?)) + }) + .collect(); + + Some(FunctionVariables { names_to_values }) +} + fn extract_var_value( cells: &[CellExpression], type_id: &ConcreteTypeId,