Skip to content

Commit ab7a447

Browse files
committed
Add workaround for JNA bug when passing small structs by value.
The current version of JNA appears to have a bug when passing small structs as arguments by value, on arm64 platforms, when the function call has too many arguments to pass the struct in registers. It turns out that our generated code does this a lot thanks to the way we pass around `RustBuffer` structs. This commit adds a temporary workaround for the issue, by padding our structs to be larger than 16 bytes, which causes them to be passed as pointers rather than by value. See mozilla/application-services#3646 for investigation of the JNA issue, and #334 to track the work of removing this workaround once a fix is released in upstream JNA.
1 parent ea7703f commit ab7a447

File tree

7 files changed

+79
-68
lines changed

7 files changed

+79
-68
lines changed

uniffi/src/ffi/foreignbytes.rs

+26-20
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,30 @@ pub struct ForeignBytes {
3131
len: i32,
3232
/// The pointer to the foreign-owned bytes.
3333
data: *const u8,
34+
/// This forces the struct to be larger than 16 bytes, as a temporary workaround for a bug in JNA.
35+
/// See https://github.com/mozilla/uniffi-rs/issues/334 for details.
36+
padding: i64,
37+
padding2: i32,
3438
}
3539

3640
impl ForeignBytes {
41+
/// Creates a `ForeignBytes` from its constituent fields.
42+
///
43+
/// This is intended mainly as an internal convenience function and should not
44+
/// be used outside of this module.
45+
///
46+
/// # Safety
47+
///
48+
/// You must ensure that the raw parts uphold the documented invariants of this class.
49+
pub unsafe fn from_raw_parts(data: *const u8, len: i32) -> Self {
50+
Self {
51+
len,
52+
data,
53+
padding: 0,
54+
padding2: 0,
55+
}
56+
}
57+
3758
/// View the foreign bytes as a `&[u8]`.
3859
///
3960
/// # Panics
@@ -72,52 +93,37 @@ mod test {
7293
#[test]
7394
fn test_foreignbytes_access() {
7495
let v = vec![1u8, 2, 3];
75-
let fbuf = ForeignBytes {
76-
len: 3,
77-
data: v.as_ptr(),
78-
};
96+
let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), 3) };
7997
assert_eq!(fbuf.len(), 3);
8098
assert_eq!(fbuf.as_slice(), &[1u8, 2, 3]);
8199
}
82100

83101
#[test]
84102
fn test_foreignbytes_empty() {
85103
let v = Vec::<u8>::new();
86-
let fbuf = ForeignBytes {
87-
len: 0,
88-
data: v.as_ptr(),
89-
};
104+
let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), 0) };
90105
assert_eq!(fbuf.len(), 0);
91106
assert_eq!(fbuf.as_slice(), &[0u8; 0]);
92107
}
93108

94109
#[test]
95110
fn test_foreignbytes_null_means_empty() {
96-
let fbuf = ForeignBytes {
97-
len: 0,
98-
data: std::ptr::null_mut(),
99-
};
111+
let fbuf = unsafe { ForeignBytes::from_raw_parts(std::ptr::null_mut(), 0) };
100112
assert_eq!(fbuf.as_slice(), &[0u8; 0]);
101113
}
102114

103115
#[test]
104116
#[should_panic]
105117
fn test_foreignbytes_null_must_have_zero_length() {
106-
let fbuf = ForeignBytes {
107-
len: 12,
108-
data: std::ptr::null_mut(),
109-
};
118+
let fbuf = unsafe { ForeignBytes::from_raw_parts(std::ptr::null_mut(), 12) };
110119
fbuf.as_slice();
111120
}
112121

113122
#[test]
114123
#[should_panic]
115124
fn test_foreignbytes_provided_len_must_be_non_negative() {
116125
let v = vec![0u8, 1, 2];
117-
let fbuf = ForeignBytes {
118-
len: -1,
119-
data: v.as_ptr(),
120-
};
126+
let fbuf = unsafe { ForeignBytes::from_raw_parts(v.as_ptr(), -1) };
121127
fbuf.as_slice();
122128
}
123129
}

uniffi/src/ffi/rustbuffer.rs

+28-40
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ pub struct RustBuffer {
5858
len: i32,
5959
/// The pointer to the allocated buffer of the `Vec<u8>`.
6060
data: *mut u8,
61+
/// This forces the struct to be larger than 16 bytes, as a temporary workaround for a bug in JNA.
62+
/// See https://github.com/mozilla/uniffi-rs/issues/334 for details.
63+
padding: i64,
6164
}
6265

