Bug 16903 - Can't catch exceptions thrown by custom ContentProvider.
Summary: Can't catch exceptions thrown by custom ContentProvider.
Status: RESOLVED DUPLICATE of bug 7634
Alias: None
Product: Android
Classification: Xamarin
Component: Mono runtime / AOT Compiler ()
Version: 4.10.0.x
Hardware: PC Mac OS
: --- normal
Target Milestone: ---
Assignee: Jonathan Pryor
URL:
Depends on:
Blocks:
 
Reported: 2013-12-19 19:07 UTC by Jon Goldberger [MSFT]
Modified: 2013-12-27 15:30 UTC (History)
2 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
ContentProviderTest.zip (16.12 KB, application/zip)
2013-12-27 09:10 UTC, Jonathan Pryor
Details
Test case that queries the ContentProvider (1.45 MB, application/zip)
2013-12-27 14:44 UTC, Ben Dodson
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED DUPLICATE of bug 7634

Comment 1 Jon Goldberger [MSFT] 2013-12-19 19:07:40 UTC
We're trying to implement a custom ContentProvider for our app. We
implement several methods, such as Query and OpenFile.

According to the Android
documentation<http://developer.android.com/guide/topics/providers/content-provider-creating.html>;,
we are supposed to be able to throw an IllegalArgumentException or
NullPointerException and have the exception get parcelled to the calling
application, rather than crash our process. In Xamarin however, throwing
either of these exceptions crashes our process.

We have tried to throw:
new ArgumentException ()
new Java.Lang.IllegalArgumentException ();

and in OpenFile():
new Java.IO.FileNotFoundException ();

and in each case, our process dies rather than parcelling the exception.

Is there any way we can get the exception parcelled properly? The
MonoDroid sample
ContentProvider<https://github.com/xamarin/monodroid-samples/blob/master/ContentProvider/LocationContentProvider.cs>throws
ArgumentExceptions, but this does not work for us.
Comment 2 Ben Dodson 2013-12-19 19:30:28 UTC
My Xamarin / Xamarin.Android version details:

==============

