Skip to content

Commit d5eb09c

Browse files
committed
Updated lower validation for Python/Ruby
The dynamic languages performed various checks in lower(): type checks, bounds checks, etc. This presented an issue when we wanted to call `inc_ref` for object types. If we did this, then another value failed its check, we would leak the reference. Prevent this by adding a `check()` method. We do the check first then once we get to `lower()`, nothing can fail.
1 parent f5f0b05 commit d5eb09c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+240
-93
lines changed

examples/callbacks/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ impl From<uniffi::UnexpectedUniFFICallbackError> for TelephoneError {
2121
}
2222

2323
// SIM cards.
24+
#[uniffi::trait_interface]
2425
pub trait SimCard: Send + Sync {
2526
fn name(&self) -> String;
2627
}
@@ -39,6 +40,7 @@ fn get_sim_cards() -> Vec<Arc<dyn SimCard>> {
3940
}
4041

4142
// The call-answer, callback interface.
43+
#[uniffi::trait_interface]
4244
pub trait CallAnswerer {
4345
fn answer(&self) -> Result<String, TelephoneError>;
4446
}

examples/sprites/tests/bindings/test_sprites.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ s.destroy()
1616
try {
1717
s.moveBy(Vector(0.0, 0.0))
1818
assert(false) { "Should not be able to call anything after `destroy`" }
19-
} catch(e: IllegalStateException) {
19+
} catch(e: InternalException) {
2020
assert(true)
2121
}
2222

examples/traits/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ fn press(button: Arc<dyn Button>) -> Arc<dyn Button> {
1313
button
1414
}
1515

16+
#[uniffi::trait_interface]
1617
pub trait Button: Send + Sync {
1718
fn name(&self) -> String;
1819
}

fixtures/coverall/tests/bindings/test_coverall.py

+7-10
Original file line numberDiff line numberDiff line change
@@ -242,16 +242,13 @@ def test_return_objects(self):
242242
coveralls = None
243243
self.assertEqual(get_num_alive(), 0)
244244

245-
# FIXME: since we're now inc-refing the handle, Python will leak objects if another lower fails.
246-
# For now, let's disable this test.
247-
#
248-
# def test_bad_objects(self):
249-
# coveralls = Coveralls("test_bad_objects")
250-
# patch = Patch(Color.RED)
251-
# # `coveralls.take_other` wants `Coveralls` not `Patch`
252-
# with self.assertRaisesRegex(TypeError, "Coveralls.*Patch"):
253-
# coveralls.take_other(patch)
254-
#
245+
def test_bad_objects(self):
246+
coveralls = Coveralls("test_bad_objects")
247+
patch = Patch(Color.RED)
248+
# `coveralls.take_other` wants `Coveralls` not `Patch`
249+
with self.assertRaisesRegex(TypeError, "Coveralls.*Patch"):
250+
coveralls.take_other(patch)
251+
255252
def test_dict_with_defaults(self):
256253
""" This does not call Rust code. """
257254

fixtures/coverall/tests/bindings/test_coverall.rb

+8-11
Original file line numberDiff line numberDiff line change
@@ -244,17 +244,14 @@ def test_return_objects
244244
assert_equal Coverall.get_num_alive(), 0
245245
end
246246

247-
# FIXME: since we're now inc-refing the handle, Ruby will leak objects if another lower fails.
248-
# For now, let's disable this test.
249-
#
250-
# def test_bad_objects
251-
# coveralls = Coverall::Coveralls.new "test_bad_objects"
252-
# patch = Coverall::Patch.new Coverall::Color::RED
253-
# # `coveralls.take_other` wants `Coveralls` not `Patch`
254-
# assert_raise_message /Expected a Coveralls instance, got.*Patch/ do
255-
# coveralls.take_other patch
256-
# end
257-
# end
247+
def test_bad_objects
248+
coveralls = Coverall::Coveralls.new "test_bad_objects"
249+
patch = Coverall::Patch.new Coverall::Color::RED
250+
# `coveralls.take_other` wants `Coveralls` not `Patch`
251+
assert_raise_message /Expected a Coveralls instance, got.*Patch/ do
252+
coveralls.take_other patch
253+
end
254+
end
258255

259256
def test_bytes
260257
coveralls = Coverall::Coveralls.new "test_bytes"

fixtures/reexport-scaffolding-macro/src/lib.rs

