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/JNIJavaTypeTranslator.swift b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift index 9bd9a7d83..fd6873266 100644 --- a/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift +++ b/Sources/JExtractSwiftLib/JNI/JNIJavaTypeTranslator.swift @@ -39,6 +39,8 @@ enum JNIJavaTypeTranslator { case .int64: return .long case .uint64: return .long + + case .int, .uint: return .long case .float: return .float case .double: return .double @@ -46,8 +48,7 @@ enum JNIJavaTypeTranslator { case .string: return .javaLangString - case .int, .uint, // FIXME: why not supported int/uint? - .unsafeRawPointer, .unsafeMutableRawPointer, + case .unsafeRawPointer, .unsafeMutableRawPointer, .unsafePointer, .unsafeMutablePointer, .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer, .unsafeBufferPointer, .unsafeMutableBufferPointer, @@ -55,4 +56,38 @@ enum JNIJavaTypeTranslator { return nil } } + + static func indirectConversionStepSwiftType(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..814da881c 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: [JavaExceptionType] = [] + + 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: [JavaExceptionType] = [] + + 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: [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 1994dce04..0ab5d8734 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.indirectConversionStepSwiftType(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,13 @@ extension JNISwift2JavaGenerator { /// Represents how to convert the JNI parameter to a Swift parameter let conversion: NativeSwiftConversionStep + + /// 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 + let conversionCheck: NativeSwiftConversionCheck? } struct NativeResult { @@ -695,6 +729,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 +1061,20 @@ extension JNISwift2JavaGenerator { } } return printer.finalize() + case .labelessAssignmentOfVariable(let name, let swiftType): + return "\(swiftType)(\(JNISwift2JavaGenerator.indirectVariableName(for: 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..f1254a4bc 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -293,11 +293,44 @@ 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, JNISwift2JavaGenerator.indirectVariableName(for: javaParameterName))) + case nil: + break + } + } + + // Make indirect variables + for (name, lowered) in indirectVariables { + printer.print("let \(JNISwift2JavaGenerator.indirectVariableName(for: name)) = \(lowered)") + } + + if !int32OverflowChecks.isEmpty { + printer.print("#if _pointerBitWidth(_32)") + + for check in int32OverflowChecks { + printer.printBraceBlock("guard \(check) else") { printer in + printer.print("environment.throwJavaException(javaException: .integerOverflow)") + printer.print(dummyReturn(for: nativeSignature)) + } + } + printer.print("#endif") } // Callee @@ -362,15 +395,6 @@ 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" - } - printer.print("do {") printer.indent() printer.print(innerBody(in: &printer)) @@ -380,7 +404,7 @@ extension JNISwift2JavaGenerator { printer.print( """ environment.throwAsException(error) - \(dummyReturn) + \(dummyReturn(for: nativeSignature)) """ ) printer.outdent() @@ -390,6 +414,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/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/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/JavaExceptionType.swift b/Sources/JavaTypes/JavaExceptionType.swift new file mode 100644 index 000000000..9bfc1a9fb --- /dev/null +++ b/Sources/JavaTypes/JavaExceptionType.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 JavaExceptionType: 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 JavaExceptionType { + public static var integerOverflow: JavaExceptionType { + JavaExceptionType(className: "org.swift.swiftkit.core.SwiftIntegerOverflowException") + } +} 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 d5ca11078..759e47466 100644 --- a/Sources/SwiftJava/Exceptions/ExceptionHandling.swift +++ b/Sources/SwiftJava/Exceptions/ExceptionHandling.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import struct JavaTypes.JavaExceptionType + 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: 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 + 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..d6ba54469 --- /dev/null +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// 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; + +/** + * 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."); + } +} \ No newline at end of file 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) + """ + ]) + } +}