Skip to content

Commit

Permalink
Add generated trait methods
Browse files Browse the repository at this point in the history
They are implemented as explicit funtions, in addition deleted defaulted asignment operators
and copy constructors, as they allowed illegal construction of objects leading to double frees

Signed-off-by: Martynas Gurskas <[email protected]>
  • Loading branch information
Lipt0nas committed Nov 30, 2023
1 parent f47dc47 commit 64f7e03
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 5 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bindgen/src/bindings/cpp/gen_cpp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use askama::Template;
use serde::{Deserialize, Serialize};
use topological_sort::TopologicalSort;
use uniffi_bindgen::{
interface::{AsType, Type},
interface::{AsType, Type, UniffiTrait},
BindingsConfig, ComponentInterface,
};

Expand Down
25 changes: 25 additions & 0 deletions bindgen/src/bindings/cpp/templates/obj.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,31 @@ namespace {{ namespace }} {
);
}

{% for method in obj.uniffi_traits() %}
{% match method %}
{%- when UniffiTrait::Display { fmt } %}
std::string {{ canonical_type_name }}::to_string() const {
return {{ namespace }}::uniffi::{{ Type::String.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->instance", fmt) %});
}
{%- when UniffiTrait::Debug { fmt } %}
std::string {{ canonical_type_name }}::to_debug_string() const {
return {{ namespace }}::uniffi::{{ Type::String.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->instance", fmt) %});
}
{%- when UniffiTrait::Eq { eq, ne } %}
bool {{ canonical_type_name }}::eq(const {{ type_name }} &other) const {
return {{ namespace }}::uniffi::{{ Type::Boolean.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->instance", eq, "other.instance") %});
}

bool {{ canonical_type_name }}::ne(const {{ type_name }} &other) const {
return {{ namespace }}::uniffi::{{ Type::Boolean.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->instance", ne, "other.instance") %});
}
{% when UniffiTrait::Hash { hash } %}
uint64_t {{ canonical_type_name }}::hash() const {
return {{ namespace }}::uniffi::{{ Type::UInt64.borrow()|lift_fn }}({% call macros::rust_call_with_prefix("this->instance", hash) %});
}
{% endmatch %}
{% endfor %}

{{ type_name }} uniffi::{{ ffi_converter_name }}::lift(void *ptr) {
return {{ type_name }}(new {{ canonical_type_name }}(ptr));
}
Expand Down
39 changes: 35 additions & 4 deletions bindgen/src/bindings/cpp/templates/obj.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ namespace uniffi { struct {{ ffi_converter_name|class_name }}; }
struct {{ canonical_type_name }} {
friend uniffi::{{ ffi_converter_name|class_name }};

{{ canonical_type_name }}() = delete;

{{ canonical_type_name }}(const {{ canonical_type_name }} &) = default;
{{ canonical_type_name }}({{ canonical_type_name }} &&) = default;
{{ canonical_type_name }}({{ canonical_type_name }} &&) = delete;

{{ canonical_type_name }} &operator=(const {{ canonical_type_name }} &) = default;
{{ canonical_type_name }} &operator=({{ canonical_type_name }} &&) = default;
{{ canonical_type_name }} &operator=(const {{ canonical_type_name }} &) = delete;
{{ canonical_type_name }} &operator=({{ canonical_type_name }} &&) = delete;

{% match obj.primary_constructor() %}
{%- when Some with (ctor) -%}
Expand All @@ -36,9 +38,38 @@ struct {{ canonical_type_name }} {
{{- method.name()|fn_name }}({% call macros::param_list(method) %});
{% endfor %}

{% for method in obj.uniffi_traits() %}
{% match method %}
{%- when UniffiTrait::Display { fmt } %}
/**
* Returns a string representation of the object, internally calls Rust's `Display` trait.
*/
std::string to_string() const;
{%- when UniffiTrait::Debug { fmt } %}
/**
* Returns a string representation of the object, internally calls Rust's `Debug` trait.
*/
std::string to_debug_string() const;
{%- when UniffiTrait::Eq { eq, ne } %}
/**
* Equality check, internally calls Rust's `Eq` trait.
*/
bool eq(const {{ type_name }} &other) const;

/**
* Inequality check, internally calls Rust's `Eq` trait.
*/
bool ne(const {{ type_name }} &other) const;
{% when UniffiTrait::Hash { hash } %}
/**
* Returns a hash of the object, internally calls Rust's `Hash` trait.
*/
uint64_t hash() const;
{% endmatch %}
{% endfor %}

private:
{{ canonical_type_name }}(void *);
{{ canonical_type_name }}() = delete;

void *instance;
};
1 change: 1 addition & 0 deletions cpp-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ test_case(todolist)
test_case(traits)
test_case(coverall)
test_case(uniffi_docstring)
test_case(trait_methods)

add_custom_target(libs ALL
BYPRODUCTS ${BINDING_FILES}
Expand Down
74 changes: 74 additions & 0 deletions cpp-tests/tests/trait_methods/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "test_common.hpp"

#include <trait_methods.hpp>

void test_trait_methods() {
auto trait = trait_methods::TraitMethods::init("trait1");
ASSERT_EQ(trait->to_string(), "TraitMethods(trait1)");
ASSERT_EQ(trait->to_debug_string(), "TraitMethods { val: \"trait1\" }");
ASSERT_EQ(trait->hash(), 8148112548604738188);

auto ptr_copy = trait;
auto trait_copy = trait_methods::TraitMethods::init("trait1");
ASSERT_EQ(trait->to_string(), trait_copy->to_string());
ASSERT_EQ(trait->to_debug_string(), trait_copy->to_debug_string());
ASSERT_EQ(trait->hash(), trait_copy->hash());

// Two different shared ptr's should differ
ASSERT_NE(trait, trait_copy);

// Two shared ptr's pointing to the same object should be equal
ASSERT_EQ(trait, ptr_copy);

// Internal equality check should work
ASSERT_TRUE(trait->eq(trait_copy));
ASSERT_FALSE(trait->ne(trait_copy));

auto trait2 = trait_methods::TraitMethods::init("trait2");
ASSERT_NE(trait, trait2);
ASSERT_FALSE(trait->eq(trait2));
ASSERT_TRUE(trait->ne(trait2));

ASSERT_NE(trait->hash(), trait2->hash());
ASSERT_NE(trait->to_string(), trait2->to_string());
ASSERT_NE(trait->to_debug_string(), trait2->to_debug_string());
}

void test_proc_methods() {
auto trait = trait_methods::ProcTraitMethods::init("trait1");
ASSERT_EQ(trait->to_string(), "ProcTraitMethods(trait1)");
ASSERT_EQ(trait->to_debug_string(), "ProcTraitMethods { val: \"trait1\" }");
ASSERT_EQ(trait->hash(), 8148112548604738188);

auto ptr_copy = trait;
auto trait_copy = trait_methods::ProcTraitMethods::init("trait1");
ASSERT_EQ(trait->to_string(), trait_copy->to_string());
ASSERT_EQ(trait->to_debug_string(), trait_copy->to_debug_string());
ASSERT_EQ(trait->hash(), trait_copy->hash());

// Two different shared ptr's should differ
ASSERT_NE(trait, trait_copy);

// Two shared ptr's pointing to the same object should be equal
ASSERT_EQ(trait, ptr_copy);

// Internal equality check should work
ASSERT_TRUE(trait->eq(trait_copy));
ASSERT_FALSE(trait->ne(trait_copy));

auto trait2 = trait_methods::ProcTraitMethods::init("trait2");
ASSERT_NE(trait, trait2);
ASSERT_FALSE(trait->eq(trait2));
ASSERT_TRUE(trait->ne(trait2));

ASSERT_NE(trait->hash(), trait2->hash());
ASSERT_NE(trait->to_string(), trait2->to_string());
ASSERT_NE(trait->to_debug_string(), trait2->to_debug_string());
}

int main() {
test_trait_methods();
test_proc_methods();

return 0;
}

0 comments on commit 64f7e03

Please sign in to comment.