Skip to content

Commit 8de9af0

Browse files
committed
Adding FfiType::Handle and use it for Rust objects
1 parent bde27cc commit 8de9af0

File tree

26 files changed

+246
-270
lines changed

26 files changed

+246
-270
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
## v0.25.0 (backend crates: v0.25.0) - (_2023-10-18_)
2325

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,15 +292,14 @@ impl KotlinCodeOracle {
292292
FfiType::Int64 | FfiType::UInt64 => "Long".to_string(),
293293
FfiType::Float32 => "Float".to_string(),
294294
FfiType::Float64 => "Double".to_string(),
295-
FfiType::RustArcPtr(_) => "Pointer".to_string(),
295+
FfiType::Handle => "UniffiHandle".to_string(),
296296
FfiType::RustBuffer(maybe_suffix) => {
297297
format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default())
298298
}
299299
FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
300300
FfiType::ForeignCallback => "ForeignCallback".to_string(),
301301
FfiType::ForeignExecutorHandle => "USize".to_string(),
302302
FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(),
303-
FfiType::RustFutureHandle => "Pointer".to_string(),
304303
FfiType::RustFutureContinuationCallback => {
305304
"UniFffiRustFutureContinuationCallbackType".to_string()
306305
}

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: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,18 @@ inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
3232

