Browse Source

Fix #2192: Add support for VB.NET delegate construction

pull/2201/head
Siegfried Pammer 5 years ago
parent
commit
3a7c69e5b9
  1. 2
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 16
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Issue2192.cs
  3. 9
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Issue2192.vb
  4. 6
      ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs
  5. 2
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  6. 2
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  7. 8
      ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs
  8. 77
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs
  9. 2
      ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs
  10. 2
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
  11. 2
      ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
  12. 4
      ICSharpCode.Decompiler/SRMExtensions.cs

2
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -105,6 +105,7 @@
<Compile Include="TestAssemblyResolver.cs" /> <Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\Correctness\DeconstructionTests.cs" /> <Compile Include="TestCases\Correctness\DeconstructionTests.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" /> <Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="TestCases\VBPretty\Issue2192.cs" />
<Compile Include="Util\FileUtilityTests.cs" /> <Compile Include="Util\FileUtilityTests.cs" />
<None Include="TestCases\Pretty\FunctionPointers.cs" /> <None Include="TestCases\Pretty\FunctionPointers.cs" />
<None Include="TestCases\Pretty\CS9_ExtensionGetEnumerator.cs" /> <None Include="TestCases\Pretty\CS9_ExtensionGetEnumerator.cs" />
@ -135,6 +136,7 @@
<None Include="TestCases\Ugly\NoExtensionMethods.Expected.cs" /> <None Include="TestCases\Ugly\NoExtensionMethods.Expected.cs" />
<Compile Include="TestCases\Ugly\NoExtensionMethods.cs" /> <Compile Include="TestCases\Ugly\NoExtensionMethods.cs" />
<None Include="TestCases\VBPretty\Issue1906.vb" /> <None Include="TestCases\VBPretty\Issue1906.vb" />
<None Include="TestCases\VBPretty\Issue2192.vb" />
<None Include="TestCases\VBPretty\Select.vb" /> <None Include="TestCases\VBPretty\Select.vb" />
<None Include="TestCases\VBPretty\VBCompoundAssign.cs" /> <None Include="TestCases\VBPretty\VBCompoundAssign.cs" />
<Compile Include="TestCases\Pretty\ThrowExpressions.cs" /> <Compile Include="TestCases\Pretty\ThrowExpressions.cs" />

16
ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Issue2192.cs

@ -0,0 +1,16 @@
using System;
using System.Linq;
public class Issue2192
{
public static void M()
{
string[] source = new string[3] {
"abc",
"defgh",
"ijklm"
};
string text = "test";
Console.WriteLine(source.Count((string w) => w.Length > text.Length));
}
}

9
ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Issue2192.vb

@ -0,0 +1,9 @@
Imports System
Imports System.Linq
Public Class Issue2192
Public Shared Sub M()
Dim words As String() = {"abc", "defgh", "ijklm"}
Dim word As String = "test"
Console.WriteLine(words.Count(Function(w) w.Length > word.Length))
End Sub
End Class

6
ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs

@ -96,6 +96,12 @@ namespace ICSharpCode.Decompiler.Tests
Run(options: options | CompilerOptions.Library); Run(options: options | CompilerOptions.Library);
} }
[Test]
public void Issue2192([ValueSource(nameof(defaultOptions))] CompilerOptions options)
{
Run(options: options | CompilerOptions.Library);
}
void Run([CallerMemberName] string testName = null, CompilerOptions options = CompilerOptions.UseDebug, DecompilerSettings settings = null) void Run([CallerMemberName] string testName = null, CompilerOptions options = CompilerOptions.UseDebug, DecompilerSettings settings = null)
{ {
var vbFile = Path.Combine(TestCasePath, testName + ".vb"); var vbFile = Path.Combine(TestCasePath, testName + ".vb");

2
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -379,7 +379,7 @@ namespace ICSharpCode.Decompiler.CSharp
var name = metadata.GetString(type.Name); var name = metadata.GetString(type.Name);
if (!type.Name.IsGeneratedName(metadata) || !type.IsCompilerGenerated(metadata)) if (!type.Name.IsGeneratedName(metadata) || !type.IsCompilerGenerated(metadata))
return false; return false;
if (name.Contains("DisplayClass") || name.Contains("AnonStorey")) if (name.Contains("DisplayClass") || name.Contains("AnonStorey") || name.Contains("Closure$"))
return true; return true;
return type.BaseType.IsKnownType(metadata, KnownTypeCode.Object) && !type.GetInterfaceImplementations().Any(); return type.BaseType.IsKnownType(metadata, KnownTypeCode.Object) && !type.GetInterfaceImplementations().Any();
} }

