From e8161681aeb0def98a1160e1bd7d4479651ae292 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sat, 21 Mar 2026 19:03:17 -0400 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 } + } } } From 37207bd3f59d034d4ed9378b16ca180a1e75d920 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 22 Mar 2026 23:26:57 -0400 Subject: [PATCH 08/11] Add `VMError.Code` --- .../VirtualMachine/JavaVirtualMachine.swift | 97 +++++++++++-------- 1 file changed, 58 insertions(+), 39 deletions(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index b1be5e2..066678a 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -129,7 +129,7 @@ public final class JavaVirtualMachine: @unchecked Sendable { typealias CreateJavaVM = @convention(c) (_ pvm: UnsafeMutablePointer?, _ penv: UnsafeMutablePointer?, _ args: UnsafeMutableRawPointer) -> jint guard let createJavaVM: CreateJavaVM = symbol(try loadLibJava(), "JNI_CreateJavaVM") else { - throw VMError.cannotLoadCreateJavaVM + throw VMError(.cannotLoadCreateJavaVM) } // Create the JVM instance. @@ -281,7 +281,7 @@ extension JavaVirtualMachine { typealias GetCreatedJavaVMs = @convention(c) (_ pvm: UnsafeMutablePointer, _ count: Int32, _ num: UnsafeMutablePointer) -> jint guard let getCreatedJavaVMs: GetCreatedJavaVMs = symbol(try loadLibJava(), "JNI_GetCreatedJavaVMs") else { - throw VMError.cannotLoadGetCreatedJavaVMs + throw VMError(.cannotLoadGetCreatedJavaVMs) } while true { @@ -350,51 +350,70 @@ extension JavaVirtualMachine { } extension JavaVirtualMachine { - /// Describes the kinds of errors that can occur when interacting with JNI. - public enum VMError: Error { - /// There is already a Java Virtual Machine. - case existingVM + /// Describes an error that occurred when interacting with JNI. + public struct VMError: Error { + /// The specific kind of error that occurred. + public let code: Code - /// JNI version mismatch error. - case jniVersion + /// The source file where the error was created. + public let file: String - /// Thread is detached from the VM. - case threadDetached + /// The source line where the error was created. + public let line: UInt - /// Out of memory. - case outOfMemory + public init(_ code: Code, file: String = #fileID, line: UInt = #line) { + self.code = code + self.file = file + self.line = line + } - /// Invalid arguments. - case invalidArguments + init?(fromJNIError error: jint, file: String = #fileID, line: UInt = #line) { + guard error != JNI_OK else { return nil } + self.code = Code(rawValue: error) + self.file = file + self.line = line + } - /// Cannot locate a `JAVA_HOME` - case javaHomeNotFound + /// The kinds of errors that can occur when interacting with JNI. + public struct Code: RawRepresentable, Equatable, Hashable, Sendable { + public let rawValue: jint - /// Cannot find `libjvm` - case libjvmNotFound + public init(rawValue: jint) { + self.rawValue = rawValue + } - /// Cannot `dlopen` `libjvm` - case libjvmNotLoaded + /// Thread is detached from the VM. (JNI_EDETACHED) + public static var threadDetached: Code { Code(rawValue: JNI_EDETACHED) } - /// Cannot load `JNI_GetCreatedJavaVMs` from `libjvm` - case cannotLoadGetCreatedJavaVMs + /// JNI version mismatch error. (JNI_EVERSION) + public static var jniVersion: Code { Code(rawValue: JNI_EVERSION) } - /// Cannot load `JNI_CreateJavaVM` from `libjvm` - case cannotLoadCreateJavaVM + /// Out of memory. (JNI_ENOMEM) + public static var outOfMemory: Code { Code(rawValue: JNI_ENOMEM) } - /// Unknown JNI error. - case unknown(jint, file: String, line: UInt) + /// There is already a Java Virtual Machine. (JNI_EEXIST) + public static var existingVM: Code { Code(rawValue: JNI_EEXIST) } - init?(fromJNIError error: jint, file: String = #fileID, line: UInt = #line) { - switch error { - case JNI_OK: return nil - case JNI_EDETACHED: self = .threadDetached - case JNI_EVERSION: self = .jniVersion - case JNI_ENOMEM: self = .outOfMemory - case JNI_EEXIST: self = .existingVM - case JNI_EINVAL: self = .invalidArguments - default: self = .unknown(error, file: file, line: line) - } + /// Invalid arguments. (JNI_EINVAL) + public static var invalidArguments: Code { Code(rawValue: JNI_EINVAL) } + + /// Cannot locate a `JAVA_HOME`. + public static var javaHomeNotFound: Code { Code(rawValue: -100) } + + /// Cannot find `libjvm`. + public static var libjvmNotFound: Code { Code(rawValue: -101) } + + /// Cannot `dlopen` `libjvm`. + public static var libjvmNotLoaded: Code { Code(rawValue: -102) } + + /// Cannot load `JNI_GetCreatedJavaVMs` from `libjvm`. + public static var cannotLoadGetCreatedJavaVMs: Code { Code(rawValue: -103) } + + /// Cannot load `JNI_CreateJavaVM` from `libjvm`. + public static var cannotLoadCreateJavaVM: Code { Code(rawValue: -104) } + + /// Unknown JNI error. Check `VMError.jniErrorCode` for the raw value. + public static var unknown: Code { Code(rawValue: -999) } } } } @@ -469,7 +488,7 @@ private func loadLibJava() throws(JavaVirtualMachine.VMError) -> DylibType { #endif guard let javaHome = systemJavaHome() else { - throw JavaVirtualMachine.VMError.javaHomeNotFound + throw JavaVirtualMachine.VMError(.javaHomeNotFound) } let javaHomeURL = URL(fileURLWithPath: javaHome, isDirectory: true) @@ -502,7 +521,7 @@ private func loadLibJava() throws(JavaVirtualMachine.VMError) -> DylibType { FileManager.default.isReadableFile(atPath: $0.path) }) else { - throw JavaVirtualMachine.VMError.libjvmNotFound + throw JavaVirtualMachine.VMError(.libjvmNotFound) } #if os(Windows) @@ -512,7 +531,7 @@ private func loadLibJava() throws(JavaVirtualMachine.VMError) -> DylibType { #endif guard let dylib else { - throw JavaVirtualMachine.VMError.libjvmNotLoaded + throw JavaVirtualMachine.VMError(.libjvmNotLoaded) } return dylib From c9dde0f6e65824ee0e13de442133ce2bc4fb4e3c Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 22 Mar 2026 23:34:18 -0400 Subject: [PATCH 09/11] Add error code matching for `VMError` --- .../VirtualMachine/JavaVirtualMachine.swift | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift index 066678a..860b3a5 100644 --- a/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift +++ b/Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift @@ -312,7 +312,7 @@ extension JavaVirtualMachine { vmOptions: vmOptions, ignoreUnrecognized: ignoreUnrecognized ) - } catch VMError.existingVM { + } catch .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. @@ -376,9 +376,9 @@ extension JavaVirtualMachine { /// The kinds of errors that can occur when interacting with JNI. public struct Code: RawRepresentable, Equatable, Hashable, Sendable { - public let rawValue: jint + public let rawValue: Int32 - public init(rawValue: jint) { + public init(rawValue: Int32) { self.rawValue = rawValue } @@ -411,13 +411,18 @@ extension JavaVirtualMachine { /// Cannot load `JNI_CreateJavaVM` from `libjvm`. public static var cannotLoadCreateJavaVM: Code { Code(rawValue: -104) } - - /// Unknown JNI error. Check `VMError.jniErrorCode` for the raw value. - public static var unknown: Code { Code(rawValue: -999) } } } } +/// Pattern matching operator to enable switching on ``JavaVirtualMachine.VMError`` codes. +public func ~= (code: JavaVirtualMachine.VMError.Code, error: any Error) -> Bool { + guard let error = error as? JavaVirtualMachine.VMError else { + return false + } + return error.code == code +} + // ==== ------------------------------------------------------------------------ // MARK: Utilities for loading libjvm and JNI entry point symbols. From 133ac29d9d66bb27835f7c1066c39ba2573936d6 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 22 Mar 2026 23:39:40 -0400 Subject: [PATCH 10/11] Update `JavaDemanglingError` --- .../JavaDemanglingError.swift | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftJavaJNICore/JavaDemanglingError.swift b/Sources/SwiftJavaJNICore/JavaDemanglingError.swift index 241cbe5..ab50418 100644 --- a/Sources/SwiftJavaJNICore/JavaDemanglingError.swift +++ b/Sources/SwiftJavaJNICore/JavaDemanglingError.swift @@ -13,10 +13,29 @@ //===----------------------------------------------------------------------===// /// Describes an error that can occur when demangling a Java name. -public enum JavaDemanglingError: Error { +public struct JavaDemanglingError: Error, Sendable { + /// The kind of demangling error. + internal let kind: Kind + + internal init(kind: Kind) { + self.kind = kind + } + /// This does not match the form of a Java mangled type name. - case invalidMangledName(String) + public static func invalidMangledName(_ name: String) -> JavaDemanglingError { + JavaDemanglingError(kind: .invalidMangledName(name)) + } /// Extra text after the mangled name. - case extraText(String) + public static func extraText(_ text: String) -> JavaDemanglingError { + JavaDemanglingError(kind: .extraText(text)) + } + + internal enum Kind: Equatable, Hashable, Sendable { + /// This does not match the form of a Java mangled type name. + case invalidMangledName(String) + + /// Extra text after the mangled name. + case extraText(String) + } } From c522948766902f09d84d20225a66989734c92812 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Sun, 22 Mar 2026 23:44:17 -0400 Subject: [PATCH 11/11] Remove typed throws for `JavaType.init(mangledName:)` --- Sources/SwiftJavaJNICore/JavaType+JavaSource.swift | 2 +- Sources/SwiftJavaJNICore/Mangling.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftJavaJNICore/JavaType+JavaSource.swift b/Sources/SwiftJavaJNICore/JavaType+JavaSource.swift index 527ca18..96f72be 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(JavaDemanglingError) { + public init(javaTypeName: String) throws { switch javaTypeName { case "boolean": self = .boolean case "byte": self = .byte diff --git a/Sources/SwiftJavaJNICore/Mangling.swift b/Sources/SwiftJavaJNICore/Mangling.swift index b01b013..0981d63 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(JavaDemanglingError) { + public init(mangledName: String) throws { 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(JavaDemanglingError) { + public init(mangledName: String) throws { // Method signatures have the form "(parameter-types)result-type". guard mangledName.starts(with: "(") else { throw JavaDemanglingError.invalidMangledName(mangledName)