Skip to content

Commit 8c4248b

Browse files
committed
[Java.Interop] Add JniEnvironment.WithinNewObjectScope.
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=37630 Context: #11 Context: https://github.com/xamarin/monodroid/commit/940136eb Context: https://bugzilla.xamarin.com/show_bug.cgi?id=15542 Release builds with [Xamarin.Android + Java.Interop][0] are crashing on pre-Honeycomb devices (API-10 and earlier): UNHANDLED EXCEPTION: System.NotSupportedException: Unable to find the default constructor on type Android.Runtime.UncaughtExceptionHandler. Please provide the missing constructor. ---> Java.Interop.JavaLocationException: Exception of type 'Java.Interop.JavaLocationException' was thrown. Java.Lang.Error: Exception of type 'Java.Lang.Error' was thrown. --- End of managed exception stack trace --- java.lang.Error: Java callstack: at mono.android.TypeManager.n_activate(Native Method) at mono.android.TypeManager.Activate(TypeManager.java:7) at android.runtime.UncaughtExceptionHandler.<init>(UncaughtExceptionHandler.java:24) at mono.android.Runtime.init(Native Method) at mono.MonoPackageManager.LoadApplication(MonoPackageManager.java:40) at mono.MonoRuntimeProvider.attachInfo(MonoRuntimeProvider.java:22) at android.app.ActivityThread.installProvider(ActivityThread.java:4122) at android.app.ActivityThread.installContentProviders(ActivityThread.java:3832) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:3788) at android.app.ActivityThread.access$2200(ActivityThread.java:132) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1082) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:150) at android.app.ActivityThread.main(ActivityThread.java:4263) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:507) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597) at dalvik.system.NativeStart.main(Native Method) --- End of inner exception stack trace --- at Java.Interop.TypeManager.n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) <0x45b540c0 + 0x00570> in <filename unknown>:0 at (wrapper dynamic-method) System.Object:010d7c44-0c93-4b7d-b78f-129ff9bedae6 (intptr,intptr,intptr,intptr,intptr,intptr) UNHANDLED EXCEPTION: System.NotSupportedException: Don't know how to convert type 'System.String' to an Android.Runtime.IJavaObject. at Android.Runtime.JNIEnv.AssertIsJavaObject (System.Type targetType) <0x45b56130 + 0x000b4> in <filename unknown>:0 at Android.Runtime.JNIEnv.<CreateNativeArrayElementToManaged>m__B (System.Type type, IntPtr source, Int32 index) <0x45b5db30 + 0x0001b> in <filename unknown>:0 at Android.Runtime.JNIEnv.GetObjectArray (IntPtr array_ptr, System.Type[] element_types) <0x45b54e60 + 0x0010f> in <filename unknown>:0 at Java.Interop.TypeManager.n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPtr signature_ptr, IntPtr jobject, IntPtr parameters_ptr) <0x45b540c0 + 0x00283> in <filename unknown>:0 at (wrapper dynamic-method) System.Object:010d7c44-0c93-4b7d-b78f-129ff9bedae6 (intptr,intptr,intptr,intptr,intptr,intptr) ... The root cause is that [Android sucks prior to API-11][1]: You can't use JNIEnv::CallVoidMethod() or JNIEnv::CallNonvirtualVoidMethod() to invoke constructors because Dalkvik raises a CloneNotSupportedException, which in turn means there's no actual point to using JNIEnv::AllocObject(), which means we need to use JNIEnv::NewObject(). JNIEnv::NewObject() sucks because it means we can enter managed code [before we've registered an instance mapping][2], which is painful. Specifically, in order to differentiate between the "Java code created this instance" vs. "managed code created this instance", Xamarin.Android's JNIEnv.NewObject() method sets an internal flag -- TypeManager.ActivationEnabled -- so that when the "activation" code path is hit it will *bail* if managed code created the instance. This prevents the constructor from nuking the stack -- C# invokes Java invokes C# (via activation) invokes Java invokes... The problem? Java.Interop has no analog to this infrastructure, and thus no way to check if we're within a "nested" JNIEnv::NewObject() invocation rooted in managed code. Consequently, on an API-10 device we'd try to activate the MainActivity instance...and possibly nuke the stack. That's the cause of this message: Unable to find the default constructor on type Android.Runtime.UncaughtExceptionHandler. We're trying to create the UncaughtExceptionHandler instance from managed code, which hits JNIEnv::NewObject(), which invokes the Java constructor, which hits the activation code path, which would then try to invoke the default constructor, which -- if it existed -- would go **BOOM**. There are two plausible fixes: 1. Drop support for API-10 and earlier devices. 2. Mirror the Xamarin.Android JNIEnv::NewObject() "hacks". (1) isn't really in the cards: even when we "dropped" bindings for API-4, we still continued to support *executing* on them while using the API-10 bindings. Which leaves (2). Add a new public read-only property, JniEnvironment.WithinNewObjectScope. This property is true while JniEnvironment.Object.NewObject() is executing -- in precisely the same way that TypeManager.ActivationEnabled is false while JNIEnv.NewObject() is executing, which can (will) include nested cross-VM invocations. (Fun!) With this support in place, Xamarin.Android can use JniEnvironment.WithinNewObjectScope instead of TypeManager.ActivationEnabled, fixing the crash. [0]: https://github.com/xamarin/monodroid/pull/317 [1]: https://code.google.com/p/android/issues/detail?id=13832 [2]: http://developer.xamarin.com/guides/android/under_the_hood/architecture/#Java_Activation
1 parent 5c89b90 commit 8c4248b

