|
| 1 | +//===----------------------------------------------------------------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2024 Apple Inc. and the Swift.org project authors |
| 6 | +// Licensed under Apache License v2.0 |
| 7 | +// |
| 8 | +// See LICENSE.txt for license information |
| 9 | +// See CONTRIBUTORS.txt for the list of Swift.org project authors |
| 10 | +// |
| 11 | +// SPDX-License-Identifier: Apache-2.0 |
| 12 | +// |
| 13 | +//===----------------------------------------------------------------------===// |
| 14 | + |
| 15 | +#if canImport(Glibc) |
| 16 | +import Glibc |
| 17 | +#elseif canImport(Musl) |
| 18 | +import Musl |
| 19 | +#elseif canImport(Bionic) |
| 20 | +import Bionic |
| 21 | +#elseif canImport(Darwin) |
| 22 | +import Darwin |
| 23 | +#elseif os(Windows) |
| 24 | +import ucrt |
| 25 | +#endif |
| 26 | + |
| 27 | +// ==== ------------------------------------------------------------------- |
| 28 | +// MARK: Local Frame Helpers |
| 29 | + |
| 30 | +// Local references are valid for the duration of a native method call. They are |
| 31 | +// freed automatically after the native method returns. Each local reference |
| 32 | +// costs some amount of Java Virtual Machine resource. Programmers need to make |
| 33 | +// sure that native methods do not excessively allocate local references. |
| 34 | +// Although local references are automatically freed after the native method |
| 35 | +// returns to Java, excessive allocation of local references may cause the VM to |
| 36 | +// run out of memory during the execution of a native method. |
| 37 | +// |
| 38 | +// See: https://docs.oracle.com/en/java/javase/21/docs/specs/jni/functions.html#local-references |
| 39 | + |
| 40 | +/// Whether to print JNI `OutOfMemoryError` stack traces to stderr. |
| 41 | +/// |
| 42 | +/// Checked once on first OOM and cached. Set the environment variable |
| 43 | +/// `SWIFT_JAVA_JNI_EXCEPTION_DESCRIBE_OOM` to `true` or `1` to enable. |
| 44 | +private let describeOOMException: Bool = { |
| 45 | + guard let value = getenv("SWIFT_JAVA_JNI_EXCEPTION_DESCRIBE_OOM") else { |
| 46 | + return false |
| 47 | + } |
| 48 | + let str = String(cString: value).lowercased() |
| 49 | + return str == "1" || str == "true" || str == "yes" |
| 50 | +}() |
| 51 | + |
| 52 | +extension UnsafeMutablePointer<JNIEnv?> { |
| 53 | + |
| 54 | + /// Handle a `PushLocalFrame` failure by optionally describing the pending |
| 55 | + /// exception to stderr, clearing it, and throwing a Swift error. |
| 56 | + /// |
| 57 | + /// Must be called while the `OutOfMemoryError` is still pending (i.e. |
| 58 | + /// before `ExceptionClear`). `ExceptionDescribe` is safe to call with a |
| 59 | + /// pending exception — it prints the stack trace to stderr and does **not** |
| 60 | + /// clear the exception. |
| 61 | + @inline(__always) |
| 62 | + internal func throwPushLocalFrameOOM(capacity: Int) throws -> Never { |
| 63 | + if describeOOMException { |
| 64 | + // Print the pending OutOfMemoryError stack trace to stderr. |
| 65 | + // ExceptionDescribe does not clear the exception. |
| 66 | + self.interface.ExceptionDescribe(self) |
| 67 | + } |
| 68 | + self.interface.ExceptionClear(self) |
| 69 | + throw JNIError.outOfMemory(framePushCapacity: capacity) |
| 70 | + } |
| 71 | + |
| 72 | + /// Execute `body` inside a JNI local reference frame. |
| 73 | + /// |
| 74 | + /// All local references created inside `body` are freed when it returns. |
| 75 | + /// This prevents local reference table overflow when making many JNI calls |
| 76 | + /// (e.g., in loops or from non-JVM threads like Swift's cooperative pool). |
| 77 | + /// |
| 78 | + /// - Parameter capacity: Hint for how many local refs will be created. |
| 79 | + /// The JVM may allocate more if needed. Must be > 0. |
| 80 | + /// - Parameter body: The closure to execute inside the local frame. |
| 81 | + /// - Returns: The value returned by `body`. |
| 82 | + /// - Throws: ``JNIError/outOfMemory`` if `PushLocalFrame` fails, or |
| 83 | + /// rethrows any error thrown by `body`. |
| 84 | + /// |
| 85 | + /// ## See Also |
| 86 | + /// - [JNI PushLocalFrame](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#PushLocalFrame) |
| 87 | + /// - [JNI PopLocalFrame](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#PopLocalFrame) |
| 88 | + @inline(__always) |
| 89 | + public func withLocalFrame<R>(capacity: Int = 16, _ body: () throws -> R) throws -> R { |
| 90 | + let pushed = self.interface.PushLocalFrame(self, Int32(capacity)) |
| 91 | + if pushed != JNI_OK { |
| 92 | + try self.throwPushLocalFrameOOM(capacity: capacity) |
| 93 | + } |
| 94 | + defer { _ = self.interface.PopLocalFrame(self, nil) } |
| 95 | + return try body() |
| 96 | + } |
| 97 | + |
| 98 | + /// Execute `body` inside a JNI local reference frame, promoting one result |
| 99 | + /// object to the outer frame. |
| 100 | + /// |
| 101 | + /// All local references created inside `body` are freed, **except** for the |
| 102 | + /// returned `jobject` which is promoted to the enclosing frame via |
| 103 | + /// `PopLocalFrame(env, result)`. |
| 104 | + /// |
| 105 | + /// Use this when constructing a new Java object inside a frame that needs |
| 106 | + /// to survive after the frame is popped. |
| 107 | + /// |
| 108 | + /// - Parameter capacity: Hint for how many local refs will be created. |
| 109 | + /// - Parameter body: Closure that returns the `jobject` to promote. |
| 110 | + /// - Returns: A new local reference in the outer frame to the same object. |
| 111 | + /// - Throws: ``JNIError/outOfMemory`` if `PushLocalFrame` fails, or |
| 112 | + /// rethrows any error thrown by `body`. |
| 113 | + /// |
| 114 | + /// ## See Also |
| 115 | + /// - [JNI PushLocalFrame](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#PushLocalFrame) |
| 116 | + /// - [JNI PopLocalFrame](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#PopLocalFrame) |
| 117 | + @inline(__always) |
| 118 | + public func withLocalFramePromotingResult(capacity: Int = 16, _ body: () throws -> jobject?) throws -> jobject? { |
| 119 | + let pushed = self.interface.PushLocalFrame(self, Int32(capacity)) |
| 120 | + if pushed != JNI_OK { |
| 121 | + try self.throwPushLocalFrameOOM(capacity: capacity) |
| 122 | + } |
| 123 | + do { |
| 124 | + let result = try body() |
| 125 | + return self.interface.PopLocalFrame(self, result) |
| 126 | + } catch { |
| 127 | + // Pop the frame (freeing all inner refs) before rethrowing. |
| 128 | + _ = self.interface.PopLocalFrame(self, nil) |
| 129 | + throw error |
| 130 | + } |
| 131 | + } |
| 132 | + |
| 133 | + /// Delete a local reference. |
| 134 | + /// |
| 135 | + /// Shorthand for `interface.DeleteLocalRef(self, ref)`. Safe to call with |
| 136 | + /// `nil` (no-op). |
| 137 | + /// |
| 138 | + /// ## See Also |
| 139 | + /// - [JNI DeleteLocalRef](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#DeleteLocalRef) |
| 140 | + @inline(__always) |
| 141 | + public func deleteLocalRef(_ ref: jobject?) { |
| 142 | + self.interface.DeleteLocalRef(self, ref) |
| 143 | + } |
| 144 | +} |
0 commit comments