+18-13
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ mod tests {
66
use cargo_metadata::Message;
77
use libloading::{Library, Symbol};
88
use std::ffi::CString;
9-
use std::os::raw::c_void;
109
use std::process::{Command, Stdio};
11-
use uniffi::{FfiConverter, ForeignCallback, RustBuffer, RustCallStatus, RustCallStatusCode};
10+
use uniffi::{
11+
FfiConverter, ForeignCallback, Handle, RustBuffer, RustCallStatus, RustCallStatusCode,
12+
};
1213
use uniffi_bindgen::ComponentInterface;
1314

1415
struct UniFfiTag;
@@ -149,34 +150,38 @@ mod tests {
149150
.ffi_func()
150151
.name(),
151152
);
152-
let coveralls_new: Symbol<
153-
unsafe extern "C" fn(RustBuffer, &mut RustCallStatus) -> *const c_void,
154-
> = get_symbol(
155-
&library,
156-
object_def.primary_constructor().unwrap().ffi_func().name(),
157-
);
153+
let coveralls_new: Symbol<unsafe extern "C" fn(RustBuffer, &mut RustCallStatus) -> Handle> =
154+
get_symbol(
155+
&library,
156+
object_def.primary_constructor().unwrap().ffi_func().name(),
157+
);
158158
let coveralls_get_name: Symbol<
159-
unsafe extern "C" fn(*const c_void, &mut RustCallStatus) -> RustBuffer,
159+
unsafe extern "C" fn(Handle, &mut RustCallStatus) -> RustBuffer,
160160
> = get_symbol(
161161
&library,
162162
object_def.get_method("get_name").ffi_func().name(),
163163
);
164-
let coveralls_free: Symbol<unsafe extern "C" fn(*const c_void, &mut RustCallStatus) -> ()> =
164+
let coveralls_inc_ref: Symbol<unsafe extern "C" fn(Handle, &mut RustCallStatus) -> ()> =
165+
get_symbol(&library, object_def.ffi_object_inc_ref().name());
166+
let coveralls_free: Symbol<unsafe extern "C" fn(Handle, &mut RustCallStatus) -> ()> =
165167
get_symbol(&library, object_def.ffi_object_free().name());
166168

167169
let num_alive = unsafe { get_num_alive(&mut call_status) };
168170
assert_eq!(call_status.code, RustCallStatusCode::Success);
169171
assert_eq!(num_alive, 0);
170172

171-
let obj_id = unsafe {
173+
let obj_handle = unsafe {
172174
coveralls_new(
173175
<String as FfiConverter<UniFfiTag>>::lower("TestName".into()),
174176
&mut call_status,
175177
)
176178
};
177179
assert_eq!(call_status.code, RustCallStatusCode::Success);
178180

179-
let name_buf = unsafe { coveralls_get_name(obj_id, &mut call_status) };
181+
unsafe { coveralls_inc_ref(obj_handle, &mut call_status) };
182+
assert_eq!(call_status.code, RustCallStatusCode::Success);
183+
184+
let name_buf = unsafe { coveralls_get_name(obj_handle, &mut call_status) };
180185
assert_eq!(call_status.code, RustCallStatusCode::Success);
181186
assert_eq!(
182187
<String as FfiConverter<UniFfiTag>>::try_lift(name_buf).unwrap(),
@@ -187,7 +192,7 @@ mod tests {
187192
assert_eq!(call_status.code, RustCallStatusCode::Success);
188193
assert_eq!(num_alive, 1);
189194

190-
unsafe { coveralls_free(obj_id, &mut call_status) };
195+
unsafe { coveralls_free(obj_handle, &mut call_status) };
191196
assert_eq!(call_status.code, RustCallStatusCode::Success);
192197

193198
let num_alive = unsafe { get_num_alive(&mut call_status) };

fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ uniffi_macros::generate_and_include_scaffolding!("../../../../fixtures/uitests/s
44
fn main() { /* empty main required by `trybuild` */}
55

66
// This will fail to compile, because the trait is not explicit Send+Sync
7+
#[uniffi::trait_interface]
78
pub trait Trait {
89
}
910

fixtures/uitests/tests/ui/interface_trait_not_sync_and_send.stderr

+4-4
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ note: required by a bound in `FfiConverterArc`
2727
= note: this error originates in the attribute macro `::uniffi::export_for_udl` (in Nightly builds, run with -Z macro-backtrace for more info)
2828

2929
error[E0277]: `(dyn ProcMacroTrait + 'static)` cannot be shared between threads safely
30-
--> tests/ui/interface_trait_not_sync_and_send.rs:11:1
30+
--> tests/ui/interface_trait_not_sync_and_send.rs:12:1
3131
|
32-
11 | #[uniffi::export]
32+
12 | #[uniffi::export]
3333
| ^^^^^^^^^^^^^^^^^ `(dyn ProcMacroTrait + 'static)` cannot be shared between threads safely
3434
|
3535
= help: the trait `Sync` is not implemented for `(dyn ProcMacroTrait + 'static)`
@@ -41,9 +41,9 @@ note: required by a bound in `FfiConverterArc`
4141
= note: this error originates in the attribute macro `uniffi::export` (in Nightly builds, run with -Z macro-backtrace for more info)
4242

4343
error[E0277]: `(dyn ProcMacroTrait + 'static)` cannot be sent between threads safely
44-
--> tests/ui/interface_trait_not_sync_and_send.rs:11:1
44+
--> tests/ui/interface_trait_not_sync_and_send.rs:12:1
4545
|
46-
11 | #[uniffi::export]
46+
12 | #[uniffi::export]
4747
| ^^^^^^^^^^^^^^^^^ `(dyn ProcMacroTrait + 'static)` cannot be sent between threads safely
4848
|
4949
= help: the trait `Send` is not implemented for `(dyn ProcMacroTrait + 'static)`

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

+4
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ pub mod filters {
413413
Ok(format!("{}.lift", ffi_converter_name(as_ct)?))
414414
}
415415

416+
pub fn check_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
417+
Ok(format!("{}.check", ffi_converter_name(as_ct)?))
418+
}
419+
416420
pub fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
417421
Ok(format!("{}.lower", ffi_converter_name(as_ct)?))
418422
}

uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
class _UniffiConverterBool(_UniffiConverterPrimitive):
1+
class _UniffiConverterBool:
22
@classmethod
33
def check(cls, value):
4-
return not not value
4+
pass
5+
6+
@classmethod
7+
def lower(cls, value):
8+
return 1 if value else 0
9+
10+
@staticmethod
11+
def lift(value):
12+
return value != 0
513

614
@classmethod
715
def read(cls, buf):
816
return cls.lift(buf.read_u8())
917

1018
@classmethod
11-
def write_unchecked(cls, value, buf):
19+
def write(cls, value, buf):
1220
buf.write_u8(value)
13-
14-
@staticmethod
15-
def lift(value):
16-
return value != 0

uniffi_bindgen/src/bindings/python/templates/BytesHelper.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ def read(buf):
77
return buf.read(size)
88

99
@staticmethod
10-
def write(value, buf):
10+
def check(value):
1111
try:
1212
memoryview(value)
1313
except TypeError:
1414
raise TypeError("a bytes-like object is required, not {!r}".format(type(value).__name__))
15+
16+
@staticmethod
17+
def write(value, buf):
1518
buf.write_i32(len(value))
1619
buf.write(value)

uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py

+4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ def read(cls, buf):
2424
handle = buf.read_u64()
2525
cls.lift(handle)
2626

27+
@classmethod
28+
def check(cls, cb):
29+
pass
30+
2731
@classmethod
2832
def lower(cls, cb):
2933
handle = cls._slab.insert(cb)

uniffi_bindgen/src/bindings/python/templates/CustomType.py

+9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ def read(buf):
1717
def lift(value):
1818
return {{ builtin|ffi_converter_name }}.lift(value)
1919

20+
@staticmethod
21+
def check(value):
22+
return {{ builtin|ffi_converter_name }}.check(value)
23+
2024
@staticmethod
2125
def lower(value):
2226
return {{ builtin|ffi_converter_name }}.lower(value)
@@ -51,6 +55,11 @@ def lift(value):
5155
builtin_value = {{ builtin|lift_fn }}(value)
5256
return {{ config.into_custom.render("builtin_value") }}
5357

58+
@staticmethod
59+
def check(value):
60+
builtin_value = {{ config.from_custom.render("value") }}
61+
return {{ builtin|check_fn }}(builtin_value)
62+
5463
@staticmethod
5564
def lower(value):
5665
builtin_value = {{ config.from_custom.render("value") }}

uniffi_bindgen/src/bindings/python/templates/DurationHelper.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ def read(buf):
1212
return datetime.timedelta(seconds=seconds, microseconds=microseconds)
1313

1414
@staticmethod
15-
def write(value, buf):
15+
def check(value):
1616
seconds = value.seconds + value.days * 24 * 3600
17-
nanoseconds = value.microseconds * 1000
1817
if seconds < 0:
1918
raise ValueError("Invalid duration, must be non-negative")
19+
20+
@staticmethod
21+
def write(value, buf):
22+
seconds = value.seconds + value.days * 24 * 3600
23+
nanoseconds = value.microseconds * 1000
2024
buf.write_i64(seconds)
2125
buf.write_u32(nanoseconds)

uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py

+17
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,23 @@ def read(buf):
8181
{%- endfor %}
8282
raise InternalError("Raw enum value doesn't match any cases")
8383

84+
def check(value):
85+
{%- if e.variants().is_empty() %}
86+
pass
87+
{%- else %}
88+
{%- for variant in e.variants() %}
89+
{%- if e.is_flat() %}
90+
if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}:
91+
{%- else %}
92+
if value.is_{{ variant.name()|var_name }}():
93+
{%- endif %}
94+
{%- for field in variant.fields() %}
95+
{{ field|check_fn }}(value.{{ field.name()|var_name }})
96+
{%- endfor %}
97+
return
98+
{%- endfor %}
99+
{%- endif %}
100+
84101
def write(value, buf):
85102
{%- for variant in e.variants() %}
86103
{%- if e.is_flat() %}

uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py

+14
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ def read(buf):
5959
{%- endfor %}
6060
raise InternalError("Raw enum value doesn't match any cases")
6161

62+
@staticmethod
63+
def check(value):
64+
{%- if e.variants().is_empty() %}
65+
pass
66+
{%- else %}
67+
{%- for variant in e.variants() %}
68+
if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}):
69+
{%- for field in variant.fields() %}
70+
{{ field|check_fn }}(value.{{ field.name()|var_name }})
71+
{%- endfor %}
72+
return
73+
{%- endfor %}
74+
{%- endif %}
75+
6276
@staticmethod
6377
def write(value, buf):
6478
{%- for variant in e.variants() %}

