Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[runtime] Setup stack trace for use in exception filters too
If we have `catch (Exception e) when (SomeMethod (e))` .. then the exception object received in `SomeMethod` has an empty trace. This is because in `handle_exception_first_pass` we populate the trace in `MonoException` object only at the "end": - filter returns TRUE - exception caught - unhandled exception But if we have a filter then the trace at that point needs to be accessible. And if the filter fails then as we unwind more frames, the earlier frames (`trace_ips`) need to be retained to get the correct full trace. `dynamic_methods` needs to be handled similarly and in `setup_stack_trace` we need to add to the existing `dynamic_methods` in the MonoException object. Fixes #7649 .
- Loading branch information
Showing
3 changed files
with
139 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
using System; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using System.IO; | ||
|
||
class CustomException : Exception | ||
{ | ||
public int Value; | ||
public CustomException (int value) => this.Value = value; | ||
} | ||
|
||
class C | ||
{ | ||
static CustomException e; | ||
|
||
static void Throw () | ||
{ | ||
try { | ||
throw new CustomException(1); | ||
} catch (CustomException ex) { | ||
e = ex; | ||
} | ||
} | ||
|
||
static int FrameCount (Exception ex) | ||
{ | ||
string fullTrace = ex.StackTrace; | ||
if (fullTrace == null) | ||
throw new Exception ("Empty trace found!"); | ||
|
||
string[] frames = fullTrace.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); | ||
|
||
// Ignore metadata | ||
frames = frames.Where (l => !l.StartsWith ("[")).ToArray (); | ||
|
||
return frames.Length; | ||
} | ||
|
||
// throws on false anyway | ||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
static bool CheckTrace (Exception ex, int numFramesToExpect, bool whatToReturn = true) | ||
{ | ||
int frames = FrameCount (ex); | ||
if (frames != numFramesToExpect) { | ||
throw new Exception ($"Exception carried {frames} frames along with it when it should have reported {numFramesToExpect}. Full trace:\n---\n{ex.StackTrace}\n---"); | ||
} | ||
|
||
return whatToReturn; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
static void ThrowCustomExceptionWithValueButFailsToCatchWithFilter () | ||
{ | ||
try { | ||
ThrowCustomException (1); | ||
} catch (CustomException ce) when (ce.Value == 9999) { | ||
throw new Exception ("This should NEVER be hit"); | ||
} | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
static void ThrowCustomException (int value) | ||
{ | ||
throw new CustomException (value); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.NoInlining)] | ||
static void ThrowFileNotFoundException () | ||
{ | ||
throw new FileNotFoundException (); | ||
} | ||
|
||
public static void Main () | ||
{ | ||
Throw (); | ||
|
||
// Single frame | ||
// Filter returns false | ||
try { | ||
throw e; | ||
} catch (Exception ex) when (CheckTrace (ex, 1, true)) { | ||
CheckTrace (ex, 1); | ||
} | ||
|
||
// Throw + filter fails, then filter matches at next level | ||
try { | ||
ThrowCustomExceptionWithValueButFailsToCatchWithFilter (); | ||
} catch (CustomException ex) when (CheckTrace (ex, 3, true)) { | ||
CheckTrace (ex, 3); | ||
} | ||
|
||
// Throw + filter fails, then filter matches, exception re-thrown and caught | ||
try { | ||
try { | ||
ThrowCustomExceptionWithValueButFailsToCatchWithFilter (); | ||
} catch (CustomException ex) when (CheckTrace (ex, 3, true)) { | ||
CheckTrace (ex, 3); | ||
|
||
// this will truncate the trace now | ||
throw ex; | ||
} | ||
} catch (Exception ex) { | ||
CheckTrace (ex, 1); | ||
} | ||
|
||
// Throw, filter matches, throw exception as-is, caught | ||
try { | ||
try { | ||
ThrowFileNotFoundException (); | ||
} catch (Exception e) when (CheckTrace (e, 2)) { | ||
// trace should remain as is | ||
throw; | ||
} | ||
} catch (Exception ex) { | ||
CheckTrace (ex, 2); | ||
} | ||
} | ||
} |