-
Notifications
You must be signed in to change notification settings - Fork 556
Improve JavaProxyThrowable by translating managed stack trace #8185
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
Changes from 20 commits
ee565e4
7c0a507
6ee31a3
7cfc3dc
78839fb
b2b38b1
ada140c
45b3e72
241ed9b
c1cabbc
4c38c11
7a8964b
1bac358
998c012
a25636d
92e5db3
4b5b56e
b68dcc2
32130ab
603f1e4
cdf4714
784014c
a78d466
691e075
af90f92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,83 @@ | ||
using System; | ||
using System.Diagnostics; | ||
using System.Reflection; | ||
|
||
using StackTraceElement = Java.Lang.StackTraceElement; | ||
|
||
namespace Android.Runtime { | ||
|
||
class JavaProxyThrowable : Java.Lang.Error { | ||
sealed class JavaProxyThrowable : Java.Lang.Error { | ||
|
||
public readonly Exception InnerException; | ||
|
||
public JavaProxyThrowable (Exception innerException) | ||
: base (GetDetailMessage (innerException)) | ||
JavaProxyThrowable (string message, Exception innerException) | ||
: base (message) | ||
{ | ||
InnerException = innerException; | ||
} | ||
|
||
static string GetDetailMessage (Exception innerException) | ||
public static JavaProxyThrowable Create (Exception innerException) | ||
{ | ||
if (innerException == null) { | ||
throw new ArgumentNullException (nameof (innerException)); | ||
} | ||
|
||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more. This is where Which still doesn't make sense, as that would mean that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh! It's because |
||
|
||
try { | ||
proxy.TranslateStackTrace (); | ||
} catch (Exception ex) { | ||
// We shouldn't throw here, just try to do the best we can do | ||
Console.WriteLine ($"JavaProxyThrowable: translation threw an exception: {ex}"); | ||
proxy = new JavaProxyThrowable (innerException.ToString (), innerException); | ||
} | ||
|
||
return proxy; | ||
} | ||
|
||
void TranslateStackTrace () | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
If the assumption in #1188 is correct, and we want
As far as I can tell,
as the complete stack trace, which I don't believe would be useful. (Now, this PR/#1188 is about |
||
{ | ||
if (innerException == null) | ||
throw new ArgumentNullException ("innerException"); | ||
var trace = new StackTrace (InnerException, fNeedFileInfo: true); | ||
if (trace.FrameCount <= 0) { | ||
return; | ||
} | ||
|
||
StackTraceElement[]? javaTrace = null; | ||
try { | ||
javaTrace = GetStackTrace (); | ||
} catch (Exception ex) { | ||
// Report... | ||
Console.WriteLine ($"JavaProxyThrowable: obtaining Java stack trace threw an exception: {ex}"); | ||
// ..but ignore | ||
} | ||
|
||
|
||
StackFrame[] frames = trace.GetFrames (); | ||
int nElements = frames.Length + (javaTrace?.Length ?? 0); | ||
StackTraceElement[] elements = new StackTraceElement[nElements]; | ||
|
||
for (int i = 0; i < frames.Length; i++) { | ||
StackFrame managedFrame = frames[i]; | ||
MethodBase? managedMethod = managedFrame.GetMethod (); | ||
|
||
var throwableFrame = new StackTraceElement ( | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be
No idea where |
||
); | ||
|
||
elements[i] = throwableFrame; | ||
} | ||
|
||
if (javaTrace != null) { | ||
for (int i = frames.Length; i < nElements; i++) { | ||
elements[i] = javaTrace[i - frames.Length]; | ||
} | ||
} | ||
|
||
return innerException.ToString (); | ||
SetStackTrace (elements); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
using System; | ||
using System.Diagnostics; | ||
using System.Globalization; | ||
using System.Reflection; | ||
|
||
using Android.App; | ||
using Android.Content; | ||
|
@@ -17,18 +19,52 @@ static Java.Lang.Throwable CreateJavaProxyThrowable (Exception e) | |
var JavaProxyThrowable_type = typeof (Java.Lang.Object) | ||
.Assembly | ||
.GetType ("Android.Runtime.JavaProxyThrowable"); | ||
return (Java.Lang.Throwable) Activator.CreateInstance (JavaProxyThrowable_type, e); | ||
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 commentThe 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 |
||
); | ||
|
||
Assert.AreNotEqual (null, create, "Unable to find the Android.Runtime.JavaProxyThrowable.Create(Exception) method"); | ||
return (Java.Lang.Throwable)create.Invoke (null, new object[] { e, false }); // Don't append Java stack trace | ||
} | ||
|
||
[Test] | ||
public void InnerExceptionIsSet () | ||
{ | ||
var ex = new InvalidOperationException ("boo!"); | ||
using (var source = new Java.Lang.Throwable ("detailMessage", CreateJavaProxyThrowable (ex))) | ||
Exception ex; | ||
try { | ||
throw new InvalidOperationException ("boo!"); | ||
} catch (Exception e) { | ||
ex = e; | ||
} | ||
|
||
using (Java.Lang.Throwable proxy = CreateJavaProxyThrowable (ex)) | ||
using (var source = new Java.Lang.Throwable ("detailMessage", proxy)) | ||
using (var alias = new Java.Lang.Throwable (source.Handle, JniHandleOwnership.DoNotTransfer)) { | ||
CompareStackTraces (ex, proxy); | ||
Assert.AreEqual ("detailMessage", alias.Message); | ||
Assert.AreSame (ex, alias.InnerException); | ||
} | ||
} | ||
|
||
void CompareStackTraces (Exception ex, Java.Lang.Throwable throwable) | ||
{ | ||
var managedTrace = new StackTrace (ex); | ||
StackFrame[] managedFrames = managedTrace.GetFrames (); | ||
Java.Lang.StackTraceElement[] javaFrames = throwable.GetStackTrace (); | ||
|
||
// Java | ||
Assert.AreEqual (managedFrames.Length, javaFrames.Length, "Java and managed stack traces have a different number of frames"); | ||
for (int i = 0; i < managedFrames.Length; i++) { | ||
var mf = managedFrames[i]; | ||
var jf = javaFrames[i]; | ||
|
||
Assert.AreEqual (mf.GetMethod ()?.Name, jf.MethodName, $"Frame {i}: method names differ"); | ||
Assert.AreEqual (mf.GetMethod ()?.DeclaringType.FullName, jf.ClassName, $"Frame {i}: class names differ"); | ||
Assert.AreEqual (mf.GetFileName (), jf.FileName, $"Frame {i}: file names differ"); | ||
Assert.AreEqual (mf.GetFileLineNumber (), jf.LineNumber, $"Frame {i}: line numbers differ"); | ||
} | ||
} | ||
} | ||
} |
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 throwArgumentNullException
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