Skip to content

Commit 0fc4b61

Browse files
jonpryorjpobst
authored andcommitted
[Java.Interop-Tests] Fix JniTypeTest.Name() missing argument (#538)
Context: dotnet/android#3393 When running the `JniTypeTest.Name()` unit test within Xamarin.Android, sometimes the unit test runner process would crash. The crash is *more* likely in Release runtimes: F o.Android_Test: java_vm_ext.cc:570] JNI DETECTED ERROR IN APPLICATION: use of invalid jobject 0xc58fa798 F o.Android_Test: java_vm_ext.cc:570] from void crc64f295cabbf85394f5.TestSuiteInstrumentation.n_onStart() F o.Android_Test: runtime.cc:630] Runtime aborting... What was particularly odd about this crash was that after enabling both GREF and LREF logging, there was no handle 0xc58fa798! Where was this invalid handle coming from? Turns Out™, 0xc58fa798 isn't a handle. Instead, it's a parameter mismatch! Commit 9f380eb had a bug. `JniTypeTest.Name()` was *attempting* to do: // Java java.lang.reflect.Method Object_hashCode = Object.class.getMethod("hashCode", new Class[0]); String Object_hashCode_returnType = Object_hashCode.getReturnType(); However, via JNI, it was *actually* doing: java.lang.reflect.Method Object_hashCode = Object.class.getMethod("hashCode", GARBAGE_VALUE); String Object_hashCode_returnType = Object_hashCode.getReturnType(); because it was only passing *one* value to `Class.getMethod()`, not two values, so the second parameter was entirely undefined and unknowable. Through sheer luck (or a terrible bug) this "worked" on a Desktop JVM and (frequently) in Debug configuration builds of the `Mono.Android_Tests-*.apk` test app in Xamarin.Android. On Android in a Release configuration, GARBAGE_VALUE was treated as a garbage value, and brought down the process because there *was*, in fact, a JNI error in the application. Fix `JniTypeTest.Name()`, and pass the correct number of parameters to `Class.getMethod(String, Class[])`. Additionally, make two (mostly unrelated) changes: * Add a `JniType.ToString()` override. * Change the exception ordering in `JavaExceptionTests.StackTrace(). While debugging the above crash, I attempted to use `JniType.ToString()` in some debug messages. These were useless, because there was no `JniType.ToString()` override. Add one. The conceptual problem `JavaExceptionTests.StackTrace()`, meanwhile, is that *eventually* `Java.Lang.Throwable` will be updated to inherit from `Java.Interop.JavaException`. If `JavaException` is caught first, then the `__ANDROID__`-specific behavior around `Java.Lang.Throwable` will no longer be tested, *and* the test will continue to pass (while testing subtly different behavior). Reorder the `catch` blocks so that when the `Java.Lang.Throwable` base class changes, the semantics of the test will be unaltered.
1 parent 0065de4 commit 0fc4b61

File tree

3 files changed

+21
-11
lines changed

3 files changed

+21
-11
lines changed

src/Java.Interop/Java.Interop/JniType.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public string Name {
5757
}
5858
}
5959

60+
public override string ToString ()
61+
{
62+
return $"JniType(Name='{Name}' PeerReference={PeerReference})";
63+
}
64+
6065
#if XA_INTEGRATION
6166
internal
6267
#else // !XA_INTEGRATION

