Skip to content

python: segfault when using Arc<dyn T> in Object where T is an exported trait #2649

@joe-p

Description

@joe-p

Edit: The solution is to make the trait being passed as Arc<dyn T> foreign. Keeping the issue open because a better error message would be nice

I have defined the following Object

#[derive(uniffi::Object, Clone)]
pub struct ABIDynamicArray {
    element_type: Arc<dyn ABIType>,
}

impl FfiToRustABIType for ABIDynamicArray {
    fn to_rust_abi_type(&self) -> RustABIType {
        eprintln!("DEBUGPRINT[43]: dynamic_array.rs:34 (before let cloned = self.clone();)");
        let cloned = self.clone();
        eprintln!("DEBUGPRINT[44]: dynamic_array.rs:35 (after let cloned = self.clone();)");

        eprintln!("DEBUGPRINT[46]: dynamic_array.rs:38 (before cloned.into())");
        let res = cloned.into();
        eprintln!("DEBUGPRINT[47]: dynamic_array.rs:39 (after cloned.into())");
        res
    }
}

#[uniffi::export]
impl ABIType for ABIDynamicArray {
    fn decoode(&self, data: &[u8]) -> ABIValue {
        let rust_abi_type = self.to_rust_abi_type();
        ABIValue::from(rust_abi_type.decode(data).unwrap())
    }

    fn encode(&self, value: ABIValue) -> Vec<u8> {
        let rust_abi_type = self.to_rust_abi_type();
        rust_abi_type.encode(&value.into()).unwrap()
    }
}

#[uniffi::export]
impl ABIDynamicArray {
    #[uniffi::constructor]
    pub fn new(element_type: Arc<dyn ABIType>) -> Self {
        ABIDynamicArray { element_type }
    }
}

Attempting to use the python bindings:

def test_abi_bool_array():
    abi_arr = AbiDynamicArray(element_type=AbiBool())

    arr = AbiValue(array=[AbiValue(bool=True)])
    encoded = abi_arr.encode(arr)

    assert encoded == b'\x000180'

I get the following segfault. One strange thing to note us that sometimes it segfaults on self.clone() and sometimes it segfaults on cloned.into(). This seems like a problem with erroneously dropping a reference in the binding somewhere in a non-deterministic way.

lldb -- "$(poetry run which python)" -X dev -m pytest -s -k array
(lldb) target create "/Users/joe/git/algorandfoundation/algokit-core/packages/python/algokit_uti
ls/.venv/bin/python"
Current executable set to '/Users/joe/git/algorandfoundation/algokit-core/packages/python/algoki
t_utils/.venv/bin/python' (arm64).
(lldb) settings set -- target.run-args  "-X" "dev" "-m" "pytest" "-s" "-k" "array"
(lldb) run
Process 7124 launched: '/Users/joe/git/algorandfoundation/algokit-core/packages/python/algokit_u
tils/.venv/bin/python' (arm64)
/Users/joe/git/algorandfoundation/algokit-core/packages/python/algokit_utils/.venv/lib/python3.1
3/site-packages/pytest_asyncio/plugin.py:211: PytestDeprecationWarning: The configuration option
 "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future
 versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function sc
ope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the 
future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
============================= test session starts ==============================
platform darwin -- Python 3.13.2, pytest-8.4.1, pluggy-1.6.0
rootdir: /Users/joe/git/algorandfoundation/algokit-core/packages/python/algokit_utils
configfile: pyproject.toml
plugins: asyncio-1.1.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_sc
ope=function
collected 3 items / 2 deselected / 1 selected                                  