2
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -190,7 +190,7 @@ namespace ICSharpCode.Decompiler.CSharp
public TranslatedExpression Build(CallInstruction inst) public TranslatedExpression Build(CallInstruction inst)
{ {
if (inst is NewObj newobj && IL.Transforms.DelegateConstruction.IsDelegateConstruction(newobj)) if (inst is NewObj newobj && IL.Transforms.DelegateConstruction.MatchDelegateConstruction(newobj, out _, out _, out _))
{ {
return HandleDelegateConstruction(newobj); return HandleDelegateConstruction(newobj);
} }

8
ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs

@ -74,7 +74,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (!storeInst.MatchStsFld(out IField field2, out ILInstruction value) || !field.Equals(field2) || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) if (!storeInst.MatchStsFld(out IField field2, out ILInstruction value) || !field.Equals(field2) || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
return false; return false;
if (!DelegateConstruction.IsDelegateConstruction(value.UnwrapConv(ConversionKind.Invalid) as NewObj, true)) if (!DelegateConstruction.MatchDelegateConstruction(value.UnwrapConv(ConversionKind.Invalid) as NewObj, out _, out _, out _, true))
return false; return false;
var nextInstruction = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex + 1); var nextInstruction = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex + 1);
if (nextInstruction == null) if (nextInstruction == null)
@ -104,7 +104,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var storeInst = trueInst.Instructions.Last(); var storeInst = trueInst.Instructions.Last();
if (!storeInst.MatchStLoc(v, out ILInstruction value)) if (!storeInst.MatchStLoc(v, out ILInstruction value))
return false; return false;
if (!DelegateConstruction.IsDelegateConstruction(value as NewObj, true)) if (!DelegateConstruction.MatchDelegateConstruction(value as NewObj, out _, out _, out _, true))
return false; return false;
// do not transform if there are other stores/loads of this variable // do not transform if there are other stores/loads of this variable
if (v.StoreCount != 2 || v.StoreInstructions.Count != 2 || v.LoadCount != 2 || v.AddressCount != 0) if (v.StoreCount != 2 || v.StoreInstructions.Count != 2 || v.LoadCount != 2 || v.AddressCount != 0)
@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (!stobj.Target.MatchLdsFlda(out var field1) || !ldobj.Target.MatchLdsFlda(out var field2) || !field1.Equals(field2)) if (!stobj.Target.MatchLdsFlda(out var field1) || !ldobj.Target.MatchLdsFlda(out var field2) || !field1.Equals(field2))
return false; return false;
if (!DelegateConstruction.IsDelegateConstruction((NewObj)stobj.Value, true)) if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true))
return false; return false;
context.Step("CachedDelegateInitializationRoslynInStaticWithLocal", inst); context.Step("CachedDelegateInitializationRoslynInStaticWithLocal", inst);
storeBeforeIf.Value = stobj.Value; storeBeforeIf.Value = stobj.Value;
@ -183,7 +183,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (!stobj.Target.MatchLdFlda(out var _, out var field1) || !ldobj.Target.MatchLdFlda(out var __, out var field2) || !field1.Equals(field2)) if (!stobj.Target.MatchLdFlda(out var _, out var field1) || !ldobj.Target.MatchLdFlda(out var __, out var field2) || !field1.Equals(field2))
return false; return false;
if (!DelegateConstruction.IsDelegateConstruction((NewObj)stobj.Value, true)) if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true))
return false; return false;
context.Step("CachedDelegateInitializationRoslynWithLocal", inst); context.Step("CachedDelegateInitializationRoslynWithLocal", inst);
storeBeforeIf.Value = stobj.Value; storeBeforeIf.Value = stobj.Value;

77
ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

