diff --git a/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs b/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs index 1f29d21cd..0f6f4eac1 100644 --- a/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs +++ b/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs @@ -114,38 +114,33 @@ static unsafe bool TryLoadClassWithFallback (JniEnvironmentInfo info, IntPtr thr { result = default; - var findClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local); - LogCreateLocalRef (findClassThrown); - Exception? pendingException = info.Runtime.GetExceptionForThrowable (ref findClassThrown, JniObjectReferenceOptions.CopyAndDispose); - if (Class_forName.IsValid) { var __args = stackalloc JniArgumentValue [3]; __args [0] = new JniArgumentValue (classNameJavaString); __args [1] = new JniArgumentValue (true); // initialize the class __args [2] = new JniArgumentValue (info.Runtime.ClassLoader); - var c = RawCallStaticObjectMethodA (info.EnvironmentPointer, out thrown, Class_reference.Handle, Class_forName.ID, (IntPtr) __args); - if (thrown == IntPtr.Zero) { - (pendingException as IJavaPeerable)?.Dispose (); + var c = RawCallStaticObjectMethodA (info.EnvironmentPointer, out var forNameThrown, Class_reference.Handle, Class_forName.ID, (IntPtr) __args); + if (forNameThrown == IntPtr.Zero) { + // Class.forName() succeeded; discard the FindClass throwable. + JniEnvironment.References.RawDeleteLocalRef (info.EnvironmentPointer, thrown); result = new JniObjectReference (c, JniObjectReferenceType.Local); JniEnvironment.LogCreateLocalRef (result); return true; } RawExceptionClear (info.EnvironmentPointer); - - if (pendingException != null) { - JniEnvironment.References.RawDeleteLocalRef (info.EnvironmentPointer, thrown); - } else { - var loadClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local); - LogCreateLocalRef (loadClassThrown); - pendingException = info.Runtime.GetExceptionForThrowable (ref loadClassThrown, JniObjectReferenceOptions.CopyAndDispose); - } + JniEnvironment.References.RawDeleteLocalRef (info.EnvironmentPointer, forNameThrown); } if (!throwOnError) { - (pendingException as IJavaPeerable)?.Dispose (); + JniEnvironment.References.RawDeleteLocalRef (info.EnvironmentPointer, thrown); return false; } + + // Both FindClass and Class.forName() failed; materialize a managed exception to throw. + var findClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local); + LogCreateLocalRef (findClassThrown); + Exception? pendingException = info.Runtime.GetExceptionForThrowable (ref findClassThrown, JniObjectReferenceOptions.CopyAndDispose); if (pendingException != null) throw pendingException; diff --git a/tests/Java.Interop-Tests/Java.Interop/JniTypeUtf8Test.cs b/tests/Java.Interop-Tests/Java.Interop/JniTypeUtf8Test.cs index ca461f7b0..c5e0fc0d6 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniTypeUtf8Test.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniTypeUtf8Test.cs @@ -90,6 +90,26 @@ public void TryFindClass_Utf8 () Assert.IsFalse (notFound.IsValid); } + [Test] + public void TryFindClass_Utf8_DoesNotLeakGlobalRefs () + { + int grefsBefore = JniEnvironment.Runtime.GlobalReferenceCount; + JniEnvironment.Types.TryFindClass ("does/not/Exist"u8, out _); + int grefsAfter = JniEnvironment.Runtime.GlobalReferenceCount; + Assert.AreEqual (grefsBefore, grefsAfter, + "TryFindClass for non-existent classes should not leak global references"); + } + + [Test] + public void TryFindClass_String_DoesNotLeakGlobalRefs () + { + int grefsBefore = JniEnvironment.Runtime.GlobalReferenceCount; + JniEnvironment.Types.TryFindClass ("does/not/Exist", out _); + int grefsAfter = JniEnvironment.Runtime.GlobalReferenceCount; + Assert.AreEqual (grefsBefore, grefsAfter, + "TryFindClass for non-existent classes should not leak global references"); + } + [Test] public void GetMethodID_Utf8_MatchesStringOverload () {