6366
impl RustBuffer {
@@ -69,6 +72,23 @@ impl RustBuffer {
6972
Self::from_vec(Vec::new())
7073
}
7174

75+
/// Creates a `RustBuffer` from its constituent fields.
76+
///
77+
/// This is intended mainly as an internal convenience function and should not
78+
/// be used outside of this module.
79+
///
80+
/// # Safety
81+
///
82+
/// You must ensure that the raw parts uphold the documented invariants of this class.
83+
pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self {
84+
Self {
85+
capacity,
86+
len,
87+
data,
88+
padding: 0,
89+
}
90+
}
91+
7292
/// Get the current length of the buffer, as a `usize`.
7393
///
7494
/// This is mostly a helper function to convert the `i32` length field
@@ -119,11 +139,7 @@ impl RustBuffer {
119139
let capacity = i32::try_from(v.capacity()).expect("buffer capacity cannot fit into a i32.");
120140
let len = i32::try_from(v.len()).expect("buffer length cannot fit into a i32.");
121141
let mut v = std::mem::ManuallyDrop::new(v);
122-
Self {
123-
capacity,
124-
len,
125-
data: v.as_mut_ptr(),
126-
}
142+
unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) }
127143
}
128144

129145
/// Converts this `RustBuffer` back into an owned `Vec<u8>`.
@@ -177,11 +193,7 @@ impl Default for RustBuffer {
177193
unsafe impl crate::deps::ffi_support::IntoFfi for RustBuffer {
178194
type Value = Self;
179195
fn ffi_default() -> Self {
180-
Self {
181-
capacity: 0,
182-
len: 0,
183-
data: std::ptr::null_mut(),
184-
}
196+
unsafe { Self::from_raw_parts(std::ptr::null_mut(), 0, 0) }
185197
}
186198
fn into_ffi_value(self) -> Self::Value {
187199
self
@@ -220,34 +232,22 @@ mod test {
220232
#[test]
221233
fn test_rustbuffer_null_means_empty() {
222234
// This is how foreign-language code might cheaply indicate an empty buffer.
223-
let rbuf = RustBuffer {
224-
capacity: 0,
225-
len: 0,
226-
data: std::ptr::null_mut(),
227-
};
235+
let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 0) };
228236
assert_eq!(rbuf.destroy_into_vec().as_slice(), &[0u8; 0]);
229237
}
230238

231239
#[test]
232240
#[should_panic]
233241
fn test_rustbuffer_null_must_have_no_capacity() {
234242
// We guard against foreign-language code providing this kind of invalid struct.
235-
let rbuf = RustBuffer {
236-
capacity: 1,
237-
len: 0,
238-
data: std::ptr::null_mut(),
239-
};
243+
let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 0, 1) };
240244
rbuf.destroy_into_vec();
241245
}
242246
#[test]
243247
#[should_panic]
244248
fn test_rustbuffer_null_must_have_zero_length() {
245249
// We guard against foreign-language code providing this kind of invalid struct.
246-
let rbuf = RustBuffer {
247-
capacity: 0,
248-
len: 12,
249-
data: std::ptr::null_mut(),
250-
};
250+
let rbuf = unsafe { RustBuffer::from_raw_parts(std::ptr::null_mut(), 12, 0) };
251251
rbuf.destroy_into_vec();
252252
}
253253

@@ -256,11 +256,7 @@ mod test {
256256
fn test_rustbuffer_provided_capacity_must_be_non_negative() {
257257
// We guard against foreign-language code providing this kind of invalid struct.
258258
let mut v = vec![0u8, 1, 2];
259-
let rbuf = RustBuffer {
260-
capacity: -7,
261-
len: 3,
262-
data: v.as_mut_ptr(),
263-
};
259+
let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, -7) };
264260
rbuf.destroy_into_vec();
265261
}
266262

@@ -269,11 +265,7 @@ mod test {
269265
fn test_rustbuffer_provided_len_must_be_non_negative() {
270266
// We guard against foreign-language code providing this kind of invalid struct.
271267
let mut v = vec![0u8, 1, 2];
272-
let rbuf = RustBuffer {
273-
capacity: 3,
274-
len: -1,
275-
data: v.as_mut_ptr(),
276-
};
268+
let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), -1, 3) };
277269
rbuf.destroy_into_vec();
278270
}
279271

