Skip to content

Commit c2757eb

Browse files
committed
Throw error when objects are passed instead of traits (#2649)
The corner case here is when an object implements a Rust-only trait. In the updated coverall fixture we have the following Python classes: - StringUtilProtocol - interface for the trait - StringUtil - object that implements the trait via rust calls - StringUtilObject - exported Rust object that imprements the trait Before `StringUtilObject` would inherit from `StringUtil`, which isn't correct conceptually and in practice it means that the Python code will try to lower the handle into a function that expects `Arc<dyn StructUtil>` when the handle was actually for `Arc<StringUtilObject>`, once Rust tries to use the arc a segfault will happen soon after. Now `StringUtilObject` inherits from `StringUtilProtocol` and we don't have the segfault. I tried to add Kotlin and Swift tests here, but it doesn't work because they throw a type error as soon as you try making that call.
1 parent 15738c7 commit c2757eb

File tree

7 files changed

+61
-5
lines changed

7 files changed

+61
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
* Methods now have typing annotations for return values ([#2625](https://github.com/mozilla/uniffi-rs/issues/2625))
3333
* Fix relative imports ([#2657](https://github.com/mozilla/uniffi-rs/pull/2657))
3434
* Fix shadowing param names with internal variables in Python (#2628/#2645)
35+
* Don't allow objects to be passed as arguments when traits are expected (#2649)
3536
- Swift:
3637
* All object protocol conformances are public ([#2671](https://github.com/mozilla/uniffi-rs/pull/2671))
3738
* Initialization functions now have a stable ordering when using external types.

fixtures/coverall/src/traits.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,33 @@ impl StringUtil for StringUtilImpl2 {
261261
format!("{a}{b}")
262262
}
263263
}
264+
265+
/// Object that implements `StringUtil`
266+
/// This lets us test what happens if foreign bindings pass in `StringUtilImpl` when `StringUtil` was expected.
267+
/// Ideally, languages should coerce the object to it's trait, however that's not supported at this time.
268+
/// The current test is simply that this doesn't cause a segfault (#2649)
269+
#[derive(uniffi::Object)]
270+
pub struct StringUtilObject {
271+
separator: String,
272+
}
273+
274+
#[uniffi::export]
275+
impl StringUtilObject {
276+
#[uniffi::constructor]
277+
pub fn new(separator: String) -> Self {
278+
Self { separator }
279+
}
280+
}
281+
282+
#[uniffi::export]
283+
impl StringUtil for StringUtilObject {
284+
fn concat(&self, a: &str, b: &str) -> String {
285+
let separator = &self.separator;
286+
format!("{a}{separator}{b}")
287+
}
288+
}
289+
290+
#[uniffi::export]
291+
pub fn concat_with_string_util(string_util: Arc<dyn StringUtil>, a: &str, b: &str) -> String {
292+
string_util.concat(a, b)
293+
}

fixtures/coverall/tests/bindings/test_coverall.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,17 @@ def test_rust_only_traits(self):
502502
self.assertEqual(traits[0].concat("cow", "boy"), "cowboy")
503503
self.assertEqual(traits[1].concat("cow", "boy"), "cowboy")
504504

505+
def test_pass_object_to_function_that_input_trait(self):
506+
string_util_obj = StringUtilObject("--")
507+
508+
# StringUtilObject implements StringUtil.
509+
# We currently don't support passing a `StringUtilObject` to a function that inputs a `dyn
510+
# StringUtil`.
511+
# This test checks that the result is a TypeError rather than a segfault (#2649)
512+
513+
with self.assertRaises(TypeError):
514+
concat_with_string_util(string_util_obj, "cow", "boy"),
515+
505516
def test_html_error(self):
506517
with self.assertRaises(HtmlError):
507518
validate_html("test")

fixtures/proc-macro/tests/bindings/test_proc_macro.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
assert trait_impl.concat_strings("foo", "bar") == "foobar"
3131
assert obj.get_trait(trait_impl).concat_strings("foo", "bar") == "foobar"
3232
assert concat_strings_by_ref(trait_impl, "foo", "bar") == "foobar"
33-
assert issubclass(StructWithTrait, Trait)
3433
assert StructWithTrait("me").concat_strings("foo", "bar") == "me: foobar"
3534

3635
trait_impl2 = obj.get_trait_with_foreign(None)

uniffi_bindgen/src/bindings/python/pipeline/interfaces.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,25 @@ pub fn pass(int: &mut Interface) -> Result<()> {
4343
Type::Interface {
4444
name,
4545
external_package_name,
46+
imp,
4647
..
47-
} => (name, external_package_name),
48+
} => {
49+
// For trait interfaces implement in Rust-only, the protocol has `Protocol` appended.
50+
// Trait interfaces with foreign implementations don't have that
51+
match imp {
52+
ObjectImpl::Trait => (format!("{name}Protocol"), external_package_name),
53+
ObjectImpl::CallbackTrait => (name.to_string(), external_package_name),
54+
ObjectImpl::Struct => {
55+
bail!("Objects can only inherit from traits, not other objects")
56+
}
57+
}
58+
}
59+
4860
Type::CallbackInterface {
4961
name,
5062
external_package_name,
5163
..
52-
} => (name, external_package_name),
64+
} => (name.to_string(), external_package_name),
5365
_ => bail!("trait_ty {:?} isn't a trait", t),
5466
};
5567
let fq = match external_package_name {

uniffi_bindgen/src/bindings/python/pipeline/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ pub fn pipeline() -> Pipeline<initial::Root, Root> {
2828
.pass(config::pass)
2929
.pass(external_types::pass)
3030
.pass(names::pass)
31-
.pass(modules::pass)
3231
.pass(interfaces::pass)
32+
.pass(modules::pass)
3333
.pass(callback_interfaces::pass)
3434
.pass(types::pass)
3535
.pass(default::pass)

uniffi_bindgen/src/bindings/python/pipeline/modules.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ pub fn pass(root: &mut Root) -> Result<()> {
3333
namespace.visit(|e: &Enum| exported_names.push(e.name.clone()));
3434
namespace.visit(|r: &Record| exported_names.push(r.name.clone()));
3535
namespace.visit(|f: &Function| exported_names.push(f.callable.name.clone()));
36-
namespace.visit(|i: &Interface| exported_names.push(i.name.clone()));
36+
namespace.visit(|i: &Interface| {
37+
exported_names.push(i.name.clone());
38+
exported_names.push(i.protocol.name.clone());
39+
});
3740
namespace.visit(|c: &CallbackInterface| exported_names.push(c.name.clone()));
3841
namespace.exported_names = exported_names;
3942

0 commit comments

Comments
 (0)