From e8161681aeb0def98a1160e1bd7d4479651ae292 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 21 Mar 2026 19:03:17 -0400 Subject: [PATCH 1/7] Use typed throws --- Sources/SwiftJavaJNICore/JavaEnvironment+Refs.swift | 2 +- Sources/SwiftJavaJNICore/JavaType+JavaSource.swift | 2 +- Sources/SwiftJavaJNICore/Mangling.swift | 6 +++--- .../VirtualMachine/JavaVirtualMachine.swift | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftJavaJNICore/JavaEnvironment+Refs.swift b/Sources/SwiftJavaJNICore/JavaEnvironment+Refs.swift index bd9494a..f7d4628 100644 --- a/Sources/SwiftJavaJNICore/JavaEnvironment+Refs.swift +++ b/Sources/SwiftJavaJNICore/JavaEnvironment+Refs.swift @@ -59,7 +59,7 @@ extension UnsafeMutablePointer { /// pending exception — it prints the stack trace to stderr and does **not** /// clear the exception. @inline(__always) - internal func throwPushLocalFrameOOM(capacity: Int) throws -> Never { + internal func throwPushLocalFrameOOM(capacity: Int) throws(JNIError) -> Never { if describeOOMException { // Print the pending OutOfMemoryError stack trace to stderr. // ExceptionDescribe does not clear the exception. diff --git a/Sources/SwiftJavaJNICore/JavaType+JavaSource.swift b/Sources/SwiftJavaJNICore/JavaType+JavaSource.swift index 96f72be..527ca18 100644 --- a/Sources/SwiftJavaJNICore/JavaType+JavaSource.swift +++ b/Sources/SwiftJavaJNICore/JavaType+JavaSource.swift @@ -16,7 +16,7 @@ extension JavaType { /// Form a Java type based on the name that is produced by /// java.lang.Class.getName(). This can be primitive types like "int", /// class types like "java.lang.String", or arrays thereof. - public init(javaTypeName: String) throws { + public init(javaTypeName: String) throws(JavaDemanglingError) { switch javaTypeName { case "boolean": self = .boolean case "byte": self = .byte diff --git a/Sources/SwiftJavaJNICore/Mangling.swift b/Sources/SwiftJavaJNICore/Mangling.swift index 0b92911..b01b013 100644 --- a/Sources/SwiftJavaJNICore/Mangling.swift +++ b/Sources/SwiftJavaJNICore/Mangling.swift @@ -14,7 +14,7 @@ extension JavaType { /// Demangle a Java type name into a representation of the type. - public init(mangledName: String) throws { + public init(mangledName: String) throws(JavaDemanglingError) { var mangledName = mangledName[...] self = try JavaType.demangleNextType(from: &mangledName) if !mangledName.isEmpty { @@ -43,7 +43,7 @@ extension JavaType { extension MethodSignature { /// Demangle the given method Java signature. - public init(mangledName: String) throws { + public init(mangledName: String) throws(JavaDemanglingError) { // Method signatures have the form "(parameter-types)result-type". guard mangledName.starts(with: "(") else { throw JavaDemanglingError.invalidMangledName(mangledName) @@ -82,7 +82,7 @@ extension MethodSignature { extension JavaType { /// Demangle the next Java type from the given string, shrinking the input /// string and producing demangled type. - static func demangleNextType(from string: inout Substring) throws -> JavaType { + static func demangleNextType(from string: inout Substring) throws(JavaDemanglingError) -> JavaType { guard let firstChar = string.first else { throw JavaDemanglingError.invalidMangledName(String(string)) } diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index 859d269..71f7aa2 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -80,7 +80,7 @@ public final class JavaVirtualMachine: @unchecked Sendable { classpath: [String] = [], vmOptions: [String] = [], ignoreUnrecognized: Bool = false - ) throws { + ) throws(VMError) { self.classpath = classpath var jvm: JavaVMPointer? = nil var environment: JNIEnvPointer? = nil @@ -141,7 +141,7 @@ public final class JavaVirtualMachine: @unchecked Sendable { self.destroyOnDeinit = .init(initialState: true) } - public func destroyJVM() throws { + public func destroyJVM() throws(VMError) { try self.detachCurrentThread() if let error = VMError(fromJNIError: jvm.pointee!.pointee.DestroyJavaVM(jvm)) { throw error @@ -177,7 +177,7 @@ extension JavaVirtualMachine { /// - Parameter /// - asDaemon: Whether this thread should be treated as a daemon /// thread in the Java Virtual Machine. - public func environment(asDaemon: Bool = false) throws -> JNIEnvironment { + public func environment(asDaemon: Bool = false) throws(VMError) -> JNIEnvironment { // Check whether this thread is already attached. If so, return the // corresponding environment. var environment: UnsafeMutableRawPointer? = nil @@ -213,7 +213,7 @@ extension JavaVirtualMachine { /// Detach the current thread from the Java Virtual Machine. All Java /// threads waiting for this thread to die are notified. - func detachCurrentThread() throws { + func detachCurrentThread() throws(VMError) { if let resultError = VMError(fromJNIError: jvm.pointee!.pointee.DetachCurrentThread(jvm)) { throw resultError } @@ -459,7 +459,7 @@ func systemJavaHome() -> String? { } /// Located the shared library that includes the `JNI_GetCreatedJavaVMs` and `JNI_CreateJavaVM` entry points to the `JNINativeInterface` function table -private func loadLibJava() throws -> DylibType { +private func loadLibJava() throws(JavaVirtualMachine.VMError) -> DylibType { #if os(Android) for libname in ["libart.so", "libdvm.so", "libnativehelper.so"] { if let lib = dlopen(libname, RTLD_NOW) { From 8273e74cc56b3176e914e37408446c3ebb4007ad Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 21 Mar 2026 19:03:28 -0400 Subject: [PATCH 2/7] Export `JavaDemanglingError` as public --- Sources/SwiftJavaJNICore/JavaDemanglingError.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJavaJNICore/JavaDemanglingError.swift b/Sources/SwiftJavaJNICore/JavaDemanglingError.swift index 445957d..241cbe5 100644 --- a/Sources/SwiftJavaJNICore/JavaDemanglingError.swift +++ b/Sources/SwiftJavaJNICore/JavaDemanglingError.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// /// Describes an error that can occur when demangling a Java name. -enum JavaDemanglingError: Error { +public enum JavaDemanglingError: Error { /// This does not match the form of a Java mangled type name. case invalidMangledName(String) From 7325041f05af086f45c2ffefb6d373534fe5af86 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 21 Mar 2026 19:03:47 -0400 Subject: [PATCH 3/7] Export `JavaVirtualMachine.VMError` as public --- .../SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index 71f7aa2..f00b952 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -347,7 +347,7 @@ extension JavaVirtualMachine { extension JavaVirtualMachine { /// Describes the kinds of errors that can occur when interacting with JNI. - enum VMError: Error { + public enum VMError: Error { /// There is already a Java Virtual Machine. case existingVM From 91b5837846c8793cfaf6c3b11880c19ed3d73dbf Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 21 Mar 2026 19:04:02 -0400 Subject: [PATCH 4/7] Remove `JavaKitError` --- .../SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index f00b952..f7797e0 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -393,10 +393,6 @@ extension JavaVirtualMachine { } } } - - enum JavaKitError: Error { - case classpathEntryNotFound(entry: String, classpath: [String]) - } } // ==== ------------------------------------------------------------------------ From 7aae1b9287432950880db10c98261c55fd154ec8 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 21 Mar 2026 19:24:50 -0400 Subject: [PATCH 5/7] Use typed throws for `JavaVirtualMachine.shared()` --- .../VirtualMachine/JavaVirtualMachine.swift | 106 +++++++++--------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index f7797e0..fc8b47a 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -260,70 +260,74 @@ extension JavaVirtualMachine { vmOptions: [String] = [], ignoreUnrecognized: Bool = false, replace: Bool = false - ) throws -> JavaVirtualMachine { + ) throws(VMError) -> JavaVirtualMachine { precondition( !classpath.contains(where: { $0.contains(FileManager.pathSeparator) }), "Classpath element must not contain `\(FileManager.pathSeparator)`! Split the path into elements! Was: \(classpath)" ) - return try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) in - // If we already have a JavaVirtualMachine instance, return it. - if replace { - print("[swift-java] Replace JVM instance!") - try sharedJVMPointer?.destroyJVM() - sharedJVMPointer = nil - } else { - if let existingInstance = sharedJVMPointer { - // FIXME: this isn't ideal; we silently ignored that we may have requested a different classpath or options - return existingInstance + return try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) throws(VMError) in + // If we already have a JavaVirtualMachine instance, return it. + if replace { + print("[swift-java] Replace JVM instance!") + try sharedJVMPointer?.destroyJVM() + sharedJVMPointer = nil + } else { + if let existingInstance = sharedJVMPointer { + // FIXME: this isn't ideal; we silently ignored that we may have requested a different classpath or options + return existingInstance + } } - } - typealias GetCreatedJavaVMs = @convention(c) (_ pvm: UnsafeMutablePointer, _ count: Int32, _ num: UnsafeMutablePointer) -> jint - guard let getCreatedJavaVMs: GetCreatedJavaVMs = symbol(try loadLibJava(), "JNI_GetCreatedJavaVMs") else { - throw VMError.cannotLoadGetCreatedJavaVMs - } + typealias GetCreatedJavaVMs = @convention(c) (_ pvm: UnsafeMutablePointer, _ count: Int32, _ num: UnsafeMutablePointer) -> jint + guard let getCreatedJavaVMs: GetCreatedJavaVMs = symbol(try loadLibJava(), "JNI_GetCreatedJavaVMs") else { + throw VMError.cannotLoadGetCreatedJavaVMs + } - while true { - var wasExistingVM: Bool = false while true { - // Query the JVM itself to determine whether there is a JVM - // instance that we don't yet know about. - var jvm: JavaVMPointer? = nil - var numJVMs: jsize = 0 - if getCreatedJavaVMs(&jvm, 1, &numJVMs) == JNI_OK, numJVMs >= 1 { - // Adopt this JVM into a new instance of the JavaVirtualMachine - // wrapper. - let javaVirtualMachine = JavaVirtualMachine(adoptingJVM: jvm!) + var wasExistingVM: Bool = false + while true { + // Query the JVM itself to determine whether there is a JVM + // instance that we don't yet know about. + var jvm: JavaVMPointer? = nil + var numJVMs: jsize = 0 + if getCreatedJavaVMs(&jvm, 1, &numJVMs) == JNI_OK, numJVMs >= 1 { + // Adopt this JVM into a new instance of the JavaVirtualMachine + // wrapper. + let javaVirtualMachine = JavaVirtualMachine(adoptingJVM: jvm!) + sharedJVMPointer = javaVirtualMachine + return javaVirtualMachine + } + + precondition( + !wasExistingVM, + "JVM reports that an instance of the JVM was already created, but we didn't see it." + ) + + // Create a new instance of the JVM. + let javaVirtualMachine: JavaVirtualMachine + do { + javaVirtualMachine = try JavaVirtualMachine( + classpath: classpath, + vmOptions: vmOptions, + ignoreUnrecognized: ignoreUnrecognized + ) + } catch VMError.existingVM { + // We raced with code outside of this JavaVirtualMachine instance + // that created a VM while we were trying to do the same. Go + // through the loop again to pick up the underlying JVM pointer. + wasExistingVM = true + continue + } catch let error as VMError { + throw error + } catch { + fatalError("Unexpected non-VMError from JavaVirtualMachine.init: \(error)") + } + sharedJVMPointer = javaVirtualMachine return javaVirtualMachine } - - precondition( - !wasExistingVM, - "JVM reports that an instance of the JVM was already created, but we didn't see it." - ) - - // Create a new instance of the JVM. - let javaVirtualMachine: JavaVirtualMachine - do { - javaVirtualMachine = try JavaVirtualMachine( - classpath: classpath, - vmOptions: vmOptions, - ignoreUnrecognized: ignoreUnrecognized - ) - } catch VMError.existingVM { - // We raced with code outside of this JavaVirtualMachine instance - // that created a VM while we were trying to do the same. Go - // through the loop again to pick up the underlying JVM pointer. - wasExistingVM = true - continue - } - - sharedJVMPointer = javaVirtualMachine - return javaVirtualMachine } - } } } From 53ee318815a3272c87e82fc92030cdc97ef90dcf Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 21 Mar 2026 19:26:15 -0400 Subject: [PATCH 6/7] Use typed throws for `LockedState` --- .../VirtualMachine/LockedState.swift | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/LockedState.swift b/Sources/SwiftJavaJNICore/VirtualMachine/LockedState.swift index 745dfa5..9b55c38 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/LockedState.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/LockedState.swift @@ -111,26 +111,24 @@ package struct LockedState { ) } - package func withLock(_ body: @Sendable (inout State) throws -> T) rethrows -> T { + package func withLock(_ body: @Sendable (inout State) throws(E) -> T) throws(E) -> T { try withLockUnchecked(body) } - package func withLockUnchecked(_ body: (inout State) throws -> T) rethrows -> T { - try _buffer.withUnsafeMutablePointers { state, lock in - _Lock.lock(lock) - defer { _Lock.unlock(lock) } - return try body(&state.pointee) - } + package func withLockUnchecked(_ body: (inout State) throws(E) -> T) throws(E) -> T { + _buffer.withUnsafeMutablePointerToElements { _Lock.lock($0) } + defer { _buffer.withUnsafeMutablePointerToElements { _Lock.unlock($0) } } + return try body(&_buffer.header) } // Ensures the managed state outlives the locked scope. - package func withLockExtendingLifetimeOfState(_ body: @Sendable (inout State) throws -> T) rethrows -> T { - try _buffer.withUnsafeMutablePointers { state, lock in - _Lock.lock(lock) - return try withExtendedLifetime(state.pointee) { - defer { _Lock.unlock(lock) } - return try body(&state.pointee) - } + package func withLockExtendingLifetimeOfState(_ body: @Sendable (inout State) throws(E) -> T) throws(E) -> T { + _buffer.withUnsafeMutablePointerToElements { _Lock.lock($0) } + defer { _buffer.withUnsafeMutablePointerToElements { _Lock.unlock($0) } } + do { + return try body(&_buffer.header) + } catch { + throw error } } } @@ -140,10 +138,10 @@ extension LockedState where State == Void { self.init(initialState: ()) } - package func withLock(_ body: @Sendable () throws -> R) rethrows -> R { - try withLock { _ in - try body() - } + package func withLock(_ body: @Sendable () throws(E) -> R) throws(E) -> R { + _buffer.withUnsafeMutablePointerToElements { _Lock.lock($0) } + defer { _buffer.withUnsafeMutablePointerToElements { _Lock.unlock($0) } } + return try body() } package func lock() { From 4d1c00f620f5ead95e08609fa874dd2b4ea2b51e Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 21 Mar 2026 19:30:55 -0400 Subject: [PATCH 7/7] Apply formatting --- .../VirtualMachine/JavaVirtualMachine.swift | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index fc8b47a..b1be5e2 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -267,67 +267,67 @@ extension JavaVirtualMachine { ) return try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) throws(VMError) in - // If we already have a JavaVirtualMachine instance, return it. - if replace { - print("[swift-java] Replace JVM instance!") - try sharedJVMPointer?.destroyJVM() - sharedJVMPointer = nil - } else { - if let existingInstance = sharedJVMPointer { - // FIXME: this isn't ideal; we silently ignored that we may have requested a different classpath or options - return existingInstance - } + // If we already have a JavaVirtualMachine instance, return it. + if replace { + print("[swift-java] Replace JVM instance!") + try sharedJVMPointer?.destroyJVM() + sharedJVMPointer = nil + } else { + if let existingInstance = sharedJVMPointer { + // FIXME: this isn't ideal; we silently ignored that we may have requested a different classpath or options + return existingInstance } + } - typealias GetCreatedJavaVMs = @convention(c) (_ pvm: UnsafeMutablePointer, _ count: Int32, _ num: UnsafeMutablePointer) -> jint - guard let getCreatedJavaVMs: GetCreatedJavaVMs = symbol(try loadLibJava(), "JNI_GetCreatedJavaVMs") else { - throw VMError.cannotLoadGetCreatedJavaVMs - } + typealias GetCreatedJavaVMs = @convention(c) (_ pvm: UnsafeMutablePointer, _ count: Int32, _ num: UnsafeMutablePointer) -> jint + guard let getCreatedJavaVMs: GetCreatedJavaVMs = symbol(try loadLibJava(), "JNI_GetCreatedJavaVMs") else { + throw VMError.cannotLoadGetCreatedJavaVMs + } + while true { + var wasExistingVM: Bool = false while true { - var wasExistingVM: Bool = false - while true { - // Query the JVM itself to determine whether there is a JVM - // instance that we don't yet know about. - var jvm: JavaVMPointer? = nil - var numJVMs: jsize = 0 - if getCreatedJavaVMs(&jvm, 1, &numJVMs) == JNI_OK, numJVMs >= 1 { - // Adopt this JVM into a new instance of the JavaVirtualMachine - // wrapper. - let javaVirtualMachine = JavaVirtualMachine(adoptingJVM: jvm!) - sharedJVMPointer = javaVirtualMachine - return javaVirtualMachine - } - - precondition( - !wasExistingVM, - "JVM reports that an instance of the JVM was already created, but we didn't see it." - ) - - // Create a new instance of the JVM. - let javaVirtualMachine: JavaVirtualMachine - do { - javaVirtualMachine = try JavaVirtualMachine( - classpath: classpath, - vmOptions: vmOptions, - ignoreUnrecognized: ignoreUnrecognized - ) - } catch VMError.existingVM { - // We raced with code outside of this JavaVirtualMachine instance - // that created a VM while we were trying to do the same. Go - // through the loop again to pick up the underlying JVM pointer. - wasExistingVM = true - continue - } catch let error as VMError { - throw error - } catch { - fatalError("Unexpected non-VMError from JavaVirtualMachine.init: \(error)") - } - + // Query the JVM itself to determine whether there is a JVM + // instance that we don't yet know about. + var jvm: JavaVMPointer? = nil + var numJVMs: jsize = 0 + if getCreatedJavaVMs(&jvm, 1, &numJVMs) == JNI_OK, numJVMs >= 1 { + // Adopt this JVM into a new instance of the JavaVirtualMachine + // wrapper. + let javaVirtualMachine = JavaVirtualMachine(adoptingJVM: jvm!) sharedJVMPointer = javaVirtualMachine return javaVirtualMachine } + + precondition( + !wasExistingVM, + "JVM reports that an instance of the JVM was already created, but we didn't see it." + ) + + // Create a new instance of the JVM. + let javaVirtualMachine: JavaVirtualMachine + do { + javaVirtualMachine = try JavaVirtualMachine( + classpath: classpath, + vmOptions: vmOptions, + ignoreUnrecognized: ignoreUnrecognized + ) + } catch VMError.existingVM { + // We raced with code outside of this JavaVirtualMachine instance + // that created a VM while we were trying to do the same. Go + // through the loop again to pick up the underlying JVM pointer. + wasExistingVM = true + continue + } catch let error as VMError { + throw error + } catch { + fatalError("Unexpected non-VMError from JavaVirtualMachine.init: \(error)") + } + + sharedJVMPointer = javaVirtualMachine + return javaVirtualMachine } + } } }