@ -49,26 +49,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foreach (var inst in function.Descendants) foreach (var inst in function.Descendants)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
if (inst is NewObj call) if (!MatchDelegateConstruction(inst, out var targetMethod, out var target,
out var delegateType, allowTransformed: false))
continue;
context.StepStartGroup($"TransformDelegateConstruction {inst.StartILOffset}", inst);
ILFunction f = TransformDelegateConstruction(inst, targetMethod, target, delegateType);
if (f != null && target is IInstructionWithVariableOperand instWithVar)
{ {
context.StepStartGroup($"TransformDelegateConstruction {call.StartILOffset}", call); if (instWithVar.Variable.Kind == VariableKind.Local)
ILFunction f = TransformDelegateConstruction(call, out ILInstruction target);
if (f != null && target is IInstructionWithVariableOperand instWithVar)
{ {
if (instWithVar.Variable.Kind == VariableKind.Local) instWithVar.Variable.Kind = VariableKind.DisplayClassLocal;
{ }
instWithVar.Variable.Kind = VariableKind.DisplayClassLocal; if (instWithVar.Variable.IsSingleDefinition && instWithVar.Variable.StoreInstructions.SingleOrDefault() is StLoc store)
} {
if (instWithVar.Variable.IsSingleDefinition && instWithVar.Variable.StoreInstructions.SingleOrDefault() is StLoc store) if (store.Value is NewObj)
{ {
if (store.Value is NewObj) instWithVar.Variable.CaptureScope = BlockContainer.FindClosestContainer(store);
{
instWithVar.Variable.CaptureScope = BlockContainer.FindClosestContainer(store);
}
} }
} }
context.StepEndGroup();
} }
context.StepEndGroup();
} }
} }
finally finally
@ -79,15 +79,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
internal static bool IsDelegateConstruction(NewObj inst, bool allowTransformed = false) internal static bool MatchDelegateConstruction(ILInstruction inst, out IMethod targetMethod,
out ILInstruction target, out IType delegateType, bool allowTransformed = false)
{ {
if (inst == null || inst.Arguments.Count != 2) targetMethod = null;
return false; target = null;
var opCode = inst.Arguments[1].OpCode; delegateType = null;
if (!(opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn || (allowTransformed && opCode == OpCode.ILFunction))) switch (inst)
return false; {
var typeKind = inst.Method.DeclaringType.Kind; case NewObj call:
return typeKind == TypeKind.Delegate || typeKind == TypeKind.Unknown; if (call.Arguments.Count != 2)
return false;
target = call.Arguments[0];
var opCode = call.Arguments[1].OpCode;
delegateType = call.Method.DeclaringType;
if (!(opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn
|| (allowTransformed && opCode == OpCode.ILFunction)))
return false;
targetMethod = ((IInstructionWithMethodOperand)call.Arguments[1]).Method;
break;
case LdVirtDelegate ldVirtDelegate:
target = ldVirtDelegate.Argument;
targetMethod = ldVirtDelegate.Method;
delegateType = ldVirtDelegate.Type;
break;
default:
return false;
}
return delegateType.Kind == TypeKind.Delegate || delegateType.Kind == TypeKind.Unknown;
} }
static bool IsAnonymousMethod(ITypeDefinition decompiledTypeDefinition, IMethod method) static bool IsAnonymousMethod(ITypeDefinition decompiledTypeDefinition, IMethod method)
@ -142,19 +161,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return new GenericContext(classTypeParameters, methodTypeParameters); return new GenericContext(classTypeParameters, methodTypeParameters);
} }
ILFunction TransformDelegateConstruction(NewObj value, out ILInstruction target) ILFunction TransformDelegateConstruction(
ILInstruction value, IMethod targetMethod,
ILInstruction target, IType delegateType)
{ {
target = null;
if (!IsDelegateConstruction(value))
return null;
var targetMethod = ((IInstructionWithMethodOperand)value.Arguments[1]).Method;
if (!IsAnonymousMethod(decompilationContext.CurrentTypeDefinition, targetMethod)) if (!IsAnonymousMethod(decompilationContext.CurrentTypeDefinition, targetMethod))
return null; return null;
if (targetMethod.MetadataToken.IsNil) if (targetMethod.MetadataToken.IsNil)
return null; return null;
if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod, context)) if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod, context))
return null; return null;
target = value.Arguments[0];
if (!ValidateDelegateTarget(target)) if (!ValidateDelegateTarget(target))
return null; return null;
var handle = (MethodDefinitionHandle)targetMethod.MetadataToken; var handle = (MethodDefinitionHandle)targetMethod.MetadataToken;
@ -172,7 +188,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var ilReader = context.CreateILReader(); var ilReader = context.CreateILReader();
var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress); var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress);
var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.Delegate, context.CancellationToken); var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.Delegate, context.CancellationToken);
function.DelegateType = value.Method.DeclaringType; function.DelegateType = delegateType;
// Embed the lambda into the parent function's ILAst, so that "Show steps" can show // Embed the lambda into the parent function's ILAst, so that "Show steps" can show
// how the lambda body is being transformed. // how the lambda body is being transformed.
value.ReplaceWith(function); value.ReplaceWith(function);
@ -194,7 +210,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
nestedContext.StepEndGroup(); nestedContext.StepEndGroup();
function.AddILRange(target); function.AddILRange(target);
function.AddILRange(value); function.AddILRange(value);
function.AddILRange(value.Arguments[1]); if (value is Call call)
function.AddILRange(call.Arguments[1]);
return function; return function;
} }