Xamarin Studio
Version 4.2.2 (build 2)
Installation UUID: 6cd056db-a992-4c21-a651-f65dfd188e2d
Runtime:
	Mono 3.2.5 ((no/964e8f0)
	GTK+ 2.24.20 theme: Raleigh
	GTK# (2.12.0.0)
	Package version: 302050000

Apple Developer Tools
Xcode 5.0 (3332.22)
Build 5A1412

Xamarin.Mac
Xamarin.Mac: Not Installed

Xamarin.iOS
Version: 7.0.4.209 (Enterprise Edition)
Hash: 23a0827
Branch: 
Build date: 2013-11-11 16:04:00-0500

Xamarin.Android
Version: 4.10.1 (Enterprise Edition)
Android SDK: /Users/bjdodson/Library/Developer/Xamarin/android-sdk-mac_x86
	Supported Android versions:
		2.1   (API level 7)
		2.2   (API level 8)
		2.3   (API level 10)
		3.1   (API level 12)
		4.0   (API level 14)
		4.0.3 (API level 15)
		4.1   (API level 16)
		4.2   (API level 17)
		4.3   (API level 18)
		4.4   (API level 19)
Java SDK: /usr
java version "1.7.0_04"
Java(TM) SE Runtime Environment (build 1.7.0_04-b21)
Java HotSpot(TM) 64-Bit Server VM (build 23.0-b21, mixed mode)

Build Information
Release ID: 402020002
Git revision: c5f82958ae7d9af652b44b87ceff777b3ad19b91
Build date: 2013-11-19 15:35:40+0000
Xamarin addins: a4044fee09138f6fd031a9944b7caaeb51e57e80

Operating System
Mac OS X 10.9.1
Darwin Bens-MacBook-Pro.local 13.0.0 Darwin Kernel Version 13.0.0
    Thu Sep 19 22:22:27 PDT 2013
    root:xnu-2422.1.72~6/RELEASE_X86_64 x86_64
Comment 3 Jonathan Pryor 2013-12-20 14:47:38 UTC
The problem is that cross-VM exception propagation is broken, as per Bug #7634.

What _should_ happen is that when there are "interleaved" stack frames between the JVM and Mono, the Mono stack frame will catch all exceptions, convert the exception into a JVM exception, and then return. The JVM will then receive an exception and re-raise it.

What instead happens is that Xamarin.Android doesn't behave properly, and instead ignores the existence of JVM frames ~entirely. If there's an unhandled exception, instead of converting the exception into a Java exception and re-raising, it just treats it as a "normal" unhandled exception, and nukes the process.

WORKAROUND.

The workaround, then, is to _not_ throw managed exceptions if you want a Java-side exception to be raised. Instead, directly raise a Java-side exception and return, using AndroidEnvironment.RaiseThrowable():

	public override Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues initialValues)
	{
		AndroidEnvironment.RaiseThrowable (
				new Java.Lang.IllegalArgumentException (
					"Test IllegalArgumentException"));
		return null;
		...

This will generate a Java-side Exception, which will then be raised on the calling-end, which can then be `caught`.

I apologize for the leaky abstraction. :-(

*** This bug has been marked as a duplicate of bug 7634 ***
Comment 5 Ben Dodson 2013-12-26 20:19:29 UTC
I tried the workaround both in our app and in the sample projected linked in Comment #1. Unfortunately calling AndroidEnvironment.RaiseThrowable() still causes the process to die rather than passing along the exception.
Comment 6 Jonathan Pryor 2013-12-27 09:10:44 UTC
Created attachment 5735 [details]
ContentProviderTest.zip

> Unfortunately calling AndroidEnvironment.RaiseThrowable() still
> causes the process to die rather than passing along the exception.

Really? It doesn't crash for me. Please see the attached ContentProviderTest.zip project. On startup, I see the stack trace from ContentProviderActivity.OnCreate(), and I don't see any crash or process exit:

> I/mono-stdout(30234): Test ContentProvider exception handling.
> I/mono-stdout(30234): Java.Lang.IllegalArgumentException: Exception of type 'Java.Lang.IllegalArgumentException' was thrown.
> I/mono-stdout(30234):   at Android.Runtime.JNIEnv.CallObjectMethod (IntPtr jobject, IntPtr jmethod, Android.Runtime.JValue[] parms)
> I/mono-stdout(30234):   at Android.Content.ContentResolver.Insert (Android.Net.Uri url, Android.Content.ContentValues values)
> I/mono-stdout(30234):   at contentprovidertest.ContentProviderActivity.InsertValues ()
> I/mono-stdout(30234):   at contentprovidertest.ContentProviderActivity.OnCreate (Android.OS.Bundle bundle)
> I/mono-stdout(30234):   --- End of managed exception stack trace ---
> I/mono-stdout(30234): java.lang.IllegalArgumentException: Test IllegalArgumentException
> I/mono-stdout(30234): 	at contentprovidertest.LocationContentProvider.n_insert(Native Method)
> I/mono-stdout(30234): 	at contentprovidertest.LocationContentProvider.insert(LocationContentProvider.java:49)
> I/mono-stdout(30234): 	at android.content.ContentProvider$Transport.insert(ContentProvider.java:220)
> I/mono-stdout(30234): 	at android.content.ContentResolver.insert(ContentResolver.java:1190)
> I/mono-stdout(30234): 	at contentprovidertest.ContentProviderActivity.n_onCreate(Native Method)
> I/mono-stdout(30234): 	at contentprovidertest.ContentProviderActivity.onCreate(ContentProviderActivity.java:28)
> I/mono-stdout(30234): 	at android.app.Activity.performCreate(Activity.java:5231)
> I/mono-stdout(30234): 	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
> I/mono-stdout(30234): 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2159)
> I/mono-stdout(30234): 	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245)
> I/mono-stdout(30234): 	at android.app.ActivityThread.access$800(ActivityThread.java:135)
> I/mono-stdout(30234): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196)
> I/mono-stdout(30234): 	at android.os.Handler.dispatchMessage(Handler.java:102)
> I/mono-stdout(30234): 	at android.os.Looper.loop(Looper.java:136)
> I/mono-stdout(30234): 	at android.app.ActivityThread.main(ActivityThread.java:5017)
> I/mono-stdout(30234): 	at java.lang.reflect.Method.invokeNative(Native Method)
> I/mono-stdout(30234): 	at java.lang.reflect.Method.invoke(Method.java:515)
> I/mono-stdout(30234): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
> I/mono-stdout(30234): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
> I/mono-stdout(30234): 	at dalvik.system.NativeStart.main(Native Method)
Comment 7 Ben Dodson 2013-12-27 14:44:03 UTC
Created attachment 5737 [details]
Test case that queries the ContentProvider

