Skip to content

Commit f37fc2a

Browse files
arg0dJauler
authored andcommitted
Fix callback vtable lifetime
Passing vtable struct via 'ref' may cause invalid memory accesses when GC defragments the heap and may move the vtable struct address. To prevent GC from moving vtable struct, pin vtable struct using GCHandle. To make the change work, update 'Reference' FFI type to use 'IntPtr' instead of 'ref'. Add some necessary casts from 'IntPtr' in callback implementation to make the code compile (the code previously used ref with concrete types). Signed-off-by: Kristupas Antanavičius <antanaviciuskristupas@gmail.com>
1 parent 8cfc55c commit f37fc2a

File tree

4 files changed

+24
-18
lines changed

4 files changed

+24
-18
lines changed

.github/workflows/cs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: C#
22

33
on:
44
push:
5-
branches: [ "main" ]
5+
branches: [ "main", "main-v0.28.3" ]
66
pull_request:
7-
branches: [ "main" ]
7+
branches: [ "main", "main-v0.28.3" ]
88

99
env:
1010
CARGO_TERM_COLOR: always

bindgen/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "uniffi-bindgen-cs"
3-
version = "0.9.0+v0.28.3"
3+
version = "0.9.2+v0.28.3"
44
edition = "2021"
55

66
[lib]
@@ -28,4 +28,4 @@ toml = "0.5"
2828

2929
uniffi_bindgen.workspace = true
3030
uniffi_meta.workspace = true
31-
uniffi_udl.workspace = true
31+
uniffi_udl.workspace = true

bindgen/src/gen_cs/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ impl CsCodeOracle {
398398
FfiType::RustBuffer(_) => "RustBuffer".to_string(),
399399
FfiType::ForeignBytes => "ForeignBytes".to_string(),
400400
FfiType::Callback(_) => "IntPtr".to_string(),
401-
FfiType::Reference(typ) => format!("ref {}", self.ffi_type_label(typ, prefix_struct)),
401+
FfiType::Reference(typ) => format!("IntPtr /*{}*/", self.ffi_type_label(typ, prefix_struct)),
402402
FfiType::RustCallStatus => "UniffiRustCallStatus".to_string(),
403403
FfiType::Struct(name) => {
404404
if prefix_struct {
@@ -410,4 +410,4 @@ impl CsCodeOracle {
410410
FfiType::VoidPointer => "IntPtr".to_string(),
411411
}
412412
}
413-
}
413+
}

bindgen/templates/CallbackInterfaceImpl.cs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ class {{ callback_impl_name }} {
2222

2323
{%- match meth.return_type() %}
2424
{%- when Some with (return_type) %}
25-
@uniffiOutReturn = {{ return_type|ffi_converter_name }}.INSTANCE.Lower(result);
25+
unsafe {
26+
{%- let return_ffi_type = return_type|ffi_type %}
27+
*({{ return_ffi_type|ffi_type_name }}*)uniffiOutReturn = {{ return_type|ffi_converter_name }}.INSTANCE.Lower(result);
28+
}
2629
{%- when None %}
2730
{%- endmatch %}
2831

@@ -109,8 +112,10 @@ class {{ callback_impl_name }} {
109112
}, cts.Token);
110113

111114
var foreignHandle = _UniFFIAsync._foreign_futures_map.Insert(cts);
112-
@uniffiOutReturn.@handle = foreignHandle;
113-
@uniffiOutReturn.@free = Marshal.GetFunctionPointerForDelegate(_UniFFIAsync.UniffiForeignFutureFreeCallback.callback);
115+
unsafe {
116+
(*(_UniFFILib.UniffiForeignFuture*)uniffiOutReturn).handle = foreignHandle;
117+
(*(_UniFFILib.UniffiForeignFuture*)uniffiOutReturn).free = Marshal.GetFunctionPointerForDelegate(_UniFFIAsync.UniffiForeignFutureFreeCallback.callback);;
118+
}
114119
{%- endif %}
115120
} else {
116121
throw new InternalException($"No callback in handlemap '{handle}'");
@@ -128,16 +133,17 @@ static void UniffiFree(ulong @handle) {
128133
{%- endfor %}
129134
static _UniFFILib.UniffiCallbackInterfaceFree _callback_interface_free = new _UniFFILib.UniffiCallbackInterfaceFree(UniffiFree);
130135

131-
public static _UniFFILib.{{ vtable|ffi_type_name }} _vtable = new _UniFFILib.{{ vtable|ffi_type_name }} {
132-
{%- for (ffi_callback, meth) in vtable_methods.iter() %}
133-
{%- let fn_type = format!("_UniFFILib.{}Method", callback_impl_name) %}
134-
{{ meth.name()|var_name() }} = Marshal.GetFunctionPointerForDelegate(_m{{ loop.index0 }}),
135-
{%- endfor %}
136-
@uniffiFree = Marshal.GetFunctionPointerForDelegate(_callback_interface_free)
137-
};
138-
139136
public static void Register() {
140-
_UniFFILib.{{ ffi_init_callback.name() }}(ref {{ callback_impl_name }}._vtable);
137+
_UniFFILib.{{ vtable|ffi_type_name }} _vtable = new _UniFFILib.{{ vtable|ffi_type_name }} {
138+
{%- for (ffi_callback, meth) in vtable_methods.iter() %}
139+
{%- let fn_type = format!("_UniFFILib.{}Method", callback_impl_name) %}
140+
{{ meth.name()|var_name() }} = Marshal.GetFunctionPointerForDelegate(_m{{ loop.index0 }}),
141+
{%- endfor %}
142+
@uniffiFree = Marshal.GetFunctionPointerForDelegate(_callback_interface_free)
143+
};
144+
145+
// Pin vtable to ensure GC does not move the vtable across the heap
146+
_UniFFILib.{{ ffi_init_callback.name() }}(GCHandle.Alloc(_vtable, GCHandleType.Pinned).AddrOfPinnedObject());
141147
}
142148
}
143149

0 commit comments

Comments
 (0)