File tree

3 files changed

+32
-3
lines changed

3 files changed

+32
-3
lines changed

src/Java.Interop/Java.Interop/JniEnvironment.Object.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,28 @@ static Object ()
1515
}
1616
}
1717

18+
public static JniObjectReference NewObject (JniObjectReference type, JniMethodInfo method)
19+
{
20+
JniEnvironment.WithinNewObjectScope = true;
21+
try {
22+
return _NewObject (type, method);
23+
}
24+
finally {
25+
JniEnvironment.WithinNewObjectScope = false;
26+
}
27+
}
28+
29+
public static unsafe JniObjectReference NewObject (JniObjectReference type, JniMethodInfo method, JniArgumentValue* args)
30+
{
31+
JniEnvironment.WithinNewObjectScope = true;
32+
try {
33+
return _NewObject (type, method, args);
34+
}
35+
finally {
36+
JniEnvironment.WithinNewObjectScope = false;
37+
}
38+
}
39+
1840
public static JniObjectReference ToString (JniObjectReference value)
1941
{
2042
return JniEnvironment.InstanceMethods.CallObjectMethod (value, Object_toString);

src/Java.Interop/Java.Interop/JniEnvironment.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public static int LocalReferenceCount {
3131
get {return Info.Value.LocalReferenceCount;}
3232
}
3333

34+
public static bool WithinNewObjectScope {
35+
get {return Info.Value.WithinNewObjectScope;}
36+
internal set {Info.Value.WithinNewObjectScope = value;}
37+
}
38+
3439
internal static void SetEnvironmentPointer (IntPtr environmentPointer)
3540
{
3641
Info.Value.EnvironmentPointer = environmentPointer;
@@ -170,6 +175,7 @@ sealed class JniEnvironmentInfo {
170175

171176
public JniRuntime Runtime {get; private set;}
172177
public int LocalReferenceCount {get; internal set;}
178+
public bool WithinNewObjectScope {get; set;}
173179

174180
public IntPtr EnvironmentPointer {
175181
get {return environmentPointer;}

tools/jnienv-gen/Generator.g.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,8 @@ partial class Generator
233233
new JniFunction {
234234
DeclaringType = ObjectOperationsCategory,
235235
Name = "NewObject",
236-
Visibility = "public",
236+
ApiName = "_NewObject",
237+
Visibility = "internal",
237238
Throws = true,
238239
Prototype = "jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);",
239240
ReturnType = "jobject",
@@ -251,8 +252,8 @@ partial class Generator
251252
new JniFunction {
252253
DeclaringType = ObjectOperationsCategory,
253254
Name = "NewObjectA",
254-
ApiName = "NewObject",
255-
Visibility = "public",
255+
ApiName = "_NewObject",
256+
Visibility = "internal",
256257
Throws = true,
257258
Prototype = "jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, jvalue*);",
258259
ReturnType = "jobject",

0 commit comments

Comments
 (0)