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

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

@ -0,0 +1,16 @@ @@ -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 @@ @@ -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 @@ -96,6 +96,12 @@ namespace ICSharpCode.Decompiler.Tests
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)
{
var vbFile = Path.Combine(TestCasePath, testName + ".vb");

2
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

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

2
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -190,7 +190,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -190,7 +190,7 @@ namespace ICSharpCode.Decompiler.CSharp
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);
}

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

@ -74,7 +74,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -74,7 +74,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!storeInst.MatchStsFld(out IField field2, out ILInstruction value) || !field.Equals(field2) || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
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;
var nextInstruction = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex + 1);
if (nextInstruction == null)
@ -104,7 +104,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -104,7 +104,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var storeInst = trueInst.Instructions.Last();
if (!storeInst.MatchStLoc(v, out ILInstruction value))
return false;
if (!DelegateConstruction.IsDelegateConstruction(value as NewObj, true))
if (!DelegateConstruction.MatchDelegateConstruction(value as NewObj, out _, out _, out _, true))
return false;
// 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)
@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!stobj.Target.MatchLdsFlda(out var field1) || !ldobj.Target.MatchLdsFlda(out var field2) || !field1.Equals(field2))
return false;
if (!DelegateConstruction.IsDelegateConstruction((NewObj)stobj.Value, true))
if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true))
return false;
context.Step("CachedDelegateInitializationRoslynInStaticWithLocal", inst);
storeBeforeIf.Value = stobj.Value;
@ -183,7 +183,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -183,7 +183,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!stobj.Target.MatchLdFlda(out var _, out var field1) || !ldobj.Target.MatchLdFlda(out var __, out var field2) || !field1.Equals(field2))
return false;
if (!DelegateConstruction.IsDelegateConstruction((NewObj)stobj.Value, true))
if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true))
return false;
context.Step("CachedDelegateInitializationRoslynWithLocal", inst);
storeBeforeIf.Value = stobj.Value;

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

@ -49,26 +49,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -49,26 +49,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foreach (var inst in function.Descendants)
{
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);
ILFunction f = TransformDelegateConstruction(call, out ILInstruction target);
if (f != null && target is IInstructionWithVariableOperand instWithVar)
if (instWithVar.Variable.Kind == VariableKind.Local)
{
if (instWithVar.Variable.Kind == VariableKind.Local)
{
instWithVar.Variable.Kind = VariableKind.DisplayClassLocal;
}
if (instWithVar.Variable.IsSingleDefinition && instWithVar.Variable.StoreInstructions.SingleOrDefault() is StLoc store)
instWithVar.Variable.Kind = VariableKind.DisplayClassLocal;
}
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
@ -79,15 +79,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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)
return false;
var opCode = inst.Arguments[1].OpCode;
if (!(opCode == OpCode.LdFtn || opCode == OpCode.LdVirtFtn || (allowTransformed && opCode == OpCode.ILFunction)))
return false;
var typeKind = inst.Method.DeclaringType.Kind;
return typeKind == TypeKind.Delegate || typeKind == TypeKind.Unknown;
targetMethod = null;
target = null;
delegateType = null;
switch (inst)
{
case NewObj call:
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)
@ -142,19 +161,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -142,19 +161,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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))
return null;
if (targetMethod.MetadataToken.IsNil)
return null;
if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod, context))
return null;
target = value.Arguments[0];
if (!ValidateDelegateTarget(target))
return null;
var handle = (MethodDefinitionHandle)targetMethod.MetadataToken;
@ -172,7 +188,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -172,7 +188,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var ilReader = context.CreateILReader();
var body = context.PEFile.Reader.GetMethodBody(methodDefinition.RelativeVirtualAddress);
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
// how the lambda body is being transformed.
value.ReplaceWith(function);
@ -194,7 +210,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -194,7 +210,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
nestedContext.StepEndGroup();
function.AddILRange(target);
function.AddILRange(value);
function.AddILRange(value.Arguments[1]);
if (value is Call call)
function.AddILRange(call.Arguments[1]);
return function;
}

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

@ -418,7 +418,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -418,7 +418,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
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);
else
HandleUseSite(ldftn.Method, ldftn);

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

@ -70,7 +70,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -70,7 +70,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
// Do not try to transform delegate construction.
// 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;
// Cannot build a collection/object initializer attached to an AnonymousTypeCreateExpression:s
// 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 @@ -693,8 +693,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case TypeKind.Struct:
break;
case TypeKind.Class:
if (!potentialDisplayClass.IsSealed)
return false;
if (!allowTypeImplementingInterfaces)
{
if (!potentialDisplayClass.DirectBaseTypes.All(t => t.IsKnownType(KnownTypeCode.Object)))

4
ICSharpCode.Decompiler/SRMExtensions.cs

@ -312,7 +312,9 @@ namespace ICSharpCode.Decompiler @@ -312,7 +312,9 @@ namespace ICSharpCode.Decompiler
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)

Loading…
Cancel
Save