Skip to content
Merged
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
144 changes: 99 additions & 45 deletions src/Java.Interop/Java.Interop/JniEnvironment.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Text;

namespace Java.Interop
{
Expand Down Expand Up @@ -56,43 +57,17 @@ static unsafe JniObjectReference TryFindClass (string classname, bool throwOnErr
return r;
}
RawExceptionClear (info.EnvironmentPointer);

var findClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local);
LogCreateLocalRef (findClassThrown);
var pendingException = info.Runtime.GetExceptionForThrowable (ref findClassThrown, JniObjectReferenceOptions.CopyAndDispose);

if (Class_forName.IsValid) {
var java = info.ToJavaName (classname);
var __args = stackalloc JniArgumentValue [3];
__args [0] = new JniArgumentValue (java);
__args [1] = new JniArgumentValue (true); // initialize the class
__args [2] = new JniArgumentValue (info.Runtime.ClassLoader);

c = RawCallStaticObjectMethodA (info.EnvironmentPointer, out thrown, Class_reference.Handle, Class_forName.ID, (IntPtr) __args);
var java = info.ToJavaName (classname);
try {
if (TryLoadClassWithFallback (info, thrown, java, throwOnError, out var result))
return result;
} finally {
JniObjectReference.Dispose (ref java);
if (thrown == IntPtr.Zero) {
(pendingException as IJavaPeerable)?.Dispose ();
var r = new JniObjectReference (c, JniObjectReferenceType.Local);
JniEnvironment.LogCreateLocalRef (r);
return r;
}
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);
}
}

if (!throwOnError) {
(pendingException as IJavaPeerable)?.Dispose ();
if (!throwOnError)
return default;
}
throw pendingException!;

throw new InvalidOperationException ($"Could not find Java class '{classname}'.");
#endif // !(FEATURE_JNIENVIRONMENT_JI_PINVOKES || FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS)
#if FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
var c = info.Invoker.FindClass (info.EnvironmentPointer, classname);
Expand Down Expand Up @@ -135,6 +110,78 @@ static unsafe JniObjectReference TryFindClass (string classname, bool throwOnErr
#endif // !FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
}

static unsafe bool TryLoadClassWithFallback (JniEnvironmentInfo info, IntPtr thrown, JniObjectReference classNameJavaString, bool throwOnError, out JniObjectReference result)
{
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 ();
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);
}
}

if (!throwOnError) {
(pendingException as IJavaPeerable)?.Dispose ();
return false;
}
if (pendingException != null)
throw pendingException;

return false;
}

#if FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS
static unsafe JniObjectReference NewJavaNameFromUtf8 (IntPtr env, ReadOnlySpan<byte> classname)
{
var terminator = classname.IndexOf ((byte) 0);
if (terminator >= 0)
classname = classname.Slice (0, terminator);

// Class names here are binary/JNI names, so `NewStringUTF()` lets the fallback
// avoid a managed UTF-16 allocation while still calling `Class.forName()`.
Span<byte> javaName = classname.Length + 1 <= 256
? stackalloc byte [classname.Length + 1]
: new byte [classname.Length + 1];

for (int i = 0; i < classname.Length; ++i)
javaName [i] = classname [i] == (byte) '/' ? (byte) '.' : classname [i];
javaName [classname.Length] = 0;

fixed (byte* pJavaName = javaName) {
var s = (*((JNIEnv**) env))->NewStringUTF (env, (IntPtr) pJavaName);
var e = JniEnvironment.GetExceptionForLastThrowable ();
if (e != null)
ExceptionDispatchInfo.Capture (e).Throw ();

var r = new JniObjectReference (s, JniObjectReferenceType.Local);
JniEnvironment.LogCreateLocalRef (r);
return r;
}
}
#endif

static bool TryRawFindClass (IntPtr env, string classname, out IntPtr klass, out IntPtr thrown)
{
#if FEATURE_JNIENVIRONMENT_JI_PINVOKES
Expand Down Expand Up @@ -331,6 +378,9 @@ public static unsafe bool TryFindClass (ReadOnlySpan<byte> classname, out JniObj

static unsafe JniObjectReference TryFindClass (ReadOnlySpan<byte> classname, bool throwOnError)
{
if (classname.Length == 0)
throw new ArgumentException ("'classname' cannot be a zero-length string.", nameof (classname));

var info = JniEnvironment.CurrentInfo;
fixed (byte* _classname_ptr = classname) {
var c = JniNativeMethods.FindClass (info.EnvironmentPointer, (IntPtr) _classname_ptr);
Expand All @@ -342,20 +392,24 @@ static unsafe JniObjectReference TryFindClass (ReadOnlySpan<byte> classname, boo
}

RawExceptionClear (info.EnvironmentPointer);

var findClassThrown = new JniObjectReference (thrown, JniObjectReferenceType.Local);
LogCreateLocalRef (findClassThrown);
var pendingException = info.Runtime.GetExceptionForThrowable (ref findClassThrown, JniObjectReferenceOptions.CopyAndDispose);

if (!throwOnError) {
(pendingException as IJavaPeerable)?.Dispose ();
return default;
var javaName = NewJavaNameFromUtf8 (info.EnvironmentPointer, classname);
try {
if (TryLoadClassWithFallback (info, thrown, javaName, throwOnError, out var result))
return result;
} finally {
JniObjectReference.Dispose (ref javaName);
}
throw pendingException!;
if (!throwOnError)
return default;

var terminator = classname.IndexOf ((byte) 0);
var errorClassName = terminator >= 0
? Encoding.UTF8.GetString (classname.Slice (0, terminator))
: Encoding.UTF8.GetString (classname);
throw new InvalidOperationException ($"Could not find Java class '{errorClassName}'.");
}
}
#endif // FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS
}
}
}

23 changes: 23 additions & 0 deletions tests/Java.Interop-Tests/Java.Interop/JniTypeUtf8Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,29 @@ public void FindClass_Utf8_ReturnsValidReference ()
}
}

[Test]
public void FindClass_Utf8_UsesSameFallbackAsStringOverload ()
{
var fromString = JniEnvironment.Types.FindClass ("java.lang.Object");
var fromUtf8 = JniEnvironment.Types.FindClass ("java.lang.Object"u8);
try {
Assert.IsTrue (JniEnvironment.Types.IsSameObject (fromString, fromUtf8));
} finally {
JniObjectReference.Dispose (ref fromString);
JniObjectReference.Dispose (ref fromUtf8);
}
}

[Test]
public void Ctor_Utf8_UsesSameFallbackAsStringOverload ()
{
using (var fromString = new JniType ("java.lang.Object"))
using (var fromUtf8 = new JniType ("java.lang.Object"u8)) {
Assert.IsTrue (JniEnvironment.Types.IsSameObject (fromString.PeerReference, fromUtf8.PeerReference));
Assert.AreEqual ("java/lang/Object", fromUtf8.Name);
}
}

[Test]
public void FindClass_Utf8_ThrowsOnNotFound ()
{
Expand Down