-
Notifications
You must be signed in to change notification settings - Fork 533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve JavaProxyThrowable by translating managed stack trace #8185
Conversation
Fixes: dotnet#1198 Given an instance of `System.Exception`, we can use the `System.Diagnostics.StackTrace` class to iterate over the list of managed stack frames, translating each of them to equivalent `Java.Lang.StackTraceElement` and afterwards passing the entire collection to `Java.Lang.Throwable.SetStackTrace`.
I think the first thing that's going to break is the public API surface. There is no |
It's fine, the class is internal to |
|
||
namespace Android.Runtime { | ||
|
||
class JavaProxyThrowable : Java.Lang.Error { | ||
|
||
public readonly Exception InnerException; | ||
|
||
public JavaProxyThrowable (Exception innerException) | ||
: base (GetDetailMessage (innerException)) | ||
protected JavaProxyThrowable (string message, Exception innerException) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type should probably be sealed
-- nothing subclasses it, and I can't think of anything to subclass it ever -- and this constructor could thus be private
.
This should also update |
NOTE: test, umm, untested! Unable to build the project locally
Pushed the additional code to the test, but I wasn't able to check whether it even compiles locally. Can't build the test solution or project here :( |
OK, found the right project to build, at least it compiles. We'll see if it runs. |
return proxy; | ||
} | ||
|
||
void TranslateStackTrace () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with this method is that it doesn't "merge", it "replaces". #1188 should have provided examples for desired behavior.
Consider:
var e = new Java.Lang.Error ("here!");
e.PrintStackTrace(Java.Lang.JavaSystem.Out);
This produces the output:
java.lang.Error: here!
at crc641c2f9234c37aaa3c.MainActivity.n_onCreate(Native Method)
at crc641c2f9234c37aaa3c.MainActivity.onCreate(MainActivity.java:30)
at android.app.Activity.performCreate(Activity.java:8341)
at android.app.Activity.performCreate(Activity.java:8320)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1417)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3622)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3778)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:138)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2303)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7884)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
If the assumption in #1188 is correct, and we want printStackTrace()
to be "more useful", replacing all of the above is not "more useful". What we'd want is to instead produce:
java.lang.Error: here!
at android_cwd.MainActivity.OnCreate(MainActivity.cs:11)
at crc641c2f9234c37aaa3c.MainActivity.n_onCreate(Native Method)
at crc641c2f9234c37aaa3c.MainActivity.onCreate(MainActivity.java:30)
…
As far as I can tell, TranslateStackTrace()
does not do any such "merging". It would thus instead produce:
at android_cwd.MainActivity.OnCreate(MainActivity.cs:11)
as the complete stack trace, which I don't believe would be useful.
(Now, this PR/#1188 is about JavaProxyThrowable
, not for new Error().PrintStackTrace()
, but PrintStackTrace()
is the main point.)
* main: [ci] XA.PublishAllLogs publishes all build logs to build artifacts (dotnet#8189) Bump to xamarin/Java.Interop/main@151b03e (dotnet#8188) [Mono.Android] Use PublicApiAnalyzers to ensure we do not break API. (dotnet#8171) [Xamarin.Android.Build.Tasks] per-RID assemblies & typemaps (dotnet#8164) [AndroidDependenciesTests] Test both manifest types (dotnet#8186) [AndroidDependenciesTests] Use platform-tools 34.0.4 (dotnet#8184)
* main: LLVM IR code generator refactoring and updates (dotnet#8140)
* main: [ci] Include build components in SBOM (dotnet#8174)
* main: [Xamarin.Android.Build.Tasks] fix `.aar` files flowing from project references (dotnet#8193)
* main: $(AndroidPackVersionSuffix)=rc.1; net8 is 34.0.0-rc.1 (dotnet#8204) Bump to dotnet/installer@ca467d68c8 8.0.100-preview.7.23364.32 (dotnet#8176) Clean up DotNetIgnore Unit Tests (dotnet#8163) [Xamarin.Android.Build.Tasks] fix duplicate `.aar` files (dotnet#8196) [Documentation] Appease PoliCheck Rule: 79604 (dotnet#8197)
static string GetDetailMessage (Exception innerException) | ||
public static JavaProxyThrowable Create (Exception? innerException, bool appendJavaStackTrace = false) | ||
{ | ||
if (innerException == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have Exception? innerException
if you're gonna throw ArgumentNullException
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was a typo
innerException passed to JavaProxyThrowable.Create shouldn't be allowed to be null
The real, fundamental, question/concern/whatever: what's the end-to-end scenario, and what's it look like, and is this an improvement? Which requires resolving a great number of unknowns: what "unhandled exception crash logger" is being used? What information does it capture? How is it presented? (#1188 mentions HockeyApp, which may have been shut down?) The fundamental assumption in #1198 is that whatever the crash reporter is, it may (was?) dropping Is that assumption even correct? If it is correct, what do things look like now? Thus: diff --git a/src/java-runtime/java/mono/android/Runtime.java b/src/java-runtime/java/mono/android/Runtime.java
index 01e62b239..a018d5708 100644
--- a/src/java-runtime/java/mono/android/Runtime.java
+++ b/src/java-runtime/java/mono/android/Runtime.java
@@ -2,6 +2,7 @@ package mono.android;
import java.lang.Thread;
import java.lang.Throwable;
+import android.util.Log;
public class Runtime {
static java.lang.Class java_lang_Class = java.lang.Class.class;;
@@ -49,6 +50,12 @@ final class XamarinUncaughtExceptionHandler implements Thread.UncaughtExceptionH
public final void uncaughtException (Thread t, Throwable e)
{
Runtime.propagateUncaughtException (t, e);
+ StackTraceElement[] stack = e.getStackTrace();
+ Log.w("*jonp*", "# jonp: e.Message=" + e.getMessage());
+ for (int i = 0; i < stack.length; ++i) {
+ StackTraceElement s = stack [i];
+ Log.w ("*jonp*", "# jonp: stack[" + i + "]=" + s.getClassName() + "." + s.getMethodName() + "(" + s.getFileName() + ":" + s.getLineNumber() + ")");
+ }
if (defaultHandler != null)
defaultHandler.uncaughtException (t, e);
This updates Next, the app! The result with 57eedfb (not #8185):
from which we see that both That background thinking in mind, how does this PR #8185 change things? I have no idea, as there are no "end-to-end" tests to speak of. It would be useful to have an on-device test which reads Additionally, the above unhandled exception output is the "easy" case: Java calls C# code, which throws an exception. What happens for a "mixed" case: Java calls C# code which calls into Java which calls into C# which throws an exception? I suspect that in such a case the Java callstack will be "weird and useless" -- you'll have a |
What do I want?
"Even better" would be a complete end-to-end case, of using Crashlytics or something, and seeing what is actually reported when the app has an unhandled exception for a Java > C# > Java > C# stack transition. |
Harking back to my previous comment, what does this PR do when using the One checkout, build, and test-run later, and
I do not consider this an improvement: we lose the entire Java-side callstack! (Aside: Additionally, the "UNHANDLED EXCEPTION" output is likewise less useful (as far as I'm concerned), and comes from C# not Java: This PR:
(Again, note complete lack of Java-side information.) 57eedfb with
Which one of these would you rather have when diagnosing a crash? For me, the latter, hands-down. I shudder to think of what the Visual Studio Unhandled Exception dialog would show if #8185 is present. |
The With #8185:
Current main:
|
* main: [ci] Remove .NET branches from classic release trigger (dotnet#8218) Bump to dotnet/installer@f8bab721ae 8.0.100-rc.1.23373.1 (dotnet#8202) [Mono.Android] Fix Context.RegisterReceiver() enumification (dotnet#7735) [ci] Add MAUI integration job (dotnet#8200) [vs-workload] Set EnableSideBySideManifests=true (dotnet#8179)
…ndroid into throwable-stacktrace * 'throwable-stacktrace' of github.com:grendello/xamarin-android: Update JavaProxyThrowable.cs
With the latest changes in 603f1e4:
I think this is an improvement. One thing does confuse me, though: Why is |
} | ||
|
||
// We prepend managed exception type to message since Java will see `JavaProxyThrowable` instead. | ||
var proxy = new JavaProxyThrowable ($"[{innerException.GetType}]: {innerException.Message}", innerException); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where [System.Func`1[System.Type]]
is coming from!
Which still doesn't make sense, as that would mean that innerException.GetType()
is Func<Type>
, but innerException
is an Exception
, so… how is that possible? I remain flummoxed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh! It's because ()
is missing! This should be innerException.GetType()
(with parens), not innerException.GetType
! I'm kinda surprised that innerException.GetType
even compiled; apparently the compiler was just implicitly converting it to a Func<Type>
, which kinda makes sense?
declaringClass: managedMethod?.DeclaringType?.FullName, | ||
methodName: managedMethod?.Name, | ||
fileName: managedFrame?.GetFileName (), | ||
lineNumber: managedFrame?.GetFileLineNumber () ?? 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be ?? -1
, as negative numbers appear to be the "don't have one" behavior. See e.g.
W *jonp* : # jonp: stack[3]=crc64ef325ad1a3b68b57.MainActivity.n_onCreate(MainActivity.java:-2)
No idea where -2
came from; I assume it's because the file has no line number info? (Which also makes no sense, as the declaration for n_onCreate()
does have a line number…)
* main: [build] Use .NET preview to create nupkgs (dotnet#8227) Bump to xamarin/xamarin-android-binutils/L_16.0.6-6.0.0@b38b5bab (dotnet#8220) [ci] Fix MAUI integration test job (dotnet#8221)
WIP commit message: Context: https://github.com/xamarin/xamarin-android/issues/1198
Context: https://github.com/xamarin/xamarin-android/issues/1188#issuecomment-358184674
Context: https://github.com/xamarin/xamarin-android/pull/4877
Context: https://github.com/xamarin/xamarin-android/pull/4927#issuecomment-875864999
What happens with unhandled exceptions?
throw new InvalidOperationException ("oops!");
This is a surprisingly complicated question:
If this happens when a debugger is attached, the debugger will get a
"first chance notification" at the `throw` site. If execution
continues, odds are high that the app will abort if there is a JNI
transition in the callstack.
If no debugger is attached, then it depends on which thread threw the
unhandled exception.
If the thread which threw the unhandled exception is a .NET Thread:
static void ThrowFromAnotherManagedThread() {
var t = new System.Threading.Thread(() => {
throw new new Java.Lang.Error ("from another thread?!");
});
t.Start ();
t.Join ();
}
Then .NET will report the unhandled exception, *and*
the app will restart:
F mono-rt : [ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: oops!
F mono-rt : at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherManagedThread>b__1_0()
F mono-rt : at System.Threading.Thread.StartCallback()
# app restarts
If the thread which threw the unhandled exception is a *Java* thread,
which could be the UI thread (e.g. thrown from an `Activity.OnCreate()`
override) or via a `Java.Lang.Thread` instance:
static void ThrowFromAnotherJavaThread() {
var t = new Java.Lang.Thread(() => {
throw new InvalidOperationException ("oops!");
});
t.Start ();
t.Join ();
}
Then .NET will report the unhandled exception, *and* the app will
*not* restart (which differs from using .NET threads):
E AndroidRuntime: Process: com.companyname.android_unhandled_exception, PID: 5436
E AndroidRuntime: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops!
E AndroidRuntime: at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0()
E AndroidRuntime: at Java.Lang.Thread.RunnableImplementor.Run()
E AndroidRuntime: at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr )
E AndroidRuntime: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr )
E AndroidRuntime: at mono.java.lang.RunnableImplementor.n_run(Native Method)
E AndroidRuntime: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
E AndroidRuntime: at java.lang.Thread.run(Thread.java:1012)
I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable
I MonoDroid:
I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
I MonoDroid: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops!
I MonoDroid: at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0()
I MonoDroid: at Java.Lang.Thread.RunnableImplementor.Run()
I MonoDroid: at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr )
I MonoDroid: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr )
I MonoDroid: at mono.java.lang.RunnableImplementor.n_run(Native Method)
I MonoDroid: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
I MonoDroid: at java.lang.Thread.run(Thread.java:1012)
I MonoDroid:
I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
I MonoDroid: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops!
I MonoDroid: at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0()
I MonoDroid: at Java.Lang.Thread.RunnableImplementor.Run()
I MonoDroid: at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr )
I MonoDroid: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr )
I MonoDroid: at mono.java.lang.RunnableImplementor.n_run(Native Method)
I MonoDroid: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
I MonoDroid: at java.lang.Thread.run(Thread.java
This "works", until we enter the world of crash logging for later
diagnosis and fixing. The problem with our historical approach is
that we would "stuff" the .NET stack trace into the "message" of the
Java-side `Throwable` instance, and the "message" may not be
transmitted as part of the crash logging!
(This is noticeable by the different indentation levels for the
`at …` lines in the crash output. Three space indents are from the
`Throwable.getMessage()` output, while four space indents are from
the Java-side stack trace.)
We *think* that we can improve this by replacing the Java-side stack
trace with a "merged" stack trace which includes both the Java-side
and .NET-side stack traces. This does nothing for unhandled exceptions
on .NET threads, but does alter the output from Java threads:
E AndroidRuntime: FATAL EXCEPTION: Thread-3
E AndroidRuntime: Process: com.companyname.android_unhandled_exception, PID: 12321
E AndroidRuntime: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops!
E AndroidRuntime: at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0)
E AndroidRuntime: at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0)
E AndroidRuntime: at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0)
E AndroidRuntime: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0)
E AndroidRuntime: at mono.java.lang.RunnableImplementor.n_run(Native Method)
E AndroidRuntime: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
E AndroidRuntime: at java.lang.Thread.run(Thread.java:1012)
I MonoDroid: UNHANDLED EXCEPTION:
I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable
I MonoDroid:
I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
I MonoDroid: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops!
I MonoDroid: at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0)
I MonoDroid: at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0)
I MonoDroid: at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0)
I MonoDroid: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0)
I MonoDroid: at mono.java.lang.RunnableImplementor.n_run(Native Method)
I MonoDroid: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
I MonoDroid: at java.lang.Thread.run(Thread.java:1012)
I MonoDroid:
I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
I MonoDroid: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops!
I MonoDroid: at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0)
I MonoDroid: at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0)
I MonoDroid: at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0)
I MonoDroid: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0)
I MonoDroid: at mono.java.lang.RunnableImplementor.n_run(Native Method)
I MonoDroid: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31)
I MonoDroid: at java.lang.Thread.run(Thread.java:1012)
Note how `at …` is always a four-space indent and always lines up.
*Hopefully* this means that crash loggers can provide more useful
information.
TODO:
* Create an "end-to-end" test which uses an actual crash logger
(which one?) in order to better understand what the
"end user" experience is.
* The "merged" stack trace always places the managed stack trace
above the Java-side stack trace. This means things will look
"weird"/"wrong" if you have an *intermixed* stack trace, e.g.
(Java code calls .NET code which calls Java code)+
which eventually throws from .NET. |
Ruh roh: Mono.Android.NET_Tests, Xamarin.Android.RuntimeTests.ExceptionTest.InnerExceptionIsSet / Release fails with:
Looks like it's being linked away? |
MethodInfo? create = JavaProxyThrowable_type.GetMethod ( | ||
"Create", | ||
BindingFlags.Static | BindingFlags.Public, | ||
new Type[] { typeof(Exception), typeof(bool) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the "Unable to find ….Create(Exception) method" error, I think it's because it's looking for Create(Exception, bool)
, which does not exist. We need to instead look for Create(Exception)
.
Fix runtime test failure
…8185) Context: #1198 Context: #1188 (comment) Context: #4877 Context: #4927 (comment) What happens with unhandled exceptions? throw new InvalidOperationException ("oops!"); This is a surprisingly complicated question: If this happens when a debugger is attached, the debugger will get a "first chance notification" at the `throw` site. If execution continues, odds are high that the app will abort if there is a JNI transition in the callstack. If no debugger is attached, then it depends on which thread threw the unhandled exception. If the thread which threw the unhandled exception is a .NET Thread: static void ThrowFromAnotherManagedThread() { var t = new System.Threading.Thread(() => { throw new new Java.Lang.Error ("from another thread?!"); }); t.Start (); t.Join (); } Then .NET will report the unhandled exception, *and* the app will restart: F mono-rt : [ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: oops! F mono-rt : at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherManagedThread>b__1_0() F mono-rt : at System.Threading.Thread.StartCallback() # app restarts If the thread which threw the unhandled exception is a *Java* thread, which could be the UI thread (e.g. thrown from an `Activity.OnCreate()` override) or via a `Java.Lang.Thread` instance: static void ThrowFromAnotherJavaThread() { var t = new Java.Lang.Thread(() => { throw new InvalidOperationException ("oops!"); }); t.Start (); t.Join (); } Then .NET will report the unhandled exception, *and* the app will *not* restart (which differs from using .NET threads): E AndroidRuntime: Process: com.companyname.android_unhandled_exception, PID: 5436 E AndroidRuntime: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops! E AndroidRuntime: at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0() E AndroidRuntime: at Java.Lang.Thread.RunnableImplementor.Run() E AndroidRuntime: at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr ) E AndroidRuntime: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr ) E AndroidRuntime: at mono.java.lang.RunnableImplementor.n_run(Native Method) E AndroidRuntime: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31) E AndroidRuntime: at java.lang.Thread.run(Thread.java:1012) I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable I MonoDroid: I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace --- I MonoDroid: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops! I MonoDroid: at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0() I MonoDroid: at Java.Lang.Thread.RunnableImplementor.Run() I MonoDroid: at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr ) I MonoDroid: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr ) I MonoDroid: at mono.java.lang.RunnableImplementor.n_run(Native Method) I MonoDroid: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31) I MonoDroid: at java.lang.Thread.run(Thread.java:1012) I MonoDroid: I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace --- I MonoDroid: android.runtime.JavaProxyThrowable: System.InvalidOperationException: oops! I MonoDroid: at android_unhandled_exception.MainActivity.<>c.<ThrowFromAnotherJavaThread>b__2_0() I MonoDroid: at Java.Lang.Thread.RunnableImplementor.Run() I MonoDroid: at Java.Lang.IRunnableInvoker.n_Run(IntPtr , IntPtr ) I MonoDroid: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V , IntPtr , IntPtr ) I MonoDroid: at mono.java.lang.RunnableImplementor.n_run(Native Method) I MonoDroid: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31) I MonoDroid: at java.lang.Thread.run(Thread.java This "works", until we enter the world of crash logging for later diagnosis and fixing. The problem with our historical approach is that we would "stuff" the .NET stack trace into the "message" of the Java-side `Throwable` instance, and the "message" may not be transmitted as part of the crash logging! (This is noticeable by the different indentation levels for the `at …` lines in the crash output. Three space indents are from the `Throwable.getMessage()` output, while four space indents are from the Java-side stack trace.) We *think* that we can improve this by replacing the Java-side stack trace with a "merged" stack trace which includes both the Java-side and .NET-side stack traces. This does nothing for unhandled exceptions on .NET threads, but does alter the output from Java threads: E AndroidRuntime: FATAL EXCEPTION: Thread-3 E AndroidRuntime: Process: com.companyname.android_unhandled_exception, PID: 12321 E AndroidRuntime: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops! E AndroidRuntime: at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0) E AndroidRuntime: at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0) E AndroidRuntime: at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0) E AndroidRuntime: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0) E AndroidRuntime: at mono.java.lang.RunnableImplementor.n_run(Native Method) E AndroidRuntime: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31) E AndroidRuntime: at java.lang.Thread.run(Thread.java:1012) I MonoDroid: UNHANDLED EXCEPTION: I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable I MonoDroid: I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace --- I MonoDroid: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops! I MonoDroid: at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0) I MonoDroid: at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0) I MonoDroid: at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0) I MonoDroid: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0) I MonoDroid: at mono.java.lang.RunnableImplementor.n_run(Native Method) I MonoDroid: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31) I MonoDroid: at java.lang.Thread.run(Thread.java:1012) I MonoDroid: I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace --- I MonoDroid: android.runtime.JavaProxyThrowable: [System.InvalidOperationException]: oops! I MonoDroid: at android_unhandled_exception.MainActivity+<>c.<ThrowFromAnotherJavaThread>b__2_0(Unknown Source:0) I MonoDroid: at Java.Lang.Thread+RunnableImplementor.Run(Unknown Source:0) I MonoDroid: at Java.Lang.IRunnableInvoker.n_Run(Unknown Source:0) I MonoDroid: at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(Unknown Source:0) I MonoDroid: at mono.java.lang.RunnableImplementor.n_run(Native Method) I MonoDroid: at mono.java.lang.RunnableImplementor.run(RunnableImplementor.java:31) I MonoDroid: at java.lang.Thread.run(Thread.java:1012) Note how `at …` is always a four-space indent and always lines up. *Hopefully* this means that crash loggers can provide more useful information. TODO: * Create an "end-to-end" test which uses an actual crash logger (which one?) in order to better understand what the "end user" experience is. * The "merged" stack trace always places the managed stack trace above the Java-side stack trace. This means things will look "weird"/"wrong" if you have an *intermixed* stack trace, e.g. (Java code calls .NET code which calls Java code)+ which eventually throws from .NET.
* main: (57 commits) Bump to dotnet/installer@2809943e7a 8.0.100-rc.2.23431.5 (dotnet#8317) [build] Use Microsoft OpenJDK 17.0.8 (dotnet#8309) [Mono.Android] Add missing `[Flags]` attribute for generated enums. (dotnet#8310) Bump to dotnet/installer@c5e45fd659 8.0.100-rc.2.23427.4 (dotnet#8305) [xaprepare] Improve dotnet-install script logging (dotnet#8312) [xaprepare] Fix dotnet-install script size check (dotnet#8311) [Xamarin.Android.Build.Tasks] improve net6.0 "out of support" message (dotnet#8307) [monodroid] Fix the EnableNativeAnalyzers build (dotnet#8293) Bump to dotnet/installer@56d8c6497c 8.0.100-rc.2.23422.31 (dotnet#8291) [Xamarin.Android.Build.Tasks] Fix APT2264 error on Windows. (dotnet#8289) [Mono.Android] Marshal .NET stack trace to Throwable.getStackTrace() (dotnet#8185) [tests] Skip sign check when installing MAUI (dotnet#8288) Bump to xamarin/monodroid@057b17fe (dotnet#8286) [Xamarin.Android.Build.Tasks] add $(AndroidStripILAfterAOT) (dotnet#8172) Bump to dotnet/installer@ec2c1ec1b1 8.0.100-rc.2.23420.6 (dotnet#8281) Bump to dotnet/installer@001d8e4465 8.0.100-rc.2.23417.14 (dotnet#8276) [Mono.Android] [IntentFilter] pathSuffix & pathAdvancedPattern (dotnet#8261) $(AndroidPackVersionSuffix)=rc.2; net8 is 34.0.0-rc.2 (dotnet#8278) Bump to xamarin/xamarin-android-tools/main@52f0866 (dotnet#8241) [build] set file extension for common `ToolExe` values (dotnet#8267) ...
* main: (102 commits) Bump to dotnet/installer@2809943e7a 8.0.100-rc.2.23431.5 (dotnet#8317) [build] Use Microsoft OpenJDK 17.0.8 (dotnet#8309) [Mono.Android] Add missing `[Flags]` attribute for generated enums. (dotnet#8310) Bump to dotnet/installer@c5e45fd659 8.0.100-rc.2.23427.4 (dotnet#8305) [xaprepare] Improve dotnet-install script logging (dotnet#8312) [xaprepare] Fix dotnet-install script size check (dotnet#8311) [Xamarin.Android.Build.Tasks] improve net6.0 "out of support" message (dotnet#8307) [monodroid] Fix the EnableNativeAnalyzers build (dotnet#8293) Bump to dotnet/installer@56d8c6497c 8.0.100-rc.2.23422.31 (dotnet#8291) [Xamarin.Android.Build.Tasks] Fix APT2264 error on Windows. (dotnet#8289) [Mono.Android] Marshal .NET stack trace to Throwable.getStackTrace() (dotnet#8185) [tests] Skip sign check when installing MAUI (dotnet#8288) Bump to xamarin/monodroid@057b17fe (dotnet#8286) [Xamarin.Android.Build.Tasks] add $(AndroidStripILAfterAOT) (dotnet#8172) Bump to dotnet/installer@ec2c1ec1b1 8.0.100-rc.2.23420.6 (dotnet#8281) Bump to dotnet/installer@001d8e4465 8.0.100-rc.2.23417.14 (dotnet#8276) [Mono.Android] [IntentFilter] pathSuffix & pathAdvancedPattern (dotnet#8261) $(AndroidPackVersionSuffix)=rc.2; net8 is 34.0.0-rc.2 (dotnet#8278) Bump to xamarin/xamarin-android-tools/main@52f0866 (dotnet#8241) [build] set file extension for common `ToolExe` values (dotnet#8267) ...
* main: (68 commits) Bump to dotnet/installer@2809943e7a 8.0.100-rc.2.23431.5 (dotnet#8317) [build] Use Microsoft OpenJDK 17.0.8 (dotnet#8309) [Mono.Android] Add missing `[Flags]` attribute for generated enums. (dotnet#8310) Bump to dotnet/installer@c5e45fd659 8.0.100-rc.2.23427.4 (dotnet#8305) [xaprepare] Improve dotnet-install script logging (dotnet#8312) [xaprepare] Fix dotnet-install script size check (dotnet#8311) [Xamarin.Android.Build.Tasks] improve net6.0 "out of support" message (dotnet#8307) [monodroid] Fix the EnableNativeAnalyzers build (dotnet#8293) Bump to dotnet/installer@56d8c6497c 8.0.100-rc.2.23422.31 (dotnet#8291) [Xamarin.Android.Build.Tasks] Fix APT2264 error on Windows. (dotnet#8289) [Mono.Android] Marshal .NET stack trace to Throwable.getStackTrace() (dotnet#8185) [tests] Skip sign check when installing MAUI (dotnet#8288) Bump to xamarin/monodroid@057b17fe (dotnet#8286) [Xamarin.Android.Build.Tasks] add $(AndroidStripILAfterAOT) (dotnet#8172) Bump to dotnet/installer@ec2c1ec1b1 8.0.100-rc.2.23420.6 (dotnet#8281) Bump to dotnet/installer@001d8e4465 8.0.100-rc.2.23417.14 (dotnet#8276) [Mono.Android] [IntentFilter] pathSuffix & pathAdvancedPattern (dotnet#8261) $(AndroidPackVersionSuffix)=rc.2; net8 is 34.0.0-rc.2 (dotnet#8278) Bump to xamarin/xamarin-android-tools/main@52f0866 (dotnet#8241) [build] set file extension for common `ToolExe` values (dotnet#8267) ...
* main: (53 commits) Bump to dotnet/installer@2809943e7a 8.0.100-rc.2.23431.5 (dotnet#8317) [build] Use Microsoft OpenJDK 17.0.8 (dotnet#8309) [Mono.Android] Add missing `[Flags]` attribute for generated enums. (dotnet#8310) Bump to dotnet/installer@c5e45fd659 8.0.100-rc.2.23427.4 (dotnet#8305) [xaprepare] Improve dotnet-install script logging (dotnet#8312) [xaprepare] Fix dotnet-install script size check (dotnet#8311) [Xamarin.Android.Build.Tasks] improve net6.0 "out of support" message (dotnet#8307) [monodroid] Fix the EnableNativeAnalyzers build (dotnet#8293) Bump to dotnet/installer@56d8c6497c 8.0.100-rc.2.23422.31 (dotnet#8291) [Xamarin.Android.Build.Tasks] Fix APT2264 error on Windows. (dotnet#8289) [Mono.Android] Marshal .NET stack trace to Throwable.getStackTrace() (dotnet#8185) [tests] Skip sign check when installing MAUI (dotnet#8288) Bump to xamarin/monodroid@057b17fe (dotnet#8286) [Xamarin.Android.Build.Tasks] add $(AndroidStripILAfterAOT) (dotnet#8172) Bump to dotnet/installer@ec2c1ec1b1 8.0.100-rc.2.23420.6 (dotnet#8281) Bump to dotnet/installer@001d8e4465 8.0.100-rc.2.23417.14 (dotnet#8276) [Mono.Android] [IntentFilter] pathSuffix & pathAdvancedPattern (dotnet#8261) $(AndroidPackVersionSuffix)=rc.2; net8 is 34.0.0-rc.2 (dotnet#8278) Bump to xamarin/xamarin-android-tools/main@52f0866 (dotnet#8241) [build] set file extension for common `ToolExe` values (dotnet#8267) ...
* main: (25 commits) Bump to dotnet/installer@2809943e7a 8.0.100-rc.2.23431.5 (dotnet#8317) [build] Use Microsoft OpenJDK 17.0.8 (dotnet#8309) [Mono.Android] Add missing `[Flags]` attribute for generated enums. (dotnet#8310) Bump to dotnet/installer@c5e45fd659 8.0.100-rc.2.23427.4 (dotnet#8305) [xaprepare] Improve dotnet-install script logging (dotnet#8312) [xaprepare] Fix dotnet-install script size check (dotnet#8311) [Xamarin.Android.Build.Tasks] improve net6.0 "out of support" message (dotnet#8307) [monodroid] Fix the EnableNativeAnalyzers build (dotnet#8293) Bump to dotnet/installer@56d8c6497c 8.0.100-rc.2.23422.31 (dotnet#8291) [Xamarin.Android.Build.Tasks] Fix APT2264 error on Windows. (dotnet#8289) [Mono.Android] Marshal .NET stack trace to Throwable.getStackTrace() (dotnet#8185) [tests] Skip sign check when installing MAUI (dotnet#8288) Bump to xamarin/monodroid@057b17fe (dotnet#8286) [Xamarin.Android.Build.Tasks] add $(AndroidStripILAfterAOT) (dotnet#8172) Bump to dotnet/installer@ec2c1ec1b1 8.0.100-rc.2.23420.6 (dotnet#8281) Bump to dotnet/installer@001d8e4465 8.0.100-rc.2.23417.14 (dotnet#8276) [Mono.Android] [IntentFilter] pathSuffix & pathAdvancedPattern (dotnet#8261) $(AndroidPackVersionSuffix)=rc.2; net8 is 34.0.0-rc.2 (dotnet#8278) Bump to xamarin/xamarin-android-tools/main@52f0866 (dotnet#8241) [build] set file extension for common `ToolExe` values (dotnet#8267) ...
* main: (38 commits) Bump to dotnet/installer@2809943e7a 8.0.100-rc.2.23431.5 (dotnet#8317) [build] Use Microsoft OpenJDK 17.0.8 (dotnet#8309) [Mono.Android] Add missing `[Flags]` attribute for generated enums. (dotnet#8310) Bump to dotnet/installer@c5e45fd659 8.0.100-rc.2.23427.4 (dotnet#8305) [xaprepare] Improve dotnet-install script logging (dotnet#8312) [xaprepare] Fix dotnet-install script size check (dotnet#8311) [Xamarin.Android.Build.Tasks] improve net6.0 "out of support" message (dotnet#8307) [monodroid] Fix the EnableNativeAnalyzers build (dotnet#8293) Bump to dotnet/installer@56d8c6497c 8.0.100-rc.2.23422.31 (dotnet#8291) [Xamarin.Android.Build.Tasks] Fix APT2264 error on Windows. (dotnet#8289) [Mono.Android] Marshal .NET stack trace to Throwable.getStackTrace() (dotnet#8185) [tests] Skip sign check when installing MAUI (dotnet#8288) Bump to xamarin/monodroid@057b17fe (dotnet#8286) [Xamarin.Android.Build.Tasks] add $(AndroidStripILAfterAOT) (dotnet#8172) Bump to dotnet/installer@ec2c1ec1b1 8.0.100-rc.2.23420.6 (dotnet#8281) Bump to dotnet/installer@001d8e4465 8.0.100-rc.2.23417.14 (dotnet#8276) [Mono.Android] [IntentFilter] pathSuffix & pathAdvancedPattern (dotnet#8261) $(AndroidPackVersionSuffix)=rc.2; net8 is 34.0.0-rc.2 (dotnet#8278) Bump to xamarin/xamarin-android-tools/main@52f0866 (dotnet#8241) [build] set file extension for common `ToolExe` values (dotnet#8267) ...
Fixes: #1198
Given an instance of
System.Exception
, we can use theSystem.Diagnostics.StackTrace
class to iterate over the list ofmanaged stack frames, translating each of them to equivalent
Java.Lang.StackTraceElement
and afterwards passing the entirecollection to
Java.Lang.Throwable.SetStackTrace
.