Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,7 @@ extension JNISwift2JavaGenerator {
"""
let class$ = environment.interface.GetObjectClass(environment, \(placeholder))
let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "\(methodSignature.mangledName)")!
environment.interface.DeleteLocalRef(environment, class$)
let arguments$: [jvalue] = [\(arguments.joined(separator: ", "))]
"""
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,19 @@ extension JNISwift2JavaGenerator {
}

printer.printBraceBlock(function.swiftDecl.signatureString) { printer in
// Push a local JNI frame so refs created during this upcall are freed on exit.
// When called from a Swift async context (e.g. cooperative thread pool) there is
// no enclosing JNI frame, so refs would otherwise accumulate indefinitely. When
// called from a Java-initiated native call there is already a frame, but pushing
// a sub-frame still frees refs earlier and prevents overflow within a single call.
let paramCount = function.originalFunctionSignature.parameters.count
printer.print(
"""
let environment$ = try! JavaVirtualMachine.shared().environment()
environment$.interface.PushLocalFrame(environment$, \(paramCount * 2 + 4))
defer { environment$.interface.PopLocalFrame(environment$, nil) }
"""
)
var upcallArguments = zip(
function.originalFunctionSignature.parameters,
function.parameterConversions
Expand Down
2 changes: 2 additions & 0 deletions Sources/JavaKit/Helpers/_JNIMethodIDCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public final class _JNIMethodIDCache: Sendable {
fatalError("Method \(method.signature) with signature \(method.signature) not found in class \(className)")
}
}
// clazz is still needed by GetMethodID above; delete the local ref only after reduce completes.
environment.interface.DeleteLocalRef(environment, clazz)
}

public subscript(_ method: Method) -> jmethodID? {
Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftJava/Exceptions/ExceptionHandling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ extension JNIEnvironment {
}
return
}
defer { self.interface.DeleteLocalRef(self, exceptionClass) }

_ = interface.ThrowNew(self, exceptionClass, javaException.message ?? "")
}
Expand Down
33 changes: 29 additions & 4 deletions Sources/SwiftJava/JavaObject+MethodCalls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ extension AnyJavaObject {
let thisClass = try environment.translatingJNIExceptions {
environment.interface.GetObjectClass(environment, javaThis)
}!
defer { environment.interface.DeleteLocalRef(environment, thisClass) }

return try environment.translatingJNIExceptions {
try Self.javaMethodLookup(
Expand All @@ -131,6 +132,7 @@ extension AnyJavaObject {
let thisClass = try environment.translatingJNIExceptions {
environment.interface.GetObjectClass(environment, javaThis)
}!
defer { environment.interface.DeleteLocalRef(environment, thisClass) }

return try environment.translatingJNIExceptions {
try Self.javaMethodLookup(
Expand All @@ -153,7 +155,11 @@ extension AnyJavaObject {
) throws -> Result {
// Retrieve the method that performs this call, then package the values and
// call it.
// Size the frame to 1 ref per argument (strings/objects each need 1;
// primitives need 0, so this slightly over-estimates) plus 1 for the result ref.
let jniMethod = Result.jniMethodCall(in: environment)
environment.interface.PushLocalFrame(environment, Int32(countArgs(repeat each args) + 2))
defer { environment.interface.PopLocalFrame(environment, nil) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, hope this'll prove helpful enough

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nil is scary actually, lets think more about it

let jniArgs = getJValues(repeat each args, in: environment)
let jniResult = try environment.translatingJNIExceptions {
jniMethod(environment, this, method, jniArgs)
Expand Down Expand Up @@ -219,7 +225,10 @@ extension AnyJavaObject {
) throws {
// Retrieve the method that performs this call, then package the arguments
// and call it.
// Size the frame to 1 ref per argument; no result ref needed for void calls.
let jniMethod = environment.interface.CallVoidMethodA!
environment.interface.PushLocalFrame(environment, Int32(countArgs(repeat each args) + 1))
defer { environment.interface.PopLocalFrame(environment, nil) }
let jniArgs = getJValues(repeat each args, in: environment)
try environment.translatingJNIExceptions {
jniMethod(environment, this, method, jniArgs)
Expand Down Expand Up @@ -269,11 +278,16 @@ extension AnyJavaObject {
in: environment
)

// Retrieve the constructor, then map the arguments and call it.
// Retrieve the constructor, then map the arguments and call it. Use a local
// frame so args are freed; promote the new object to the outer frame
// via PopLocalFrame(result).
// Size the frame to 1 ref per argument; result is promoted via PopLocalFrame(newObj).
environment.interface.PushLocalFrame(environment, Int32(countArgs(repeat each arguments) + 1))
let jniArgs = getJValues(repeat each arguments, in: environment)
return try environment.translatingJNIExceptions {
let newObj = try environment.translatingJNIExceptions {
environment.interface.NewObjectA!(environment, thisClass, methodID, jniArgs)
}!
return environment.interface.PopLocalFrame(environment, newObj)!
}
}

Expand All @@ -285,6 +299,7 @@ extension AnyJavaObject {

// Retrieve the Java class instance from the object.
let thisClass = environment.interface.GetObjectClass(environment, this)!
defer { environment.interface.DeleteLocalRef(environment, thisClass) }

return environment.interface.GetFieldID(environment, thisClass, fieldName, FieldType.jniMangling)
}
Expand All @@ -306,6 +321,9 @@ extension AnyJavaObject {

let fieldID = getJNIFieldID(fieldName, fieldType: fieldType)!
let jniMethod = FieldType.jniFieldSet(in: environment)
// Frame of 2: 1 for the field value's local ref, 1 buffer.
environment.interface.PushLocalFrame(environment, 2)
defer { environment.interface.PopLocalFrame(environment, nil) }
jniMethod(environment, javaThis, fieldID, newValue.getJNIValue(in: environment))
}
}
Expand Down Expand Up @@ -337,8 +355,12 @@ extension JavaClass {
)
}!

// Retrieve the method that performs this call, then
// Retrieve the method that performs this call, then package the values and
// call it.
// Size the frame to 1 ref per argument plus 1 for the result ref.
let jniMethod = Result.jniStaticMethodCall(in: environment)
environment.interface.PushLocalFrame(environment, Int32(countArgs(repeat each arguments) + 2))
defer { environment.interface.PopLocalFrame(environment, nil) }
let jniArgs = getJValues(repeat each arguments, in: environment)
let jniResult = try environment.translatingJNIExceptions {
jniMethod(environment, thisClass, methodID, jniArgs)
Expand Down Expand Up @@ -371,8 +393,11 @@ extension JavaClass {
)
}!

// Retrieve the method that performs this call, then
// Retrieve the method that performs this call
// Size the frame to 1 ref per argument; no result ref needed for void calls.
let jniMethod = environment.interface.CallStaticVoidMethodA
environment.interface.PushLocalFrame(environment, Int32(countArgs(repeat each arguments) + 1))
defer { environment.interface.PopLocalFrame(environment, nil) }
let jniArgs = getJValues(repeat each arguments, in: environment)
try environment.translatingJNIExceptions {
jniMethod!(environment, thisClass, methodID, jniArgs)
Expand Down
6 changes: 6 additions & 0 deletions Sources/SwiftJava/JavaObjectHolder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ public final class JavaObjectHolder {
public init(object: jobject, environment: JNIEnvironment) {
self.object = environment.interface.NewGlobalRef(environment, object)
self.environment = environment

// If we are taking over a local ref, let's delete that.
let refType = environment.interface.GetObjectRefType(environment, object)
if refType == JNILocalRefType {
environment.interface.DeleteLocalRef(environment, object)
}
}

/// Forget this Java object, meaning that it is no longer used from anywhere
Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public final class _JNIMethodIDCache: Sendable {
let clazz: jobject
if let jniClass = environment.interface.FindClass(environment, className) {
clazz = environment.interface.NewGlobalRef(environment, jniClass)!
environment.interface.DeleteLocalRef(environment, jniClass)
self.javaObjectHolder = nil
} else {
// Clear any ClassNotFound exceptions from FindClass
Expand Down
2 changes: 2 additions & 0 deletions Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ struct JNIClosureTests {
SwiftModule.emptyClosure(closure: {
let class$ = environment.interface.GetObjectClass(environment, closure)
let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "()V")!
environment.interface.DeleteLocalRef(environment, class$)
let arguments$: [jvalue] = []
environment.interface.CallVoidMethodA(environment, closure, methodID$, arguments$)
}
Expand Down Expand Up @@ -127,6 +128,7 @@ struct JNIClosureTests {
SwiftModule.closureWithArgumentsAndReturn(closure: { _0, _1 in
let class$ = environment.interface.GetObjectClass(environment, closure)
let methodID$ = environment.interface.GetMethodID(environment, class$, "apply", "(JZ)J")!
environment.interface.DeleteLocalRef(environment, class$)
let arguments$: [jvalue] = [_0.getJValue(in: environment), _1.getJValue(in: environment)]
return Int64(fromJNI: environment.interface.CallLongMethodA(environment, closure, methodID$, arguments$), in: environment)
}
Expand Down
6 changes: 6 additions & 0 deletions Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,15 @@ struct JNIProtocolTests {
"""
extension SwiftJavaSomeProtocolWrapper {
public func method() {
let environment$ = try! JavaVirtualMachine.shared().environment()
environment$.interface.PushLocalFrame(environment$, 4)
defer { environment$.interface.PopLocalFrame(environment$, nil) }
_javaSomeProtocolInterface.method()
}
public func withObject(c: SomeClass) -> SomeClass {
let environment$ = try! JavaVirtualMachine.shared().environment()
environment$.interface.PushLocalFrame(environment$, 6)
defer { environment$.interface.PopLocalFrame(environment$, nil) }
let cClass = try! JavaClass<JavaSomeClass>(environment: JavaVirtualMachine.shared().environment())
let cPointer = UnsafeMutablePointer<SomeClass>.allocate(capacity: 1)
cPointer.initialize(to: c)
Expand Down
Loading