diff --git a/fixtures/coverall/src/coverall.udl b/fixtures/coverall/src/coverall.udl index 6b1c2d304b..713662b612 100644 --- a/fixtures/coverall/src/coverall.udl +++ b/fixtures/coverall/src/coverall.udl @@ -22,6 +22,9 @@ namespace coverall { void test_getters(Getters g); sequence ancestor_names(NodeTrait node); + + Getters test_round_trip_through_rust(Getters getters); + void test_round_trip_through_foreign(Getters getters); }; dictionary SimpleDict { @@ -207,6 +210,7 @@ interface Getters { string? get_option(string v, boolean arg2); sequence get_list(sequence v, boolean arg2); void get_nothing(string v); + Coveralls round_trip_object(Coveralls coveralls); }; // Test trait #2 diff --git a/fixtures/coverall/src/lib.rs b/fixtures/coverall/src/lib.rs index 82b7236cc9..fed578a9f0 100644 --- a/fixtures/coverall/src/lib.rs +++ b/fixtures/coverall/src/lib.rs @@ -10,7 +10,10 @@ use std::time::SystemTime; use once_cell::sync::Lazy; mod traits; -pub use traits::{ancestor_names, get_traits, make_rust_getters, test_getters, Getters, NodeTrait}; +pub use traits::{ + ancestor_names, get_traits, make_rust_getters, test_getters, test_round_trip_through_foreign, + test_round_trip_through_rust, Getters, NodeTrait, +}; static NUM_ALIVE: Lazy> = Lazy::new(|| RwLock::new(0)); diff --git a/fixtures/coverall/src/traits.rs b/fixtures/coverall/src/traits.rs index 15785ef0c6..5ba65694ec 100644 --- a/fixtures/coverall/src/traits.rs +++ b/fixtures/coverall/src/traits.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use super::{ComplexError, CoverallError}; +use super::{ComplexError, CoverallError, Coveralls}; use std::sync::{Arc, Mutex}; // namespace functions. @@ -41,6 +41,16 @@ pub trait Getters: Send + Sync { fn get_option(&self, v: String, arg2: bool) -> Result, ComplexError>; fn get_list(&self, v: Vec, arg2: bool) -> Vec; fn get_nothing(&self, v: String); + fn round_trip_object(&self, coveralls: Arc) -> Arc; +} + +pub fn test_round_trip_through_rust(getters: Arc) -> Arc { + getters +} + +pub fn test_round_trip_through_foreign(getters: Arc) { + let coveralls = getters.round_trip_object(Arc::new(Coveralls::new("round-trip".to_owned()))); + assert_eq!(coveralls.get_name(), "round-trip"); } struct RustGetters; @@ -90,6 +100,10 @@ impl Getters for RustGetters { } fn get_nothing(&self, _v: String) {} + + fn round_trip_object(&self, coveralls: Arc) -> Arc { + coveralls + } } pub fn make_rust_getters() -> Arc { diff --git a/fixtures/coverall/tests/bindings/test_coverall.kts b/fixtures/coverall/tests/bindings/test_coverall.kts index 8bf3b0077b..d46de1f7ec 100644 --- a/fixtures/coverall/tests/bindings/test_coverall.kts +++ b/fixtures/coverall/tests/bindings/test_coverall.kts @@ -257,6 +257,10 @@ class KotlinGetters : Getters { @Suppress("UNUSED_PARAMETER") override fun getNothing(v: String) = Unit + + override fun roundTripObject(coveralls: Coveralls): Coveralls { + return coveralls + } } // Test traits implemented in Rust @@ -372,6 +376,13 @@ getTraits().let { traits -> // not possible through the `NodeTrait` interface (see #1787). } +makeRustGetters().let { rustGetters -> + // Check that these don't cause use-after-free bugs + testRoundTripThroughRust(rustGetters) + + testRoundTripThroughForeign(KotlinGetters()) +} + // This tests that the UniFFI-generated scaffolding doesn't introduce any unexpected locking. // We have one thread busy-wait for a some period of time, while a second thread repeatedly // increments the counter and then checks if the object is still busy. The second thread should diff --git a/fixtures/coverall/tests/bindings/test_coverall.py b/fixtures/coverall/tests/bindings/test_coverall.py index 17593bc833..33375cbfeb 100644 --- a/fixtures/coverall/tests/bindings/test_coverall.py +++ b/fixtures/coverall/tests/bindings/test_coverall.py @@ -314,6 +314,9 @@ def get_list(self, v, arg2): def get_nothing(self, _v): return None + def round_trip_object(self, coveralls): + return coveralls + class PyNode: def __init__(self): self.parent = None @@ -413,5 +416,13 @@ def test_path(self): py_node.set_parent(None) traits[0].set_parent(None) + def test_round_tripping(self): + rust_getters = make_rust_getters(); + coveralls = Coveralls("test_round_tripping") + # Check that these don't cause use-after-free bugs + test_round_trip_through_rust(rust_getters) + + test_round_trip_through_foreign(PyGetters()) + if __name__=='__main__': unittest.main() diff --git a/fixtures/coverall/tests/bindings/test_coverall.swift b/fixtures/coverall/tests/bindings/test_coverall.swift index c6fcba4290..6508ed331d 100644 --- a/fixtures/coverall/tests/bindings/test_coverall.swift +++ b/fixtures/coverall/tests/bindings/test_coverall.swift @@ -283,6 +283,10 @@ class SwiftGetters: Getters { func getList(v: [Int32], arg2: Bool) -> [Int32] { arg2 ? v : [] } func getNothing(v: String) -> () { } + + func roundTripObject(coveralls: Coveralls) -> Coveralls { + return coveralls + } } @@ -412,3 +416,12 @@ do { swiftNode.setParent(parent: nil) traits[0].setParent(parent: nil) } + +// Test round tripping +do { + let rustGetters = makeRustGetters() + // Check that these don't cause use-after-free bugs + let _ = testRoundTripThroughRust(getters: rustGetters) + + testRoundTripThroughForeign(getters: SwiftGetters()) +} diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt index 48f7e9ec77..d9a9aafcdc 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt @@ -132,6 +132,12 @@ abstract class FFIObject( // To be overridden in subclasses. } + fun uniffiCloneHandle(): UniffiHandle { + return rustCall() { status -> + _UniFFILib.INSTANCE.{{ obj.ffi_object_clone().name() }}(handle, status) + } + } + override fun destroy() { // Only allow a single call to this method. // TODO: maybe we should log a warning if called more than once? @@ -162,7 +168,7 @@ abstract class FFIObject( } while (! this.callCounter.compareAndSet(c, c + 1L)) // Now we can safely do the method call without the handle being freed concurrently. try { - return block(this.handle) + return block(this.uniffiCloneHandle()) } finally { // This decrement always matches the increment we performed above. if (this.callCounter.decrementAndGet() == 0L) { diff --git a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt index 38b3d970c6..58c485f13d 100644 --- a/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt +++ b/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt @@ -109,7 +109,7 @@ public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Uniffi override fun lower(value: {{ type_name }}): UniffiHandle { {%- match obj.imp() %} {%- when ObjectImpl::Struct %} - return value.handle + return value.uniffiCloneHandle() {%- when ObjectImpl::Trait %} return handleMap.newHandle(value) {%- endmatch %} diff --git a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py index c3e30d5be7..48b20688bb 100644 --- a/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py +++ b/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py @@ -21,6 +21,9 @@ def __del__(self): if handle is not None: _rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, handle) + def uniffi_clone_handle(self): + return _rust_call(_UniffiLib.{{ obj.ffi_object_clone().name() }}, self._uniffi_handle) + # Used by alternative constructors or any methods which return this type. @classmethod def _make_instance_(cls, handle): @@ -54,13 +57,13 @@ def __eq__(self, other: object) -> {{ eq.return_type().unwrap()|type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_handle", eq) %}) + return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self.uniffi_clone_handle()", eq) %}) def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}: if not isinstance(other, {{ type_name }}): return NotImplemented - return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_handle", ne) %}) + return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self.uniffi_clone_handle()", ne) %}) {%- when UniffiTrait::Hash { hash } %} {%- call py::method_decl("__hash__", hash) %} {% endmatch %} @@ -95,9 +98,7 @@ def check(value: {{ type_name }}): def lower(value: {{ type_name }}): {%- match obj.imp() %} {%- when ObjectImpl::Struct %} - if not isinstance(value, {{ impl_name }}): - raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__)) - return value._uniffi_handle + return value.uniffi_clone_handle() {%- when ObjectImpl::Trait %} return {{ ffi_converter_name }}._handle_map.new_handle(value) {%- endmatch %} diff --git a/uniffi_bindgen/src/bindings/python/templates/macros.py b/uniffi_bindgen/src/bindings/python/templates/macros.py index 1fddb2df83..94562aaf5b 100644 --- a/uniffi_bindgen/src/bindings/python/templates/macros.py +++ b/uniffi_bindgen/src/bindings/python/templates/macros.py @@ -106,7 +106,7 @@ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): {%- call setup_args_extra_indent(meth) %} return _uniffi_rust_call_async( _UniffiLib.{{ meth.ffi_func().name() }}( - self._uniffi_handle, {% call arg_list_lowered(meth) %} + self.uniffi_clone_handle(), {% call arg_list_lowered(meth) %} ), _UniffiLib.{{ meth.ffi_rust_future_poll(ci) }}, _UniffiLib.{{ meth.ffi_rust_future_complete(ci) }}, @@ -135,14 +135,14 @@ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}": {%- call setup_args_extra_indent(meth) %} return {{ return_type|lift_fn }}( - {% call to_ffi_call_with_prefix("self._uniffi_handle", meth) %} + {% call to_ffi_call_with_prefix("self.uniffi_clone_handle()", meth) %} ) {%- when None %} def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}): {%- call setup_args_extra_indent(meth) %} - {% call to_ffi_call_with_prefix("self._uniffi_handle", meth) %} + {% call to_ffi_call_with_prefix("self.uniffi_clone_handle()", meth) %} {% endmatch %} {% endif %} diff --git a/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb b/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb index b63c1e21ca..efeea1362d 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb @@ -32,7 +32,14 @@ def self._uniffi_check(inst) end def self._uniffi_lower(inst) - return inst.instance_variable_get :@handle + return inst._uniffi_clone_handle() + end + + def _uniffi_clone_handle() + return {{ ci.namespace()|class_name_rb }}.rust_call( + :{{ obj.ffi_object_clone().name() }}, + @handle + ) end {%- match obj.primary_constructor() %} @@ -62,14 +69,14 @@ def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %}) {%- when Some with (return_type) -%} def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) {%- call rb::setup_args_extra_indent(meth) %} - result = {% call rb::to_ffi_call_with_prefix("@handle", meth) %} + result = {% call rb::to_ffi_call_with_prefix("_uniffi_clone_handle()", meth) %} return {{ "result"|lift_rb(return_type) }} end {%- when None -%} def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %}) {%- call rb::setup_args_extra_indent(meth) %} - {% call rb::to_ffi_call_with_prefix("@handle", meth) %} + {% call rb::to_ffi_call_with_prefix("_uniffi_clone_handle()", meth) %} end {% endmatch %} {% endfor %} diff --git a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift index 7be8e34d7f..2fca662c54 100644 --- a/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift +++ b/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift @@ -14,6 +14,10 @@ public class {{ impl_class_name }}: {{ protocol_name }} { self.handle = handle } + public func uniffiCloneHandle() -> UInt64 { + return try! rustCall { {{ obj.ffi_object_clone().name() }}(self.handle, $0) } + } + {%- match obj.primary_constructor() %} {%- when Some with (cons) %} public convenience init({% call swift::arg_list_decl(cons) -%}) {% call swift::throws(cons) %} { @@ -42,7 +46,7 @@ public class {{ impl_class_name }}: {{ protocol_name }} { return {% call swift::try(meth) %} await uniffiRustCallAsync( rustFutureFunc: { {{ meth.ffi_func().name() }}( - self.handle + self.uniffiCloneHandle() {%- for arg in meth.arguments() -%} , {{ arg|lower_fn }}({{ arg.name()|var_name }}) @@ -75,14 +79,14 @@ public class {{ impl_class_name }}: {{ protocol_name }} { public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} -> {{ return_type|type_name }} { return {% call swift::try(meth) %} {{ return_type|lift_fn }}( - {% call swift::to_ffi_call_with_prefix("self.handle", meth) %} + {% call swift::to_ffi_call_with_prefix("self.uniffiCloneHandle()", meth) %} ) } {%- when None %} public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} { - {% call swift::to_ffi_call_with_prefix("self.handle", meth) %} + {% call swift::to_ffi_call_with_prefix("self.uniffiCloneHandle()", meth) %} } {%- endmatch -%} @@ -112,7 +116,7 @@ public struct {{ ffi_converter_name }}: FfiConverter { public static func lower(_ value: {{ type_name }}) -> UInt64 { {%- match obj.imp() %} {%- when ObjectImpl::Struct %} - return value.handle + return value.uniffiCloneHandle() {%- when ObjectImpl::Trait %} return handleMap.newHandle(obj: value) {%- endmatch %} diff --git a/uniffi_bindgen/src/interface/ffi.rs b/uniffi_bindgen/src/interface/ffi.rs index de8db86334..9c9e183dd4 100644 --- a/uniffi_bindgen/src/interface/ffi.rs +++ b/uniffi_bindgen/src/interface/ffi.rs @@ -141,6 +141,29 @@ pub struct FfiFunction { } impl FfiFunction { + pub fn ffi_clone(name: String) -> Self { + Self { + name, + arguments: vec![FfiArgument { + name: "handle".to_owned(), + type_: FfiType::Handle, + }], + return_type: Some(FfiType::Handle), + ..Default::default() + } + } + + pub fn ffi_free(name: String) -> Self { + Self { + name, + arguments: vec![FfiArgument { + name: "handle".to_owned(), + type_: FfiType::Handle, + }], + ..Default::default() + } + } + pub fn callback_init(module_path: &str, trait_name: &str) -> Self { Self { name: uniffi_meta::init_callback_fn_symbol_name(module_path, trait_name), diff --git a/uniffi_bindgen/src/interface/object.rs b/uniffi_bindgen/src/interface/object.rs index 290e412598..00ee9b2695 100644 --- a/uniffi_bindgen/src/interface/object.rs +++ b/uniffi_bindgen/src/interface/object.rs @@ -57,8 +57,6 @@ //! # Ok::<(), anyhow::Error>(()) //! ``` -use std::iter; - use anyhow::Result; use uniffi_meta::Checksum; @@ -99,6 +97,8 @@ pub struct Object { // hash value we're trying to calculate here, so excluding it // avoids a weird circular dependency in the calculation. #[checksum_ignore] + pub(super) ffi_func_clone: FfiFunction, + #[checksum_ignore] pub(super) ffi_func_free: FfiFunction, // Ffi function to initialize the foreign callback for trait interfaces #[checksum_ignore] @@ -158,6 +158,10 @@ impl Object { self.uniffi_traits.iter().collect() } + pub fn ffi_object_clone(&self) -> &FfiFunction { + &self.ffi_func_clone + } + pub fn ffi_object_free(&self) -> &FfiFunction { &self.ffi_func_free } @@ -169,7 +173,8 @@ impl Object { } pub fn iter_ffi_function_definitions(&self) -> impl Iterator { - iter::once(&self.ffi_func_free) + [&self.ffi_func_clone, &self.ffi_func_free] + .into_iter() .chain(&self.ffi_init_callback) .chain(self.constructors.iter().map(|f| &f.ffi_func)) .chain(self.methods.iter().map(|f| &f.ffi_func)) @@ -236,7 +241,8 @@ impl AsType for Object { impl From for Object { fn from(meta: uniffi_meta::ObjectMetadata) -> Self { - let ffi_free_name = meta.free_ffi_symbol_name(); + let ffi_func_clone = FfiFunction::ffi_clone(meta.clone_ffi_symbol_name()); + let ffi_func_free = FfiFunction::ffi_free(meta.free_ffi_symbol_name()); Object { module_path: meta.module_path, name: meta.name, @@ -244,10 +250,8 @@ impl From for Object { constructors: Default::default(), methods: Default::default(), uniffi_traits: Default::default(), - ffi_func_free: FfiFunction { - name: ffi_free_name, - ..Default::default() - }, + ffi_func_clone, + ffi_func_free, ffi_init_callback: None, } } diff --git a/uniffi_macros/src/export/scaffolding.rs b/uniffi_macros/src/export/scaffolding.rs index 9a677ec3e4..172951f1f9 100644 --- a/uniffi_macros/src/export/scaffolding.rs +++ b/uniffi_macros/src/export/scaffolding.rs @@ -143,14 +143,13 @@ impl ScaffoldingBits { // pointer. quote! { { - let foreign_arc = ::std::boxed::Box::leak(unsafe { Box::from_raw(uniffi_self_lowered as *mut ::std::sync::Arc) }); - // Take a clone for our own use. - Ok(::std::sync::Arc::clone(foreign_arc)) + Ok(>::consume_handle(uniffi_self_lowered)) } } } else { quote! { #lift_impl::try_lift(uniffi_self_lowered) } }; + let lift_closure = sig.lift_closure(Some(quote! { match #try_lift_self { Ok(v) => v, diff --git a/uniffi_macros/src/export/trait_interface.rs b/uniffi_macros/src/export/trait_interface.rs index a537a71349..7d1db542ac 100644 --- a/uniffi_macros/src/export/trait_interface.rs +++ b/uniffi_macros/src/export/trait_interface.rs @@ -13,7 +13,6 @@ use crate::{ object::interface_meta_static_var, util::{derive_ffi_traits, ident_to_string, tagged_impl_header}, }; -use uniffi_meta::free_fn_symbol_name; pub(super) fn gen_trait_scaffolding( mod_path: &str, @@ -28,12 +27,27 @@ pub(super) fn gen_trait_scaffolding( let trait_name = ident_to_string(&self_ident); let trait_impl = callback_interface::trait_impl(mod_path, &self_ident, &items) .unwrap_or_else(|e| e.into_compile_error()); + let clone_fn_ident = Ident::new( + &uniffi_meta::clone_fn_symbol_name(mod_path, &trait_name), + Span::call_site(), + ); let free_fn_ident = Ident::new( - &free_fn_symbol_name(mod_path, &trait_name), + &uniffi_meta::free_fn_symbol_name(mod_path, &trait_name), Span::call_site(), ); let free_tokens = quote! { + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn #clone_fn_ident( + handle: ::uniffi::Handle, + call_status: &mut ::uniffi::RustCallStatus + ) -> ::uniffi::Handle { + uniffi::rust_call(call_status, || { + Ok(>::clone_handle(handle)) + }) + } + #[doc(hidden)] #[no_mangle] pub extern "C" fn #free_fn_ident( diff --git a/uniffi_macros/src/object.rs b/uniffi_macros/src/object.rs index 114183019e..0195f6c7e1 100644 --- a/uniffi_macros/src/object.rs +++ b/uniffi_macros/src/object.rs @@ -1,7 +1,6 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::DeriveInput; -use uniffi_meta::free_fn_symbol_name; use crate::util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}; @@ -9,7 +8,14 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result syn::Result ::uniffi::Handle { + uniffi::rust_call(call_status, || { + Ok(<#ident as ::uniffi::HandleAlloc>::clone_handle(handle)) + }) + } + #[doc(hidden)] #[no_mangle] pub extern "C" fn #free_fn_ident( @@ -69,7 +86,7 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { Some(h) => h, None => ::uniffi::deps::anyhow::bail!("{}::try_lift: null handle", #name), }; - Ok(<#ident as ::uniffi::HandleAlloc>::get_arc(handle)) + Ok(<#ident as ::uniffi::HandleAlloc>::consume_handle(v)) } fn write(obj: ::std::sync::Arc, buf: &mut Vec) { diff --git a/uniffi_meta/src/ffi_names.rs b/uniffi_meta/src/ffi_names.rs index 44a5bc3e63..ebc125410b 100644 --- a/uniffi_meta/src/ffi_names.rs +++ b/uniffi_meta/src/ffi_names.rs @@ -39,6 +39,12 @@ pub fn free_fn_symbol_name(namespace: &str, object_name: &str) -> String { format!("uniffi_{namespace}_fn_free_{object_name}") } +/// FFI symbol name for the `clone` function for an object. +pub fn clone_fn_symbol_name(namespace: &str, object_name: &str) -> String { + let object_name = object_name.to_ascii_lowercase(); + format!("uniffi_{namespace}_fn_clone_{object_name}") +} + /// FFI symbol name for the `init_callback` function for a callback interface pub fn init_callback_fn_symbol_name(namespace: &str, callback_interface_name: &str) -> String { let callback_interface_name = callback_interface_name.to_ascii_lowercase(); diff --git a/uniffi_meta/src/lib.rs b/uniffi_meta/src/lib.rs index 1c8a66801c..c99c142b3f 100644 --- a/uniffi_meta/src/lib.rs +++ b/uniffi_meta/src/lib.rs @@ -319,6 +319,14 @@ pub struct CallbackInterfaceMetadata { } impl ObjectMetadata { + /// FFI symbol name for the `clone` function for this object. + /// + /// This function is used to increment the reference count before lowering an object to pass + /// back to Rust. + pub fn clone_ffi_symbol_name(&self) -> String { + clone_fn_symbol_name(&self.module_path, &self.name) + } + /// FFI symbol name for the `free` function for this object. /// /// This function is used to free the memory used by this object.