tests/test_utils.py DEBUGPRINT[43]: dynamic_array.rs:34 (before let cloned = self.clone();)
Process 7124 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x0000000102d3e37c libalgokit_utils_ffi.dylib`_$LT$algokit_utils_ffi..abi..abi_typ
e..dynamic_array..ABIDynamicArray$u20$as$u20$algokit_utils_ffi..abi..abi_type..FfiToRustABIType$
GT$::to_rust_abi_type::h7cd0d5bd89393e60 + 68
libalgokit_utils_ffi.dylib`_$LT$algokit_utils_ffi..abi..abi_type..dynamic_array..ABIDynamicArray
$u20$as$u20$algokit_utils_ffi..abi..abi_type..FfiToRustABIType$GT$::to_rust_abi_type::h7cd0d5bd8
9393e60:
->  0x102d3e37c <+68>: ldadd  x22, x8, [x20]
    0x102d3e380 <+72>: tbnz   x8, #0x3f, 0x102d3e3d0 ; <+152>
    0x102d3e384 <+76>: ldr    x21, [x21, #0x8]
    0x102d3e388 <+80>: stp    x20, x21, [sp]
Target 0: (python) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  * frame #0: 0x0000000102d3e37c libalgokit_utils_ffi.dylib`_$LT$algokit_utils_ffi..abi..abi_typ
e..dynamic_array..ABIDynamicArray$u20$as$u20$algokit_utils_ffi..abi..abi_type..FfiToRustABIType$
GT$::to_rust_abi_type::h7cd0d5bd89393e60 + 68
    frame #1: 0x0000000102d3edec libalgokit_utils_ffi.dylib`_$LT$algokit_utils_ffi..abi..abi_typ
e..dynamic_array..ABIDynamicArray$u20$as$u20$algokit_utils_ffi..abi..abi_type..ABIType$GT$::enco
de::h000991ce3b96c180 + 36
    frame #2: 0x0000000102d22628 libalgokit_utils_ffi.dylib`uniffi_core::ffi::rustcalls::rust_ca
ll::h521bb2588921a417 + 412
    frame #3: 0x0000000102d3f018 libalgokit_utils_ffi.dylib`uniffi_algokit_utils_ffi_fn_method_a
bidynamicarray_encode + 44
    frame #4: 0x00000001016c0050 libpython3.13.dylib`ffi_call_SYSV + 80
    frame #5: 0x00000001016beae4 libpython3.13.dylib`ffi_call_int + 1452
    frame #6: 0x00000001016be52c libpython3.13.dylib`ffi_call + 52
    frame #7: 0x0000000101d27620 libpython3.13.dylib`_call_function_pointer + 248
    frame #8: 0x0000000101d27194 libpython3.13.dylib`_ctypes_callproc + 724
    frame #9: 0x0000000101d24768 libpython3.13.dylib`PyCFuncPtr_call + 332
    frame #10: 0x000000010137760c libpython3.13.dylib`_PyEval_EvalFrameDefault + 55044
    frame #11: 0x0000000101466d84 libpython3.13.dylib`_PyObject_Call_Prepend + 296
    frame #12: 0x0000000101466798 libpython3.13.dylib`slot_tp_call + 216
    frame #13: 0x000000010137985c libpython3.13.dylib`_PyEval_EvalFrameDefault + 63828
    frame #14: 0x0000000101466d84 libpython3.13.dylib`_PyObject_Call_Prepend + 296
    frame #15: 0x0000000101466798 libpython3.13.dylib`slot_tp_call + 216
    frame #16: 0x000000010137760c libpython3.13.dylib`_PyEval_EvalFrameDefault + 55044
    frame #17: 0x0000000101466d84 libpython3.13.dylib`_PyObject_Call_Prepend + 296
    frame #18: 0x0000000101466798 libpython3.13.dylib`slot_tp_call + 216
    frame #19: 0x000000010137985c libpython3.13.dylib`_PyEval_EvalFrameDefault + 63828
    frame #20: 0x0000000101466d84 libpython3.13.dylib`_PyObject_Call_Prepend + 296
    frame #21: 0x0000000101466798 libpython3.13.dylib`slot_tp_call + 216
    frame #22: 0x000000010137985c libpython3.13.dylib`_PyEval_EvalFrameDefault + 63828
    frame #23: 0x0000000101466d84 libpython3.13.dylib`_PyObject_Call_Prepend + 296
    frame #24: 0x0000000101466798 libpython3.13.dylib`slot_tp_call + 216
    frame #25: 0x000000010137985c libpython3.13.dylib`_PyEval_EvalFrameDefault + 63828
    frame #26: 0x00000001014640c8 libpython3.13.dylib`PyEval_EvalCode + 132
    frame #27: 0x0000000101463bd8 libpython3.13.dylib`builtin_exec + 388
    frame #28: 0x0000000101600160 libpython3.13.dylib`cfunction_vectorcall_FASTCALL_KEYWORDS.llv
m.12452703498012727648 + 88
    frame #29: 0x0000000101374908 libpython3.13.dylib`_PyEval_EvalFrameDefault + 43520
    frame #30: 0x000000010154edcc libpython3.13.dylib`pymain_run_module + 232
    frame #31: 0x000000010154dfc4 libpython3.13.dylib`Py_RunMain + 488
    frame #32: 0x00000001014a758c libpython3.13.dylib`pymain_main + 468
    frame #33: 0x00000001014a73ac libpython3.13.dylib`Py_BytesMain + 36
    frame #34: 0x0000000190582b98 dyld`start + 6076
(lldb) 

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions