diff --git a/Sources/SwiftJavaJNICore/JavaDemanglingError.swift b/Sources/SwiftJavaJNICore/JavaDemanglingError.swift index 445957d..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. -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) + } } 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/Mangling.swift b/Sources/SwiftJavaJNICore/Mangling.swift index 0b92911..0981d63 100644 --- a/Sources/SwiftJavaJNICore/Mangling.swift +++ b/Sources/SwiftJavaJNICore/Mangling.swift @@ -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..860b3a5 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 @@ -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. @@ -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 } @@ -260,13 +260,13 @@ 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 + 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!") @@ -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 { @@ -312,12 +312,16 @@ 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. wasExistingVM = true continue + } catch let error as VMError { + throw error + } catch { + fatalError("Unexpected non-VMError from JavaVirtualMachine.init: \(error)") } sharedJVMPointer = javaVirtualMachine @@ -346,57 +350,77 @@ extension JavaVirtualMachine { } extension JavaVirtualMachine { - /// Describes the kinds of errors that can occur when interacting with JNI. - 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: Int32 - /// Cannot find `libjvm` - case libjvmNotFound + public init(rawValue: Int32) { + 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) } } } +} - enum JavaKitError: Error { - case classpathEntryNotFound(entry: String, classpath: [String]) +/// 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 } // ==== ------------------------------------------------------------------------ @@ -459,7 +483,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) { @@ -469,7 +493,7 @@ private func loadLibJava() throws -> DylibType { #endif guard let javaHome = systemJavaHome() else { - throw JavaVirtualMachine.VMError.javaHomeNotFound + throw JavaVirtualMachine.VMError(.javaHomeNotFound) } let javaHomeURL = URL(fileURLWithPath: javaHome, isDirectory: true) @@ -502,7 +526,7 @@ private func loadLibJava() throws -> DylibType { FileManager.default.isReadableFile(atPath: $0.path) }) else { - throw JavaVirtualMachine.VMError.libjvmNotFound + throw JavaVirtualMachine.VMError(.libjvmNotFound) } #if os(Windows) @@ -512,7 +536,7 @@ private func loadLibJava() throws -> DylibType { #endif guard let dylib else { - throw JavaVirtualMachine.VMError.libjvmNotLoaded + throw JavaVirtualMachine.VMError(.libjvmNotLoaded) } return dylib 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() {