2
ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs

@ -418,7 +418,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
else if (inst is LdFtn ldftn && !ldftn.Method.IsLocalFunction && IsLocalFunctionMethod(ldftn.Method, context)) else if (inst is LdFtn ldftn && !ldftn.Method.IsLocalFunction && IsLocalFunctionMethod(ldftn.Method, context))
{ {
if (ldftn.Parent is NewObj newObj && DelegateConstruction.IsDelegateConstruction(newObj)) if (ldftn.Parent is NewObj newObj && DelegateConstruction.MatchDelegateConstruction(newObj, out _, out _, out _))
HandleUseSite(ldftn.Method, newObj); HandleUseSite(ldftn.Method, newObj);
else else
HandleUseSite(ldftn.Method, ldftn); HandleUseSite(ldftn.Method, ldftn);

2
ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

@ -70,7 +70,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
// Do not try to transform delegate construction. // Do not try to transform delegate construction.
// DelegateConstruction transform cannot deal with this. // DelegateConstruction transform cannot deal with this.
if (DelegateConstruction.IsDelegateConstruction(newObjInst) || TransformDisplayClassUsage.IsPotentialClosure(context, newObjInst)) if (DelegateConstruction.MatchDelegateConstruction(newObjInst, out _, out _, out _) || TransformDisplayClassUsage.IsPotentialClosure(context, newObjInst))
return false; return false;
// Cannot build a collection/object initializer attached to an AnonymousTypeCreateExpression:s // Cannot build a collection/object initializer attached to an AnonymousTypeCreateExpression:s
// anon = new { A = 5 } { 3,4,5 } is invalid syntax. // anon = new { A = 5 } { 3,4,5 } is invalid syntax.

2
ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

@ -693,8 +693,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case TypeKind.Struct: case TypeKind.Struct:
break; break;
case TypeKind.Class: case TypeKind.Class:
if (!potentialDisplayClass.IsSealed)
return false;
if (!allowTypeImplementingInterfaces) if (!allowTypeImplementingInterfaces)
{ {
if (!potentialDisplayClass.DirectBaseTypes.All(t => t.IsKnownType(KnownTypeCode.Object))) if (!potentialDisplayClass.DirectBaseTypes.All(t => t.IsKnownType(KnownTypeCode.Object)))

4
ICSharpCode.Decompiler/SRMExtensions.cs

@ -312,7 +312,9 @@ namespace ICSharpCode.Decompiler
public static bool IsGeneratedName(this StringHandle handle, MetadataReader metadata) public static bool IsGeneratedName(this StringHandle handle, MetadataReader metadata)
{ {
return !handle.IsNil && metadata.GetString(handle).StartsWith("<", StringComparison.Ordinal); return !handle.IsNil
&& (metadata.GetString(handle).StartsWith("<", StringComparison.Ordinal)
|| metadata.GetString(handle).Contains("$"));
} }
public static bool HasGeneratedName(this MethodDefinitionHandle handle, MetadataReader metadata) public static bool HasGeneratedName(this MethodDefinitionHandle handle, MetadataReader metadata)

Loading…
Cancel
Save