From e9f43826748b4aebea1683b38e1775c88971f571 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 12 Mar 2026 10:56:28 +0100 Subject: [PATCH 01/12] clear local ref in JavaObjectHolder --- Sources/SwiftJava/JavaObjectHolder.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/SwiftJava/JavaObjectHolder.swift b/Sources/SwiftJava/JavaObjectHolder.swift index 65557e23..e6d0ca28 100644 --- a/Sources/SwiftJava/JavaObjectHolder.swift +++ b/Sources/SwiftJava/JavaObjectHolder.swift @@ -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 From e5733df5725ce0b98ba4a2034195ded408731f87 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 12 Mar 2026 10:56:55 +0100 Subject: [PATCH 02/12] cleanup localref in JNI cache --- Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift index 3b9cecc8..4ba4c541 100644 --- a/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift +++ b/Sources/SwiftJavaRuntimeSupport/_JNIMethodIDCache.swift @@ -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 From d995929582e1f32ef9be60e57ce67b32e6f0e9ca Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 12 Mar 2026 11:00:48 +0100 Subject: [PATCH 03/12] delete local refs when doing method lookups --- Sources/SwiftJava/JavaObject+MethodCalls.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/SwiftJava/JavaObject+MethodCalls.swift b/Sources/SwiftJava/JavaObject+MethodCalls.swift index 1e86792b..f1e68a14 100644 --- a/Sources/SwiftJava/JavaObject+MethodCalls.swift +++ b/Sources/SwiftJava/JavaObject+MethodCalls.swift @@ -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( @@ -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( @@ -285,6 +287,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) } From a386a15fdd7a586f3cab0d638dff4931f516de0b Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 12 Mar 2026 12:54:46 +0100 Subject: [PATCH 04/12] make frame count based on args --- .../SwiftJava/JavaObject+MethodCalls.swift | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftJava/JavaObject+MethodCalls.swift b/Sources/SwiftJava/JavaObject+MethodCalls.swift index f1e68a14..9275a459 100644 --- a/Sources/SwiftJava/JavaObject+MethodCalls.swift +++ b/Sources/SwiftJava/JavaObject+MethodCalls.swift @@ -155,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) } let jniArgs = getJValues(repeat each args, in: environment) let jniResult = try environment.translatingJNIExceptions { jniMethod(environment, this, method, jniArgs) @@ -221,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) @@ -271,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)! } } @@ -309,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)) } } @@ -340,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) @@ -374,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) From 8e491cd9831866f217c86ce46973f05bc7c35e73 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 12 Mar 2026 12:54:59 +0100 Subject: [PATCH 05/12] more small cleanups --- .../JNI/JNISwift2JavaGenerator+NativeTranslation.swift | 1 + Sources/JavaKit/Helpers/_JNIMethodIDCache.swift | 2 ++ Sources/SwiftJava/Exceptions/ExceptionHandling.swift | 1 + 3 files changed, 4 insertions(+) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift index 599808fb..b3e39d67 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+NativeTranslation.swift @@ -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: ", "))] """ ) diff --git a/Sources/JavaKit/Helpers/_JNIMethodIDCache.swift b/Sources/JavaKit/Helpers/_JNIMethodIDCache.swift index 31c6a2c0..a54e547b 100644 --- a/Sources/JavaKit/Helpers/_JNIMethodIDCache.swift +++ b/Sources/JavaKit/Helpers/_JNIMethodIDCache.swift @@ -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? { diff --git a/Sources/SwiftJava/Exceptions/ExceptionHandling.swift b/Sources/SwiftJava/Exceptions/ExceptionHandling.swift index 55d676b3..d78533cd 100644 --- a/Sources/SwiftJava/Exceptions/ExceptionHandling.swift +++ b/Sources/SwiftJava/Exceptions/ExceptionHandling.swift @@ -54,6 +54,7 @@ extension JNIEnvironment { } return } + defer { self.interface.DeleteLocalRef(self, exceptionClass) } _ = interface.ThrowNew(self, exceptionClass, javaException.message ?? "") } From ce785da7688fe3b215ffe20e47074ce5197ff186 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 12 Mar 2026 13:17:19 +0100 Subject: [PATCH 06/12] fix tests --- Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift index 8cab76f0..841420d7 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIClosureTests.swift @@ -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$) } @@ -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) } From c702ebd1821d9ccbea908b2e64b1d98eda877022 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 12 Mar 2026 16:01:42 +0100 Subject: [PATCH 07/12] protocol wrappers emit a frame --- .../JNISwift2JavaGenerator+SwiftThunkPrinting.swift | 13 +++++++++++++ Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 0f2ab299..15027fab 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -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 diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index 0c504693..06f19647 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -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(environment: JavaVirtualMachine.shared().environment()) let cPointer = UnsafeMutablePointer.allocate(capacity: 1) cPointer.initialize(to: c) From 430119518b4d42eb5360c685793af85b0178cb7e Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Mon, 16 Mar 2026 15:34:50 +0100 Subject: [PATCH 08/12] try running tests on android --- .github/workflows/pull_request.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 46acd6b5..aca2b7d8 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -281,3 +281,12 @@ jobs: run: ./scripts/run-linkage-test.sh env: JAVA_HOME: ${{ env.JAVA_HOME }} + + android-sdk-testing: + name: Android SDK with emulator testing + uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@0.0.8 + with: + enable_linux_checks: false + enable_windows_checks: false + enable_android_sdk_build: true + enable_android_sdk_checks: true \ No newline at end of file From c960adb4432024015a20bab40e0bd9115f75cbc3 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 17 Mar 2026 08:24:08 +0100 Subject: [PATCH 09/12] remove android test again --- .github/workflows/pull_request.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index aca2b7d8..a94188a9 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -280,13 +280,4 @@ jobs: - name: Run linkage test run: ./scripts/run-linkage-test.sh env: - JAVA_HOME: ${{ env.JAVA_HOME }} - - android-sdk-testing: - name: Android SDK with emulator testing - uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@0.0.8 - with: - enable_linux_checks: false - enable_windows_checks: false - enable_android_sdk_build: true - enable_android_sdk_checks: true \ No newline at end of file + JAVA_HOME: ${{ env.JAVA_HOME }} \ No newline at end of file From 3f126bf5f75a658c7669013a06587de441846113 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 17 Mar 2026 09:07:14 +0100 Subject: [PATCH 10/12] use new frame helpers --- ...ift2JavaGenerator+SwiftThunkPrinting.swift | 54 +++++++++---------- .../JNI/JNIProtocolTests.swift | 26 ++++----- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift index 15027fab..b13ac55e 100644 --- a/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift +++ b/Sources/JExtractSwiftLib/JNI/JNISwift2JavaGenerator+SwiftThunkPrinting.swift @@ -171,43 +171,41 @@ extension JNISwift2JavaGenerator { } printer.printBraceBlock(function.swiftDecl.signatureString) { printer in + let resultType = function.originalFunctionSignature.result.type + let returnStmt = !resultType.isVoid ? "return " : "" + // If the protocol function is non-throwing, we have no option but to force try. + // The error thrown by `withLocalFrame` is an OOM error anyway. + let withLocalFrameTryKeyword = function.originalFunctionSignature.isThrowing ? "try" : "try!" + // 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 - ).map { param, conversion in - // Wrap-java does not extract parameter names, so no labels - conversion.render(&printer, param.parameterName!) - } + let estimatedRefCount = paramCount * 2 + 4 + printer.print("let environment$ = try! JavaVirtualMachine.shared().environment()") + printer.printBraceBlock("\(returnStmt)\(withLocalFrameTryKeyword) environment$.withLocalFrame(capacity: \(estimatedRefCount))") { printer in + var upcallArguments = zip( + function.originalFunctionSignature.parameters, + function.parameterConversions + ).map { param, conversion in + // Wrap-java does not extract parameter names, so no labels + conversion.render(&printer, param.parameterName!) + } - // If the underlying translated method requires - // a SwiftArena, we pass in the global arena - if translatedDecl.translatedFunctionSignature.requiresSwiftArena { - upcallArguments.append("JavaSwiftArena.defaultAutoArena") - } + // If the underlying translated method requires + // a SwiftArena, we pass in the global arena + if translatedDecl.translatedFunctionSignature.requiresSwiftArena { + upcallArguments.append("JavaSwiftArena.defaultAutoArena") + } - let tryClause = function.originalFunctionSignature.isThrowing ? "try " : "" - let javaUpcall = - "\(tryClause)\(wrapper.javaInterfaceVariableName).\(function.swiftFunctionName)(\(upcallArguments.joined(separator: ", ")))" + let tryClause = function.originalFunctionSignature.isThrowing ? "try " : "" + let javaUpcall = + "\(tryClause)\(wrapper.javaInterfaceVariableName).\(function.swiftFunctionName)(\(upcallArguments.joined(separator: ", ")))" - let resultType = function.originalFunctionSignature.result.type - let result = function.resultConversion.render(&printer, javaUpcall) - if resultType.isVoid { - printer.print(result) - } else { - printer.print("return \(result)") + let result = function.resultConversion.render(&printer, javaUpcall) + printer.print("\(returnStmt)\(result)") } } } diff --git a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift index 06f19647..526c8967 100644 --- a/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift +++ b/Tests/JExtractSwiftTests/JNI/JNIProtocolTests.swift @@ -316,23 +316,23 @@ 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() + try! environment$.withLocalFrame(capacity: 4) { + _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(environment: JavaVirtualMachine.shared().environment()) - let cPointer = UnsafeMutablePointer.allocate(capacity: 1) - cPointer.initialize(to: c) - guard let unwrapped$ = _javaSomeProtocolInterface.withObject(cClass.wrapMemoryAddressUnsafe(Int64(Int(bitPattern: cPointer))), JavaSwiftArena.defaultAutoArena) else { - fatalError("Upcall to withObject unexpectedly returned nil") + return try! environment$.withLocalFrame(capacity: 6) { + let cClass = try! JavaClass(environment: JavaVirtualMachine.shared().environment()) + let cPointer = UnsafeMutablePointer.allocate(capacity: 1) + cPointer.initialize(to: c) + guard let unwrapped$ = _javaSomeProtocolInterface.withObject(cClass.wrapMemoryAddressUnsafe(Int64(Int(bitPattern: cPointer))), JavaSwiftArena.defaultAutoArena) else { + fatalError("Upcall to withObject unexpectedly returned nil") + } + let result$MemoryAddress$ = unwrapped$.as(JavaJNISwiftInstance.self)!.memoryAddress() + let result$Pointer = UnsafeMutablePointer(bitPattern: Int(result$MemoryAddress$))! + return result$Pointer.pointee } - let result$MemoryAddress$ = unwrapped$.as(JavaJNISwiftInstance.self)!.memoryAddress() - let result$Pointer = UnsafeMutablePointer(bitPattern: Int(result$MemoryAddress$))! - return result$Pointer.pointee } } """, From d66be7fe21b001410b97789818a7ba31ec59936f Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 17 Mar 2026 09:07:30 +0100 Subject: [PATCH 11/12] remove usages of explicit frames in method calls --- .../SwiftJava/JavaObject+MethodCalls.swift | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/Sources/SwiftJava/JavaObject+MethodCalls.swift b/Sources/SwiftJava/JavaObject+MethodCalls.swift index 9275a459..c7523c69 100644 --- a/Sources/SwiftJava/JavaObject+MethodCalls.swift +++ b/Sources/SwiftJava/JavaObject+MethodCalls.swift @@ -155,11 +155,7 @@ 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) } let jniArgs = getJValues(repeat each args, in: environment) let jniResult = try environment.translatingJNIExceptions { jniMethod(environment, this, method, jniArgs) @@ -225,10 +221,7 @@ 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) @@ -278,16 +271,10 @@ extension AnyJavaObject { in: environment ) - // 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) - let newObj = try environment.translatingJNIExceptions { + return try environment.translatingJNIExceptions { environment.interface.NewObjectA!(environment, thisClass, methodID, jniArgs) }! - return environment.interface.PopLocalFrame(environment, newObj)! } } @@ -321,9 +308,6 @@ 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)) } } @@ -355,12 +339,7 @@ extension JavaClass { ) }! - // 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) @@ -393,11 +372,7 @@ extension JavaClass { ) }! - // 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) From 02a407e3bb5441e675bdc48742c2ed60ace7215a Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Tue, 17 Mar 2026 09:08:15 +0100 Subject: [PATCH 12/12] fix yaml format --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index a94188a9..46acd6b5 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -280,4 +280,4 @@ jobs: - name: Run linkage test run: ./scripts/run-linkage-test.sh env: - JAVA_HOME: ${{ env.JAVA_HOME }} \ No newline at end of file + JAVA_HOME: ${{ env.JAVA_HOME }}