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) + """ + ]) + } +}