Skip to content

Commit 937f9b7

Browse files
authored
Merge pull request #335 from mozilla/jna-arm64-workaround; r=eoger
Add workaround for JNA bug when passing small structs by value.
2 parents ea7703f + ab7a447 commit 937f9b7

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)