Skip to content

Commit 7d6584b

Browse files
authored
Use typed throws (#12)
* Use typed throws * Export `JavaDemanglingError` as public * Export `JavaVirtualMachine.VMError` as public * Remove `JavaKitError` * Use typed throws for `JavaVirtualMachine.shared()` * Use typed throws for `LockedState` * Apply formatting * Add `VMError.Code` * Add error code matching for `VMError` * Update `JavaDemanglingError` * Remove typed throws for `JavaType.init(mangledName:)`
1 parent 08a9128 commit 7d6584b

5 files changed

Lines changed: 113 additions & 72 deletions

File tree

Sources/SwiftJavaJNICore/JavaDemanglingError.swift

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,29 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
/// Describes an error that can occur when demangling a Java name.
16-
enum JavaDemanglingError: Error {
16+
public struct JavaDemanglingError: Error, Sendable {
17+
/// The kind of demangling error.
18+
internal let kind: Kind
19+
20+
internal init(kind: Kind) {
21+
self.kind = kind
22+
}
23+
1724
/// This does not match the form of a Java mangled type name.
18-
case invalidMangledName(String)
25+
public static func invalidMangledName(_ name: String) -> JavaDemanglingError {
26+
JavaDemanglingError(kind: .invalidMangledName(name))
27+
}
1928

2029
/// Extra text after the mangled name.
21-
case extraText(String)
30+
public static func extraText(_ text: String) -> JavaDemanglingError {
31+
JavaDemanglingError(kind: .extraText(text))
32+
}
33+
34+
internal enum Kind: Equatable, Hashable, Sendable {
35+
/// This does not match the form of a Java mangled type name.
36+
case invalidMangledName(String)
37+
38+
/// Extra text after the mangled name.
39+
case extraText(String)
40+
}
2241
}

Sources/SwiftJavaJNICore/JavaEnvironment+Refs.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ extension UnsafeMutablePointer<JNIEnv?> {
5959
/// pending exception — it prints the stack trace to stderr and does **not**
6060
/// clear the exception.
6161
@inline(__always)
62-
internal func throwPushLocalFrameOOM(capacity: Int) throws -> Never {
62+
internal func throwPushLocalFrameOOM(capacity: Int) throws(JNIError) -> Never {
6363
if describeOOMException {
6464
// Print the pending OutOfMemoryError stack trace to stderr.
6565
// ExceptionDescribe does not clear the exception.

Sources/SwiftJavaJNICore/Mangling.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ extension MethodSignature {
8282
extension JavaType {
8383
/// Demangle the next Java type from the given string, shrinking the input
8484
/// string and producing demangled type.
85-
static func demangleNextType(from string: inout Substring) throws -> JavaType {
85+
static func demangleNextType(from string: inout Substring) throws(JavaDemanglingError) -> JavaType {
8686
guard let firstChar = string.first else {
8787
throw JavaDemanglingError.invalidMangledName(String(string))
8888
}

Sources/SwiftJavaJNICore/VirtualMachine/JavaVirtualMachine.swift

Lines changed: 73 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
8080
classpath: [String] = [],
8181
vmOptions: [String] = [],
8282
ignoreUnrecognized: Bool = false
83-
) throws {
83+
) throws(VMError) {
8484
self.classpath = classpath
8585
var jvm: JavaVMPointer? = nil
8686
var environment: JNIEnvPointer? = nil
@@ -129,7 +129,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
129129

130130
typealias CreateJavaVM = @convention(c) (_ pvm: UnsafeMutablePointer<JavaVMPointer?>?, _ penv: UnsafeMutablePointer<JNIEnvPointer?>?, _ args: UnsafeMutableRawPointer) -> jint
131131
guard let createJavaVM: CreateJavaVM = symbol(try loadLibJava(), "JNI_CreateJavaVM") else {
132-
throw VMError.cannotLoadCreateJavaVM
132+
throw VMError(.cannotLoadCreateJavaVM)
133133
}
134134

135135
// Create the JVM instance.
@@ -141,7 +141,7 @@ public final class JavaVirtualMachine: @unchecked Sendable {
141141
self.destroyOnDeinit = .init(initialState: true)
142142
}
143143

144-
public func destroyJVM() throws {
144+
public func destroyJVM() throws(VMError) {
145145
try self.detachCurrentThread()
146146
if let error = VMError(fromJNIError: jvm.pointee!.pointee.DestroyJavaVM(jvm)) {
147147
throw error
@@ -177,7 +177,7 @@ extension JavaVirtualMachine {
177177
/// - Parameter
178178
/// - asDaemon: Whether this thread should be treated as a daemon
179179
/// thread in the Java Virtual Machine.
180-
public func environment(asDaemon: Bool = false) throws -> JNIEnvironment {
180+
public func environment(asDaemon: Bool = false) throws(VMError) -> JNIEnvironment {
181181
// Check whether this thread is already attached. If so, return the
182182
// corresponding environment.
183183
var environment: UnsafeMutableRawPointer? = nil
@@ -213,7 +213,7 @@ extension JavaVirtualMachine {
213213

214214
/// Detach the current thread from the Java Virtual Machine. All Java
215215
/// threads waiting for this thread to die are notified.
216-
func detachCurrentThread() throws {
216+
func detachCurrentThread() throws(VMError) {
217217
if let resultError = VMError(fromJNIError: jvm.pointee!.pointee.DetachCurrentThread(jvm)) {
218218
throw resultError
219219
}
@@ -260,13 +260,13 @@ extension JavaVirtualMachine {
260260
vmOptions: [String] = [],
261261
ignoreUnrecognized: Bool = false,
262262
replace: Bool = false
263-
) throws -> JavaVirtualMachine {
263+
) throws(VMError) -> JavaVirtualMachine {
264264
precondition(
265265
!classpath.contains(where: { $0.contains(FileManager.pathSeparator) }),
266266
"Classpath element must not contain `\(FileManager.pathSeparator)`! Split the path into elements! Was: \(classpath)"
267267
)
268268

269-
return try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) in
269+
return try sharedJVM.withLock { (sharedJVMPointer: inout JavaVirtualMachine?) throws(VMError) in
270270
// If we already have a JavaVirtualMachine instance, return it.
271271
if replace {
272272
print("[swift-java] Replace JVM instance!")
@@ -281,7 +281,7 @@ extension JavaVirtualMachine {
281281

282282
typealias GetCreatedJavaVMs = @convention(c) (_ pvm: UnsafeMutablePointer<JavaVMPointer?>, _ count: Int32, _ num: UnsafeMutablePointer<Int32>) -> jint
283283
guard let getCreatedJavaVMs: GetCreatedJavaVMs = symbol(try loadLibJava(), "JNI_GetCreatedJavaVMs") else {
284-
throw VMError.cannotLoadGetCreatedJavaVMs
284+
throw VMError(.cannotLoadGetCreatedJavaVMs)
285285
}
286286

287287
while true {
@@ -312,12 +312,16 @@ extension JavaVirtualMachine {
312312
vmOptions: vmOptions,
313313
ignoreUnrecognized: ignoreUnrecognized
314314
)
315-
} catch VMError.existingVM {
315+
} catch .existingVM {
316316
// We raced with code outside of this JavaVirtualMachine instance
317317
// that created a VM while we were trying to do the same. Go
318318
// through the loop again to pick up the underlying JVM pointer.
319319
wasExistingVM = true
320320
continue
321+
} catch let error as VMError {
322+
throw error
323+
} catch {
324+
fatalError("Unexpected non-VMError from JavaVirtualMachine.init: \(error)")
321325
}
322326

323327
sharedJVMPointer = javaVirtualMachine
@@ -346,57 +350,77 @@ extension JavaVirtualMachine {
346350
}
347351

348352
extension JavaVirtualMachine {
349-
/// Describes the kinds of errors that can occur when interacting with JNI.
350-
enum VMError: Error {
351-
/// There is already a Java Virtual Machine.
352-
case existingVM
353+
/// Describes an error that occurred when interacting with JNI.
354+
public struct VMError: Error {
355+
/// The specific kind of error that occurred.
356+
public let code: Code
353357

354-
/// JNI version mismatch error.
355-
case jniVersion
358+
/// The source file where the error was created.
359+
public let file: String
356360

357-
/// Thread is detached from the VM.
358-
case threadDetached
361+
/// The source line where the error was created.
362+
public let line: UInt
359363

360-
/// Out of memory.
361-
case outOfMemory
364+
public init(_ code: Code, file: String = #fileID, line: UInt = #line) {
365+
self.code = code
366+
self.file = file
367+
self.line = line
368+
}
362369

363-
/// Invalid arguments.
364-
case invalidArguments
370+
init?(fromJNIError error: jint, file: String = #fileID, line: UInt = #line) {
371+
guard error != JNI_OK else { return nil }
372+
self.code = Code(rawValue: error)
373+
self.file = file
374+
self.line = line
375+
}
365376

366-
/// Cannot locate a `JAVA_HOME`
367-
case javaHomeNotFound
377+
/// The kinds of errors that can occur when interacting with JNI.
378+
public struct Code: RawRepresentable, Equatable, Hashable, Sendable {
379+
public let rawValue: Int32
368380

369-
/// Cannot find `libjvm`
370-
case libjvmNotFound
381+
public init(rawValue: Int32) {
382+
self.rawValue = rawValue
383+
}
371384

372-
/// Cannot `dlopen` `libjvm`
373-
case libjvmNotLoaded
385+
/// Thread is detached from the VM. (JNI_EDETACHED)
386+
public static var threadDetached: Code { Code(rawValue: JNI_EDETACHED) }
374387

375-
/// Cannot load `JNI_GetCreatedJavaVMs` from `libjvm`
376-
case cannotLoadGetCreatedJavaVMs
388+
/// JNI version mismatch error. (JNI_EVERSION)
389+
public static var jniVersion: Code { Code(rawValue: JNI_EVERSION) }
377390

378-
/// Cannot load `JNI_CreateJavaVM` from `libjvm`
379-
case cannotLoadCreateJavaVM
391+
/// Out of memory. (JNI_ENOMEM)
392+
public static var outOfMemory: Code { Code(rawValue: JNI_ENOMEM) }
380393

381-
/// Unknown JNI error.
382-
case unknown(jint, file: String, line: UInt)
394+
/// There is already a Java Virtual Machine. (JNI_EEXIST)
395+
public static var existingVM: Code { Code(rawValue: JNI_EEXIST) }
383396

384-
init?(fromJNIError error: jint, file: String = #fileID, line: UInt = #line) {
385-
switch error {
386-
case JNI_OK: return nil
387-
case JNI_EDETACHED: self = .threadDetached
388-
case JNI_EVERSION: self = .jniVersion
389-
case JNI_ENOMEM: self = .outOfMemory
390-
case JNI_EEXIST: self = .existingVM
391-
case JNI_EINVAL: self = .invalidArguments
392-
default: self = .unknown(error, file: file, line: line)
393-
}
397+
/// Invalid arguments. (JNI_EINVAL)
398+
public static var invalidArguments: Code { Code(rawValue: JNI_EINVAL) }
399+
400+
/// Cannot locate a `JAVA_HOME`.
401+
public static var javaHomeNotFound: Code { Code(rawValue: -100) }
402+
403+
/// Cannot find `libjvm`.
404+
public static var libjvmNotFound: Code { Code(rawValue: -101) }
405+
406+
/// Cannot `dlopen` `libjvm`.
407+
public static var libjvmNotLoaded: Code { Code(rawValue: -102) }
408+
409+
/// Cannot load `JNI_GetCreatedJavaVMs` from `libjvm`.
410+
public static var cannotLoadGetCreatedJavaVMs: Code { Code(rawValue: -103) }
411+
412+
/// Cannot load `JNI_CreateJavaVM` from `libjvm`.
413+
public static var cannotLoadCreateJavaVM: Code { Code(rawValue: -104) }
394414
}
395415
}
416+
}
396417

397-
enum JavaKitError: Error {
398-
case classpathEntryNotFound(entry: String, classpath: [String])
418+
/// Pattern matching operator to enable switching on ``JavaVirtualMachine.VMError`` codes.
419+
public func ~= (code: JavaVirtualMachine.VMError.Code, error: any Error) -> Bool {
420+
guard let error = error as? JavaVirtualMachine.VMError else {
421+
return false
399422
}
423+
return error.code == code
400424
}
401425

402426
// ==== ------------------------------------------------------------------------
@@ -459,7 +483,7 @@ func systemJavaHome() -> String? {
459483
}
460484

461485
/// Located the shared library that includes the `JNI_GetCreatedJavaVMs` and `JNI_CreateJavaVM` entry points to the `JNINativeInterface` function table
462-
private func loadLibJava() throws -> DylibType {
486+
private func loadLibJava() throws(JavaVirtualMachine.VMError) -> DylibType {
463487
#if os(Android)
464488
for libname in ["libart.so", "libdvm.so", "libnativehelper.so"] {
465489
if let lib = dlopen(libname, RTLD_NOW) {
@@ -469,7 +493,7 @@ private func loadLibJava() throws -> DylibType {
469493
#endif
470494

471495
guard let javaHome = systemJavaHome() else {
472-
throw JavaVirtualMachine.VMError.javaHomeNotFound
496+
throw JavaVirtualMachine.VMError(.javaHomeNotFound)
473497
}
474498

475499
let javaHomeURL = URL(fileURLWithPath: javaHome, isDirectory: true)
@@ -502,7 +526,7 @@ private func loadLibJava() throws -> DylibType {
502526
FileManager.default.isReadableFile(atPath: $0.path)
503527
})
504528
else {
505-
throw JavaVirtualMachine.VMError.libjvmNotFound
529+
throw JavaVirtualMachine.VMError(.libjvmNotFound)
506530
}
507531

508532
#if os(Windows)
@@ -512,7 +536,7 @@ private func loadLibJava() throws -> DylibType {
512536
#endif
513537

514538
guard let dylib else {
515-
throw JavaVirtualMachine.VMError.libjvmNotLoaded
539+
throw JavaVirtualMachine.VMError(.libjvmNotLoaded)
516540
}
517541

518542
return dylib

Sources/SwiftJavaJNICore/VirtualMachine/LockedState.swift

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -111,26 +111,24 @@ package struct LockedState<State> {
111111
)
112112
}
113113

114-
package func withLock<T>(_ body: @Sendable (inout State) throws -> T) rethrows -> T {
114+
package func withLock<T, E: Error>(_ body: @Sendable (inout State) throws(E) -> T) throws(E) -> T {
115115
try withLockUnchecked(body)
116116
}
117117

118-
package func withLockUnchecked<T>(_ body: (inout State) throws -> T) rethrows -> T {
119-
try _buffer.withUnsafeMutablePointers { state, lock in
120-
_Lock.lock(lock)
121-
defer { _Lock.unlock(lock) }
122-
return try body(&state.pointee)
123-
}
118+
package func withLockUnchecked<T, E: Error>(_ body: (inout State) throws(E) -> T) throws(E) -> T {
119+
_buffer.withUnsafeMutablePointerToElements { _Lock.lock($0) }
120+
defer { _buffer.withUnsafeMutablePointerToElements { _Lock.unlock($0) } }
121+
return try body(&_buffer.header)
124122
}
125123

126124
// Ensures the managed state outlives the locked scope.
127-
package func withLockExtendingLifetimeOfState<T>(_ body: @Sendable (inout State) throws -> T) rethrows -> T {
128-
try _buffer.withUnsafeMutablePointers { state, lock in
129-
_Lock.lock(lock)
130-
return try withExtendedLifetime(state.pointee) {
131-
defer { _Lock.unlock(lock) }
132-
return try body(&state.pointee)
133-
}
125+
package func withLockExtendingLifetimeOfState<T, E: Error>(_ body: @Sendable (inout State) throws(E) -> T) throws(E) -> T {
126+
_buffer.withUnsafeMutablePointerToElements { _Lock.lock($0) }
127+
defer { _buffer.withUnsafeMutablePointerToElements { _Lock.unlock($0) } }
128+
do {
129+
return try body(&_buffer.header)
130+
} catch {
131+
throw error
134132
}
135133
}
136134
}
@@ -140,10 +138,10 @@ extension LockedState where State == Void {
140138
self.init(initialState: ())
141139
}
142140

143-
package func withLock<R: Sendable>(_ body: @Sendable () throws -> R) rethrows -> R {
144-
try withLock { _ in
145-
try body()
146-
}
141+
package func withLock<R: Sendable, E: Error>(_ body: @Sendable () throws(E) -> R) throws(E) -> R {
142+
_buffer.withUnsafeMutablePointerToElements { _Lock.lock($0) }
143+
defer { _buffer.withUnsafeMutablePointerToElements { _Lock.unlock($0) } }
144+
return try body()
147145
}
148146

149147
package func lock() {

0 commit comments

Comments
 (0)