Skip to content

Commit d24ffae

Browse files
committed
jextract/jni: handle UnsafeRawBufferPointer params
This allows passing a Java byte array to an `UnsafeRawBufferPointer` accepting API in Swift. This conversion needs to copy a bunch but makes pretty much sense. This is in JNI mode specifically. In FFM mode we could do better by allocating and passing a MemorySegment but this is to unblock APIs in JNI mode specifically.
1 parent c4a2934 commit d24ffae

6 files changed

Lines changed: 182 additions & 1 deletion

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import SwiftJava
16+
17+
/// Sum all bytes in the buffer
18+
public func sumOfBytes(data: UnsafeRawBufferPointer) -> Int64 {
19+
var sum: Int64 = 0
20+
for byte in data {
21+
sum += Int64(byte)
22+
}
23+
return sum
24+
}
25+
26+
/// Return the count of bytes in the buffer
27+
public func bufferCount(data: UnsafeRawBufferPointer) -> Int64 {
28+
Int64(data.count)
29+
}
30+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of Swift.org project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
package com.example.swift;
16+
17+
import org.junit.jupiter.api.Test;
18+
19+
import static org.junit.jupiter.api.Assertions.*;
20+
21+
public class UnsafeRawBufferPointerTest {
22+
@Test
23+
void sumOfBytes() {
24+
byte[] input = new byte[] { 1, 2, 3, 4, 5 };
25+
assertEquals(15, MySwiftLibrary.sumOfBytes(input));
26+
}
27+
28+
@Test
29+
void sumOfBytes_empty() {
30+
byte[] input = new byte[] {};
31+
assertEquals(0, MySwiftLibrary.sumOfBytes(input));
32+
}
33+
34+
@Test
35+
void bufferCount() {
36+
byte[] input = new byte[] { 10, 20, 30, 40 };
37+
assertEquals(4, MySwiftLibrary.bufferCount(input));
38+
}
39+
40+
@Test
41+
void bufferCount_empty() {
42+
byte[] input = new byte[] {};
43+
assertEquals(0, MySwiftLibrary.bufferCount(input));
44+
}
45+
}

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ extension JNISwift2JavaGenerator {
242242
let javaName = javaIdentifiers.makeJavaMethodName(decl)
243243

244244
// Swift -> Java
245-
var translatedFunctionSignature = try translate(
245+
var translatedFunctionSignature = try self.translate(
246246
functionSignature: decl.functionSignature,
247247
methodName: javaName,
248248
parentName: parentName,
@@ -509,6 +509,12 @@ extension JNISwift2JavaGenerator {
509509
case .foundationData, .essentialsData:
510510
break // Handled as wrapped struct
511511

512+
case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
513+
return TranslatedParameter(
514+
parameter: JavaParameter(name: parameterName, type: .array(.byte)),
515+
conversion: .placeholder
516+
)
517+
512518
case .foundationUUID, .essentialsUUID:
513519
return TranslatedParameter(
514520
parameter: JavaParameter(name: parameterName, type: .javaUtilUUID),

Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,16 @@ extension JNISwift2JavaGenerator {
125125
parameterName: parameterName
126126
)
127127

128+
case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer:
129+
return NativeParameter(
130+
parameters: [
131+
JavaParameter(name: parameterName, type: .array(.byte))
132+
],
133+
conversion: .jniByteArrayToUnsafeRawBufferPointer(.placeholder, name: parameterName),
134+
indirectConversion: nil,
135+
conversionCheck: nil
136+
)
137+
128138
case .foundationDate, .essentialsDate, .foundationData, .essentialsData:
129139
// Handled as wrapped struct
130140
break
@@ -1154,6 +1164,9 @@ extension JNISwift2JavaGenerator {
11541164
/// `SwiftType(inner)`
11551165
indirect case labelessInitializer(NativeSwiftConversionStep, swiftType: SwiftType)
11561166

1167+
/// Converts a jbyteArray to UnsafeRawBufferPointer via GetByteArrayElements + defer
1168+
indirect case jniByteArrayToUnsafeRawBufferPointer(NativeSwiftConversionStep, name: String)
1169+
11571170
/// Constructs a Swift tuple from individually-converted elements.
11581171
/// E.g. `(label0: conv0, conv1)` for `(label0: Int, String)`
11591172
indirect case tupleConstruct(elements: [(label: String?, conversion: NativeSwiftConversionStep)])
@@ -1713,6 +1726,21 @@ extension JNISwift2JavaGenerator {
17131726
let inner = inner.render(&printer, placeholder)
17141727
return "\(swiftType)(\(inner))"
17151728

1729+
case .jniByteArrayToUnsafeRawBufferPointer(let inner, let name):
1730+
let inner = inner.render(&printer, placeholder)
1731+
let countVar = "\(name)$count"
1732+
let ptrVar = "\(name)$ptr"
1733+
let rbpVar = "\(name)$rbp"
1734+
printer.print(
1735+
"""
1736+
let \(countVar) = Int(environment.interface.GetArrayLength(environment, \(inner)))
1737+
let \(ptrVar) = environment.interface.GetByteArrayElements(environment, \(inner), nil)!
1738+
defer { environment.interface.ReleaseByteArrayElements(environment, \(inner), \(ptrVar), jint(JNI_ABORT)) }
1739+
let \(rbpVar) = UnsafeRawBufferPointer(start: \(ptrVar), count: \(countVar))
1740+
"""
1741+
)
1742+
return rbpVar
1743+
17161744
case .tupleConstruct(let elements):
17171745
let parts = elements.enumerated().map { idx, element in
17181746
let converted = element.conversion.render(&printer, "\(placeholder)_\(idx)")

Sources/JExtractSwiftLib/JavaTypes/JavaType+JDK.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ extension JavaType {
5858
static var javaUtilUUID: JavaType {
5959
.class(package: "java.util", name: "UUID")
6060
}
61+
6162
}

Tests/JExtractSwiftTests/ByteArrayTests.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,75 @@ final class ByteArrayTests {
183183
expectedChunks: expectedSwiftChunks
184184
)
185185
}
186+
187+
// ==== -----------------------------------------------------------------------
188+
// MARK: JNI mode tests
189+
190+
@Test("Import: accept [UInt8] array (JNI)")
191+
func func_accept_array_uint8_jni() throws {
192+
let text = "public func acceptArray(array: [UInt8])"
193+
try assertOutput(input: text, .jni, .swift, detectChunkByInitialLines: 2, expectedChunks: [
194+
"""
195+
@_cdecl("Java_com_example_swift_SwiftModule__00024acceptArray___3B")
196+
public func Java_com_example_swift_SwiftModule__00024acceptArray___3B(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, array: jbyteArray?) {
197+
SwiftModule.acceptArray(array: [UInt8](fromJNI: array, in: environment))
198+
}
199+
"""
200+
])
201+
try assertOutput(input: text, .jni, .java, detectChunkByInitialLines: 2, expectedChunks: [
202+
"""
203+
public static void acceptArray(@Unsigned byte[] array) {
204+
SwiftModule.$acceptArray(Objects.requireNonNull(array, "array must not be null"));
205+
}
206+
""",
207+
"private static native void $acceptArray(byte[] array);",
208+
])
209+
}
210+
211+
@Test("Import: return [UInt8] array (JNI)")
212+
func func_return_array_uint8_jni() throws {
213+
let text = "public func returnArray() -> [UInt8]"
214+
try assertOutput(input: text, .jni, .swift, detectChunkByInitialLines: 2, expectedChunks: [
215+
"""
216+
@_cdecl("Java_com_example_swift_SwiftModule__00024returnArray__")
217+
public func Java_com_example_swift_SwiftModule__00024returnArray__(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass) -> jbyteArray? {
218+
return SwiftModule.returnArray().getJNILocalRefValue(in: environment)
219+
}
220+
"""
221+
])
222+
try assertOutput(input: text, .jni, .java, detectChunkByInitialLines: 2, expectedChunks: [
223+
"""
224+
@Unsigned
225+
public static byte[] returnArray() {
226+
return SwiftModule.$returnArray();
227+
}
228+
""",
229+
"private static native byte[] $returnArray();",
230+
])
231+
}
232+
233+
@Test("Import: accept UnsafeRawBufferPointer (JNI)")
234+
func func_accept_unsafeRawBufferPointer_jni() throws {
235+
let text = "public func receiveBuffer(data: UnsafeRawBufferPointer)"
236+
try assertOutput(input: text, .jni, .swift, detectChunkByInitialLines: 2, expectedChunks: [
237+
"""
238+
@_cdecl("Java_com_example_swift_SwiftModule__00024receiveBuffer___3B")
239+
public func Java_com_example_swift_SwiftModule__00024receiveBuffer___3B(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, data: jbyteArray?) {
240+
let data$count = Int(environment.interface.GetArrayLength(environment, data))
241+
let data$ptr = environment.interface.GetByteArrayElements(environment, data, nil)!
242+
defer { environment.interface.ReleaseByteArrayElements(environment, data, data$ptr, jint(JNI_ABORT)) }
243+
let data$rbp = UnsafeRawBufferPointer(start: data$ptr, count: data$count)
244+
SwiftModule.receiveBuffer(data: data$rbp)
245+
}
246+
"""
247+
])
248+
try assertOutput(input: text, .jni, .java, detectChunkByInitialLines: 2, expectedChunks: [
249+
"""
250+
public static void receiveBuffer(byte[] data) {
251+
SwiftModule.$receiveBuffer(data);
252+
}
253+
""",
254+
"private static native void $receiveBuffer(byte[] data);",
255+
])
256+
}
186257
}

0 commit comments

Comments
 (0)