Skip to content

Commit

Permalink
[Java.Interop] Add JniEnvironment.WithinNewObjectScope.
Browse files Browse the repository at this point in the history
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=37630
Context: #11
Context: xamarin/monodroid@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]: xamarin/monodroid#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
  • Loading branch information
jonpryor committed Jan 13, 2016
1 parent 5c89b90 commit 8c4248b
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 3 deletions.
22 changes: 22 additions & 0 deletions src/Java.Interop/Java.Interop/JniEnvironment.Object.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,28 @@ static Object ()
}
}

public static JniObjectReference NewObject (JniObjectReference type, JniMethodInfo method)
{
JniEnvironment.WithinNewObjectScope = true;
try {
return _NewObject (type, method);
}
finally {
JniEnvironment.WithinNewObjectScope = false;
}
}

public static unsafe JniObjectReference NewObject (JniObjectReference type, JniMethodInfo method, JniArgumentValue* args)
{
JniEnvironment.WithinNewObjectScope = true;
try {
return _NewObject (type, method, args);
}
finally {
JniEnvironment.WithinNewObjectScope = false;
}
}

public static JniObjectReference ToString (JniObjectReference value)
{
return JniEnvironment.InstanceMethods.CallObjectMethod (value, Object_toString);
Expand Down
6 changes: 6 additions & 0 deletions src/Java.Interop/Java.Interop/JniEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public static int LocalReferenceCount {
get {return Info.Value.LocalReferenceCount;}
}

public static bool WithinNewObjectScope {
get {return Info.Value.WithinNewObjectScope;}
internal set {Info.Value.WithinNewObjectScope = value;}
}

internal static void SetEnvironmentPointer (IntPtr environmentPointer)
{
Info.Value.EnvironmentPointer = environmentPointer;
Expand Down Expand Up @@ -170,6 +175,7 @@ sealed class JniEnvironmentInfo {

public JniRuntime Runtime {get; private set;}
public int LocalReferenceCount {get; internal set;}
public bool WithinNewObjectScope {get; set;}

public IntPtr EnvironmentPointer {
get {return environmentPointer;}
Expand Down
7 changes: 4 additions & 3 deletions tools/jnienv-gen/Generator.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ partial class Generator
new JniFunction {
DeclaringType = ObjectOperationsCategory,
Name = "NewObject",
Visibility = "public",
ApiName = "_NewObject",
Visibility = "internal",
Throws = true,
Prototype = "jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);",
ReturnType = "jobject",
Expand All @@ -251,8 +252,8 @@ partial class Generator
new JniFunction {
DeclaringType = ObjectOperationsCategory,
Name = "NewObjectA",
ApiName = "NewObject",
Visibility = "public",
ApiName = "_NewObject",
Visibility = "internal",
Throws = true,
Prototype = "jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, jvalue*);",
ReturnType = "jobject",
Expand Down

0 comments on commit 8c4248b

Please sign in to comment.