uniffi_bindgen/src/bindings/python/templates/Float32Helper.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ def read(buf):
44
return buf.read_float()
55

66
@staticmethod
7-
def write_unchecked(value, buf):
7+
def write(value, buf):
88
buf.write_float(value)

uniffi_bindgen/src/bindings/python/templates/Float64Helper.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ def read(buf):
44
return buf.read_double()
55

66
@staticmethod
7-
def write_unchecked(value, buf):
7+
def write(value, buf):
88
buf.write_double(value)

uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ class {{ ffi_converter_name }}:
1212
_slab = UniffiSlab()
1313

1414
@classmethod
15-
def lower(cls, eventloop):
15+
def check(cls, eventloop):
1616
if not isinstance(eventloop, asyncio.BaseEventLoop):
1717
raise TypeError("_uniffi_executor_callback: Expected EventLoop instance")
18+
19+
@classmethod
20+
def lower(cls, eventloop):
1821
return cls._slab.insert(eventloop)
1922

2023
@classmethod

uniffi_bindgen/src/bindings/python/templates/Int16Helper.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ def read(buf):
88
return buf.read_i16()
99

1010
@staticmethod
11-
def write_unchecked(value, buf):
11+
def write(value, buf):
1212
buf.write_i16(value)

uniffi_bindgen/src/bindings/python/templates/Int32Helper.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ def read(buf):
88
return buf.read_i32()
99

1010
@staticmethod
11-
def write_unchecked(value, buf):
11+
def write(value, buf):
1212
buf.write_i32(value)

uniffi_bindgen/src/bindings/python/templates/Int64Helper.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ def read(buf):
88
return buf.read_i64()
99

1010
@staticmethod
11-
def write_unchecked(value, buf):
11+
def write(value, buf):
1212
buf.write_i64(value)

uniffi_bindgen/src/bindings/python/templates/Int8Helper.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ def read(buf):
88
return buf.read_i8()
99

1010
@staticmethod
11-
def write_unchecked(value, buf):
11+
def write(value, buf):
1212
buf.write_i8(value)

0 commit comments

Comments
 (0)