From dc3411df3c263bb5fc0adabc2415361854180491 Mon Sep 17 00:00:00 2001
From: pelekon <13712101+pelekon@users.noreply.github.com>
Date: Sat, 22 Nov 2025 20:48:59 +0100
Subject: [PATCH 1/5] Make JNI handling of Int/UInt match FFM mode.
---
.../JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
index 9bd9a7d83..4082ab33f 100644
--- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
@@ -40,14 +40,20 @@ enum JNIJavaTypeTranslator {
case .int64: return .long
case .uint64: return .long
+ // FIXME: 32 bit consideration.
+ // The 'FunctionDescriptor' uses 'SWIFT_INT' which relies on the running
+ // machine arch. That means users can't pass Java 'long' values to the
+ // function without casting. But how do we generate code that runs both
+ // 32 and 64 bit machine?
+ case .int, .uint: return .long
+
case .float: return .float
case .double: return .double
case .void: return .void
case .string: return .javaLangString
- case .int, .uint, // FIXME: why not supported int/uint?
- .unsafeRawPointer, .unsafeMutableRawPointer,
+ case .unsafeRawPointer, .unsafeMutableRawPointer,
.unsafePointer, .unsafeMutablePointer,
.unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
.unsafeBufferPointer, .unsafeMutableBufferPointer,
From db9a600d9793ab9dd8f839b554f6080c9670456f Mon Sep 17 00:00:00 2001
From: pelekon <13712101+pelekon@users.noreply.github.com>
Date: Mon, 24 Nov 2025 19:57:42 +0100
Subject: [PATCH 2/5] Add support for checks during conversion of values from
java to swift, implemented besic check for overflow of Int/UInt.
---
.../JNI/JNIJavaTypeTranslator.swift | 41 +++++--
...t2JavaGenerator+JavaBindingsPrinting.swift | 2 +-
...ISwift2JavaGenerator+JavaTranslation.swift | 32 +++++-
...wift2JavaGenerator+NativeTranslation.swift | 73 ++++++++++---
...ift2JavaGenerator+SwiftThunkPrinting.swift | 52 +++++++--
.../SwiftTypes/SwiftType.swift | 13 ++-
Sources/JavaTypes/JavaException.swift | 30 ++++++
.../Exceptions/ExceptionHandling.swift | 14 +++
.../core/SwiftIntegerOverflowException.java | 21 ++++
.../JNI/JNIConversionChecksTests.swift | 101 ++++++++++++++++++
10 files changed, 347 insertions(+), 32 deletions(-)
create mode 100644 Sources/JavaTypes/JavaException.swift
create mode 100644 SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java
create mode 100644 Tests/JExtractSwiftTests/JNI/JNIConversionChecksTests.swift
diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
index 4082ab33f..fa380b121 100644
--- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
@@ -39,12 +39,7 @@ enum JNIJavaTypeTranslator {
case .int64: return .long
case .uint64: return .long
-
- // FIXME: 32 bit consideration.
- // The 'FunctionDescriptor' uses 'SWIFT_INT' which relies on the running
- // machine arch. That means users can't pass Java 'long' values to the
- // function without casting. But how do we generate code that runs both
- // 32 and 64 bit machine?
+
case .int, .uint: return .long
case .float: return .float
@@ -61,4 +56,38 @@ enum JNIJavaTypeTranslator {
return nil
}
}
+
+ static func indirectConversionSetepSwiftType(for knownKind: SwiftKnownTypeDeclKind, from knownTypes: SwiftKnownTypes) -> SwiftType? {
+ switch knownKind {
+ case .int: knownTypes.int64
+ case .uint: knownTypes.uint64
+
+ case .bool, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64,
+ .float, .double, .void, .string,
+ .unsafeRawPointer, .unsafeMutableRawPointer,
+ .unsafePointer, .unsafeMutablePointer,
+ .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
+ .unsafeBufferPointer, .unsafeMutableBufferPointer,
+ .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol,
+ .array:
+ nil
+ }
+ }
+
+ static func checkStep(for knownKind: SwiftKnownTypeDeclKind, from knownTypes: SwiftKnownTypes) -> JNISwift2JavaGenerator.NativeSwiftConversionCheck? {
+ switch knownKind {
+ case .int: .check32BitIntOverflow(typeWithMinAndMax: knownTypes.int32)
+ case .uint: .check32BitIntOverflow(typeWithMinAndMax: knownTypes.uint32)
+
+ case .bool, .int8, .uint8, .int16, .uint16, .int32, .uint32, .int64, .uint64,
+ .float, .double, .void, .string,
+ .unsafeRawPointer, .unsafeMutableRawPointer,
+ .unsafePointer, .unsafeMutablePointer,
+ .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer,
+ .unsafeBufferPointer, .unsafeMutableBufferPointer,
+ .optional, .foundationData, .foundationDataProtocol, .essentialsData, .essentialsDataProtocol,
+ .array:
+ nil
+ }
+ }
}
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift
index 07d23dc0f..46d722137 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaBindingsPrinting.swift
@@ -445,7 +445,7 @@ extension JNISwift2JavaGenerator {
let translatedSignature = translatedDecl.translatedFunctionSignature
let resultType = translatedSignature.resultType.javaType
var parameters = translatedDecl.translatedFunctionSignature.parameters.map { $0.parameter.renderParameter() }
- let throwsClause = translatedDecl.isThrowing && !translatedDecl.isAsync ? " throws Exception" : ""
+ let throwsClause = translatedDecl.throwsClause()
let generics = translatedDecl.translatedFunctionSignature.parameters.reduce(into: [(String, [JavaType])]()) { generics, parameter in
guard case .generic(let name, let extends) = parameter.parameter.type else {
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift
index 9c3cdbf1a..32f1c4386 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift
@@ -114,6 +114,12 @@ extension JNISwift2JavaGenerator {
.class(package: nil,name: caseName)
)
])
+ var exceptions: [JavaException] = []
+
+ if enumCase.parameters.contains(where: \.type.isArchDependingInteger) {
+ exceptions.append(.integerOverflow)
+ }
+
let getAsCaseFunction = TranslatedFunctionDecl(
name: getAsCaseName,
isStatic: false,
@@ -137,12 +143,15 @@ extension JNISwift2JavaGenerator {
javaType: .class(package: nil, name: "Optional<\(caseName)>"),
outParameters: conversions.flatMap(\.translated.outParameters),
conversion: enumCase.parameters.isEmpty ? constructRecordConversion : .aggregate(variable: ("$nativeParameters", nativeParametersType), [constructRecordConversion])
- )
+ ),
+ exceptions: exceptions
),
nativeFunctionSignature: NativeFunctionSignature(
selfParameter: NativeParameter(
parameters: [JavaParameter(name: "self", type: .long)],
- conversion: .extractSwiftValue(.placeholder, swiftType: .nominal(enumCase.enumType), allowNil: false)
+ conversion: .extractSwiftValue(.placeholder, swiftType: .nominal(enumCase.enumType), allowNil: false),
+ indirectConversion: nil,
+ conversionCheck: nil
),
parameters: [],
result: NativeResult(
@@ -291,12 +300,19 @@ extension JNISwift2JavaGenerator {
genericRequirements: functionSignature.genericRequirements
)
+ var exceptions: [JavaException] = []
+
+ if functionSignature.parameters.contains(where: \.type.isArchDependingInteger) {
+ exceptions.append(.integerOverflow)
+ }
+
let resultType = try translate(swiftResult: functionSignature.result)
return TranslatedFunctionSignature(
selfParameter: selfParameter,
parameters: parameters,
- resultType: resultType
+ resultType: resultType,
+ exceptions: exceptions
)
}
@@ -955,12 +971,22 @@ extension JNISwift2JavaGenerator {
var annotations: [JavaAnnotation] {
self.translatedFunctionSignature.annotations
}
+
+ func throwsClause() -> String {
+ guard !translatedFunctionSignature.exceptions.isEmpty else {
+ return isThrowing && !isAsync ? " throws Exception" : ""
+ }
+
+ let signatureExceptions = translatedFunctionSignature.exceptions.compactMap(\.type.className).joined(separator: ", ")
+ return " throws \(signatureExceptions)\(isThrowing ? ", Exception" : "")"
+ }
}
struct TranslatedFunctionSignature {
var selfParameter: TranslatedParameter?
var parameters: [TranslatedParameter]
var resultType: TranslatedResult
+ var exceptions: [JavaException]
// if the result type implied any annotations,
// propagate them onto the function the result is returned from
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift
index 1994dce04..494207f01 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift
@@ -103,11 +103,16 @@ extension JNISwift2JavaGenerator {
throw JavaTranslationError.unsupportedSwiftType(type)
}
+ let indirectStepType = JNIJavaTypeTranslator.indirectConversionSetepSwiftType(for: knownType, from: knownTypes)
+ let indirectCheck = JNIJavaTypeTranslator.checkStep(for: knownType, from: knownTypes)
+
return NativeParameter(
parameters: [
JavaParameter(name: parameterName, type: javaType)
],
- conversion: .initFromJNI(.placeholder, swiftType: type)
+ conversion: indirectStepType != nil ? .labelessAssignmentOfVariable(.placeholder, swiftType: type) : .initFromJNI(.placeholder, swiftType: type),
+ indirectConversion: indirectStepType.flatMap { .initFromJNI(.placeholder, swiftType: $0) },
+ conversionCheck: indirectCheck
)
}
@@ -129,7 +134,9 @@ extension JNISwift2JavaGenerator {
fatalErrorMessage: "\(parameterName) was null in call to \\(#function), but Swift requires non-optional!"
),
wrapperName: nominalTypeName
- )
+ ),
+ indirectConversion: nil,
+ conversionCheck: nil
)
}
@@ -138,7 +145,9 @@ extension JNISwift2JavaGenerator {
parameters: [
JavaParameter(name: parameterName, type: .long)
],
- conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: type))
+ conversion: .pointee(.extractSwiftValue(.placeholder, swiftType: type)),
+ indirectConversion: nil,
+ conversionCheck: nil
)
case .tuple([]):
@@ -146,7 +155,9 @@ extension JNISwift2JavaGenerator {
parameters: [
JavaParameter(name: parameterName, type: .void)
],
- conversion: .placeholder
+ conversion: .placeholder,
+ indirectConversion: nil,
+ conversionCheck: nil
)
case .function(let fn):
@@ -169,7 +180,9 @@ extension JNISwift2JavaGenerator {
conversion: .closureLowering(
parameters: parameters,
result: result
- )
+ ),
+ indirectConversion: nil,
+ conversionCheck: nil
)
case .optional(let wrapped):
@@ -242,7 +255,9 @@ extension JNISwift2JavaGenerator {
.placeholder,
typeMetadataVariableName: .combinedName(component: "typeMetadataAddress"),
protocolNames: protocolNames
- )
+ ),
+ indirectConversion: nil,
+ conversionCheck: nil
)
}
@@ -272,7 +287,9 @@ extension JNISwift2JavaGenerator {
.initFromJNI(.placeholder, swiftType: swiftType),
discriminatorName: discriminatorName,
valueName: valueName
- )
+ ),
+ indirectConversion: nil,
+ conversionCheck: nil
)
}
@@ -285,7 +302,9 @@ extension JNISwift2JavaGenerator {
parameters: [
JavaParameter(name: parameterName, type: javaType)
],
- conversion: .optionalMap(.initializeJavaKitWrapper(.placeholder, wrapperName: nominalTypeName))
+ conversion: .optionalMap(.initializeJavaKitWrapper(.placeholder, wrapperName: nominalTypeName)),
+ indirectConversion: nil,
+ conversionCheck: nil
)
}
@@ -300,7 +319,9 @@ extension JNISwift2JavaGenerator {
allowNil: true
)
)
- )
+ ),
+ indirectConversion: nil,
+ conversionCheck: nil
)
default:
@@ -434,7 +455,9 @@ extension JNISwift2JavaGenerator {
parameters: [
JavaParameter(name: parameterName, type: javaType)
],
- conversion: .getJValue(.placeholder)
+ conversion: .getJValue(.placeholder),
+ indirectConversion: nil,
+ conversionCheck: nil
)
}
@@ -570,7 +593,9 @@ extension JNISwift2JavaGenerator {
parameters: [
JavaParameter(name: parameterName, type: .array(javaType)),
],
- conversion: .initFromJNI(.placeholder, swiftType: .array(elementType))
+ conversion: .initFromJNI(.placeholder, swiftType: .array(elementType)),
+ indirectConversion: nil,
+ conversionCheck: nil
)
}
@@ -594,7 +619,9 @@ extension JNISwift2JavaGenerator {
convertLongFromJNI: false
))))
]
- )
+ ),
+ indirectConversion: nil,
+ conversionCheck: nil
)
default:
@@ -616,6 +643,12 @@ extension JNISwift2JavaGenerator {
/// Represents how to convert the JNI parameter to a Swift parameter
let conversion: NativeSwiftConversionStep
+
+ /// Represents swift type for indirect variable used in required checks, e.g Int64 for Int overflow check on 32-bit platforms
+ let indirectConversion: NativeSwiftConversionStep?
+
+ /// Represents check operations executed in if/guard conditional block for check during conversion
+ let conversionCheck: NativeSwiftConversionCheck?
}
struct NativeResult {
@@ -695,6 +728,8 @@ extension JNISwift2JavaGenerator {
/// `{ (args) -> return body }`
indirect case closure(args: [String] = [], body: NativeSwiftConversionStep)
+ indirect case labelessAssignmentOfVariable(NativeSwiftConversionStep, swiftType: SwiftType)
+
/// Returns the conversion string applied to the placeholder.
func render(_ printer: inout CodePrinter, _ placeholder: String) -> String {
// NOTE: 'printer' is used if the conversion wants to cause side-effects.
@@ -1025,6 +1060,20 @@ extension JNISwift2JavaGenerator {
}
}
return printer.finalize()
+ case .labelessAssignmentOfVariable(let name, let swiftType):
+ return "\(swiftType)(indirect_\(name.render(&printer, placeholder)))"
+ }
+ }
+ }
+
+ enum NativeSwiftConversionCheck {
+ case check32BitIntOverflow(typeWithMinAndMax: SwiftType)
+
+ // Returns the check string
+ func render(_ printer: inout CodePrinter, _ placeholder: String) -> String {
+ switch self {
+ case .check32BitIntOverflow(let minMaxSource):
+ return "\(placeholder) >= \(minMaxSource).min && \(placeholder) <= \(minMaxSource).max"
}
}
}
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift
index 91a0b0e73..d6e6ad392 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift
@@ -293,11 +293,43 @@ extension JNISwift2JavaGenerator {
let tryClause: String = decl.isThrowing ? "try " : ""
// Regular parameters.
- var arguments = [String]()
+ var arguments: [String] = [String]()
+ var indirectVariables: [(name: String, lowered: String)] = []
+ var int32OverflowChecks: [String] = []
+
for (idx, parameter) in nativeSignature.parameters.enumerated() {
let javaParameterName = translatedDecl.translatedFunctionSignature.parameters[idx].parameter.name
let lowered = parameter.conversion.render(&printer, javaParameterName)
arguments.append(lowered)
+
+ parameter.indirectConversion.flatMap {
+ indirectVariables.append((javaParameterName, $0.render(&printer, javaParameterName)))
+ }
+
+ switch parameter.conversionCheck {
+ case .check32BitIntOverflow:
+ int32OverflowChecks.append(parameter.conversionCheck!.render(&printer, "indirect_\(javaParameterName)"))
+ case nil:
+ break
+ }
+ }
+
+ // Make indirect variables
+ for (name, lowered) in indirectVariables {
+ printer.print("let indirect_\(name) = \(lowered)")
+ }
+
+ if !int32OverflowChecks.isEmpty {
+ printer.print("#if _pointerBitWidth(_32)")
+
+ for check in int32OverflowChecks {
+ printer.printBraceBlock("guard \(check) else") { printer in
+ let dummyReturn = dummyReturn(for: nativeSignature)
+ printer.print("environment.throwJavaException(javaException: .integerOverflow)")
+ printer.print(dummyReturn)
+ }
+ }
+ printer.print("#endif")
}
// Callee
@@ -362,14 +394,7 @@ extension JNISwift2JavaGenerator {
}
if decl.isThrowing, !decl.isAsync {
- let dummyReturn: String
-
- if nativeSignature.result.javaType.isVoid {
- dummyReturn = ""
- } else {
- // We assume it is something that implements JavaValue
- dummyReturn = "return \(nativeSignature.result.javaType.swiftTypeName(resolver: { _ in "" })).jniPlaceholderValue"
- }
+ let dummyReturn = dummyReturn(for: nativeSignature)
printer.print("do {")
printer.indent()
@@ -390,6 +415,15 @@ extension JNISwift2JavaGenerator {
}
}
+ private func dummyReturn(for nativeSignature: NativeFunctionSignature) -> String {
+ return if nativeSignature.result.javaType.isVoid {
+ "return"
+ } else {
+ // We assume it is something that implements JavaValue
+ "return \(nativeSignature.result.javaType.swiftTypeName(resolver: { _ in "" })).jniPlaceholderValue"
+ }
+ }
+
private func printCDecl(
_ printer: inout CodePrinter,
_ translatedDecl: TranslatedFunctionDecl,
diff --git a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
index b2f8d6eac..5852446af 100644
--- a/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
+++ b/Sources/JExtractSwiftLib/SwiftTypes/SwiftType.swift
@@ -103,13 +103,24 @@ enum SwiftType: Equatable {
switch self {
case .nominal(let nominal):
switch nominal.nominalTypeDecl.knownTypeKind {
- case .uint8, .uint16, .uint32, .uint64: true
+ case .uint8, .uint16, .uint32, .uint64, .uint: true
default: false
}
default: false
}
}
+ var isArchDependingInteger: Bool {
+ switch self {
+ case .nominal(let nominal):
+ switch nominal.nominalTypeDecl.knownTypeKind {
+ case .int, .uint: true
+ default: false
+ }
+ default: false
+ }
+ }
+
var isRawTypeCompatible: Bool {
switch self {
case .nominal(let nominal):
diff --git a/Sources/JavaTypes/JavaException.swift b/Sources/JavaTypes/JavaException.swift
new file mode 100644
index 000000000..266ff9ab9
--- /dev/null
+++ b/Sources/JavaTypes/JavaException.swift
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+/// Describes a Java exception class (e.g. `SwiftIntegerOverflowException`)
+public struct JavaException: Equatable, Hashable {
+ public let type: JavaType
+ public let message: String?
+
+ public init(className name: some StringProtocol, message: String? = nil) {
+ self.type = JavaType(className: name)
+ self.message = message
+ }
+}
+
+extension JavaException {
+ public static var integerOverflow: JavaException {
+ JavaException(className: "org.swift.swiftkit.core.SwiftIntegerOverflowException")
+ }
+}
diff --git a/Sources/SwiftJava/Exceptions/ExceptionHandling.swift b/Sources/SwiftJava/Exceptions/ExceptionHandling.swift
index d5ca11078..447b82fe7 100644
--- a/Sources/SwiftJava/Exceptions/ExceptionHandling.swift
+++ b/Sources/SwiftJava/Exceptions/ExceptionHandling.swift
@@ -12,6 +12,8 @@
//
//===----------------------------------------------------------------------===//
+import struct JavaTypes.JavaException
+
extension JNIEnvironment {
/// Execute a JNI call and check for an exception at the end. Translate
/// any Java exception into an error.
@@ -43,4 +45,16 @@ extension JNIEnvironment {
interface.ThrowNew(self, exceptionClass, String(describing: error))
}
}
+
+ public func throwJavaException(javaException: JavaException) {
+ guard let exceptionClass = self.interface.FindClass(self, javaException.type.className!) else {
+ // Otherwise, create a exception with a message.
+ _ = try! Exception.withJNIClass(in: self) { exceptionClass in
+ interface.ThrowNew(self, exceptionClass, "An exception(\(String(describing: javaException))) occured!")
+ }
+ return
+ }
+
+ _ = interface.ThrowNew(self, exceptionClass, javaException.message ?? "")
+ }
}
diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java
new file mode 100644
index 000000000..529721b59
--- /dev/null
+++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+package org.swift.swiftkit.core;
+
+public class SwiftIntegerOverflowException extends RuntimeException {
+ public SwiftIntegerOverflowException() {
+ super("Swift runtime has detected IntegerOverflow! Most probably you are running 32-bit application while using Swift's Int type.");
+ }
+}
\ No newline at end of file
diff --git a/Tests/JExtractSwiftTests/JNI/JNIConversionChecksTests.swift b/Tests/JExtractSwiftTests/JNI/JNIConversionChecksTests.swift
new file mode 100644
index 000000000..d30226d55
--- /dev/null
+++ b/Tests/JExtractSwiftTests/JNI/JNIConversionChecksTests.swift
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+@testable import JExtractSwiftLib
+import Testing
+
+struct JNIConversionChecksTests {
+ private let signedSource = """
+ public struct MyStruct {
+ public var normalInt: Int = 0
+
+ public init(normalInt: Int) {
+ self.normalInt = normalInt
+ }
+ }
+ """
+ private let unsignedSource = """
+ public struct MyStruct {
+ public var unsignedInt: UInt = 0
+
+ public init(unsignedInt: UInt) {
+ self.unsignedInt = unsignedInt
+ }
+ }
+ """
+
+ @Test func generatesUnsignedSetterWithCheck() throws {
+ try assertOutput(input: unsignedSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyStruct__00024setUnsignedInt__JJ")
+ func Java_com_example_swift_MyStruct__00024setUnsignedInt__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) {
+ let indirect_newValue = UInt64(fromJNI: newValue, in: environment)
+ #if _pointerBitWidth(_32)
+ guard indirect_newValue >= UInt32.min && indirect_newValue <= UInt32.max else {
+ environment.throwJavaException(javaException: .integerOverflow)
+ return
+ """,
+ """
+ #endif
+ assert(self != 0, "self memory address was null")
+ let selfBits$ = Int(Int64(fromJNI: self, in: environment))
+ let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
+ guard let self$ else {
+ fatalError("self memory address was null in call to \\(#function)!")
+ }
+ self$.pointee.unsignedInt = UInt(indirect_newValue)
+ """
+ ])
+ }
+
+ @Test func generatesUnsignedGetterWithoutCheck() throws {
+ try assertOutput(input: unsignedSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyStruct__00024getUnsignedInt__J")
+ func Java_com_example_swift_MyStruct__00024getUnsignedInt__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong {
+ assert(self != 0, "self memory address was null")
+ let selfBits$ = Int(Int64(fromJNI: self, in: environment))
+ let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
+ guard let self$ else {
+ fatalError("self memory address was null in call to \\(#function)!")
+ }
+ return self$.pointee.unsignedInt.getJNIValue(in: environment)
+ """
+ ])
+ }
+
+ @Test func generatesSignedSetterWithCheck() throws {
+ try assertOutput(input: signedSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyStruct__00024setNormalInt__JJ")
+ func Java_com_example_swift_MyStruct__00024setNormalInt__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) {
+ let indirect_newValue = Int64(fromJNI: newValue, in: environment)
+ #if _pointerBitWidth(_32)
+ guard indirect_newValue >= Int32.min && indirect_newValue <= Int32.max else {
+ environment.throwJavaException(javaException: .integerOverflow)
+ return
+ """,
+ """
+ #endif
+ assert(self != 0, "self memory address was null")
+ let selfBits$ = Int(Int64(fromJNI: self, in: environment))
+ let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
+ guard let self$ else {
+ fatalError("self memory address was null in call to \\(#function)!")
+ }
+ self$.pointee.normalInt = Int(indirect_newValue)
+ """
+ ])
+ }
+}
From 9aed0c3c93eef4b8196027fe29da0115dce3d7e1 Mon Sep 17 00:00:00 2001
From: pelekon <13712101+pelekon@users.noreply.github.com>
Date: Sat, 29 Nov 2025 17:00:17 +0100
Subject: [PATCH 3/5] Add documentation for SwiftIntegerOverflowException (ngl,
generated with ChatGPT)
---
.../core/SwiftIntegerOverflowException.java | 20 +++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java
index 529721b59..d6ba54469 100644
--- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java
+++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java
@@ -14,6 +14,26 @@
package org.swift.swiftkit.core;
+/**
+ * Exception thrown when a Swift runtime detects integer overflow,
+ * most likely caused by running a 32-bit application while using Swift's Int type.
+ *
+ * This custom unchecked exception is intended to signal a platform incompatibility
+ * between Swift's Int expectations and the underlying Java runtime architecture. It is typically
+ * thrown automatically by underlaying code for method.
+ *
+ *
+ *
+ * Inheritance hierarchy:
+ *
+ * - {@link java.lang.RuntimeException}
+ * - SwiftIntegerOverflowException
+ *
+ *
+ *
+ * @see java.lang.RuntimeException
+ * @see Swift Int documentation
+ */
public class SwiftIntegerOverflowException extends RuntimeException {
public SwiftIntegerOverflowException() {
super("Swift runtime has detected IntegerOverflow! Most probably you are running 32-bit application while using Swift's Int type.");
From 269e96a9b2ae913ff6564a584eeca983ce15a0d1 Mon Sep 17 00:00:00 2001
From: pelekon <13712101+pelekon@users.noreply.github.com>
Date: Sat, 29 Nov 2025 17:14:43 +0100
Subject: [PATCH 4/5] Review changes.
---
.../JNI/JNIJavaTypeTranslator.swift | 2 +-
.../JNISwift2JavaGenerator+JavaTranslation.swift | 6 +++---
.../JNISwift2JavaGenerator+NativeTranslation.swift | 7 ++++---
.../JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 13 ++++++-------
.../JNI/JNISwift2JavaGenerator.swift | 6 ++++++
...{JavaException.swift => JavaExceptionType.swift} | 8 ++++----
6 files changed, 24 insertions(+), 18 deletions(-)
rename Sources/JavaTypes/{JavaException.swift => JavaExceptionType.swift} (77%)
diff --git a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
index fa380b121..fd6873266 100644
--- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift
@@ -57,7 +57,7 @@ enum JNIJavaTypeTranslator {
}
}
- static func indirectConversionSetepSwiftType(for knownKind: SwiftKnownTypeDeclKind, from knownTypes: SwiftKnownTypes) -> SwiftType? {
+ static func indirectConversionStepSwiftType(for knownKind: SwiftKnownTypeDeclKind, from knownTypes: SwiftKnownTypes) -> SwiftType? {
switch knownKind {
case .int: knownTypes.int64
case .uint: knownTypes.uint64
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift
index 32f1c4386..814da881c 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+JavaTranslation.swift
@@ -114,7 +114,7 @@ extension JNISwift2JavaGenerator {
.class(package: nil,name: caseName)
)
])
- var exceptions: [JavaException] = []
+ var exceptions: [JavaExceptionType] = []
if enumCase.parameters.contains(where: \.type.isArchDependingInteger) {
exceptions.append(.integerOverflow)
@@ -300,7 +300,7 @@ extension JNISwift2JavaGenerator {
genericRequirements: functionSignature.genericRequirements
)
- var exceptions: [JavaException] = []
+ var exceptions: [JavaExceptionType] = []
if functionSignature.parameters.contains(where: \.type.isArchDependingInteger) {
exceptions.append(.integerOverflow)
@@ -986,7 +986,7 @@ extension JNISwift2JavaGenerator {
var selfParameter: TranslatedParameter?
var parameters: [TranslatedParameter]
var resultType: TranslatedResult
- var exceptions: [JavaException]
+ var exceptions: [JavaExceptionType]
// if the result type implied any annotations,
// propagate them onto the function the result is returned from
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift
index 494207f01..ab2ea9211 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift
@@ -103,7 +103,7 @@ extension JNISwift2JavaGenerator {
throw JavaTranslationError.unsupportedSwiftType(type)
}
- let indirectStepType = JNIJavaTypeTranslator.indirectConversionSetepSwiftType(for: knownType, from: knownTypes)
+ let indirectStepType = JNIJavaTypeTranslator.indirectConversionStepSwiftType(for: knownType, from: knownTypes)
let indirectCheck = JNIJavaTypeTranslator.checkStep(for: knownType, from: knownTypes)
return NativeParameter(
@@ -644,7 +644,8 @@ extension JNISwift2JavaGenerator {
/// Represents how to convert the JNI parameter to a Swift parameter
let conversion: NativeSwiftConversionStep
- /// Represents swift type for indirect variable used in required checks, e.g Int64 for Int overflow check on 32-bit platforms
+ /// Represents swift type for conversion checks. This will introduce a new name$indirect variable used in required checks.
+ /// e.g Int64 for Int overflow check on 32-bit platforms
let indirectConversion: NativeSwiftConversionStep?
/// Represents check operations executed in if/guard conditional block for check during conversion
@@ -1061,7 +1062,7 @@ extension JNISwift2JavaGenerator {
}
return printer.finalize()
case .labelessAssignmentOfVariable(let name, let swiftType):
- return "\(swiftType)(indirect_\(name.render(&printer, placeholder)))"
+ return "\(swiftType)(\(JNISwift2JavaGenerator.indirectVariableName(for: name.render(&printer, placeholder)))"
}
}
}
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift
index d6e6ad392..f1254a4bc 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift
@@ -308,7 +308,9 @@ extension JNISwift2JavaGenerator {
switch parameter.conversionCheck {
case .check32BitIntOverflow:
- int32OverflowChecks.append(parameter.conversionCheck!.render(&printer, "indirect_\(javaParameterName)"))
+ int32OverflowChecks.append(
+ parameter.conversionCheck!.render(
+ &printer, JNISwift2JavaGenerator.indirectVariableName(for: javaParameterName)))
case nil:
break
}
@@ -316,7 +318,7 @@ extension JNISwift2JavaGenerator {
// Make indirect variables
for (name, lowered) in indirectVariables {
- printer.print("let indirect_\(name) = \(lowered)")
+ printer.print("let \(JNISwift2JavaGenerator.indirectVariableName(for: name)) = \(lowered)")
}
if !int32OverflowChecks.isEmpty {
@@ -324,9 +326,8 @@ extension JNISwift2JavaGenerator {
for check in int32OverflowChecks {
printer.printBraceBlock("guard \(check) else") { printer in
- let dummyReturn = dummyReturn(for: nativeSignature)
printer.print("environment.throwJavaException(javaException: .integerOverflow)")
- printer.print(dummyReturn)
+ printer.print(dummyReturn(for: nativeSignature))
}
}
printer.print("#endif")
@@ -394,8 +395,6 @@ extension JNISwift2JavaGenerator {
}
if decl.isThrowing, !decl.isAsync {
- let dummyReturn = dummyReturn(for: nativeSignature)
-
printer.print("do {")
printer.indent()
printer.print(innerBody(in: &printer))
@@ -405,7 +404,7 @@ extension JNISwift2JavaGenerator {
printer.print(
"""
environment.throwAsException(error)
- \(dummyReturn)
+ \(dummyReturn(for: nativeSignature))
"""
)
printer.outdent()
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
index 3b84cfb97..eb5382d50 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator.swift
@@ -91,3 +91,9 @@ package class JNISwift2JavaGenerator: Swift2JavaGenerator {
}
}
}
+
+extension JNISwift2JavaGenerator {
+ static func indirectVariableName(for parameterName: String) -> String {
+ "\(parameterName)$indirect"
+ }
+}
diff --git a/Sources/JavaTypes/JavaException.swift b/Sources/JavaTypes/JavaExceptionType.swift
similarity index 77%
rename from Sources/JavaTypes/JavaException.swift
rename to Sources/JavaTypes/JavaExceptionType.swift
index 266ff9ab9..9bfc1a9fb 100644
--- a/Sources/JavaTypes/JavaException.swift
+++ b/Sources/JavaTypes/JavaExceptionType.swift
@@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//
/// Describes a Java exception class (e.g. `SwiftIntegerOverflowException`)
-public struct JavaException: Equatable, Hashable {
+public struct JavaExceptionType: Equatable, Hashable {
public let type: JavaType
public let message: String?
@@ -23,8 +23,8 @@ public struct JavaException: Equatable, Hashable {
}
}
-extension JavaException {
- public static var integerOverflow: JavaException {
- JavaException(className: "org.swift.swiftkit.core.SwiftIntegerOverflowException")
+extension JavaExceptionType {
+ public static var integerOverflow: JavaExceptionType {
+ JavaExceptionType(className: "org.swift.swiftkit.core.SwiftIntegerOverflowException")
}
}
From 291e5479ac4fdf56dfb88e16a4e740040d8ada08 Mon Sep 17 00:00:00 2001
From: pelekon <13712101+pelekon@users.noreply.github.com>
Date: Sat, 29 Nov 2025 19:17:06 +0100
Subject: [PATCH 5/5] More tests.
---
.../MySwiftLibrary/EnumWithValueCases.swift | 17 ++
.../MySwiftLibrary/ObjectWithInts.swift | 27 ++
.../example/swift/EnumWithValueCasesTest.java | 34 +++
.../com/example/swift/ObjectWithIntsTest.java | 42 ++++
...wift2JavaGenerator+NativeTranslation.swift | 2 +-
.../BridgedValues/JavaValue+Integers.swift | 115 +++++++++
.../Exceptions/ExceptionHandling.swift | 4 +-
.../JNI/JNIConversionChecksTests.swift | 101 --------
.../JNI/JNIIntConversionChecksTests.swift | 231 ++++++++++++++++++
9 files changed, 469 insertions(+), 104 deletions(-)
create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/EnumWithValueCases.swift
create mode 100644 Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ObjectWithInts.swift
create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumWithValueCasesTest.java
create mode 100644 Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ObjectWithIntsTest.java
delete mode 100644 Tests/JExtractSwiftTests/JNI/JNIConversionChecksTests.swift
create mode 100644 Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift
diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/EnumWithValueCases.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/EnumWithValueCases.swift
new file mode 100644
index 000000000..1faa2c628
--- /dev/null
+++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/EnumWithValueCases.swift
@@ -0,0 +1,17 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+public enum EnumWithValueCases {
+ case firstCase(UInt)
+}
diff --git a/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ObjectWithInts.swift b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ObjectWithInts.swift
new file mode 100644
index 000000000..f77dc24ba
--- /dev/null
+++ b/Samples/SwiftJavaExtractJNISampleApp/Sources/MySwiftLibrary/ObjectWithInts.swift
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+public final class ObjectWithInts {
+ public var normalInt: Int
+ public var unsignedInt: UInt
+
+ public init(normalInt: Int, unsignedInt: UInt) {
+ self.normalInt = normalInt
+ self.unsignedInt = unsignedInt
+ }
+
+ public func callMe(arg: UInt) -> UInt {
+ return arg
+ }
+}
diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumWithValueCasesTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumWithValueCasesTest.java
new file mode 100644
index 000000000..840e7b64f
--- /dev/null
+++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/EnumWithValueCasesTest.java
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.swift;
+
+import org.junit.jupiter.api.Test;
+import org.swift.swiftkit.core.ConfinedSwiftMemorySession;
+import org.swift.swiftkit.core.SwiftArena;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class EnumWithValueCasesTest {
+ @Test
+ void fn() {
+ try (var arena = SwiftArena.ofConfined()) {
+ EnumWithValueCases e = EnumWithValueCases.firstCase(48, arena);
+ EnumWithValueCases.FirstCase c = (EnumWithValueCases.FirstCase) e.getCase();
+ assertNotNull(c);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ObjectWithIntsTest.java b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ObjectWithIntsTest.java
new file mode 100644
index 000000000..e43e2c7da
--- /dev/null
+++ b/Samples/SwiftJavaExtractJNISampleApp/src/test/java/com/example/swift/ObjectWithIntsTest.java
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+package com.example.swift;
+
+import org.junit.jupiter.api.Test;
+import org.swift.swiftkit.core.ConfinedSwiftMemorySession;
+import org.swift.swiftkit.core.SwiftArena;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ObjectWithIntsTest {
+ @Test
+ void init() {
+ try (var arena = SwiftArena.ofConfined()) {
+ ObjectWithInts obj = ObjectWithInts.init(-45, 45, arena);
+ assertEquals(-45, obj.getNormalInt());
+ assertEquals(45, obj.getUnsignedInt());
+ }
+ }
+
+ @Test
+ void callMe() {
+ try (var arena = SwiftArena.ofConfined()) {
+ ObjectWithInts obj = ObjectWithInts.init(-45, 45, arena);
+ assertEquals(66, obj.callMe(66));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift
index ab2ea9211..0ab5d8734 100644
--- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift
+++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift
@@ -1062,7 +1062,7 @@ extension JNISwift2JavaGenerator {
}
return printer.finalize()
case .labelessAssignmentOfVariable(let name, let swiftType):
- return "\(swiftType)(\(JNISwift2JavaGenerator.indirectVariableName(for: name.render(&printer, placeholder)))"
+ return "\(swiftType)(\(JNISwift2JavaGenerator.indirectVariableName(for: name.render(&printer, placeholder))))"
}
}
}
diff --git a/Sources/SwiftJava/BridgedValues/JavaValue+Integers.swift b/Sources/SwiftJava/BridgedValues/JavaValue+Integers.swift
index 005753eb6..b134a4236 100644
--- a/Sources/SwiftJava/BridgedValues/JavaValue+Integers.swift
+++ b/Sources/SwiftJava/BridgedValues/JavaValue+Integers.swift
@@ -523,6 +523,64 @@ extension Int: JavaValue {
0
}
}
+extension UInt: JavaValue {
+
+ public typealias JNIType = jint
+
+ public static var jvalueKeyPath: WritableKeyPath { \.i }
+
+ public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) }
+
+ public init(fromJNI value: JNIType, in environment: JNIEnvironment) {
+ self = UInt(value)
+ }
+
+ public static var javaType: JavaType { .int }
+
+ public static func jniMethodCall(
+ in environment: JNIEnvironment
+ ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) {
+ environment.interface.CallIntMethodA
+ }
+
+ public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet {
+ environment.interface.GetIntField
+ }
+
+ public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet {
+ environment.interface.SetIntField
+ }
+
+ public static func jniStaticMethodCall(
+ in environment: JNIEnvironment
+ ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) {
+ environment.interface.CallStaticIntMethodA
+ }
+
+ public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet {
+ environment.interface.GetStaticIntField
+ }
+
+ public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet {
+ environment.interface.SetStaticIntField
+ }
+
+ public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray {
+ environment.interface.NewIntArray
+ }
+
+ public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion {
+ environment.interface.GetIntArrayRegion
+ }
+
+ public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion {
+ environment.interface.SetIntArrayRegion
+ }
+
+ public static var jniPlaceholderValue: jint {
+ 0
+ }
+}
#elseif _pointerBitWidth(_64)
extension Int: JavaValue {
public typealias JNIType = jlong
@@ -581,4 +639,61 @@ extension Int: JavaValue {
0
}
}
+extension UInt: JavaValue {
+ public typealias JNIType = jlong
+
+ public static var jvalueKeyPath: WritableKeyPath { \.j }
+
+ public func getJNIValue(in environment: JNIEnvironment) -> JNIType { JNIType(self) }
+
+ public init(fromJNI value: JNIType, in environment: JNIEnvironment) {
+ self = UInt(value)
+ }
+
+ public static var javaType: JavaType { .long }
+
+ public static func jniMethodCall(
+ in environment: JNIEnvironment
+ ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) {
+ environment.interface.CallLongMethodA
+ }
+
+ public static func jniFieldGet(in environment: JNIEnvironment) -> JNIFieldGet {
+ environment.interface.GetLongField
+ }
+
+ public static func jniFieldSet(in environment: JNIEnvironment) -> JNIFieldSet {
+ environment.interface.SetLongField
+ }
+
+ public static func jniStaticMethodCall(
+ in environment: JNIEnvironment
+ ) -> ((JNIEnvironment, jobject, jmethodID, UnsafePointer?) -> JNIType) {
+ environment.interface.CallStaticLongMethodA
+ }
+
+ public static func jniStaticFieldGet(in environment: JNIEnvironment) -> JNIStaticFieldGet {
+ environment.interface.GetStaticLongField
+ }
+
+ public static func jniStaticFieldSet(in environment: JNIEnvironment) -> JNIStaticFieldSet {
+ environment.interface.SetStaticLongField
+ }
+
+ public static func jniNewArray(in environment: JNIEnvironment) -> JNINewArray {
+ environment.interface.NewLongArray
+ }
+
+ public static func jniGetArrayRegion(in environment: JNIEnvironment) -> JNIGetArrayRegion {
+ environment.interface.GetLongArrayRegion
+ }
+
+ public static func jniSetArrayRegion(in environment: JNIEnvironment) -> JNISetArrayRegion {
+ environment.interface.SetLongArrayRegion
+ }
+
+ public static var jniPlaceholderValue: jlong {
+ 0
+ }
+}
#endif
diff --git a/Sources/SwiftJava/Exceptions/ExceptionHandling.swift b/Sources/SwiftJava/Exceptions/ExceptionHandling.swift
index 447b82fe7..759e47466 100644
--- a/Sources/SwiftJava/Exceptions/ExceptionHandling.swift
+++ b/Sources/SwiftJava/Exceptions/ExceptionHandling.swift
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import struct JavaTypes.JavaException
+import struct JavaTypes.JavaExceptionType
extension JNIEnvironment {
/// Execute a JNI call and check for an exception at the end. Translate
@@ -46,7 +46,7 @@ extension JNIEnvironment {
}
}
- public func throwJavaException(javaException: JavaException) {
+ public func throwJavaException(javaException: JavaExceptionType) {
guard let exceptionClass = self.interface.FindClass(self, javaException.type.className!) else {
// Otherwise, create a exception with a message.
_ = try! Exception.withJNIClass(in: self) { exceptionClass in
diff --git a/Tests/JExtractSwiftTests/JNI/JNIConversionChecksTests.swift b/Tests/JExtractSwiftTests/JNI/JNIConversionChecksTests.swift
deleted file mode 100644
index d30226d55..000000000
--- a/Tests/JExtractSwiftTests/JNI/JNIConversionChecksTests.swift
+++ /dev/null
@@ -1,101 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// This source file is part of the Swift.org open source project
-//
-// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
-// Licensed under Apache License v2.0
-//
-// See LICENSE.txt for license information
-// See CONTRIBUTORS.txt for the list of Swift.org project authors
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-//===----------------------------------------------------------------------===//
-
-@testable import JExtractSwiftLib
-import Testing
-
-struct JNIConversionChecksTests {
- private let signedSource = """
- public struct MyStruct {
- public var normalInt: Int = 0
-
- public init(normalInt: Int) {
- self.normalInt = normalInt
- }
- }
- """
- private let unsignedSource = """
- public struct MyStruct {
- public var unsignedInt: UInt = 0
-
- public init(unsignedInt: UInt) {
- self.unsignedInt = unsignedInt
- }
- }
- """
-
- @Test func generatesUnsignedSetterWithCheck() throws {
- try assertOutput(input: unsignedSource, .jni, .swift, expectedChunks: [
- """
- @_cdecl("Java_com_example_swift_MyStruct__00024setUnsignedInt__JJ")
- func Java_com_example_swift_MyStruct__00024setUnsignedInt__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) {
- let indirect_newValue = UInt64(fromJNI: newValue, in: environment)
- #if _pointerBitWidth(_32)
- guard indirect_newValue >= UInt32.min && indirect_newValue <= UInt32.max else {
- environment.throwJavaException(javaException: .integerOverflow)
- return
- """,
- """
- #endif
- assert(self != 0, "self memory address was null")
- let selfBits$ = Int(Int64(fromJNI: self, in: environment))
- let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
- guard let self$ else {
- fatalError("self memory address was null in call to \\(#function)!")
- }
- self$.pointee.unsignedInt = UInt(indirect_newValue)
- """
- ])
- }
-
- @Test func generatesUnsignedGetterWithoutCheck() throws {
- try assertOutput(input: unsignedSource, .jni, .swift, expectedChunks: [
- """
- @_cdecl("Java_com_example_swift_MyStruct__00024getUnsignedInt__J")
- func Java_com_example_swift_MyStruct__00024getUnsignedInt__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong {
- assert(self != 0, "self memory address was null")
- let selfBits$ = Int(Int64(fromJNI: self, in: environment))
- let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
- guard let self$ else {
- fatalError("self memory address was null in call to \\(#function)!")
- }
- return self$.pointee.unsignedInt.getJNIValue(in: environment)
- """
- ])
- }
-
- @Test func generatesSignedSetterWithCheck() throws {
- try assertOutput(input: signedSource, .jni, .swift, expectedChunks: [
- """
- @_cdecl("Java_com_example_swift_MyStruct__00024setNormalInt__JJ")
- func Java_com_example_swift_MyStruct__00024setNormalInt__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) {
- let indirect_newValue = Int64(fromJNI: newValue, in: environment)
- #if _pointerBitWidth(_32)
- guard indirect_newValue >= Int32.min && indirect_newValue <= Int32.max else {
- environment.throwJavaException(javaException: .integerOverflow)
- return
- """,
- """
- #endif
- assert(self != 0, "self memory address was null")
- let selfBits$ = Int(Int64(fromJNI: self, in: environment))
- let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
- guard let self$ else {
- fatalError("self memory address was null in call to \\(#function)!")
- }
- self$.pointee.normalInt = Int(indirect_newValue)
- """
- ])
- }
-}
diff --git a/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift b/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift
new file mode 100644
index 000000000..ed57079e5
--- /dev/null
+++ b/Tests/JExtractSwiftTests/JNI/JNIIntConversionChecksTests.swift
@@ -0,0 +1,231 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of Swift.org project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+@testable import JExtractSwiftLib
+import Testing
+
+struct JNIIntConversionChecksTests {
+ private let signedSource = """
+ public struct MyStruct {
+ public var normalInt: Int = 0
+
+ public init(normalInt: Int) {
+ self.normalInt = normalInt
+ }
+ }
+ """
+ private let unsignedSource = """
+ public struct MyStruct {
+ public var unsignedInt: UInt = 0
+
+ public init(unsignedInt: UInt) {
+ self.unsignedInt = unsignedInt
+ }
+ }
+ """
+ private let signedFuncSource = """
+ public struct MyStruct {
+ public func dummyFunc(arg: Int) -> Int {
+ return arg
+ }
+ }
+ """
+ private let unsignedFuncSource = """
+ public struct MyStruct {
+ public func dummyFunc(arg: UInt) -> UInt {
+ return arg
+ }
+ }
+ """
+ private let enumSource = """
+ public enum MyEnum {
+ case firstCase
+ case secondCase(UInt)
+ }
+ """
+
+ @Test func generatesInitWithSignedCheck() throws {
+ try assertOutput(input: signedSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyStruct__00024init__J")
+ func Java_com_example_swift_MyStruct__00024init__J(environment: UnsafeMutablePointer!, thisClass: jclass, normalInt: jlong) -> jlong {
+ let normalInt$indirect = Int64(fromJNI: normalInt, in: environment)
+ #if _pointerBitWidth(_32)
+ guard normalInt$indirect >= Int32.min && normalInt$indirect <= Int32.max else {
+ environment.throwJavaException(javaException: .integerOverflow)
+ return Int64.jniPlaceholderValue
+ """,
+ """
+ #endif
+ let result$ = UnsafeMutablePointer.allocate(capacity: 1)
+ result$.initialize(to: MyStruct.init(normalInt: Int(normalInt$indirect)))
+ let resultBits$ = Int64(Int(bitPattern: result$))
+ return resultBits$.getJNIValue(in: environment)
+ """
+ ])
+ }
+
+ @Test func geberatesInitWithUnsignedCheck() throws {
+ try assertOutput(input: unsignedSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyStruct__00024init__J")
+ func Java_com_example_swift_MyStruct__00024init__J(environment: UnsafeMutablePointer!, thisClass: jclass, unsignedInt: jlong) -> jlong {
+ let unsignedInt$indirect = UInt64(fromJNI: unsignedInt, in: environment)
+ #if _pointerBitWidth(_32)
+ guard unsignedInt$indirect >= UInt32.min && unsignedInt$indirect <= UInt32.max else {
+ environment.throwJavaException(javaException: .integerOverflow)
+ return Int64.jniPlaceholderValue
+ """,
+ """
+ #endif
+ let result$ = UnsafeMutablePointer.allocate(capacity: 1)
+ result$.initialize(to: MyStruct.init(unsignedInt: UInt(unsignedInt$indirect)))
+ let resultBits$ = Int64(Int(bitPattern: result$))
+ return resultBits$.getJNIValue(in: environment)
+ """
+ ])
+ }
+
+ @Test func generatesUnsignedSetterWithCheck() throws {
+ try assertOutput(input: unsignedSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyStruct__00024setUnsignedInt__JJ")
+ func Java_com_example_swift_MyStruct__00024setUnsignedInt__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) {
+ let newValue$indirect = UInt64(fromJNI: newValue, in: environment)
+ #if _pointerBitWidth(_32)
+ guard newValue$indirect >= UInt32.min && newValue$indirect <= UInt32.max else {
+ environment.throwJavaException(javaException: .integerOverflow)
+ return
+ """,
+ """
+ #endif
+ assert(self != 0, "self memory address was null")
+ let selfBits$ = Int(Int64(fromJNI: self, in: environment))
+ let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
+ guard let self$ else {
+ fatalError("self memory address was null in call to \\(#function)!")
+ }
+ self$.pointee.unsignedInt = UInt(newValue$indirect)
+ """
+ ])
+ }
+
+ @Test func generatesUnsignedGetterWithoutCheck() throws {
+ try assertOutput(input: unsignedSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyStruct__00024getUnsignedInt__J")
+ func Java_com_example_swift_MyStruct__00024getUnsignedInt__J(environment: UnsafeMutablePointer!, thisClass: jclass, self: jlong) -> jlong {
+ assert(self != 0, "self memory address was null")
+ let selfBits$ = Int(Int64(fromJNI: self, in: environment))
+ let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
+ guard let self$ else {
+ fatalError("self memory address was null in call to \\(#function)!")
+ }
+ return self$.pointee.unsignedInt.getJNIValue(in: environment)
+ """
+ ])
+ }
+
+ @Test func generatesSignedSetterWithCheck() throws {
+ try assertOutput(input: signedSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyStruct__00024setNormalInt__JJ")
+ func Java_com_example_swift_MyStruct__00024setNormalInt__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, newValue: jlong, self: jlong) {
+ let newValue$indirect = Int64(fromJNI: newValue, in: environment)
+ #if _pointerBitWidth(_32)
+ guard newValue$indirect >= Int32.min && newValue$indirect <= Int32.max else {
+ environment.throwJavaException(javaException: .integerOverflow)
+ return
+ """,
+ """
+ #endif
+ assert(self != 0, "self memory address was null")
+ let selfBits$ = Int(Int64(fromJNI: self, in: environment))
+ let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
+ guard let self$ else {
+ fatalError("self memory address was null in call to \\(#function)!")
+ }
+ self$.pointee.normalInt = Int(newValue$indirect)
+ """
+ ])
+ }
+
+ @Test func generatesFuncWithSignedCheck() throws {
+ try assertOutput(input: signedFuncSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyStruct__00024dummyFunc__JJ")
+ func Java_com_example_swift_MyStruct__00024dummyFunc__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jlong, self: jlong) -> jlong {
+ let arg$indirect = Int64(fromJNI: arg, in: environment)
+ #if _pointerBitWidth(_32)
+ guard arg$indirect >= Int32.min && arg$indirect <= Int32.max else {
+ environment.throwJavaException(javaException: .integerOverflow)
+ return Int64.jniPlaceholderValue
+ """,
+ """
+ #endif
+ assert(self != 0, "self memory address was null")
+ let selfBits$ = Int(Int64(fromJNI: self, in: environment))
+ let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
+ guard let self$ else {
+ fatalError("self memory address was null in call to \\(#function)!")
+ }
+ return self$.pointee.dummyFunc(arg: Int(arg$indirect)).getJNIValue(in: environment)
+ """
+ ])
+ }
+
+ @Test func generatesFuncWithUnsignedCheck() throws {
+ try assertOutput(input: unsignedFuncSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyStruct__00024dummyFunc__JJ")
+ func Java_com_example_swift_MyStruct__00024dummyFunc__JJ(environment: UnsafeMutablePointer!, thisClass: jclass, arg: jlong, self: jlong) -> jlong {
+ let arg$indirect = UInt64(fromJNI: arg, in: environment)
+ #if _pointerBitWidth(_32)
+ guard arg$indirect >= UInt32.min && arg$indirect <= UInt32.max else {
+ environment.throwJavaException(javaException: .integerOverflow)
+ return Int64.jniPlaceholderValue
+ """,
+ """
+ assert(self != 0, "self memory address was null")
+ let selfBits$ = Int(Int64(fromJNI: self, in: environment))
+ let self$ = UnsafeMutablePointer(bitPattern: selfBits$)
+ guard let self$ else {
+ fatalError("self memory address was null in call to \\(#function)!")
+ }
+ return self$.pointee.dummyFunc(arg: UInt(arg$indirect)).getJNIValue(in: environment)
+ """
+ ])
+ }
+
+ @Test func generatesEnumCaseWithUnsignedCheck() throws {
+ try assertOutput(input: enumSource, .jni, .swift, expectedChunks: [
+ """
+ @_cdecl("Java_com_example_swift_MyEnum__00024secondCase__J")
+ func Java_com_example_swift_MyEnum__00024secondCase__J(environment: UnsafeMutablePointer!, thisClass: jclass, arg0: jlong) -> jlong {
+ let arg0$indirect = UInt64(fromJNI: arg0, in: environment)
+ #if _pointerBitWidth(_32)
+ guard arg0$indirect >= UInt32.min && arg0$indirect <= UInt32.max else {
+ environment.throwJavaException(javaException: .integerOverflow)
+ return Int64.jniPlaceholderValue
+ """,
+ """
+ #endif
+ let result$ = UnsafeMutablePointer.allocate(capacity: 1)
+ result$.initialize(to: MyEnum.secondCase(UInt(arg0$indirect)))
+ let resultBits$ = Int64(Int(bitPattern: result$))
+ return resultBits$.getJNIValue(in: environment)
+ """
+ ])
+ }
+}