diff --git a/chain/vm/src/host/context/tx_context_ref.rs b/chain/vm/src/host/context/tx_context_ref.rs index c58b3b307c..05f1a69cf2 100644 --- a/chain/vm/src/host/context/tx_context_ref.rs +++ b/chain/vm/src/host/context/tx_context_ref.rs @@ -1,4 +1,7 @@ -use std::{ops::Deref, sync::Arc}; +use std::{ + ops::Deref, + sync::{Arc, Weak}, +}; use crate::host::context::{TxContext, TxResult}; @@ -62,7 +65,30 @@ impl TxContextRef { Arc::ptr_eq(&this.0, &other.0) } + /// Returns a raw pointer to the underlying `TxContext`. + /// + /// This is useful for pointer comparisons, particularly when comparing + /// with weak references to determine if they point to the same context. + pub fn as_ptr(&self) -> *const TxContext { + Arc::as_ptr(&self.0) + } + pub fn into_ref(self) -> Arc { self.0 } + + /// Creates a new [`Weak`] pointer to the [`TxContext`]. + /// + /// This is the preferred way to obtain a non‑owning reference to the underlying + /// `TxContext` when you need to store a handle that should not keep the transaction + /// alive on its own. In particular, this method underpins the weak‑pointer pattern + /// used by [`DebugHandle`], which holds a `Weak` so that debug tooling + /// can observe a transaction while it exists, without extending its lifetime. + /// + /// Callers that use the returned [`Weak`] must call [`Weak::upgrade`] before + /// accessing the `TxContext` and be prepared to handle the case where upgrading + /// fails because the transaction context has already been dropped. + pub fn downgrade(&self) -> Weak { + Arc::downgrade(&self.0) + } } diff --git a/framework/scenario/src/api/impl_vh/debug_api.rs b/framework/scenario/src/api/impl_vh/debug_api.rs index 3076d6a24a..2babf9dbd1 100644 --- a/framework/scenario/src/api/impl_vh/debug_api.rs +++ b/framework/scenario/src/api/impl_vh/debug_api.rs @@ -1,6 +1,5 @@ use multiversx_chain_vm::{ executor::{VMHooks, VMHooksEarlyExit}, - host::context::TxContextRef, host::vm_hooks::{TxVMHooksContext, VMHooksDispatcher}, }; use multiversx_sc::{chain_core::types::ReturnCode, err_msg}; @@ -32,7 +31,7 @@ impl VMHooksApiBackend for DebugApiBackend { where F: FnOnce(&mut dyn VMHooks) -> Result, { - let tx_context_ref = TxContextRef(handle.context.clone()); + let tx_context_ref = handle.to_tx_context_ref(); let vh_context = TxVMHooksContext::new(tx_context_ref, ContractDebugInstanceState); let mut dispatcher = VMHooksDispatcher::new(vh_context); f(&mut dispatcher).unwrap_or_else(|err| ContractDebugInstanceState::early_exit_panic(err)) diff --git a/framework/scenario/src/api/impl_vh/debug_handle_vh.rs b/framework/scenario/src/api/impl_vh/debug_handle_vh.rs index 7f3da0d20f..086d166d17 100644 --- a/framework/scenario/src/api/impl_vh/debug_handle_vh.rs +++ b/framework/scenario/src/api/impl_vh/debug_handle_vh.rs @@ -1,6 +1,6 @@ -use std::sync::Arc; +use std::sync::Weak; -use multiversx_chain_vm::host::context::TxContext; +use multiversx_chain_vm::host::context::{TxContext, TxContextRef}; use multiversx_sc::{ api::{HandleConstraints, RawHandle}, codec::TryStaticCast, @@ -10,22 +10,30 @@ use crate::executor::debug::ContractDebugStack; #[derive(Clone)] pub struct DebugHandle { - /// TODO: would be nice to be an actual TxContextRef, - /// but that requires changing the debugger scripts - pub(crate) context: Arc, + /// Only keep a weak reference to the context, to avoid stray handles keeping the context from being released. + /// Using the pointer after the context is released will panic. + pub(crate) context: Weak, raw_handle: RawHandle, } impl DebugHandle { + /// Should almost never call directly, only used directly in a test. + pub fn new_with_explicit_context_ref(context: Weak, raw_handle: RawHandle) -> Self { + Self { + context, + raw_handle, + } + } + pub fn is_on_current_context(&self) -> bool { - Arc::ptr_eq( - &self.context, - &ContractDebugStack::static_peek().tx_context_ref.into_ref(), + std::ptr::eq( + self.context.as_ptr(), + ContractDebugStack::static_peek().tx_context_ref.as_ptr(), ) } pub fn is_on_same_context(&self, other: &DebugHandle) -> bool { - Arc::ptr_eq(&self.context, &other.context) + Weak::ptr_eq(&self.context, &other.context) } pub fn assert_current_context(&self) { @@ -34,6 +42,30 @@ impl DebugHandle { "Managed value not used in original context" ); } + + /// Upgrades the weak reference to a strong `TxContextRef`. + /// + /// This method attempts to upgrade the weak reference stored in this handle + /// to a strong reference. This is necessary when you need to access the + /// underlying `TxContext` for operations. + /// + /// # Panics + /// + /// Panics if the `TxContext` is no longer valid (has been dropped). This can + /// happen if the object was created on a VM execution stack frame that has + /// already been popped, or if objects are mixed between different execution + /// contexts during whitebox testing. + pub fn to_tx_context_ref(&self) -> TxContextRef { + let tx_context_arc = self.context.upgrade().unwrap_or_else(|| { + panic!( + "TxContext is no longer valid for handle {}. +The object was created on a VM execution stack frame that has already been popped. +This can sometimes happen during whitebox testing if the objects are mixed between execution contexts.", + self.raw_handle + ) + }); + TxContextRef::new(tx_context_arc) + } } impl core::fmt::Debug for DebugHandle { @@ -44,10 +76,8 @@ impl core::fmt::Debug for DebugHandle { impl HandleConstraints for DebugHandle { fn new(handle: multiversx_sc::api::RawHandle) -> Self { - Self { - context: ContractDebugStack::static_peek().tx_context_ref.into_ref(), - raw_handle: handle, - } + let context = ContractDebugStack::static_peek().tx_context_ref.downgrade(); + DebugHandle::new_with_explicit_context_ref(context, handle) } fn to_be_bytes(&self) -> [u8; 4] { @@ -73,7 +103,7 @@ impl PartialEq for DebugHandle { impl PartialEq for DebugHandle { fn eq(&self, other: &DebugHandle) -> bool { - Arc::ptr_eq(&self.context, &other.context) && self.raw_handle == other.raw_handle + Weak::ptr_eq(&self.context, &other.context) && self.raw_handle == other.raw_handle } } diff --git a/tools/rust-debugger/format-tests/src/format_tests.rs b/tools/rust-debugger/format-tests/src/format_tests.rs index 4e878b2df6..28129aa9a8 100644 --- a/tools/rust-debugger/format-tests/src/format_tests.rs +++ b/tools/rust-debugger/format-tests/src/format_tests.rs @@ -1,4 +1,7 @@ -use multiversx_sc_scenario::imports::*; +use multiversx_sc_scenario::{ + executor::debug::{ContractDebugInstance, ContractDebugStack}, + imports::*, +}; macro_rules! push { ($list: ident, $name:ident, $expected: expr ) => {{ @@ -13,7 +16,8 @@ macro_rules! push { // they have to be cloned if used before that point #[allow(clippy::redundant_clone)] fn main() { - DebugApi::dummy(); + // Set up a dummy context on the debug stack, required for all managed types + ContractDebugStack::static_push(ContractDebugInstance::dummy()); // Used by the python script which checks the variable summaries let mut to_check: Vec<(String, String)> = Vec::new(); @@ -76,7 +80,11 @@ fn main() { let hex_esdt_safe_address = Address::new(hex_esdt_safe); let esdt_safe_managed_address: ManagedAddress = ManagedAddress::from(hex_esdt_safe_address); - push!(to_check, esdt_safe_managed_address, "\"esdt-safe_____________\" - (32) 0x00000000000000000500657364742d736166655f5f5f5f5f5f5f5f5f5f5f5f5f"); + push!( + to_check, + esdt_safe_managed_address, + "\"esdt-safe_____________\" - (32) 0x00000000000000000500657364742d736166655f5f5f5f5f5f5f5f5f5f5f5f5f" + ); let test_token_identifier: TestTokenIdentifier = TestTokenIdentifier::new("TEST-123456"); push!(to_check, test_token_identifier, "\"str:TEST-123456\""); @@ -137,7 +145,11 @@ fn main() { 100, 5000u64.into(), )); - push!(to_check, managed_vec_of_payments, "(2) { [0] = { token_identifier: \"MYTOK-123456\", nonce: 42, amount: 1000 }, [1] = { token_identifier: \"MYTOK-abcdef\", nonce: 100, amount: 5000 } }"); + push!( + to_check, + managed_vec_of_payments, + "(2) { [0] = { token_identifier: \"MYTOK-123456\", nonce: 42, amount: 1000 }, [1] = { token_identifier: \"MYTOK-abcdef\", nonce: 100, amount: 5000 } }" + ); let egld_or_esdt_token_identifier_egld: EgldOrEsdtTokenIdentifier = EgldOrEsdtTokenIdentifier::egld(); @@ -169,7 +181,11 @@ fn main() { DebugApi, ManagedVec>, > = ManagedOption::some(managed_vec_of_addresses.clone()); - push!(to_check, managed_option_of_vec_of_addresses, "ManagedOption::some((1) { [0] = (32) 0x000000000000000000010000000000000000000000000000000000000002ffff })"); + push!( + to_check, + managed_option_of_vec_of_addresses, + "ManagedOption::some((1) { [0] = (32) 0x000000000000000000010000000000000000000000000000000000000002ffff })" + ); // 5. SC wasm - heap let heap_address: Address = managed_address.to_address(); @@ -244,7 +260,25 @@ fn main() { "OptionalValue::Some()" ); + // Invalid TxContext test - simulate access after context change + // This test relies on the debugger pretty printer's error handling + // to detect when a weak pointer becomes invalid + let biguint_with_invalid_context = + unsafe { BigUint::::from_handle(create_handle_from_dropped_context()) }; + push!( + to_check, + biguint_with_invalid_context, + "" + ); + breakpoint_marker_end_of_main(); + + // Clean up the dummy entry on stack + ContractDebugStack::static_pop(); +} + +fn create_handle_from_dropped_context() -> DebugHandle { + DebugHandle::new_with_explicit_context_ref(std::sync::Weak::new(), -100i32) } fn breakpoint_marker_end_of_main() {} diff --git a/tools/rust-debugger/pretty-printers/format-python.sh b/tools/rust-debugger/pretty-printers/format-python.sh new file mode 100755 index 0000000000..1fb4d0e0e3 --- /dev/null +++ b/tools/rust-debugger/pretty-printers/format-python.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Python code formatting script for MultiversX SDK pretty-printers +echo "🐍 Formatting Python code..." + +# Get the SDK root directory (3 levels up from this script) +SDK_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +PYTHON_CMD="$SDK_ROOT/.venv/bin/python" + +# Find Python files in this directory +PYTHON_FILES=$(find . -name "*.py" -maxdepth 1) + +if [ -z "$PYTHON_FILES" ]; then + echo "No Python files found to format" + exit 0 +fi + +echo "Found $(echo "$PYTHON_FILES" | wc -l) Python files to format" + +# Check if virtual environment exists +if [ ! -f "$PYTHON_CMD" ]; then + echo "❌ Python virtual environment not found at $PYTHON_CMD" + echo "Please run 'configure_python_environment' from the SDK root first" + exit 1 +fi + +# 1. Sort imports with isort +echo "📦 Sorting imports with isort..." +$PYTHON_CMD -m isort $PYTHON_FILES + +# 2. Format code with black +echo "🖤 Formatting code with black..." +$PYTHON_CMD -m black $PYTHON_FILES + +# 3. Check style with flake8 (optional) +if [ "$1" = "--check" ]; then + echo "🔍 Checking style with flake8..." + $PYTHON_CMD -m flake8 $PYTHON_FILES --max-line-length=88 --extend-ignore=E203,W503 +fi + +echo "✅ Python formatting complete!" \ No newline at end of file diff --git a/tools/rust-debugger/pretty-printers/multiversx_sc_lldb_pretty_printers.py b/tools/rust-debugger/pretty-printers/multiversx_sc_lldb_pretty_printers.py index cc48f7df9b..b9fccd051b 100644 --- a/tools/rust-debugger/pretty-printers/multiversx_sc_lldb_pretty_printers.py +++ b/tools/rust-debugger/pretty-printers/multiversx_sc_lldb_pretty_printers.py @@ -4,14 +4,15 @@ ### Version: 0.64.0 ############################################################################## -from functools import partial -import string -from typing import Callable, Collection, Iterable, List, Tuple, Type -from lldb import SBValue, SBDebugger -import lldb -from pathlib import Path import re +import string import struct +from functools import partial +from pathlib import Path +from typing import Callable, Collection, Iterable, List, Tuple, Type + +import lldb # type: ignore[import] +from lldb import SBDebugger, SBValue # type: ignore[import] VM_TYPE_ADDRESS = "0000000050" DEBUG_API_TYPE = "multiversx_sc_scenario::api::impl_vh::vm_hooks_api::VMHooksApi" @@ -27,15 +28,21 @@ BIG_INT_TYPE = f"{MANAGED_BASIC_PATH}::big_int::BigInt<{DEBUG_API_TYPE} ?>" BIG_FLOAT_TYPE = f"{MANAGED_BASIC_PATH}::big_float::BigFloat<{DEBUG_API_TYPE} ?>" -MANAGED_BUFFER_TYPE = f"{MANAGED_BASIC_PATH}::managed_buffer::ManagedBuffer<{DEBUG_API_TYPE} ?>" +MANAGED_BUFFER_TYPE = ( + f"{MANAGED_BASIC_PATH}::managed_buffer::ManagedBuffer<{DEBUG_API_TYPE} ?>" +) # 3. SC wasm - Managed wrapped types ## 3a. general MANAGED_WRAPPED_PATH = "multiversx_sc::types::managed::wrapped" BIG_UINT_TYPE = f"{MANAGED_WRAPPED_PATH}::num::big_uint::BigUint<{DEBUG_API_TYPE} ?>" NON_ZERO_BIG_UINT_TYPE = f"{MANAGED_WRAPPED_PATH}::num::non_zero_big_uint::NonZeroBigUint<{DEBUG_API_TYPE} ?>" -MANAGED_ADDRESS_TYPE = f"{MANAGED_WRAPPED_PATH}::managed_address::ManagedAddress<{DEBUG_API_TYPE} ?>" -MANAGED_BYTE_ARRAY_TYPE = f"{MANAGED_WRAPPED_PATH}::managed_byte_array::ManagedByteArray<{DEBUG_API_TYPE} ?>" +MANAGED_ADDRESS_TYPE = ( + f"{MANAGED_WRAPPED_PATH}::managed_address::ManagedAddress<{DEBUG_API_TYPE} ?>" +) +MANAGED_BYTE_ARRAY_TYPE = ( + f"{MANAGED_WRAPPED_PATH}::managed_byte_array::ManagedByteArray<{DEBUG_API_TYPE} ?>" +) ## 3b. tokens & payments MANAGED_WRAPPED_TOKEN_PATH = "multiversx_sc::types::managed::wrapped::token" ESDT_TOKEN_IDENTIFIER_TYPE = f"{MANAGED_WRAPPED_TOKEN_PATH}::esdt_token_identifier::EsdtTokenIdentifier<{DEBUG_API_TYPE} ?>" @@ -49,7 +56,9 @@ MANAGED_OPTION_TYPE = f"{MANAGED_WRAPPED_PATH}::managed_option::ManagedOption<{DEBUG_API_TYPE}, {ANY_TYPE}>" # ManagedVec MANAGED_VEC_INNER_TYPE_INDEX = 1 -MANAGED_VEC_TYPE = f"{MANAGED_WRAPPED_PATH}::managed_vec::ManagedVec<{DEBUG_API_TYPE}, {ANY_TYPE}>" +MANAGED_VEC_TYPE = ( + f"{MANAGED_WRAPPED_PATH}::managed_vec::ManagedVec<{DEBUG_API_TYPE}, {ANY_TYPE}>" +) # 4. SC wasm - Managed multi value types @@ -64,12 +73,17 @@ TEST_SC_ADDRESS_TYPE = f"{INTERACTION_EXPR_PATH}::test_sc_address::TestSCAddress" TEST_ADDRESS_TYPE = f"{INTERACTION_EXPR_PATH}::test_address::TestAddress" -TEST_TOKEN_IDENTIFIER_TYPE = f"{INTERACTION_EXPR_PATH}::test_token_identifier::TestTokenIdentifier" +TEST_TOKEN_IDENTIFIER_TYPE = ( + f"{INTERACTION_EXPR_PATH}::test_token_identifier::TestTokenIdentifier" +) # 7. MultiversX codec - Multi-types MULTI_TYPES_PATH = "multiversx_sc_codec::multi_types" -OPTIONAL_VALUE_TYPE = f"{MULTI_TYPES_PATH}::multi_value_optional::OptionalValue<{ANY_TYPE}>" +OPTIONAL_VALUE_TYPE = ( + f"{MULTI_TYPES_PATH}::multi_value_optional::OptionalValue<{ANY_TYPE}>" +) + class InvalidHandle(Exception): def __init__(self, raw_handle: int, map_: lldb.value) -> None: @@ -78,12 +92,24 @@ def __init__(self, raw_handle: int, map_: lldb.value) -> None: super().__init__(error) +class InvalidWeakPointer(Exception): + def __init__(self, raw_handle: int) -> None: + error = f"" + super().__init__(error) + + def check_invalid_handle(callable: Callable) -> Callable: def wrapped(*args) -> str: try: return callable(*args) except InvalidHandle as e: return str(e) + except InvalidWeakPointer as e: + return str(e) + except Exception as e: + # Catch any other exceptions that might occur during pointer dereferencing + return f"" + return wrapped @@ -151,12 +177,12 @@ def bytes_to_int(bytes: Collection[int]) -> int: def num_bigint_data_to_int(value_vec_u64: lldb.value) -> int: - value_hex = ''.join(reversed(list(map(sb_value_to_hex, value_vec_u64.sbvalue)))) + value_hex = "".join(reversed(list(map(sb_value_to_hex, value_vec_u64.sbvalue)))) return hex_to_int(value_hex) def ints_to_hex(ints: Iterable[int]) -> str: - return ''.join(map(u8_to_hex, ints)) + return "".join(map(u8_to_hex, ints)) def buffer_to_bytes(buffer: lldb.value) -> List[int]: @@ -179,7 +205,7 @@ def bytes_to_handle(bytes: List[int]) -> int: -112 """ bytes_hex = ints_to_hex(bytes) - return struct.unpack('>i', bytearray.fromhex(bytes_hex))[0] + return struct.unpack(">i", bytearray.fromhex(bytes_hex))[0] def format_buffer_hex_string(buffer_hex: str) -> str: @@ -199,27 +225,31 @@ def ascii_to_string(buffer_iterator: Iterable[int]) -> str: >>> ascii_to_string([116, 101, 115, 116]) 'test' """ - return ''.join(map(chr, buffer_iterator)) + return "".join(map(chr, buffer_iterator)) + def buffer_to_bytes_without_vm_type(buffer: lldb.value) -> List[int]: buffer_ints = buffer_to_bytes(buffer) buffer_vm_type = buffer_to_bytes(VM_TYPE_ADDRESS) - if buffer_ints[:len(buffer_vm_type)] == buffer_vm_type: - return buffer_ints[len(buffer_vm_type):] + if buffer_ints[: len(buffer_vm_type)] == buffer_vm_type: + return buffer_ints[len(buffer_vm_type) :] return buffer_ints + def buffer_as_string(buffer: lldb.value) -> str: buffer_ints = buffer_to_bytes_without_vm_type(buffer) buffer_string = ascii_to_string(buffer_ints) return f'"{buffer_string}"' + def interaction_type_as_string(buffer: lldb.value, prefix: str) -> str: buffer_ints = buffer_to_bytes(buffer) buffer_string = ascii_to_string(buffer_ints) return f'"{prefix}:{buffer_string}"' + def mixed_representation(buffer: lldb.value) -> str: buffer_hex = format_buffer_hex(buffer) buffer_string = buffer_as_string(buffer) @@ -237,8 +267,8 @@ def parse_handles_from_buffer_hex(buffer_hex: str) -> List[int]: """ raw_handles = [] for handle_bytes_iter in zip(*[iter(buffer_hex)] * 8): - handle_bytes_hex = ''.join(handle_bytes_iter) - raw_handle = struct.unpack('>i', bytearray.fromhex(handle_bytes_hex))[0] + handle_bytes_hex = "".join(handle_bytes_iter) + raw_handle = struct.unpack(">i", bytearray.fromhex(handle_bytes_hex))[0] raw_handles.append(raw_handle) return raw_handles @@ -277,14 +307,32 @@ def map_picker(self) -> Callable: def lookup(self, full_value: lldb.value) -> lldb.value: return full_value - def extract_value_from_raw_handle(self, context: lldb.value, raw_handle: int, map_picker: Callable) -> lldb.value: - managed_types = context[0].managed_types.data.value + def extract_value_from_raw_handle( + self, context: lldb.value, raw_handle: int, map_picker: Callable + ) -> lldb.value: + weak_inner = context.ptr.pointer # object is of Rust type: ArcInner + + # Check the strong count to see if the object is still alive + # If strong count is 0, the object has been dropped + strong_atomic_usize = weak_inner.strong # object is of Rust type: Atomic + strong_count = int(strong_atomic_usize.v.value) + if strong_count == 0: + raise InvalidWeakPointer(raw_handle) + + # Handle Weak by accessing the ptr field directly + # context is Weak, which contains ptr that points to the WeakInner + # The WeakInner contains a pointer to the actual TxContext data + # In LLDB, we access: context.ptr.pointer (NonNull>) -> pointer.data -> TxContext + tx_context = weak_inner.data + managed_types = tx_context.managed_types.data.value chosen_map = map_picker(managed_types) value = map_lookup(chosen_map, raw_handle) return value @check_invalid_handle - def summary_from_raw_handle(self, raw_handle: int, context: lldb.value, type_info: lldb.SBType) -> str: + def summary_from_raw_handle( + self, raw_handle: int, context: lldb.value, type_info: lldb.SBType + ) -> str: map_picker = self.map_picker() value = self.extract_value_from_raw_handle(context, raw_handle, map_picker) return self.value_summary(value, context, type_info) @@ -294,10 +342,13 @@ def summary(self, original_value: lldb.value) -> str: managed_value = self.lookup(original_value) handle = managed_value.handle raw_handle = int(handle.raw_handle) + # Handle Weak by getting the context from the handle context = handle.context return self.summary_from_raw_handle(raw_handle, context, type_info) - def value_summary(self, value: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: + def value_summary( + self, value: lldb.value, context: lldb.value, type_info: lldb.SBType + ) -> str: pass @@ -305,7 +356,9 @@ class ManagedVecItem(Handler): def item_size(self) -> int: pass - def summarize_item(self, bytes: List[int], context: lldb.value, type_info: lldb.SBType) -> str: + def summarize_item( + self, bytes: List[int], context: lldb.value, type_info: lldb.SBType + ) -> str: pass @@ -317,7 +370,9 @@ class PlainManagedVecItem(ManagedVecItem, ManagedType): def item_size(self) -> int: return 4 - def summarize_item(self, handle_bytes: List[int], context: lldb.value, type_info: lldb.SBType) -> str: + def summarize_item( + self, handle_bytes: List[int], context: lldb.value, type_info: lldb.SBType + ) -> str: raw_handle = bytes_to_handle(handle_bytes) return self.summary_from_raw_handle(raw_handle, context, type_info) @@ -325,7 +380,7 @@ def summarize_item(self, handle_bytes: List[int], context: lldb.value, type_info class NumBigInt(Handler): def summary(self, num_big_int: lldb.value) -> str: value_int = num_bigint_data_to_int(num_big_int.data.data) - if num_big_int.sign.sbvalue.GetValue() == 'Minus': + if num_big_int.sign.sbvalue.GetValue() == "Minus": return str(-value_int) return str(value_int) @@ -340,7 +395,9 @@ class BigInt(PlainManagedVecItem, ManagedType): def map_picker(self) -> Callable: return pick_big_int - def value_summary(self, value: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: + def value_summary( + self, value: lldb.value, context: lldb.value, type_info: lldb.SBType + ) -> str: return str(value.sbvalue.GetSummary()) @@ -348,23 +405,29 @@ class BigFloat(PlainManagedVecItem, ManagedType): def map_picker(self) -> Callable: return pick_big_float - def value_summary(self, value: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: + def value_summary( + self, value: lldb.value, context: lldb.value, type_info: lldb.SBType + ) -> str: return str(value.sbvalue.GetValue()) class ManagedBuffer(PlainManagedVecItem, ManagedType): - def value_summary(self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: + def value_summary( + self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType + ) -> str: return mixed_representation(buffer) class BigUint(PlainManagedVecItem, ManagedType): def map_picker(self) -> Callable: return pick_big_int - + def lookup(self, big_uint: lldb.value) -> lldb.value: return big_uint.value - def value_summary(self, big_uint: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: + def value_summary( + self, big_uint: lldb.value, context: lldb.value, type_info: lldb.SBType + ) -> str: return big_uint.sbvalue.GetSummary() @@ -372,7 +435,9 @@ class EsdtTokenIdentifier(PlainManagedVecItem, ManagedType): def lookup(self, token_identifier: lldb.value) -> lldb.value: return token_identifier.token_id.buffer - def value_summary(self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: + def value_summary( + self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType + ) -> str: return buffer_as_string(buffer) @@ -380,24 +445,35 @@ class ManagedAddress(PlainManagedVecItem, ManagedType): def lookup(self, managed_address: lldb.value) -> lldb.value: return managed_address.bytes.buffer - def value_summary(self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: + def value_summary( + self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType + ) -> str: return mixed_representation(buffer) + class ManagedByteArray(PlainManagedVecItem, ManagedType): def lookup(self, managed_byte_array: lldb.value) -> lldb.value: return managed_byte_array.buffer - def value_summary(self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: + def value_summary( + self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType + ) -> str: return mixed_representation(buffer) class ManagedOption(PlainManagedVecItem, ManagedType): - def summary_from_raw_handle(self, raw_handle: int, context: lldb.value, type_info: lldb.SBType) -> str: + def summary_from_raw_handle( + self, raw_handle: int, context: lldb.value, type_info: lldb.SBType + ) -> str: if raw_handle == MANAGED_OPTION_NONE_HANDLE: return "ManagedOption::none()" - inner_type_handler, inner_type = get_inner_type_handler(type_info, MANAGED_OPTION_INNER_TYPE_INDEX) - assert(isinstance(inner_type_handler, ManagedType)) - inner_summary = inner_type_handler.summary_from_raw_handle(raw_handle, context, inner_type) + inner_type_handler, inner_type = get_inner_type_handler( + type_info, MANAGED_OPTION_INNER_TYPE_INDEX + ) + assert isinstance(inner_type_handler, ManagedType) + inner_summary = inner_type_handler.summary_from_raw_handle( + raw_handle, context, inner_type + ) return f"ManagedOption::some({inner_summary})" @@ -410,7 +486,7 @@ def split_bytes(bytes: List[int], sizes: Iterable[int]) -> List[List[int]]: chunks = [] i = 0 for size in sizes: - chunks.append(bytes[i: i + size]) + chunks.append(bytes[i : i + size]) i += size return chunks @@ -425,7 +501,7 @@ def split_bytes_fixed_size(bytes: List[int], size: int) -> List[List[int]]: i = 0 byte_count = len(bytes) while i < byte_count: - chunks.append(bytes[i: i + size]) + chunks.append(bytes[i : i + size]) i += size return chunks @@ -442,9 +518,15 @@ def summary(self, payment: lldb.value) -> str: def item_size(self) -> int: return sum(self.COMPONENT_SIZES) - def summarize_item(self, bytes: List[int], context: lldb.value, type_info: lldb.SBType) -> str: - token_id_handle_bytes, nonce_bytes, amount_handle_bytes = split_bytes(bytes, self.COMPONENT_SIZES) - token_id = EsdtTokenIdentifier().summarize_item(token_id_handle_bytes, context, None) + def summarize_item( + self, bytes: List[int], context: lldb.value, type_info: lldb.SBType + ) -> str: + token_id_handle_bytes, nonce_bytes, amount_handle_bytes = split_bytes( + bytes, self.COMPONENT_SIZES + ) + token_id = EsdtTokenIdentifier().summarize_item( + token_id_handle_bytes, context, None + ) nonce = bytes_to_int(nonce_bytes) amount = BigInt().summarize_item(amount_handle_bytes, context, None) return self.to_string(token_id, nonce, amount) @@ -457,24 +539,33 @@ class EgldOrEsdtTokenIdentifier(PlainManagedVecItem, ManagedType): def lookup(self, egld_or_esdt_token_identifier: lldb.value) -> lldb.value: return egld_or_esdt_token_identifier.token_id.buffer - def value_summary(self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: + def value_summary( + self, buffer: lldb.value, context: lldb.value, type_info: lldb.SBType + ) -> str: token_id = buffer_as_string(buffer) if token_id == '"EGLD-000000"': return "EgldOrEsdtTokenIdentifier::egld()" - return f"EgldOrEsdtTokenIdentifier::esdt({token_id})" + return f"EgldOrEsdtTokenIdentifier::esdt({token_id})" class ManagedVec(PlainManagedVecItem, ManagedType): def lookup(self, managed_vec: lldb.value) -> lldb.value: return managed_vec.buffer - def value_summary(self, value: lldb.value, context: lldb.value, type_info: lldb.SBType) -> str: - item_handler, inner_type = get_inner_type_handler(type_info, MANAGED_VEC_INNER_TYPE_INDEX) - assert(isinstance(item_handler, ManagedVecItem)) + def value_summary( + self, value: lldb.value, context: lldb.value, type_info: lldb.SBType + ) -> str: + item_handler, inner_type = get_inner_type_handler( + type_info, MANAGED_VEC_INNER_TYPE_INDEX + ) + assert isinstance(item_handler, ManagedVecItem) buffer_bytes = buffer_to_bytes(value) item_size = item_handler.item_size() bytes_of_all_items = split_bytes_fixed_size(buffer_bytes, item_size) - items = [item_handler.summarize_item(bytes, context, inner_type) for bytes in bytes_of_all_items] + items = [ + item_handler.summarize_item(bytes, context, inner_type) + for bytes in bytes_of_all_items + ] return format_vec(items) @@ -493,25 +584,33 @@ def summary(self, boxed_bytes: lldb.value) -> str: buffer_hex = ints_to_hex(raw) return format_buffer_hex_string(buffer_hex) + class TestSCAddress(Handler): def summary(self, test_sc_address: lldb.value) -> str: buffer = lldb.value(test_sc_address.sbvalue.GetChildAtIndex(0)) return interaction_type_as_string(buffer, "sc") - + + class TestAddress(Handler): def summary(self, test_address: lldb.value) -> str: buffer = lldb.value(test_address.sbvalue.GetChildAtIndex(0)) return interaction_type_as_string(buffer, "address") - + + class TestTokenIdentifier(Handler): def summary(self, test_address: lldb.value) -> str: buffer = lldb.value(test_address.sbvalue.GetChildAtIndex(0)) return interaction_type_as_string(buffer, "str") + class OptionalValue(Handler): def summary(self, optional_value: lldb.value) -> str: base_type = optional_value.sbvalue.GetType().GetName() - if optional_value.value.sbvalue.GetType().GetName().startswith(f'{base_type}::Some'): + if ( + optional_value.value.sbvalue.GetType() + .GetName() + .startswith(f"{base_type}::Some") + ): summary = optional_value.value.sbvalue.GetChildAtIndex(0).GetSummary() return f"OptionalValue::Some({summary})" return "OptionalValue::None" @@ -550,10 +649,12 @@ def summary(self, optional_value: lldb.value) -> str: class UnknownType(Exception): def __init__(self, type_name: str) -> None: - super().__init__(f'unknown type: {type_name}') + super().__init__(f"unknown type: {type_name}") -def get_inner_type_handler(type_info: lldb.SBType, inner_type_index: int) -> Tuple[Handler, lldb.SBType]: +def get_inner_type_handler( + type_info: lldb.SBType, inner_type_index: int +) -> Tuple[Handler, lldb.SBType]: inner_type = type_info.GetTemplateArgumentType(inner_type_index).GetCanonicalType() handler = get_handler(inner_type.GetName()) return handler, inner_type @@ -573,7 +674,7 @@ def summarize_handler(handler_type: Type[Handler], valobj: SBValue, dictionary) def __lldb_init_module(debugger: SBDebugger, dict): - python_module_name = Path(__file__).with_suffix('').name + python_module_name = Path(__file__).with_suffix("").name for rust_type, handler_class in MULTIVERSX_WASM_TYPE_HANDLERS: # Add summary binding @@ -585,4 +686,4 @@ def __lldb_init_module(debugger: SBDebugger, dict): # print(f"Registered: {summary_command}") # Enable categories - debugger.HandleCommand('type category enable multiversx-sc') + debugger.HandleCommand("type category enable multiversx-sc") diff --git a/tools/rust-debugger/pretty-printers/pyproject.toml b/tools/rust-debugger/pretty-printers/pyproject.toml new file mode 100644 index 0000000000..400b7c3f24 --- /dev/null +++ b/tools/rust-debugger/pretty-printers/pyproject.toml @@ -0,0 +1,13 @@ +[tool.black] +line-length = 88 +target-version = ['py38', 'py39', 'py310', 'py311'] +include = '\.pyi?$' +extend-exclude = ''' +/( + # directories + \.eggs + | \.git + | \.venv + | target +)/ +''' \ No newline at end of file