Austin Wise
Go to home page
2018-04-25

Adding a new feature to CoreCLR: BindingFlags.DoNotWrapException

Background

If you have ever used reflection in .NET to invoke a method, you probably have dealt with TargetInvocationException. Whenever an exception is thrown as the result of invoking a method with reflection, it is wrapped in a TargetInvocationException. Any exception-handling logic downstream of the reflection invocation callsite has to add a catch (TargetInvocationException ex) and duplicate their logic to handle the InnerException property of ex.

There are a few workarounds for this wrapping behavior that I am aware of. One is to simply wrap the reflection invocation location and have it rethrow the InnerException of the TargetInvocationException. However, this loses the call stack information when you rethrow an exception. The more modern version of this strategy is using ExceptionDispatchInfo to capture the inner exception and rethrow it, but it would be nice if one did not have to catch the exception in the first place.

The Interface

Last August, while perusing the commit log of CoreCLR, I noticed that Microsoft's Atsushi Kanamori merged a pull request to CoreCLR1 that added an intriguing enumeration value to BindingFlags: DoNotWrapExceptions:

[Flags]
public enum BindingFlags
{
+     DoNotWrapExceptions = 0x02000000, // Disables wrapping exceptions in TargetInvocationException
}

The linked design review showed an example of how using the new flag would prevent exceptions from being wrapped in TargetInvocationException:

public class Program
{
	public static void Main()
	{
		try {
			var bf = BindingFlags.Static |
                            BindingFlags.Public |
                            BindingFlags.InvokeMethod;

            //The new flag
            bf |= BindingFlags.DoNotWrapExceptions;

			typeof(Program).InvokeMember("LateBoundTarget", bf, null, null, null);

		} catch(TargetInvocationException) {
			Console.WriteLine("catches before the new flag");

		} catch(InvalidOperationException) {
			Console.WriteLine("catches after the new flag");
		}
	}

	public static void LateBoundTarget() {
		throw new InvalidOperationException();
	}
}

This looks like a handy way to avoid dealing with the TargetInvocationException. There was only one problem with this new flag: it was as of yet unimplemented.

The Implementation

If you have ever peeked and poked around mscorlib using IlSpy or the like before .NET Core was open source, or if you have looked at that source of System.Private.CoreLib after the open sourcing, you have undoubtedly had your exploration end at method that looks like this just as things were getting interesting:

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void FailFast(string message);

These methods are called FCalls and are documented in the Book of the Runtime. They allow C# code to call directly into the C++ code of the CLR.

While a lot of the reflection systems is now2 written in C#, the final invocation of the method happens in C++. This is also where exceptions thrown during the reflection invocation are caught and wrapped in TargetInvocationException.

The implementation of this feature was relatively simple: find the places in reflection that wrap, and just make them not do that. In this simplified example, it was just a matter of adding a path that does not wrap the method invoke in a TRY-CATCH block:

+    bool fExceptionThrown = false;
+    if (fWrapExceptions)
+    {
-        bool fExceptionThrown = false;
         EX_TRY_THREAD(pThread) {
             CallDescrWorkerReflectionWrapper(&callDescrData, &catchFrame);
         } EX_CATCH{
             // Abuse retval to store the exception object
             gc.retVal = GET_THROWABLE();

             fExceptionThrown = true;
         } EX_END_CATCH(SwallowAllExceptions);
+    }
+    else
+    {
+        CallDescrWorkerWithHandler(&callDescrData);
+    }

In one case the wrapping happened in C#. In this case it was pretty to use C# 6's exception filters to make the wrapping conditional:

                         try
                         {
                             ace.m_ctor(instance);
                         }
-                        catch (Exception e)
+                        catch (Exception e) when (wrapExceptions)
                         {
                             throw new TargetInvocationException(e);
                         }

Conclusion

You can try out this new feature in the .NET Core 2.1 preview. It should ship shortly in the final .NET Core 2.1 release. Since implementing this feature in CoreCLR, Microsoft's Atsushi has implemented it in CoreRT and I have also implemented it in Mono. Hopefully the .NET Framework will also gain this flag and thus make it possible to include this feature in a future version of .NET standard.

Acknowledgements

Thanks to Caspar Hansen for reviewing drafts of this.

Footnotes

1

The linked pull request is to the CoreRT repository, however that part of repo is synced with CoreCLR automatically.

2

In .NET Framework 1.0, pretty much all of reflection was implemented using FCalls. I can't recall exactly when, but it was either .NET 1.1 or 2.0 where they reimplemented much of reflection in C#. At the time, this was claimed to be a big performance boost. The reduction of the use of FCalls continues in CoreCLR. CoreRT represents an existence proof how little C++ code you can write when implementing a .NET runtime, but this is the subject of a future blog post, not a foot note.