From 7e3b36aaa7421bf83eb66446676e3406edbd13d9 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Thu, 25 Jul 2019 16:42:40 +0200 Subject: [PATCH] #1563: Where possible, replace an explicit interface implementation call with a call to the interface member. --- .../ICSharpCode.Decompiler.Tests.csproj | 2 ++ .../ILPrettyTestRunner.cs | 6 ++++ .../DirectCallToExplicitInterfaceImpl.cs | 14 ++++++++ .../DirectCallToExplicitInterfaceImpl.il | 36 +++++++++++++++++++ ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 10 ++++++ 5 files changed, 68 insertions(+) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/DirectCallToExplicitInterfaceImpl.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/DirectCallToExplicitInterfaceImpl.il diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 4ce11c1db..7ee64ffc4 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -66,6 +66,7 @@ + @@ -74,6 +75,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index bfdbf2156..d7c4aea81 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -100,6 +100,12 @@ namespace ICSharpCode.Decompiler.Tests Run(settings: new DecompilerSettings { RemoveDeadCode = true }); } + [Test] + public void DirectCallToExplicitInterfaceImpl() + { + Run(); + } + [Test] public void CS1xSwitch_Debug() { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/DirectCallToExplicitInterfaceImpl.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/DirectCallToExplicitInterfaceImpl.cs new file mode 100644 index 000000000..7e8fc4ee3 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/DirectCallToExplicitInterfaceImpl.cs @@ -0,0 +1,14 @@ +using System; + +public sealed class TestClass : IDisposable +{ + void IDisposable.Dispose() + { + } + + public void Test(TestClass other) + { + ((IDisposable)this).Dispose(); + ((IDisposable)other).Dispose(); + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/DirectCallToExplicitInterfaceImpl.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/DirectCallToExplicitInterfaceImpl.il new file mode 100644 index 000000000..2e5183e67 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/DirectCallToExplicitInterfaceImpl.il @@ -0,0 +1,36 @@ +.assembly extern mscorlib +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. + .ver 4:0:0:0 +} +.assembly DirectCallToExplicitInterfaceImpl +{ + .ver 1:0:0:0 +} +.module DirectCallToExplicitInterfaceImpl.exe + +.class public auto ansi sealed TestClass + extends [mscorlib]System.Object + implements [mscorlib]System.IDisposable +{ + // Methods + + .method private final hidebysig newslot virtual + instance void System.IDisposable.Dispose () cil managed + { + .override method instance void [mscorlib]System.IDisposable::Dispose() + ret + } + + .method public hidebysig void Test (class TestClass other) cil managed + { + ldarg.0 + call instance void TestClass::System.IDisposable.Dispose() + + ldarg.1 + call instance void TestClass::System.IDisposable.Dispose() + + ret + } + +} // end of class DirectCallToExplicitInterfaceImpl diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index c40282243..c76b37932 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -172,6 +172,16 @@ namespace ICSharpCode.Decompiler.CSharp IReadOnlyList argumentToParameterMap = null, IType constrainedTo = null) { + if (method.IsExplicitInterfaceImplementation && callOpCode == OpCode.Call) { + // Direct non-virtual call to explicit interface implementation. + // This can't really be represented in C#, but at least in the case where + // the class is sealed, we can equivalently call the interface member instead: + var interfaceMembers = method.ExplicitlyImplementedInterfaceMembers.ToList(); + if (method.DeclaringTypeDefinition?.Kind == TypeKind.Class && method.DeclaringTypeDefinition.IsSealed && interfaceMembers.Count == 1) { + method = (IMethod)interfaceMembers.Single(); + callOpCode = OpCode.CallVirt; + } + } // Used for Call, CallVirt and NewObj var expectedTargetDetails = new ExpectedTargetDetails { CallOpCode = callOpCode