Skip to content

Commit b06a1f8

Browse files
committed
Adding FfiType::Handle and use it for Rust objects
1 parent 49e6d77 commit b06a1f8

File tree

27 files changed

+251
-286
lines changed

27 files changed

+251
-286
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
- The `rust_future_continuation_callback_set` FFI function was removed. `rust_future_poll` now
2020
inputs the callback pointer. External bindings authors will need to update their code.
21+
- The object handle FFI has changed. External bindings generators will need to update their code to
22+
use the new handle system.
2123

2224
### What's new?
2325

uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,15 +327,14 @@ impl KotlinCodeOracle {
327327
FfiType::Int64 | FfiType::UInt64 => "Long".to_string(),
328328
FfiType::Float32 => "Float".to_string(),
329329
FfiType::Float64 => "Double".to_string(),
330-
FfiType::RustArcPtr(_) => "Pointer".to_string(),
330+
FfiType::Handle => "UniffiHandle".to_string(),
331331
FfiType::RustBuffer(maybe_suffix) => {
332332
format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default())
333333
}
334334
FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
335335
FfiType::ForeignCallback => "ForeignCallback".to_string(),
336336
FfiType::ForeignExecutorHandle => "USize".to_string(),
337337
FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(),
338-
FfiType::RustFutureHandle => "Pointer".to_string(),
339338
FfiType::RustFutureContinuationCallback => {
340339
"UniFffiRustFutureContinuationCallbackType".to_string()
341340
}

uniffi_bindgen/src/bindings/kotlin/templates/Async.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuat
1313
}
1414