tests/Java.Interop-Tests/Java.Interop/JavaExceptionTests.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,30 @@ public void StackTrace ()
1414
{
1515
try {
1616
new JniType ("this/type/had/better/not/exist");
17-
} catch (JavaException e) {
17+
}
18+
#if __ANDROID__
19+
catch (Java.Lang.Throwable e) {
1820
Assert.IsTrue (
1921
string.Equals ("this/type/had/better/not/exist", e.Message, StringComparison.OrdinalIgnoreCase) ||
2022
e.Message.StartsWith ("Didn't find class \"this.type.had.better.not.exist\" on path: DexPathList"));
2123
Assert.IsTrue (
2224
// ART
23-
e.JavaStackTrace.StartsWith ("java.lang.ClassNotFoundException: ", StringComparison.Ordinal) ||
25+
e.StackTrace.Contains ("java.lang.ClassNotFoundException: ", StringComparison.Ordinal) ||
2426
// Dalvik, JVM
25-
e.JavaStackTrace.StartsWith ("java.lang.NoClassDefFoundError: this/type/had/better/not/exist", StringComparison.Ordinal));
27+
e.StackTrace.Contains ("java.lang.NoClassDefFoundError: this/type/had/better/not/exist", StringComparison.Ordinal));
2628
e.Dispose ();
27-
#if __ANDROID__
28-
} catch (Java.Lang.Throwable e) {
29+
}
30+
#endif // __ANDROID__
31+
catch (JavaException e) {
2932
Assert.IsTrue (
3033
string.Equals ("this/type/had/better/not/exist", e.Message, StringComparison.OrdinalIgnoreCase) ||
3134
e.Message.StartsWith ("Didn't find class \"this.type.had.better.not.exist\" on path: DexPathList"));
3235
Assert.IsTrue (
3336
// ART
34-
e.StackTrace.Contains ("java.lang.ClassNotFoundException: ", StringComparison.Ordinal) ||
37+
e.JavaStackTrace.StartsWith ("java.lang.ClassNotFoundException: ", StringComparison.Ordinal) ||
3538
// Dalvik, JVM
36-
e.StackTrace.Contains ("java.lang.NoClassDefFoundError: this/type/had/better/not/exist", StringComparison.Ordinal));
39+
e.JavaStackTrace.StartsWith ("java.lang.NoClassDefFoundError: this/type/had/better/not/exist", StringComparison.Ordinal));
3740
e.Dispose ();
38-
#endif // __ANDROID__
3941
}
4042
}
4143

tests/Java.Interop-Tests/Java.Interop/JniTypeTest.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,11 @@ public unsafe void Name ()
141141
var Class_getMethod = Class_class.GetInstanceMethod ("getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
142142
var Method_getReturnType = Method_class.GetInstanceMethod ("getReturnType", "()Ljava/lang/Class;");
143143
var hashCode_str = JniEnvironment.Strings.NewString ("hashCode");
144-
var hashCode_args = stackalloc JniArgumentValue [1];
145-
hashCode_args [0] = new JniArgumentValue (hashCode_str);
146-
var Object_hashCode = JniEnvironment.InstanceMethods.CallObjectMethod (Object_class.PeerReference, Class_getMethod, hashCode_args);
144+
var emptyArray = JniEnvironment.Arrays.NewObjectArray (0, Class_class.PeerReference, new JniObjectReference ());
145+
var getHashcodeMethodArgs = stackalloc JniArgumentValue [2];
146+
getHashcodeMethodArgs [0] = new JniArgumentValue (hashCode_str);
147+
getHashcodeMethodArgs [1] = new JniArgumentValue (emptyArray);
148+
var Object_hashCode = JniEnvironment.InstanceMethods.CallObjectMethod (Object_class.PeerReference, Class_getMethod, getHashcodeMethodArgs);
147149
var Object_hashCode_rt = JniEnvironment.InstanceMethods.CallObjectMethod (Object_hashCode, Method_getReturnType);
148150
try {
149151
Assert.AreEqual ("java/lang/Object", Object_class.Name);
@@ -154,6 +156,7 @@ public unsafe void Name ()
154156
JniObjectReference.Dispose (ref hashCode_str);
155157
JniObjectReference.Dispose (ref Object_hashCode);
156158
JniObjectReference.Dispose (ref Object_hashCode_rt);
159+
JniObjectReference.Dispose (ref emptyArray);
157160
}
158161
}
159162
}

0 commit comments

Comments
 (0)