Invalid mock assembly produced
Opened this issue · 8 comments
I'm generating mock assemblies and then inspecting them with ILSpy. I noticed that in some cases the generated assembly contains methods that can't be processed with ILSpy. I'm unsure if the problem lies in ILSpy or Refasmer. If I generate reference assemblies instead of the mocks, then I'm not facing the issue. Also, not all methods are impacted by the problem in a mock assembly.
The below commands
refasmer /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.2/ref/net7.0/System.Runtime.dll -v -c -O=out -p -n -m
ilspycmd -o X out/System.Runtime.dll
produce an exception:
Processing 1 assemblies
Processing /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.2/ref/net7.0/System.Runtime.dll
[System.Runtime.dll] Using custom entity filter
All done
Error decompiling @06000001 Microsoft.CodeAnalysis.EmbeddedAttribute..ctor
in assembly "out/System.Runtime.dll"
---> System.ArgumentNullException: Value cannot be null. (Parameter 'methodReference')
at ICSharpCode.Decompiler.TypeSystem.MetadataModule.ResolveMethod(EntityHandle methodReference, GenericContext context) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\TypeSystem\MetadataModule.cs:line 421
at ICSharpCode.Decompiler.IL.ILReader.DecodeCall(OpCode opCode) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\IL\ILReader.cs:line 1517
at ICSharpCode.Decompiler.IL.ILReader.DecodeInstruction() in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\IL\ILReader.cs:line 926
at ICSharpCode.Decompiler.IL.ILReader.ReadInstructions(CancellationToken cancellationToken) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\IL\ILReader.cs:line 443
at ICSharpCode.Decompiler.IL.ILReader.ReadIL(MethodDefinitionHandle method, MethodBodyBlock body, GenericContext genericContext, ILFunctionKind kind, CancellationToken cancellationToken) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\IL\ILReader.cs:line 580
at ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DecompileBody(IMethod method, EntityDeclaration entityDecl, DecompileRun decompileRun, ITypeResolveContext decompilationContext) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\CSharp\CSharpDecompiler.cs:line 1542
-- continuing with outer exception (ICSharpCode.Decompiler.DecompilerException) --
at ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DecompileBody(IMethod method, EntityDeclaration entityDecl, DecompileRun decompileRun, ITypeResolveContext decompilationContext) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\CSharp\CSharpDecompiler.cs:line 1604
at ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DoDecompile(IMethod method, DecompileRun decompileRun, ITypeResolveContext decompilationContext) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\CSharp\CSharpDecompiler.cs:line 1478
at ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DoDecompile(ITypeDefinition typeDef, DecompileRun decompileRun, ITypeResolveContext decompilationContext) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\CSharp\CSharpDecompiler.cs:line 1318
at ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DoDecompileTypes(IEnumerable`1 types, DecompileRun decompileRun, ITypeResolveContext decompilationContext, SyntaxTree syntaxTree) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\CSharp\CSharpDecompiler.cs:line 574
at ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DecompileWholeModuleAsSingleFile(Boolean sortTypes) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\CSharp\CSharpDecompiler.cs:line 610
at ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DecompileWholeModuleAsSingleFile() in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\CSharp\CSharpDecompiler.cs:line 584
at ICSharpCode.Decompiler.CSharp.CSharpDecompiler.DecompileWholeModuleAsString() in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler\CSharp\CSharpDecompiler.cs:line 866
at ICSharpCode.Decompiler.Console.ILSpyCmdProgram.Decompile(String assemblyFileName, TextWriter output, String typeName) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler.Console\IlspyCmdProgram.cs:line 234
at ICSharpCode.Decompiler.Console.ILSpyCmdProgram.OnExecute(CommandLineApplication app) in D:\GitWorkspace\ILSpy_72\ICSharpCode.Decompiler.Console\IlspyCmdProgram.cs:line 153
From the below two commands, only the latter one is failing, so not all types are impacted:
ilspycmd out/System.Runtime.dll -t System.Security.Cryptography.CryptographicException
ilspycmd out/System.Runtime.dll -t Microsoft.CodeAnalysis.EmbeddedAttribute
Removing the -m
works without any problem:
refasmer /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.2/ref/net7.0/System.Runtime.dll -v -c -O=out -p -n
ilspycmd -o X out/System.Runtime.dll
I'm running the above on MacOS. The input DLL is the dotnet 7 reference assembly System.Runtime.dll
.
The problem is that we use System.NotImplementedException
in mocked method bodies, and this is a rare case when System.NotImplementedException
itself is defined in the assembly we are creating a mocked version of. And even more: it is defined in the assembly later than the attribute in question; that's why the import logic fails.
Not sure what to do about that, honestly. I've tried to get that exception imported first, before everything else, and it failed in JetBrains.Refasmer.MetadataImporter::Import
with
if (dstHandle != _typeDefinitionCache[srcHandle])
throw new Exception("WTF: type handle mismatch");
As I remember, all methods in mockup mode are replaced with throw new NotImplementedException()
. And we cannot import NotImplementedException itself, because it has constructor which is method and therefore should be replaced with throw new NotImplementedException()
. I also don't know how to fix this without changing Refasmer logic.
I think that acceptable solution will be to check if NotImplementedException is defined in assembly and return error when trying to mockup such assembly. Or (a little harder to implement) maybe specify exception type (should be from assembly references) to throw.
Well, technically such an implementation should be valid:
class NotImplementedException {
public NotImplementedException() {
throw new NotImplementedException();
}
}
It is possible to write and compile in C#, so it has to be possible to express in metadata. But I'm not sure what exact sequence of actions would help us to achieve this.
What is the reason for using throw new NotImplementedException();
instead of throw null;
?
Never used and never seen throw null
is real life but maybe is can be a solution
throw null
is a well-known way to throw a NullReferenceException
without actually referencing a NullReferenceException
😅
Though now I can't remember in what context I saw it.