1515
internal suspend fun<T, F, E: Exception> uniffiRustCallAsync(
16-
rustFuture: Pointer,
17-
pollFunc: (Pointer, UniFffiRustFutureContinuationCallbackType, USize) -> Unit,
18-
completeFunc: (Pointer, RustCallStatus) -> F,
19-
freeFunc: (Pointer) -> Unit,
16+
rustFuture: UniffiHandle,
17+
pollFunc: (UniffiHandle, UniFffiRustFutureContinuationCallbackType, USize) -> Unit,
18+
completeFunc: (UniffiHandle, RustCallStatus) -> F,
19+
freeFunc: (UniffiHandle) -> Unit,
2020
liftFunc: (F) -> T,
2121
errorHandler: CallStatusErrorHandler<E>
2222
): T {

uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// Handles are defined as unsigned in Rust, but that's doesn't work well with JNA.
2+
// We can pretend its signed since Rust handles are opaque values and Kotlin handles don't use all
3+
// 64 bits.
4+
typealias UniffiHandle = Long
5+
16
// A handful of classes and functions to support the generated data structures.
27
// This would be a good candidate for isolating in its own ffi-support lib.
38
// Error runtime.

uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
{{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
33
// The base class for all UniFFI Object types.
44
//
5-
// This class provides core operations for working with the Rust `Arc<T>` pointer to
6-
// the live Rust struct on the other side of the FFI.
5+
// This class provides core operations for working with the Rust handle to the live Rust struct on
6+
// the other side of the FFI.
77
//
88
// There's some subtlety here, because we have to be careful not to operate on a Rust
99
// struct after it has been dropped, and because we must expose a public API for freeing
1010
// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
1111
//
12-
// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct.
13-
// Method calls need to read this pointer from the object's state and pass it in to
12+
// * Each `FFIObject` instance holds an opaque handle to the underlying Rust struct.
13+
// Method calls need to read this handle from the object's state and pass it in to
1414
// the Rust FFI.
1515
//
16-
// * When an `FFIObject` is no longer needed, its pointer should be passed to a
16+
// * When an `FFIObject` is no longer needed, its handle should be passed to a
1717
// special destructor function provided by the Rust FFI, which will drop the
1818
// underlying Rust struct.
1919
//
@@ -30,13 +30,13 @@
3030
// the destructor has been called, and must never call the destructor more than once.
3131
// Doing so may trigger memory unsafety.
3232
//
33-
// If we try to implement this with mutual exclusion on access to the pointer, there is the
33+
// If we try to implement this with mutual exclusion on access to the handle, there is the
3434
// possibility of a race between a method call and a concurrent call to `destroy`:
3535
//
36-
// * Thread A starts a method call, reads the value of the pointer, but is interrupted
37-
// before it can pass the pointer over the FFI to Rust.
36+
// * Thread A starts a method call, reads the value of the handle, but is interrupted
37+
// before it can pass the handle over the FFI to Rust.
3838
// * Thread B calls `destroy` and frees the underlying Rust struct.
39-
// * Thread A resumes, passing the already-read pointer value to Rust and triggering
39+
// * Thread A resumes, passing the already-read handle value to Rust and triggering
4040
// a use-after-free.
4141
//
4242
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
@@ -83,24 +83,24 @@
8383
//
8484
abstract class FFIObject: Disposable, AutoCloseable {
8585

86-
constructor(pointer: Pointer) {
87-
this.pointer = pointer
86+
constructor(handle: UniffiHandle) {
87+
this.handle = handle
8888
}
8989

9090
/**
9191
* This constructor can be used to instantiate a fake object.
9292
*
9393
* **WARNING: Any object instantiated with this constructor cannot be passed to an actual Rust-backed object.**
94-
* Since there isn't a backing [Pointer] the FFI lower functions will crash.
95-
* @param noPointer Placeholder value so we can have a constructor separate from the default empty one that may be
94+
* Since there isn't a backing [UniffiHandle] the FFI lower functions will crash.
95+
* @param NoHandle Placeholder value so we can have a constructor separate from the default empty one that may be
9696
* implemented for classes extending [FFIObject].
9797
*/
9898
@Suppress("UNUSED_PARAMETER")
99-
constructor(noPointer: NoPointer) {
100-
this.pointer = null
99+
constructor(noHandle: NoHandle) {
100+
this.handle = null
101101
}
102102

103-
protected val pointer: Pointer?
103+
protected val handle: UniffiHandle?
104104

105105
private val wasDestroyed = AtomicBoolean(false)
106106
private val callCounter = AtomicLong(1)
@@ -125,7 +125,7 @@ abstract class FFIObject: Disposable, AutoCloseable {
125125
this.destroy()
126126
}
127127

128-
internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
128+
internal inline fun <R> callWithHandle(block: (handle: UniffiHandle) -> R): R {
129129
// Check and increment the call counter, to keep the object alive.
130130
// This needs a compare-and-set retry loop in case of concurrent updates.
131131
do {
@@ -137,9 +137,9 @@ abstract class FFIObject: Disposable, AutoCloseable {
137137
throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
138138
}
139139
} while (! this.callCounter.compareAndSet(c, c + 1L))
140-
// Now we can safely do the method call without the pointer being freed concurrently.
140+
// Now we can safely do the method call without the handle being freed concurrently.
141141
try {
142-
return block(this.pointer!!)
142+
return block(this.handle!!)
143143
} finally {
144144
// This decrement always matches the increment we performed above.
145145
if (this.callCounter.decrementAndGet() == 0L) {
@@ -150,4 +150,4 @@ abstract class FFIObject: Disposable, AutoCloseable {
150150
}
151151

152152
/** Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. */
153-
object NoPointer
153+
object NoHandle

uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99
{%- call kt::docstring(obj, 0) %}
1010
open class {{ impl_class_name }} : FFIObject, {{ interface_name }} {
1111

12-
constructor(pointer: Pointer): super(pointer)
12+
constructor(handle: UniffiHandle): super(handle)
1313

1414
/**
1515
* This constructor can be used to instantiate a fake object.
1616
*
1717
* **WARNING: Any object instantiated with this constructor cannot be passed to an actual Rust-backed object.**
18-
* Since there isn't a backing [Pointer] the FFI lower functions will crash.
19-
* @param noPointer Placeholder value so we can have a constructor separate from the default empty one that may be
18+
* Since there isn't a backing [UniffiHandle] the FFI lower functions will crash.
19+
* @param noHandle Placeholder value so we can have a constructor separate from the default empty one that may be
2020
* implemented for classes extending [FFIObject].
2121
*/
22-
constructor(noPointer: NoPointer): super(noPointer)
22+
constructor(noHandle: NoHandle): super(noHandle)
2323

2424
{%- match obj.primary_constructor() %}
2525
{%- when Some with (cons) %}
@@ -38,9 +38,9 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} {
3838
* Clients **must** call this method once done with the object, or cause a memory leak.
3939
*/
4040
override protected fun freeRustArcPtr() {
41-
this.pointer?.let { ptr ->
41+
this.handle?.let { handle ->
4242
rustCall() { status ->
43-
_UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(ptr, status)
43+
_UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(handle, status)
4444
}
4545
}
4646
}
@@ -58,9 +58,9 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} {
5858
{%- call kt::arg_list_decl(meth) -%}
5959
){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
6060
return uniffiRustCallAsync(
61-
callWithPointer { thisPtr ->
61+
callWithHandle { uniffiHandle ->
6262
_UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}(
63-
thisPtr,
63+
uniffiHandle,
6464
{% call kt::arg_list_lowered(meth) %}
6565
)
6666
},
@@ -86,20 +86,16 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} {
8686
{%- else -%}
8787
{%- match meth.return_type() -%}
8888
{%- when Some with (return_type) -%}
89-
override fun {{ meth.name()|fn_name }}(
90-
{%- call kt::arg_list_protocol(meth) -%}
91-
): {{ return_type|type_name(ci) }} =
92-
callWithPointer {
89+
override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name(ci) }} =
90+
callWithHandle {
9391
{%- call kt::to_ffi_call_with_prefix("it", meth) %}
9492
}.let {
9593
{{ return_type|lift_fn }}(it)
9694
}
9795

9896
{%- when None -%}
99-
override fun {{ meth.name()|fn_name }}(
100-
{%- call kt::arg_list_protocol(meth) -%}
101-
) =
102-
callWithPointer {
97+
override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) =
98+
callWithHandle {
10399
{%- call kt::to_ffi_call_with_prefix("it", meth) %}
104100
}
105101
{% endmatch %}
@@ -157,35 +153,31 @@ open class {{ impl_class_name }} : FFIObject, {{ interface_name }} {
157153
{% include "CallbackInterfaceImpl.kt" %}
158154
{%- endif %}
159155

160-
public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
156+
public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, UniffiHandle> {
161157
{%- if obj.is_trait_interface() %}
162158
internal val handleMap = ConcurrentHandleMap<{{ interface_name }}>()
163159
{%- endif %}
164160

165-
override fun lower(value: {{ type_name }}): Pointer {
161+
override fun lower(value: {{ type_name }}): UniffiHandle {
166162
{%- match obj.imp() %}
167163
{%- when ObjectImpl::Struct %}
168-
return value.callWithPointer { it }
164+
return value.handle
169165
{%- when ObjectImpl::Trait %}
170-
return Pointer(handleMap.insert(value))
166+
return UniffiHandle(handleMap.insert(value))
171167
{%- endmatch %}
172168
}
173169

174-
override fun lift(value: Pointer): {{ type_name }} {
170+
override fun lift(value: UniffiHandle): {{ type_name }} {
175171
return {{ impl_class_name }}(value)
176172
}
177173

178174
override fun read(buf: ByteBuffer): {{ type_name }} {
179-
// The Rust code always writes pointers as 8 bytes, and will
180-
// fail to compile if they don't fit.
181-
return lift(Pointer(buf.getLong()))
175+
return lift(buf.getLong())
182176
}
183177

184178
override fun allocationSize(value: {{ type_name }}) = 8
185179

186180
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
187-
// The Rust code always expects pointers written as 8 bytes,
188-
// and will fail to compile if they don't fit.
189-
buf.putLong(Pointer.nativeValue(lower(value)))
181+
buf.putLong(lower(value))
190182
}
191183
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ impl PythonCodeOracle {
350350
FfiType::UInt64 => "ctypes.c_uint64".to_string(),
351351
FfiType::Float32 => "ctypes.c_float".to_string(),
352352
FfiType::Float64 => "ctypes.c_double".to_string(),
353-
FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(),
353+
FfiType::Handle => "ctypes.c_uint64".to_string(),
354354
FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
355355
Some(suffix) => format!("_UniffiRustBuffer{suffix}"),
356356
None => "_UniffiRustBuffer".to_string(),
@@ -360,7 +360,6 @@ impl PythonCodeOracle {
360360
// Pointer to an `asyncio.EventLoop` instance
361361
FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(),
362362
FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(),
363-
FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(),
364363
FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(),
365364
FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(),
366365
}

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

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,30 @@
77

88
class {{ impl_name }}:
99
{%- call py::docstring(obj, 4) %}
10-
11-
_pointer: ctypes.c_void_p
10+
_uniffi_handle: ctypes.c_int64
1211

1312
{%- match obj.primary_constructor() %}
1413
{%- when Some with (cons) %}
1514
def __init__(self, {% call py::arg_list_decl(cons) -%}):
1615
{%- call py::docstring(cons, 8) %}
1716
{%- call py::setup_args_extra_indent(cons) %}
18-
self._pointer = {% call py::to_ffi_call(cons) %}
17+
self._uniffi_handle = {% call py::to_ffi_call(cons) %}
1918
{%- when None %}
2019
{%- endmatch %}
2120

2221
def __del__(self):
2322
# In case of partial initialization of instances.
24-
pointer = getattr(self, "_pointer", None)
25-
if pointer is not None:
26-
_rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, pointer)
23+
handle = getattr(self, "_uniffi_handle", None)
24+
if handle is not None:
25+
_rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, handle)
2726

2827
# Used by alternative constructors or any methods which return this type.
2928
@classmethod
30-
def _make_instance_(cls, pointer):
29+
def _make_instance_(cls, handle):
3130
# Lightly yucky way to bypass the usual __init__ logic
32-
# and just create a new instance with the required pointer.
31+
# and just create a new instance with the required handle.
3332
inst = cls.__new__(cls)
34-
inst._pointer = pointer
33+
inst._uniffi_handle = handle
3534
return inst
3635

3736
{%- for cons in obj.alternate_constructors() %}
@@ -41,8 +40,8 @@ def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}):
4140
{%- call py::docstring(cons, 8) %}
4241
{%- call py::setup_args_extra_indent(cons) %}
4342
# Call the (fallible) function before creating any half-baked object instances.
44-
pointer = {% call py::to_ffi_call(cons) %}
45-
return cls._make_instance_(pointer)
43+
uniffi_handle = {% call py::to_ffi_call(cons) %}
44+
return cls._make_instance_(uniffi_handle)
4645
{% endfor %}
4746

4847
{%- for meth in obj.methods() -%}
@@ -60,13 +59,13 @@ def __eq__(self, other: object) -> {{ eq.return_type().unwrap()|type_name }}:
6059
if not isinstance(other, {{ type_name }}):
6160
return NotImplemented
6261

63-
return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", eq) %})
62+
return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_handle", eq) %})
6463

6564
def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}:
6665
if not isinstance(other, {{ type_name }}):
6766
return NotImplemented
6867

69-
return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", ne) %})
68+
return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_handle", ne) %})
7069
{%- when UniffiTrait::Hash { hash } %}
7170
{%- call py::method_decl("__hash__", hash) %}
7271
{% endmatch %}
@@ -94,16 +93,14 @@ def lower(value: {{ protocol_name }}):
9493
{%- when ObjectImpl::Struct %}
9594
if not isinstance(value, {{ impl_name }}):
9695
raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__))
97-
return value._pointer
96+
return value._uniffi_handle
9897
{%- when ObjectImpl::Trait %}
9998
return {{ ffi_converter_name }}._handle_map.insert(value)
10099
{%- endmatch %}
101100

102101
@classmethod
103102
def read(cls, buf: _UniffiRustBuffer):
104103
ptr = buf.read_u64()
105-
if ptr == 0:
106-
raise InternalError("Raw pointer value was null")
107104
return cls.lift(ptr)
108105

109106
@classmethod

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}):
118118
{%- call setup_args_extra_indent(meth) %}
119119
return _uniffi_rust_call_async(
120120
_UniffiLib.{{ meth.ffi_func().name() }}(
121-
self._pointer, {% call arg_list_lowered(meth) %}
121+
self._uniffi_handle, {% call arg_list_lowered(meth) %}
122122
),
123123
_UniffiLib.{{ meth.ffi_rust_future_poll(ci) }},
124124
_UniffiLib.{{ meth.ffi_rust_future_complete(ci) }},
@@ -148,15 +148,15 @@ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_typ
148148
{%- call docstring(meth, 8) %}
149149
{%- call setup_args_extra_indent(meth) %}
150150
return {{ return_type|lift_fn }}(
151-
{% call to_ffi_call_with_prefix("self._pointer", meth) %}
151+
{% call to_ffi_call_with_prefix("self._uniffi_handle", meth) %}
152152
)
153153

154154
{%- when None %}
155155

156156
def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}):
157157
{%- call docstring(meth, 8) %}
158158
{%- call setup_args_extra_indent(meth) %}
159-
{% call to_ffi_call_with_prefix("self._pointer", meth) %}
159+
{% call to_ffi_call_with_prefix("self._uniffi_handle", meth) %}
160160
{% endmatch %}
161161
{% endif %}
162162

0 commit comments

Comments
 (0)