3333
// The base class for all UniFFI Object types.
3434
//
35-
// This class provides core operations for working with the Rust `Arc<T>` pointer to
36-
// the live Rust struct on the other side of the FFI.
35+
// This class provides core operations for working with the Rust handle to the live Rust struct on
36+
// the other side of the FFI.
3737
//
3838
// There's some subtlety here, because we have to be careful not to operate on a Rust
3939
// struct after it has been dropped, and because we must expose a public API for freeing
4040
// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
4141
//
42-
// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct.
43-
// Method calls need to read this pointer from the object's state and pass it in to
42+
// * Each `FFIObject` instance holds an opaque handle to the underlying Rust struct.
43+
// Method calls need to read this handle from the object's state and pass it in to
4444
// the Rust FFI.
4545
//
46-
// * When an `FFIObject` is no longer needed, its pointer should be passed to a
46+
// * When an `FFIObject` is no longer needed, its handle should be passed to a
4747
// special destructor function provided by the Rust FFI, which will drop the
4848
// underlying Rust struct.
4949
//
@@ -60,13 +60,13 @@ inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
6060
// the destructor has been called, and must never call the destructor more than once.
6161
// Doing so may trigger memory unsafety.
6262
//
63-
// If we try to implement this with mutual exclusion on access to the pointer, there is the
63+
// If we try to implement this with mutual exclusion on access to the handle, there is the
6464
// possibility of a race between a method call and a concurrent call to `destroy`:
6565
//
66-
// * Thread A starts a method call, reads the value of the pointer, but is interrupted
67-
// before it can pass the pointer over the FFI to Rust.
66+
// * Thread A starts a method call, reads the value of the handle, but is interrupted
67+
// before it can pass the handle over the FFI to Rust.
6868
// * Thread B calls `destroy` and frees the underlying Rust struct.
69-
// * Thread A resumes, passing the already-read pointer value to Rust and triggering
69+
// * Thread A resumes, passing the already-read handle value to Rust and triggering
7070
// a use-after-free.
7171
//
7272
// One possible solution would be to use a `ReadWriteLock`, with each method call taking
@@ -112,7 +112,7 @@ inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
112112
// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
113113
//
114114
abstract class FFIObject(
115-
protected val pointer: Pointer
115+
internal val handle: UniffiHandle
116116
): Disposable, AutoCloseable {
117117

118118
private val wasDestroyed = AtomicBoolean(false)
@@ -138,7 +138,7 @@ abstract class FFIObject(
138138
this.destroy()
139139
}
140140

141-
internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
141+
internal inline fun <R> callWithHandle(block: (handle: UniffiHandle) -> R): R {
142142
// Check and increment the call counter, to keep the object alive.
143143
// This needs a compare-and-set retry loop in case of concurrent updates.
144144
do {
@@ -150,9 +150,9 @@ abstract class FFIObject(
150150
throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
151151
}
152152
} while (! this.callCounter.compareAndSet(c, c + 1L))
153-
// Now we can safely do the method call without the pointer being freed concurrently.
153+
// Now we can safely do the method call without the handle being freed concurrently.
154154
try {
155-
return block(this.pointer)
155+
return block(this.handle)
156156
} finally {
157157
// This decrement always matches the increment we performed above.
158158
if (this.callCounter.decrementAndGet() == 0L) {

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

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
{% include "Interface.kt" %}
77

88
class {{ impl_class_name }}(
9-
pointer: Pointer
10-
) : FFIObject(pointer), {{ interface_name }}{
9+
handle: UniffiHandle
10+
) : FFIObject(handle), {{ interface_name }}{
1111

1212
{%- match obj.primary_constructor() %}
1313
{%- when Some with (cons) %}
@@ -26,7 +26,7 @@ class {{ impl_class_name }}(
2626
*/
2727
override protected fun freeRustArcPtr() {
2828
rustCall() { status ->
29-
_UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.pointer, status)
29+
_UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.handle, status)
3030
}
3131
}
3232

@@ -40,9 +40,9 @@ class {{ impl_class_name }}(
4040
@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
4141
override suspend fun {{ meth.name()|fn_name }}({%- call kt::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name }}{% when None %}{%- endmatch %} {
4242
return uniffiRustCallAsync(
43-
callWithPointer { thisPtr ->
43+
callWithHandle { uniffiHandle ->
4444
_UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}(
45-
thisPtr,
45+
uniffiHandle,
4646
{% call kt::arg_list_lowered(meth) %}
4747
)
4848
},
@@ -69,15 +69,15 @@ class {{ impl_class_name }}(
6969
{%- match meth.return_type() -%}
7070
{%- when Some with (return_type) -%}
7171
override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name }} =
72-
callWithPointer {
72+
callWithHandle {
7373
{%- call kt::to_ffi_call_with_prefix("it", meth) %}
7474
}.let {
7575
{{ return_type|lift_fn }}(it)
7676
}
7777

7878
{%- when None -%}
7979
override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) =
80-
callWithPointer {
80+
callWithHandle {
8181
{%- call kt::to_ffi_call_with_prefix("it", meth) %}
8282
}
8383
{% endmatch %}
@@ -103,35 +103,31 @@ class {{ impl_class_name }}(
103103
{% include "CallbackInterfaceImpl.kt" %}
104104
{%- endif %}
105105

106-
public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
106+
public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, UniffiHandle> {
107107
{%- if obj.is_trait_interface() %}
108108
internal val handleMap = ConcurrentHandleMap<{{ interface_name }}>()
109109
{%- endif %}
110110

111-
override fun lower(value: {{ type_name }}): Pointer {
111+
override fun lower(value: {{ type_name }}): UniffiHandle {
112112
{%- match obj.imp() %}
113113
{%- when ObjectImpl::Struct %}
114-
return value.callWithPointer { it }
114+
return value.handle
115115
{%- when ObjectImpl::Trait %}
116-
return Pointer(handleMap.insert(value))
116+
return UniffiHandle(handleMap.insert(value))
117117
{%- endmatch %}
118118
}
119119

120-
override fun lift(value: Pointer): {{ type_name }} {
120+
override fun lift(value: UniffiHandle): {{ type_name }} {
121121
return {{ impl_class_name }}(value)
122122
}
123123

124124
override fun read(buf: ByteBuffer): {{ type_name }} {
125-
// The Rust code always writes pointers as 8 bytes, and will
126-
// fail to compile if they don't fit.
127-
return lift(Pointer(buf.getLong()))
125+
return lift(buf.getLong())
128126
}
129127

130128
override fun allocationSize(value: {{ type_name }}) = 8
131129

132130
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
133-
// The Rust code always expects pointers written as 8 bytes,
134-
// and will fail to compile if they don't fit.
135-
buf.putLong(Pointer.nativeValue(lower(value)))
131+
buf.putLong(lower(value))
136132
}
137133
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ impl PythonCodeOracle {
311311
FfiType::UInt64 => "ctypes.c_uint64".to_string(),
312312
FfiType::Float32 => "ctypes.c_float".to_string(),
313313
FfiType::Float64 => "ctypes.c_double".to_string(),
314-
FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(),
314+
FfiType::Handle => "ctypes.c_uint64".to_string(),
315315
FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
316316
Some(suffix) => format!("_UniffiRustBuffer{suffix}"),
317317
None => "_UniffiRustBuffer".to_string(),
@@ -321,7 +321,6 @@ impl PythonCodeOracle {
321321
// Pointer to an `asyncio.EventLoop` instance
322322
FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(),
323323
FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(),
324-
FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(),
325324
FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(),
326325
FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(),
327326
}

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,29 @@
55
{% include "Protocol.py" %}
66

77
class {{ impl_name }}:
8-
_pointer: ctypes.c_void_p
8+
_uniffi_handle: ctypes.c_int64
99

1010
{%- match obj.primary_constructor() %}
1111
{%- when Some with (cons) %}
1212
def __init__(self, {% call py::arg_list_decl(cons) -%}):
1313
{%- call py::setup_args_extra_indent(cons) %}
14-
self._pointer = {% call py::to_ffi_call(cons) %}
14+
self._uniffi_handle = {% call py::to_ffi_call(cons) %}
1515
{%- when None %}
1616
{%- endmatch %}
1717

1818
def __del__(self):
1919
# In case of partial initialization of instances.
20-
pointer = getattr(self, "_pointer", None)
21-
if pointer is not None:
22-
_rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, pointer)
20+
handle = getattr(self, "_uniffi_handle", None)
21+
if handle is not None:
22+
_rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, handle)
2323

2424
# Used by alternative constructors or any methods which return this type.
2525
@classmethod
26-
def _make_instance_(cls, pointer):
26+
def _make_instance_(cls, handle):
2727
# Lightly yucky way to bypass the usual __init__ logic
28-
# and just create a new instance with the required pointer.
28+
# and just create a new instance with the required handle.
2929
inst = cls.__new__(cls)
30-
inst._pointer = pointer
30+
inst._uniffi_handle = handle
3131
return inst
3232

3333
{%- for cons in obj.alternate_constructors() %}
@@ -36,8 +36,8 @@ def _make_instance_(cls, pointer):
3636
def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}):
3737
{%- call py::setup_args_extra_indent(cons) %}
3838
# Call the (fallible) function before creating any half-baked object instances.
39-
pointer = {% call py::to_ffi_call(cons) %}
40-
return cls._make_instance_(pointer)
39+
uniffi_handle = {% call py::to_ffi_call(cons) %}
40+
return cls._make_instance_(uniffi_handle)
4141
{% endfor %}
4242

4343
{%- for meth in obj.methods() -%}
@@ -55,13 +55,13 @@ def __eq__(self, other: object) -> {{ eq.return_type().unwrap()|type_name }}:
5555
if not isinstance(other, {{ type_name }}):
5656
return NotImplemented
5757

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

6060
def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}:
6161
if not isinstance(other, {{ type_name }}):
6262
return NotImplemented
6363

64-
return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", ne) %})
64+
return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_handle", ne) %})
6565
{%- when UniffiTrait::Hash { hash } %}
6666
{%- call py::method_decl("__hash__", hash) %}
6767
{% endmatch %}
@@ -89,16 +89,14 @@ def lower(value: {{ protocol_name }}):
8989
{%- when ObjectImpl::Struct %}
9090
if not isinstance(value, {{ impl_name }}):
9191
raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__))
92-
return value._pointer
92+
return value._uniffi_handle
9393
{%- when ObjectImpl::Trait %}
9494
return {{ ffi_converter_name }}._handle_map.insert(value)
9595
{%- endmatch %}
9696

9797
@classmethod
9898
def read(cls, buf: _UniffiRustBuffer):
9999
ptr = buf.read_u64()
100-
if ptr == 0:
101-
raise InternalError("Raw pointer value was null")
102100
return cls.lift(ptr)
103101

104102
@classmethod

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}):
104104
{%- call setup_args_extra_indent(meth) %}
105105
return _uniffi_rust_call_async(
106106
_UniffiLib.{{ meth.ffi_func().name() }}(
107-
self._pointer, {% call arg_list_lowered(meth) %}
107+
self._uniffi_handle, {% call arg_list_lowered(meth) %}
108108
),
109109
_UniffiLib.{{ meth.ffi_rust_future_poll(ci) }},
110110
_UniffiLib.{{ meth.ffi_rust_future_complete(ci) }},
@@ -133,14 +133,14 @@ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}):
133133
def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}":
134134
{%- call setup_args_extra_indent(meth) %}
135135
return {{ return_type|lift_fn }}(
136-
{% call to_ffi_call_with_prefix("self._pointer", meth) %}
136+
{% call to_ffi_call_with_prefix("self._uniffi_handle", meth) %}
137137
)
138138

