From e59eba28124de882b93b58d4de5953f4d3b98dab Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 28 Nov 2025 14:24:09 +0100 Subject: [PATCH 1/7] introduce JNI_OnLoad --- Sources/SwiftJava/generated/JavaThread.swift | 336 +++++++++++++++++++ Sources/SwiftJava/swift-java.config | 1 + Sources/SwiftJavaRuntimeSupport/JNI.swift | 18 + Sources/SwiftJavaRuntimeSupport/OnLoad.swift | 16 + 4 files changed, 371 insertions(+) create mode 100644 Sources/SwiftJava/generated/JavaThread.swift create mode 100644 Sources/SwiftJavaRuntimeSupport/JNI.swift create mode 100644 Sources/SwiftJavaRuntimeSupport/OnLoad.swift diff --git a/Sources/SwiftJava/generated/JavaThread.swift b/Sources/SwiftJava/generated/JavaThread.swift new file mode 100644 index 000000000..e49f2fb47 --- /dev/null +++ b/Sources/SwiftJava/generated/JavaThread.swift @@ -0,0 +1,336 @@ +// Auto-generated by Java-to-Swift wrapper generator. +import CSwiftJavaJNI + +@JavaClass("java.lang.Thread") +open class JavaThread: JavaObject { + @JavaMethod + @_nonoverride public convenience init(_ arg0: String, environment: JNIEnvironment? = nil) + + @JavaMethod + @_nonoverride public convenience init(environment: JNIEnvironment? = nil) + + @JavaMethod + open func getName() -> String + + @JavaMethod + open func run() + + @JavaMethod + open func interrupt() + + @JavaMethod + open override func toString() -> String + + @JavaMethod + open override func clone() throws -> JavaObject! + + @JavaMethod + open func join(_ arg0: Int64, _ arg1: Int32) throws + + @JavaMethod + open func join() throws + + @JavaMethod + open func join(_ arg0: Int64) throws + + @JavaMethod + open func setContextClassLoader(_ arg0: JavaClassLoader?) + + @JavaMethod + open func setPriority(_ arg0: Int32) + + @JavaMethod + open func setDaemon(_ arg0: Bool) + + @JavaMethod + open func start() + + @JavaMethod + open func getPriority() -> Int32 + + @JavaMethod + open func isDaemon() -> Bool + + @JavaMethod + open func getContextClassLoader() -> JavaClassLoader! + + @JavaMethod + open func isVirtual() -> Bool + + @JavaMethod + open func isAlive() -> Bool + + @JavaMethod + open func threadId() -> Int64 + + @JavaMethod + open func getUncaughtExceptionHandler() -> JavaThread.UncaughtExceptionHandler! + + @JavaMethod + open func stop() + + @JavaMethod + open func isInterrupted() -> Bool + + @JavaMethod + open func setName(_ arg0: String) + + @JavaMethod + open func checkAccess() + + @JavaMethod + open func getId() -> Int64 + + @JavaMethod + open func getState() -> JavaThread.State! + + @JavaMethod + open func setUncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) +} +extension JavaThread { + @JavaInterface("java.lang.Thread$Builder") + public struct Builder { + @JavaMethod + public func name(_ arg0: String) -> JavaThread.Builder! + + @JavaMethod + public func name(_ arg0: String, _ arg1: Int64) -> JavaThread.Builder! + + @JavaMethod + public func uncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) -> JavaThread.Builder! + + @JavaMethod + public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder! + } +} +extension JavaThread.Builder { + @JavaInterface("java.lang.Thread$Builder$OfPlatform", extends: JavaThread.Builder.self) + public struct OfPlatform { + @JavaMethod + public func name(_ arg0: String, _ arg1: Int64) -> JavaThread.Builder! + + @JavaMethod + public func name(_ arg0: String, _ arg1: Int64) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func name(_ arg0: String) -> JavaThread.Builder! + + @JavaMethod + public func name(_ arg0: String) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func priority(_ arg0: Int32) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func daemon() -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func daemon(_ arg0: Bool) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func uncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func uncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) -> JavaThread.Builder! + + @JavaMethod + public func stackSize(_ arg0: Int64) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder.OfPlatform! + + @JavaMethod + public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder! + } +} +extension JavaThread.Builder { + @JavaInterface("java.lang.Thread$Builder$OfVirtual", extends: JavaThread.Builder.self) + public struct OfVirtual { + @JavaMethod + public func name(_ arg0: String, _ arg1: Int64) -> JavaThread.Builder! + + @JavaMethod + public func name(_ arg0: String, _ arg1: Int64) -> JavaThread.Builder.OfVirtual! + + @JavaMethod + public func name(_ arg0: String) -> JavaThread.Builder! + + @JavaMethod + public func name(_ arg0: String) -> JavaThread.Builder.OfVirtual! + + @JavaMethod + public func uncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) -> JavaThread.Builder! + + @JavaMethod + public func uncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) -> JavaThread.Builder.OfVirtual! + + @JavaMethod + public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder.OfVirtual! + + @JavaMethod + public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder! + } +} +extension JavaThread { + @JavaClass("java.lang.Thread$State") + open class State: JavaObject { + public enum StateCases: Equatable { + case NEW + case RUNNABLE + case BLOCKED + case WAITING + case TIMED_WAITING + case TERMINATED + } + + public var enumValue: StateCases! { + let classObj = self.javaClass + if self.equals(classObj.NEW?.as(JavaObject.self)) { + return StateCases.NEW + } else if self.equals(classObj.RUNNABLE?.as(JavaObject.self)) { + return StateCases.RUNNABLE + } else if self.equals(classObj.BLOCKED?.as(JavaObject.self)) { + return StateCases.BLOCKED + } else if self.equals(classObj.WAITING?.as(JavaObject.self)) { + return StateCases.WAITING + } else if self.equals(classObj.TIMED_WAITING?.as(JavaObject.self)) { + return StateCases.TIMED_WAITING + } else if self.equals(classObj.TERMINATED?.as(JavaObject.self)) { + return StateCases.TERMINATED + } else { + return nil + } + } + + public convenience init(_ enumValue: StateCases, environment: JNIEnvironment? = nil) { + let _environment = if let environment { + environment + } else { + try! JavaVirtualMachine.shared().environment() + } + let classObj = try! JavaClass(environment: _environment) + switch enumValue { + case .NEW: + if let NEW = classObj.NEW { + self.init(javaHolder: NEW.javaHolder) + } else { + fatalError("Enum value NEW was unexpectedly nil, please re-run swift-java on the most updated Java class") + } + case .RUNNABLE: + if let RUNNABLE = classObj.RUNNABLE { + self.init(javaHolder: RUNNABLE.javaHolder) + } else { + fatalError("Enum value RUNNABLE was unexpectedly nil, please re-run swift-java on the most updated Java class") + } + case .BLOCKED: + if let BLOCKED = classObj.BLOCKED { + self.init(javaHolder: BLOCKED.javaHolder) + } else { + fatalError("Enum value BLOCKED was unexpectedly nil, please re-run swift-java on the most updated Java class") + } + case .WAITING: + if let WAITING = classObj.WAITING { + self.init(javaHolder: WAITING.javaHolder) + } else { + fatalError("Enum value WAITING was unexpectedly nil, please re-run swift-java on the most updated Java class") + } + case .TIMED_WAITING: + if let TIMED_WAITING = classObj.TIMED_WAITING { + self.init(javaHolder: TIMED_WAITING.javaHolder) + } else { + fatalError("Enum value TIMED_WAITING was unexpectedly nil, please re-run swift-java on the most updated Java class") + } + case .TERMINATED: + if let TERMINATED = classObj.TERMINATED { + self.init(javaHolder: TERMINATED.javaHolder) + } else { + fatalError("Enum value TERMINATED was unexpectedly nil, please re-run swift-java on the most updated Java class") + } + } + } + } +} +extension JavaClass { + @JavaStaticField(isFinal: true) + public var NEW: JavaThread.State! + + @JavaStaticField(isFinal: true) + public var RUNNABLE: JavaThread.State! + + @JavaStaticField(isFinal: true) + public var BLOCKED: JavaThread.State! + + @JavaStaticField(isFinal: true) + public var WAITING: JavaThread.State! + + @JavaStaticField(isFinal: true) + public var TIMED_WAITING: JavaThread.State! + + @JavaStaticField(isFinal: true) + public var TERMINATED: JavaThread.State! + + @JavaStaticMethod + public func values() -> [JavaThread.State?] + + @JavaStaticMethod + public func valueOf(_ arg0: String) -> JavaThread.State! +} +extension JavaThread { + @JavaInterface("java.lang.Thread$UncaughtExceptionHandler") + public struct UncaughtExceptionHandler { + @JavaMethod + public func uncaughtException(_ arg0: JavaThread?, _ arg1: Throwable?) + } +} +extension JavaClass { + @JavaStaticField(isFinal: true) + public var MIN_PRIORITY: Int32 + + @JavaStaticField(isFinal: true) + public var NORM_PRIORITY: Int32 + + @JavaStaticField(isFinal: true) + public var MAX_PRIORITY: Int32 + + @JavaStaticMethod + public func currentThread() -> JavaThread! + + @JavaStaticMethod + public func onSpinWait() + + @JavaStaticMethod + public func holdsLock(_ arg0: JavaObject?) -> Bool + + @JavaStaticMethod + public func interrupted() -> Bool + + @JavaStaticMethod + public func activeCount() -> Int32 + + @JavaStaticMethod + public func enumerate(_ arg0: [JavaThread?]) -> Int32 + + @JavaStaticMethod + public func yield() + + @JavaStaticMethod + public func sleep(_ arg0: Int64) throws + + @JavaStaticMethod + public func sleep(_ arg0: Int64, _ arg1: Int32) throws + + @JavaStaticMethod + public func ofPlatform() -> JavaThread.Builder.OfPlatform! + + @JavaStaticMethod + public func ofVirtual() -> JavaThread.Builder.OfVirtual! + + @JavaStaticMethod + public func dumpStack() + + @JavaStaticMethod + public func setDefaultUncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) + + @JavaStaticMethod + public func getDefaultUncaughtExceptionHandler() -> JavaThread.UncaughtExceptionHandler! +} diff --git a/Sources/SwiftJava/swift-java.config b/Sources/SwiftJava/swift-java.config index d07ff1620..d43096a73 100644 --- a/Sources/SwiftJava/swift-java.config +++ b/Sources/SwiftJava/swift-java.config @@ -23,6 +23,7 @@ "java.lang.Void" : "JavaVoid", "java.lang.CharSequence": "CharSequence", "java.lang.Appendable": "Appendable", + "java.lang.Thread": "JavaThread", "java.util.Optional": "JavaOptional", "java.util.OptionalDouble": "JavaOptionalDouble", "java.util.OptionalInt": "JavaOptionalInt", diff --git a/Sources/SwiftJavaRuntimeSupport/JNI.swift b/Sources/SwiftJavaRuntimeSupport/JNI.swift new file mode 100644 index 000000000..387d19380 --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/JNI.swift @@ -0,0 +1,18 @@ +// +// JNI.swift +// swift-java +// +// Created by Mads on 28/11/2025. +// + +import SwiftJava + +final class JNI { + static var shared: JNI! + + let applicationClassLoader: JavaClassLoader + + init(fromVM javaVM: JavaVirtualMachine) { + self.applicationClassLoader = try! JavaClass(environment: javaVM.environment()).currentThread().getContextClassLoader() + } +} diff --git a/Sources/SwiftJavaRuntimeSupport/OnLoad.swift b/Sources/SwiftJavaRuntimeSupport/OnLoad.swift new file mode 100644 index 000000000..e4cac3d1f --- /dev/null +++ b/Sources/SwiftJavaRuntimeSupport/OnLoad.swift @@ -0,0 +1,16 @@ +// +// OnLoad.swift +// swift-java +// +// Created by Mads on 28/11/2025. +// + +import CSwiftJavaJNI +import SwiftJava + +@_cdecl("JNI_OnLoad") +func SwiftJavaRuntimeSupport_JNI_OnLoad(javaVM: JavaVMPointer, reserved: UnsafeMutableRawPointer) -> jint { + JNI.shared = JNI(fromVM: JavaVirtualMachine(adoptingJVM: javaVM)) + + return JNI_VERSION_1_6 +} From 963f23ff6df17e3facbe130cd289b801594070e6 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 28 Nov 2025 19:29:43 +0100 Subject: [PATCH 2/7] use class loader for caching --- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 16 ++- Sources/SwiftJava/generated/JavaThread.swift | 107 ------------------ .../DefaultCaches.swift | 6 +- Sources/SwiftJavaRuntimeSupport/JNI.swift | 19 +++- Sources/SwiftJavaRuntimeSupport/OnLoad.swift | 16 --- .../_JNIBoxedConversions.swift | 24 ++-- .../_JNIMethodIDCache.swift | 25 +++- 7 files changed, 71 insertions(+), 142 deletions(-) delete mode 100644 Sources/SwiftJavaRuntimeSupport/OnLoad.swift diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 91a0b0e73..cd1b1180e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -119,6 +119,8 @@ extension JNISwift2JavaGenerator { private func printGlobalSwiftThunkSources(_ printer: inout CodePrinter) throws { printHeader(&printer) + printJNIOnLoad(&printer) + for decl in analysis.importedGlobalFuncs { printSwiftFunctionThunk(&printer, decl) printer.println() @@ -130,6 +132,18 @@ extension JNISwift2JavaGenerator { } } + private func printJNIOnLoad(_ printer: inout CodePrinter) { + printer.print( + """ + @_cdecl("JNI_OnLoad") + func JNI_OnLoad(javaVM: JavaVMPointer, reserved: UnsafeMutableRawPointer) -> jint { + SwiftJavaRuntimeSupport._JNI_OnLoad(javaVM, reserved) + return JNI_VERSION_1_6 + } + """ + ) + } + private func printNominalTypeThunks(_ printer: inout CodePrinter, _ type: ImportedNominalType) throws { printHeader(&printer) @@ -222,7 +236,7 @@ extension JNISwift2JavaGenerator { let methodSignature = MethodSignature(resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType)) let methods = #"[.init(name: "", signature: "\#(methodSignature.mangledName)")]"# - return #"_JNIMethodIDCache(environment: try! JavaVirtualMachine.shared().environment(), className: "\#(nativeParametersClassName)", methods: \#(methods))"# + return #"_JNIMethodIDCache(environment: try! JavaVirtualMachine.shared().environment(), className: "\#(nativeParametersClassName)", methods: \#(methods), isSystemClass: false)"# } private func printEnumGetAsCaseThunk( diff --git a/Sources/SwiftJava/generated/JavaThread.swift b/Sources/SwiftJava/generated/JavaThread.swift index e49f2fb47..c71e933b4 100644 --- a/Sources/SwiftJava/generated/JavaThread.swift +++ b/Sources/SwiftJava/generated/JavaThread.swift @@ -81,9 +81,6 @@ open class JavaThread: JavaObject { @JavaMethod open func getId() -> Int64 - @JavaMethod - open func getState() -> JavaThread.State! - @JavaMethod open func setUncaughtExceptionHandler(_ arg0: JavaThread.UncaughtExceptionHandler?) } @@ -171,110 +168,6 @@ extension JavaThread.Builder { public func inheritInheritableThreadLocals(_ arg0: Bool) -> JavaThread.Builder! } } -extension JavaThread { - @JavaClass("java.lang.Thread$State") - open class State: JavaObject { - public enum StateCases: Equatable { - case NEW - case RUNNABLE - case BLOCKED - case WAITING - case TIMED_WAITING - case TERMINATED - } - - public var enumValue: StateCases! { - let classObj = self.javaClass - if self.equals(classObj.NEW?.as(JavaObject.self)) { - return StateCases.NEW - } else if self.equals(classObj.RUNNABLE?.as(JavaObject.self)) { - return StateCases.RUNNABLE - } else if self.equals(classObj.BLOCKED?.as(JavaObject.self)) { - return StateCases.BLOCKED - } else if self.equals(classObj.WAITING?.as(JavaObject.self)) { - return StateCases.WAITING - } else if self.equals(classObj.TIMED_WAITING?.as(JavaObject.self)) { - return StateCases.TIMED_WAITING - } else if self.equals(classObj.TERMINATED?.as(JavaObject.self)) { - return StateCases.TERMINATED - } else { - return nil - } - } - - public convenience init(_ enumValue: StateCases, environment: JNIEnvironment? = nil) { - let _environment = if let environment { - environment - } else { - try! JavaVirtualMachine.shared().environment() - } - let classObj = try! JavaClass(environment: _environment) - switch enumValue { - case .NEW: - if let NEW = classObj.NEW { - self.init(javaHolder: NEW.javaHolder) - } else { - fatalError("Enum value NEW was unexpectedly nil, please re-run swift-java on the most updated Java class") - } - case .RUNNABLE: - if let RUNNABLE = classObj.RUNNABLE { - self.init(javaHolder: RUNNABLE.javaHolder) - } else { - fatalError("Enum value RUNNABLE was unexpectedly nil, please re-run swift-java on the most updated Java class") - } - case .BLOCKED: - if let BLOCKED = classObj.BLOCKED { - self.init(javaHolder: BLOCKED.javaHolder) - } else { - fatalError("Enum value BLOCKED was unexpectedly nil, please re-run swift-java on the most updated Java class") - } - case .WAITING: - if let WAITING = classObj.WAITING { - self.init(javaHolder: WAITING.javaHolder) - } else { - fatalError("Enum value WAITING was unexpectedly nil, please re-run swift-java on the most updated Java class") - } - case .TIMED_WAITING: - if let TIMED_WAITING = classObj.TIMED_WAITING { - self.init(javaHolder: TIMED_WAITING.javaHolder) - } else { - fatalError("Enum value TIMED_WAITING was unexpectedly nil, please re-run swift-java on the most updated Java class") - } - case .TERMINATED: - if let TERMINATED = classObj.TERMINATED { - self.init(javaHolder: TERMINATED.javaHolder) - } else { - fatalError("Enum value TERMINATED was unexpectedly nil, please re-run swift-java on the most updated Java class") - } - } - } - } -} -extension JavaClass { - @JavaStaticField(isFinal: true) - public var NEW: JavaThread.State! - - @JavaStaticField(isFinal: true) - public var RUNNABLE: JavaThread.State! - - @JavaStaticField(isFinal: true) - public var BLOCKED: JavaThread.State! - - @JavaStaticField(isFinal: true) - public var WAITING: JavaThread.State! - - @JavaStaticField(isFinal: true) - public var TIMED_WAITING: JavaThread.State! - - @JavaStaticField(isFinal: true) - public var TERMINATED: JavaThread.State! - - @JavaStaticMethod - public func values() -> [JavaThread.State?] - - @JavaStaticMethod - public func valueOf(_ arg0: String) -> JavaThread.State! -} extension JavaThread { @JavaInterface("java.lang.Thread$UncaughtExceptionHandler") public struct UncaughtExceptionHandler { diff --git a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift index 1c3079bc3..24f0d83de 100644 --- a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift +++ b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift @@ -29,7 +29,8 @@ extension _JNIMethodIDCache { private static let cache = _JNIMethodIDCache( environment: try! JavaVirtualMachine.shared().environment(), className: "java/util/concurrent/CompletableFuture", - methods: [completeMethod, completeExceptionallyMethod] + methods: [completeMethod, completeExceptionallyMethod], + isSystemClass: true ) public static var `class`: jclass { @@ -53,7 +54,8 @@ extension _JNIMethodIDCache { private static let cache = _JNIMethodIDCache( environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Exception", - methods: [messageConstructor] + methods: [messageConstructor], + isSystemClass: true ) public static var `class`: jclass { diff --git a/Sources/SwiftJavaRuntimeSupport/JNI.swift b/Sources/SwiftJavaRuntimeSupport/JNI.swift index 387d19380..b6f0117df 100644 --- a/Sources/SwiftJavaRuntimeSupport/JNI.swift +++ b/Sources/SwiftJavaRuntimeSupport/JNI.swift @@ -1,11 +1,19 @@ +//===----------------------------------------------------------------------===// // -// JNI.swift -// swift-java +// This source file is part of the Swift.org open source project // -// Created by Mads on 28/11/2025. +// 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 +// +//===----------------------------------------------------------------------===// import SwiftJava +import CSwiftJavaJNI final class JNI { static var shared: JNI! @@ -16,3 +24,8 @@ final class JNI { self.applicationClassLoader = try! JavaClass(environment: javaVM.environment()).currentThread().getContextClassLoader() } } + +// Called by generated code, and not automatically by Java. +public func _JNI_OnLoad(_ javaVM: JavaVMPointer, _ reserved: UnsafeMutableRawPointer) { + JNI.shared = JNI(fromVM: JavaVirtualMachine(adoptingJVM: javaVM)) +} diff --git a/Sources/SwiftJavaRuntimeSupport/OnLoad.swift b/Sources/SwiftJavaRuntimeSupport/OnLoad.swift deleted file mode 100644 index e4cac3d1f..000000000 --- a/Sources/SwiftJavaRuntimeSupport/OnLoad.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// OnLoad.swift -// swift-java -// -// Created by Mads on 28/11/2025. -// - -import CSwiftJavaJNI -import SwiftJava - -@_cdecl("JNI_OnLoad") -func SwiftJavaRuntimeSupport_JNI_OnLoad(javaVM: JavaVMPointer, reserved: UnsafeMutableRawPointer) -> jint { - JNI.shared = JNI(fromVM: JavaVirtualMachine(adoptingJVM: javaVM)) - - return JNI_VERSION_1_6 -} diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift index 68d98ffcd..bedda775b 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift @@ -28,47 +28,55 @@ public enum _JNIBoxedConversions { private static let booleanCache = _JNIMethodIDCache( environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Boolean", - methods: [booleanMethod] + methods: [booleanMethod], + isSystemClass: true ) private static let byteCache = _JNIMethodIDCache( environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Byte", - methods: [byteMethod] + methods: [byteMethod], + isSystemClass: true ) private static let charCache = _JNIMethodIDCache( environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Character", - methods: [charMethod] + methods: [charMethod], + isSystemClass: true ) private static let shortCache = _JNIMethodIDCache( environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Short", - methods: [shortMethod] + methods: [shortMethod], + isSystemClass: true ) private static let intCache = _JNIMethodIDCache( environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Integer", - methods: [intMethod] + methods: [intMethod], + isSystemClass: true ) private static let longCache = _JNIMethodIDCache( environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Long", - methods: [longMethod] + methods: [longMethod], + isSystemClass: true ) private static let floatCache = _JNIMethodIDCache( environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Float", - methods: [floatMethod] + methods: [floatMethod], + isSystemClass: true ) private static let doubleCache = _JNIMethodIDCache( environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Double", - methods: [doubleMethod] + methods: [doubleMethod], + isSystemClass: true ) public static func box(_ value: jboolean, in env: JNIEnvironment) -> jobject { diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index dd7eb5d13..e45f5f7c3 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -39,11 +39,27 @@ public final class _JNIMethodIDCache: Sendable { self._class! } - public init(environment: UnsafeMutablePointer!, className: String, methods: [Method]) { - guard let clazz = environment.interface.FindClass(environment, className) else { - fatalError("Class \(className) could not be found!") + /// An optional reference to a java object holder + /// if we cached this class through the class loader + /// This is to make sure that the underlying reference remains valid + nonisolated(unsafe) private let javaObjectHolder: JavaObjectHolder? + + public init(environment: UnsafeMutablePointer!, className: String, methods: [Method], isSystemClass: Bool) { + let clazz: jobject + if isSystemClass { + guard let jniClass = environment.interface.FindClass(environment, className) else { + fatalError("Class \(className) could not be found!") + } + clazz = jniClass + self.javaObjectHolder = nil + } else { + guard let javaClass = try? JNI.shared.applicationClassLoader.loadClass(className.replacingOccurrences(of: "/", with: ".")) else { + fatalError("Non-system class \(className) could not be found!") + } + clazz = javaClass.javaThis + self.javaObjectHolder = javaClass.javaHolder } - self._class = environment.interface.NewGlobalRef(environment, clazz)! + self._class = clazz self.methods = methods.reduce(into: [:]) { (result, method) in if method.isStatic { if let methodID = environment.interface.GetStaticMethodID(environment, clazz, method.name, method.signature) { @@ -61,7 +77,6 @@ public final class _JNIMethodIDCache: Sendable { } } - public subscript(_ method: Method) -> jmethodID? { methods[method] } From 99899ccaaedb9769965153fdf39639b381e6d86d Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 28 Nov 2025 21:10:34 +0100 Subject: [PATCH 3/7] remember global ref --- .../JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift | 2 -- .../SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift | 8 -------- Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift | 6 ++++-- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index cd1b1180e..3ed9ec01d 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -236,7 +236,7 @@ extension JNISwift2JavaGenerator { let methodSignature = MethodSignature(resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType)) let methods = #"[.init(name: "", signature: "\#(methodSignature.mangledName)")]"# - return #"_JNIMethodIDCache(environment: try! JavaVirtualMachine.shared().environment(), className: "\#(nativeParametersClassName)", methods: \#(methods), isSystemClass: false)"# + return #"_JNIMethodIDCache(className: "\#(nativeParametersClassName)", methods: \#(methods), isSystemClass: false)"# } private func printEnumGetAsCaseThunk( diff --git a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift index 24f0d83de..365bd8b99 100644 --- a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift +++ b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift @@ -27,7 +27,6 @@ extension _JNIMethodIDCache { ) private static let cache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/util/concurrent/CompletableFuture", methods: [completeMethod, completeExceptionallyMethod], isSystemClass: true @@ -52,7 +51,6 @@ extension _JNIMethodIDCache { private static let messageConstructor = Method(name: "", signature: "(Ljava/lang/String;)V") private static let cache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Exception", methods: [messageConstructor], isSystemClass: true diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift index bedda775b..e1a5115e3 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift @@ -26,54 +26,46 @@ public enum _JNIBoxedConversions { private static let doubleMethod = _JNIMethodIDCache.Method(name: "valueOf", signature: "(D)Ljava/lang/Double;", isStatic: true) private static let booleanCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Boolean", methods: [booleanMethod], isSystemClass: true ) private static let byteCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Byte", methods: [byteMethod], isSystemClass: true ) private static let charCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Character", methods: [charMethod], isSystemClass: true ) private static let shortCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Short", methods: [shortMethod], isSystemClass: true ) private static let intCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Integer", methods: [intMethod], isSystemClass: true ) private static let longCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Long", methods: [longMethod], isSystemClass: true ) private static let floatCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Float", methods: [floatMethod], isSystemClass: true ) private static let doubleCache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "java/lang/Double", methods: [doubleMethod], isSystemClass: true diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index e45f5f7c3..5078cc429 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -44,13 +44,15 @@ public final class _JNIMethodIDCache: Sendable { /// This is to make sure that the underlying reference remains valid nonisolated(unsafe) private let javaObjectHolder: JavaObjectHolder? - public init(environment: UnsafeMutablePointer!, className: String, methods: [Method], isSystemClass: Bool) { + public init(className: String, methods: [Method], isSystemClass: Bool) { + let environment = try! JavaVirtualMachine.shared().environment() + let clazz: jobject if isSystemClass { guard let jniClass = environment.interface.FindClass(environment, className) else { fatalError("Class \(className) could not be found!") } - clazz = jniClass + clazz = environment.interface.NewGlobalRef(environment, jniClass)! self.javaObjectHolder = nil } else { guard let javaClass = try? JNI.shared.applicationClassLoader.loadClass(className.replacingOccurrences(of: "/", with: ".")) else { From bbad3b7a859efe6555a90357d1f27292801dda14 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 28 Nov 2025 21:15:44 +0100 Subject: [PATCH 4/7] format --- Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index 5078cc429..9e7e532b9 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -50,8 +50,8 @@ public final class _JNIMethodIDCache: Sendable { let clazz: jobject if isSystemClass { guard let jniClass = environment.interface.FindClass(environment, className) else { - fatalError("Class \(className) could not be found!") - } + fatalError("Class \(className) could not be found!") + } clazz = environment.interface.NewGlobalRef(environment, jniClass)! self.javaObjectHolder = nil } else { From fdd3370128268bf2476787db1d0d5ced859589a4 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Sat, 29 Nov 2025 08:26:59 +0100 Subject: [PATCH 5/7] use fallback instead of explicit system class bool --- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 2 +- .../DefaultCaches.swift | 6 ++--- .../_JNIBoxedConversions.swift | 24 +++++++------------ .../_JNIMethodIDCache.swift | 17 +++++++------ 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 3ed9ec01d..752c7ff9c 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -236,7 +236,7 @@ extension JNISwift2JavaGenerator { let methodSignature = MethodSignature(resultType: .void, parameterTypes: enumCase.parameterConversions.map(\.native.javaType)) let methods = #"[.init(name: "", signature: "\#(methodSignature.mangledName)")]"# - return #"_JNIMethodIDCache(className: "\#(nativeParametersClassName)", methods: \#(methods), isSystemClass: false)"# + return #"_JNIMethodIDCache(className: "\#(nativeParametersClassName)", methods: \#(methods))"# } private func printEnumGetAsCaseThunk( diff --git a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift index 365bd8b99..03a8b26db 100644 --- a/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift +++ b/Sources/SwiftJavaRuntimeSupport/DefaultCaches.swift @@ -28,8 +28,7 @@ extension _JNIMethodIDCache { private static let cache = _JNIMethodIDCache( className: "java/util/concurrent/CompletableFuture", - methods: [completeMethod, completeExceptionallyMethod], - isSystemClass: true + methods: [completeMethod, completeExceptionallyMethod] ) public static var `class`: jclass { @@ -52,8 +51,7 @@ extension _JNIMethodIDCache { private static let cache = _JNIMethodIDCache( className: "java/lang/Exception", - methods: [messageConstructor], - isSystemClass: true + methods: [messageConstructor] ) public static var `class`: jclass { diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift index e1a5115e3..ad4572caa 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIBoxedConversions.swift @@ -27,48 +27,40 @@ public enum _JNIBoxedConversions { private static let booleanCache = _JNIMethodIDCache( className: "java/lang/Boolean", - methods: [booleanMethod], - isSystemClass: true + methods: [booleanMethod] ) private static let byteCache = _JNIMethodIDCache( className: "java/lang/Byte", - methods: [byteMethod], - isSystemClass: true + methods: [byteMethod] ) private static let charCache = _JNIMethodIDCache( className: "java/lang/Character", - methods: [charMethod], - isSystemClass: true + methods: [charMethod] ) private static let shortCache = _JNIMethodIDCache( className: "java/lang/Short", - methods: [shortMethod], - isSystemClass: true + methods: [shortMethod] ) private static let intCache = _JNIMethodIDCache( className: "java/lang/Integer", - methods: [intMethod], - isSystemClass: true + methods: [intMethod] ) private static let longCache = _JNIMethodIDCache( className: "java/lang/Long", - methods: [longMethod], - isSystemClass: true + methods: [longMethod] ) private static let floatCache = _JNIMethodIDCache( className: "java/lang/Float", - methods: [floatMethod], - isSystemClass: true + methods: [floatMethod] ) private static let doubleCache = _JNIMethodIDCache( className: "java/lang/Double", - methods: [doubleMethod], - isSystemClass: true + methods: [doubleMethod] ) public static func box(_ value: jboolean, in env: JNIEnvironment) -> jobject { diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index 9e7e532b9..539d58602 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -44,23 +44,22 @@ public final class _JNIMethodIDCache: Sendable { /// This is to make sure that the underlying reference remains valid nonisolated(unsafe) private let javaObjectHolder: JavaObjectHolder? - public init(className: String, methods: [Method], isSystemClass: Bool) { + public init(className: String, methods: [Method]) { let environment = try! JavaVirtualMachine.shared().environment() let clazz: jobject - if isSystemClass { - guard let jniClass = environment.interface.FindClass(environment, className) else { - fatalError("Class \(className) could not be found!") - } + if let jniClass = environment.interface.FindClass(environment, className) { clazz = environment.interface.NewGlobalRef(environment, jniClass)! self.javaObjectHolder = nil - } else { - guard let javaClass = try? JNI.shared.applicationClassLoader.loadClass(className.replacingOccurrences(of: "/", with: ".")) else { - fatalError("Non-system class \(className) could not be found!") - } + } else if let javaClass = try? JNI.shared.applicationClassLoader.loadClass( + className.replacingOccurrences(of: "/", with: ".") + ) { clazz = javaClass.javaThis self.javaObjectHolder = javaClass.javaHolder + } else { + fatalError("Class \(className) could not be found!") } + self._class = clazz self.methods = methods.reduce(into: [:]) { (result, method) in if method.isStatic { From 72a7368beed8cd0b06c31e2cff8604e340e1e1a8 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 1 Dec 2025 12:24:12 +0100 Subject: [PATCH 6/7] fix cache --- Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift index 972b903b6..16a9c899e 100644 --- a/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift +++ b/Sources/SwiftJavaRuntimeSupport/JNIMethodIDCaches.swift @@ -58,7 +58,6 @@ extension _JNIMethodIDCache { ) private static let cache = _JNIMethodIDCache( - environment: try! JavaVirtualMachine.shared().environment(), className: "org/swift/swiftkit/core/SimpleCompletableFuture", methods: [completeMethod, completeExceptionallyMethod] ) From 544e343f8e06398acb9a555d0cdb8157d7433957 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 1 Dec 2025 13:06:20 +0100 Subject: [PATCH 7/7] clear exceptions for callback --- .../_JNIMethodIDCache.swift | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index 539d58602..fbadf1916 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -51,13 +51,18 @@ public final class _JNIMethodIDCache: Sendable { if let jniClass = environment.interface.FindClass(environment, className) { clazz = environment.interface.NewGlobalRef(environment, jniClass)! self.javaObjectHolder = nil - } else if let javaClass = try? JNI.shared.applicationClassLoader.loadClass( - className.replacingOccurrences(of: "/", with: ".") - ) { - clazz = javaClass.javaThis - self.javaObjectHolder = javaClass.javaHolder } else { - fatalError("Class \(className) could not be found!") + // Clear any ClassNotFound exceptions from FindClass + environment.interface.ExceptionClear(environment) + + if let javaClass = try? JNI.shared.applicationClassLoader.loadClass( + className.replacingOccurrences(of: "/", with: ".") + ) { + clazz = javaClass.javaThis + self.javaObjectHolder = javaClass.javaHolder + } else { + fatalError("Class \(className) could not be found!") + } } self._class = clazz