@@ -282,11 +274,7 @@ mod test {
282274
fn test_rustbuffer_provided_len_must_not_exceed_capacity() {
283275
// We guard against foreign-language code providing this kind of invalid struct.
284276
let mut v = vec![0u8, 1, 2];
285-
let rbuf = RustBuffer {
286-
capacity: 2,
287-
len: 3,
288-
data: v.as_mut_ptr(),
289-
};
277+
let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, 2) };
290278
rbuf.destroy_into_vec();
291279
}
292280

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ internal open class {{e.name()}} : RustError() {
113113
{% for value in e.values() -%}
114114
{{loop.index}} -> return {{e.name()}}Exception.{{value}}(message) as E
115115
{% endfor -%}
116-
else -> throw RuntimeException("Invalid error received...")
116+
else -> throw RuntimeException("Invalid error received: $code, $message")
117117
}
118118
}
119119
}

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
// A rust-owned buffer is represented by its capacity, its current length, and a
33
// pointer to the underlying data.
44

5-
@Structure.FieldOrder("capacity", "len", "data")
5+
@Structure.FieldOrder("capacity", "len", "data", "padding")
66
open class RustBuffer : Structure() {
77
@JvmField var capacity: Int = 0
88
@JvmField var len: Int = 0
99
@JvmField var data: Pointer? = null
10+
// Ref https://github.com/mozilla/uniffi-rs/issues/334 for this weird "padding" field.
11+
@JvmField var padding: Long = 0
1012

1113
class ByValue : RustBuffer(), Structure.ByValue
1214

@@ -37,10 +39,13 @@ open class RustBuffer : Structure() {
3739
// then we might as well copy it into a `RustBuffer`. But it's here for API
3840
// completeness.
3941

40-
@Structure.FieldOrder("len", "data")
42+
@Structure.FieldOrder("len", "data", "padding", "padding2")
4143
open class ForeignBytes : Structure() {
4244
@JvmField var len: Int = 0
4345
@JvmField var data: Pointer? = null
46+
// Ref https://github.com/mozilla/uniffi-rs/issues/334 for these weird "padding" fields.
47+
@JvmField var padding: Long = 0
48+
@JvmField var padding2: Int = 0
4449

4550
class ByValue : ForeignBytes(), Structure.ByValue
4651
}
@@ -137,4 +142,4 @@ class RustBufferBuilder() {
137142
bbuf.put(v)
138143
}
139144
}
140-
}
145+
}

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ class RustBuffer(ctypes.Structure):
44
("capacity", ctypes.c_int32),
55
("len", ctypes.c_int32),
66
("data", ctypes.POINTER(ctypes.c_char)),
7+
# Ref https://github.com/mozilla/uniffi-rs/issues/334 for this weird "padding" field.
8+
("padding", ctypes.c_int64),
79
]
810

911
@staticmethod
@@ -139,7 +141,10 @@ class ForeignBytes(ctypes.Structure):
139141
_fields_ = [
140142
("len", ctypes.c_int32),
141143
("data", ctypes.POINTER(ctypes.c_char)),
144+
# Ref https://github.com/mozilla/uniffi-rs/issues/334 for these weird "padding" fields.
145+
("padding", ctypes.c_int64),
146+
("padding2", ctypes.c_int32),
142147
]
143148

144149
def __str__(self):
145-
return "ForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len])
150+
return "ForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len])

uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift

+5-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ extension RustBuffer {
66
{{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignBytes(bufferPointer: ptr), err)
77
}
88
}
9-
self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data)
9+
// Ref https://github.com/mozilla/uniffi-rs/issues/334 for the extra "padding" arg.
10+
self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data, padding: 0)
1011
}
1112

1213
// Frees the buffer in place.
@@ -20,6 +21,7 @@ extension RustBuffer {
2021

2122
extension ForeignBytes {
2223
init(bufferPointer: UnsafeBufferPointer<UInt8>) {
23-
self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress)
24+
// Ref https://github.com/mozilla/uniffi-rs/issues/334 for the extra "padding" args.
25+
self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress, padding: 0, padding2: 0)
2426
}
25-
}
27+
}

uniffi_bindgen/src/bindings/swift/templates/Template-Bridging-Header.h

+5
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@ typedef struct RustBuffer
1111
int32_t capacity;
1212
int32_t len;
1313
uint8_t *_Nullable data;
14+
// Ref https://github.com/mozilla/uniffi-rs/issues/334 for this weird "padding" field.
15+
int64_t padding;
1416
} RustBuffer;
1517

1618
typedef struct ForeignBytes
1719
{
1820
int32_t len;
1921
const uint8_t *_Nullable data;
22+
// Ref https://github.com/mozilla/uniffi-rs/issues/334 for these weird "padding" fields.
23+
int64_t padding;
24+
int32_t padding2;
2025
} ForeignBytes;
2126

2227
// Error definitions

0 commit comments

Comments
 (0)