139139
{%- when None %}
140140

141141
def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}):
142142
{%- call setup_args_extra_indent(meth) %}
143-
{% call to_ffi_call_with_prefix("self._pointer", meth) %}
143+
{% call to_ffi_call_with_prefix("self._uniffi_handle", meth) %}
144144
{% endmatch %}
145145
{% endif %}
146146

uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ mod filters {
150150
FfiType::UInt64 => ":uint64".to_string(),
151151
FfiType::Float32 => ":float".to_string(),
152152
FfiType::Float64 => ":double".to_string(),
153-
FfiType::RustArcPtr(_) => ":pointer".to_string(),
153+
FfiType::Handle => ":uint64".to_string(),
154154
FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(),
155155
FfiType::ForeignBytes => "ForeignBytes".to_string(),
156156
// Callback interfaces are not yet implemented, but this needs to return something in
@@ -162,9 +162,7 @@ mod filters {
162162
FfiType::ForeignExecutorHandle => {
163163
unimplemented!("Foreign executors are not implemented")
164164
}
165-
FfiType::RustFutureHandle
166-
| FfiType::RustFutureContinuationCallback
167-
| FfiType::RustFutureContinuationData => {
165+
FfiType::RustFutureContinuationCallback | FfiType::RustFutureContinuationData => {
168166
unimplemented!("Async functions are not implemented")
169167
}
170168
})

0 commit comments

Comments
 (0)