Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Native code crashes on constructor?.Invoke() (null-propagation)

After scratching my head for the better part of the day, I stumbled upon a very weird issue with .NET code that is compiled using .NET Native (used for Windows UWP apps).

The following code works fine in any .NET runtime environment, including Mono, Xamarin, etc. :

public class ABC {}
// ...
var constr = typeof(ABC).GetTypeInfo().DeclaredConstructors.First();

var abc = (ABC) constr?.Invoke(new object[0]);
// abc now contains an instance of ABC

On Windows UWP with .NET Native compilation, the code throws an exception of type NotImplementedException

However, when the null propagation operator is removed, it works perfectly on .NET Native:

public class ABC {}
// ...
var constr = typeof(ABC).GetTypeInfo().DeclaredConstructors.First();

var abc1 = (ABC) constr.Invoke(new object[0]);
// abc1 now contains an instance of ABC

// the following line throws an exception on .NET Native
// but it works fine on any other .NET runtime
var abc2 = (ABC) constr?.Invoke(new object[0]);

The line in the stack trace where the exception occurs is:

at System.Reflection.ConstructorInfo.Invoke(Object[] parameters) 
in f:\dd\ndp\fxcore\CoreRT\src\System.Private.Reflection\src\System\Reflection\ConstructorInfo.cs:line 41

This smells like a bug in the compiler or runtime. What's going on here? Am I missing something?

like image 671
Philippe Leybaert Avatar asked May 08 '17 20:05

Philippe Leybaert


1 Answers

Turns out that it is a bug.

More info here: https://github.com/dotnet/corert/issues/3565

  • ConstructorInfo.Invoke(object[]) method in the System.Reflection reference assembly (C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETPortable\v4.5\Profile\Profile78\System.Reflection.dll says that the Invoke method is not virtual.
  • Somewhere someone decided that the method should be virtual and they changed it in the implementation. The reference assembly that the C# code compiles against was left untouched.
  • Normally this is not a big deal because C# pretty much always calls methods virtually (even if they're not virtual), because it needs the side effect of the virtual call (throw a NullReferenceException on null this).
  • Except with the null propagation operator the C# compiler knows that a NullReferenceException cannot occur and it decides to emit a normal call instruction instead of callvirt to prevent the unnecessary null check. Doing a normal call to the ConstructorInfo.Invoke(object[]) method results in us landing in a method that should never be called.

The good news is that ConstructorInfo.Invoke(object[]) is no longer virtual as part of the NetStandard 2.0 compatibility effort (the previous link was to an old snapshot). That version of .NET Native hasn't shipped yet. The only workaround for now is not to let C# compiler optimize the callvirt to a call by avoiding the operator.

like image 140
Philippe Leybaert Avatar answered Nov 19 '22 03:11

Philippe Leybaert