The ContentProvider in Attachment #5735 [details] was not set to be exported. I modified it by adding 'android:exported="true"' in the manifest, and then queried it from the attached application. (I also modified it to raise an exception on Query).  My process crashed with the below.







I/MonoDroid(15237): UNHANDLED EXCEPTION: Java.Lang.IllegalArgumentException: Exception of type 'Java.Lang.IllegalArgumentException' was thrown.
I/MonoDroid(15237): at Android.Runtime.JNIEnv.NewString (string) <0x000a0>
I/MonoDroid(15237): at Android.Runtime.JNIEnv.CopyArray (string[],intptr) <0x00043>
I/MonoDroid(15237): at Android.Content.ContentProvider.n_Query_Landroid_net_Uri_arrayLjava_lang_String_Ljava_lang_String_arrayLjava_lang_String_Ljava_lang_String_ (intptr,intptr,intptr,intptr,intptr,intptr,intptr) <0x0018b>
I/MonoDroid(15237): at (wrapper dynamic-method) object.00bf8d06-d41e-4c81-938d-0fcf8f7e374c (intptr,intptr,intptr,intptr,intptr,intptr,intptr) <0x0007b>
I/MonoDroid(15237): 
I/MonoDroid(15237):   --- End of managed exception stack trace ---
I/MonoDroid(15237): java.lang.IllegalArgumentException: Test IllegalArgumentException
I/MonoDroid(15237): 	at contentprovidertest.LocationContentProvider.n_query(Native Method)
I/MonoDroid(15237): 	at contentprovidertest.LocationContentProvider.query(LocationContentProvider.java:65)
I/MonoDroid(15237): 	at android.content.ContentProvider.query(ContentProvider.java:855)
I/MonoDroid(15237): 	at android.content.ContentProvider$Transport.query(ContentProvider.java:200)
I/MonoDroid(15237): 	at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:112)
I/MonoDroid(15237): 	at android.os.Binder.execTransact(Binder.java:404)
I/MonoDroid(15237): 	at dalvik.system.NativeStart.run(Native Method)
E/mono    (15237): 
E/mono    (15237): Unhandled Exception:
E/mono    (15237): Java.Lang.IllegalArgumentException: Exception of type 'Java.Lang.IllegalArgumentException' was thrown.
E/mono    (15237): at Android.Runtime.JNIEnv.NewString (string) <0x000a0>
E/mono    (15237): at Android.Runtime.JNIEnv.CopyArray (string[],intptr) <0x00043>
E/mono    (15237): at Android.Content.ContentProvider.n_Query_Landroid_net_Uri_arrayLjava_lang_String_Ljava_lang_String_arrayLjava_lang_String_Ljava_lang_String_ (intptr,intptr,intptr,intptr,intptr,intptr,intptr) <0x0018b>
E/mono    (15237): at (wrapper dynamic-method) object.00bf8d06-d41e-4c81-938d-0fcf8f7e374c (intptr,intptr,intptr,intptr,intptr,intptr,intptr) <0x0007b>
E/mono    (15237): 
E/mono    (15237):   --- End of managed exception stack trace ---
E/mono    (15237): java.lang.IllegalArgumentException: Test IllegalArgumentException
E/mono    (15237): 	at contentprovidertest.LocationContentProvider.n_query(Native Method)
E/mono    (15237): 	at contentprovidertest.LocationContentProvider.query(LocationContentProvider.java:65)
E/mono    (15237): 	at android.content.ContentProvider.query(ContentProvider.java:855)
E/mono    (15237): 	at android.content.ContentProvider$Transport.query(ContentProvider.java:200)
E/mono    (15237): 	at android.content.Cont
E/mono-rt (15237): [ERROR] FATAL UNHANDLED EXCEPTION: Java.Lang.IllegalArgumentException: Exception of type 'Java.Lang.IllegalArgumentException' was thrown.
E/mono-rt (15237): at Android.Runtime.JNIEnv.NewString (string) <0x000a0>
E/mono-rt (15237): at Android.Runtime.JNIEnv.CopyArray (string[],intptr) <0x00043>
E/mono-rt (15237): at Android.Content.ContentProvider.n_Query_Landroid_net_Uri_arrayLjava_lang_String_Ljava_lang_String_arrayLjava_lang_String_Ljava_lang_String_ (intptr,intptr,intptr,intptr,intptr,intptr,intptr) <0x0018b>
E/mono-rt (15237): at (wrapper dynamic-method) object.00bf8d06-d41e-4c81-938d-0fcf8f7e374c (intptr,intptr,intptr,intptr,intptr,intptr,intptr) <0x0007b>
E/mono-rt (15237): 
E/mono-rt (15237):   --- End of managed exception stack trace ---
E/mono-rt (15237): java.lang.IllegalArgumentException: Test IllegalArgumentException
E/mono-rt (15237): 	at contentprovidertest.LocationContentProvider.n_query(Native Method)
E/mono-rt (15237): 	at contentprovidertest.LocationContentProvider.query(LocationContentProvider.java:65)
E/mono-rt (15237): 	at android.content.ContentProvider.query(ContentProvider.java:855)
E/mono-rt (15237): 	at android.content.ContentProvider$Transport.query(ContentProvider.java:200)
E/mono-rt (15237): 	at android
I/ActivityManager(  782): Process contentprovidertest.contentprovidertest (pid 15237) has died.
Comment 8 Jonathan Pryor 2013-12-27 15:30:30 UTC
Oof; I see what's happening. The problem is Android.Content.ContentProvider.n_Query_Landroid_net_Uri_arrayLjava_lang_String_Ljava_lang_String_arrayLjava_lang_String_Ljava_lang_String_(), which is a JNI registered function responsible for marshaling parameters to/from managed code from/to JNI. It takes the general form:

    static TReturn n_MashalFunc (IntPtr env, IntPtr native_this, ...args...)
    {
        TSelf self = Object.GetObject<TSelf>(native_this);
        ...marshal args...
        TReturn ret = self.MarshalFunc (marshaled args...);
        ...unmarshal args...
        return ret;
    }

The problem is after AndroidEnvironment.RaiseThrowable() is called, a Java-side exception is pending, to be thrown either when returning to Java, or when another Java method is called.

In the case of ContentProvider.Query(), it has a String[] parameter, which requires that the string values be marshaled back (in case your overridden ContentProvider.Query() method changed the array contents). This in turn requires calling JNIEnv.NewString() as part of the "unmarshal args" code, and since a Java exception is now pending it is raised, and things blow up.

Unfortunately this means that Bug #7634 must be properly fixed.

The only workaround I can think of is to have the ContentProvider in Java, allowing the Java code to throw IllegalArgumentException/etc. You could use [ExportAttribute] on a Java.Lang.Object C# subclass to expose additional methods to your Java code, so e.g. you could have a `Java.Lang.String[] errors` parameter, and the Java code could pass an array and throw the stored string if necessary.