mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Merge pull request #4222 from icsharpcode/fix/2199-vb-closure
Fix/2199 vb closure
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
public class Issue2199
|
||||
{
|
||||
private class Item
|
||||
{
|
||||
public string Text;
|
||||
}
|
||||
|
||||
public event EventHandler Click;
|
||||
|
||||
public event EventHandler DoubleClick;
|
||||
|
||||
public void Register(string value)
|
||||
{
|
||||
Item item = new Item();
|
||||
item.Text = value.Trim();
|
||||
#if EXPECTED_OUTPUT
|
||||
Click += [SpecialName] () => {
|
||||
Console.WriteLine(item.Text);
|
||||
};
|
||||
DoubleClick += [SpecialName] () => {
|
||||
Console.WriteLine(item.Text);
|
||||
};
|
||||
#else
|
||||
Click += delegate {
|
||||
Console.WriteLine(item.Text);
|
||||
};
|
||||
DoubleClick += delegate {
|
||||
Console.WriteLine(item.Text);
|
||||
};
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
Imports System
|
||||
|
||||
Public Class Issue2199
|
||||
Public Event Click As EventHandler
|
||||
Public Event DoubleClick As EventHandler
|
||||
|
||||
Private Class Item
|
||||
Public Text As String
|
||||
End Class
|
||||
|
||||
Public Sub Register(value As String)
|
||||
Dim item As New Item()
|
||||
item.Text = value.Trim()
|
||||
AddHandler Click, Sub() Console.WriteLine(item.Text)
|
||||
AddHandler DoubleClick, Sub() Console.WriteLine(item.Text)
|
||||
End Sub
|
||||
End Class
|
||||
@@ -151,6 +151,12 @@ namespace ICSharpCode.Decompiler.Tests
|
||||
await Run(options: options | CompilerOptions.Library);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Issue2199([ValueSource(nameof(defaultOptions))] CompilerOptions options)
|
||||
{
|
||||
await Run(options: options | CompilerOptions.Library);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task VBOnError([ValueSource(nameof(defaultOptions))] CompilerOptions options)
|
||||
{
|
||||
@@ -222,7 +228,7 @@ namespace ICSharpCode.Decompiler.Tests
|
||||
settings ??= new DecompilerSettings { FileScopedNamespaces = false, SortCustomAttributes = true };
|
||||
var decompiled = await Tester.DecompileCSharp(executable.PathToAssembly, settings).ConfigureAwait(false);
|
||||
|
||||
CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(options).ToArray());
|
||||
CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(options).Append("EXPECTED_OUTPUT").ToArray());
|
||||
Tester.RepeatOnIOError(() => File.Delete(decompiled));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,6 +479,8 @@ namespace ICSharpCode.Decompiler.CSharp
|
||||
return true;
|
||||
if (settings.AnonymousTypes && type.IsAnonymousType(metadata))
|
||||
return true;
|
||||
if (settings.AnonymousMethods && type.IsDelegate(metadata) && name.StartsWith("VB$AnonymousDelegate_", StringComparison.Ordinal))
|
||||
return true;
|
||||
if (settings.Dynamic && type.IsDelegate(metadata) && (name.StartsWith("<>A", StringComparison.Ordinal) || name.StartsWith("<>F", StringComparison.Ordinal)))
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
@@ -195,6 +196,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
var ilReader = context.CreateILReader();
|
||||
var body = context.PEFile.GetMethodBody(methodDefinition.RelativeVirtualAddress);
|
||||
var function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.Delegate, context.CancellationToken);
|
||||
if (TryUnwrapVisualBasicDelegateWrapper(function, targetMethod, out var unwrappedMethod))
|
||||
{
|
||||
targetMethod = unwrappedMethod;
|
||||
methodDefinition = context.PEFile.Metadata.GetMethodDefinition((MethodDefinitionHandle)targetMethod.MetadataToken);
|
||||
if (!methodDefinition.HasBody())
|
||||
return null;
|
||||
genericContext = GenericContextFromTypeArguments(targetMethod.Substitution);
|
||||
if (genericContext == null)
|
||||
return null;
|
||||
body = context.PEFile.GetMethodBody(methodDefinition.RelativeVirtualAddress);
|
||||
function = ilReader.ReadIL((MethodDefinitionHandle)targetMethod.MetadataToken, body, genericContext.Value, ILFunctionKind.Delegate, context.CancellationToken);
|
||||
}
|
||||
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.
|
||||
@@ -215,6 +228,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
nestedContext.StepStartGroup("DelegateConstruction (nested lambdas)", function);
|
||||
((IILTransform)this).Run(function, nestedContext);
|
||||
nestedContext.StepEndGroup();
|
||||
if (TryUnwrapTransformedVisualBasicDelegateWrapper(function, delegateType, out var unwrappedFunction))
|
||||
{
|
||||
function.ReplaceWith(unwrappedFunction);
|
||||
function = unwrappedFunction;
|
||||
}
|
||||
function.AddILRange(target);
|
||||
function.AddILRange(value);
|
||||
if (value is Call call)
|
||||
@@ -222,6 +240,64 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
return function;
|
||||
}
|
||||
|
||||
static bool TryUnwrapTransformedVisualBasicDelegateWrapper(ILFunction function, IType delegateType, out ILFunction unwrappedFunction)
|
||||
{
|
||||
unwrappedFunction = null;
|
||||
if (function.Body is not BlockContainer container)
|
||||
return false;
|
||||
var block = container.EntryPoint;
|
||||
if (block.Instructions.Count != 2 || !block.Instructions[1].MatchLeave(container, out _))
|
||||
return false;
|
||||
if (block.Instructions[0] is not CallVirt callVirt)
|
||||
return false;
|
||||
if (callVirt.Method.Name != "Invoke" || callVirt.Method.DeclaringType.Kind != TypeKind.Delegate)
|
||||
return false;
|
||||
if (callVirt.Arguments.Count != 1 || callVirt.Arguments[0] is not ILFunction innerFunction)
|
||||
return false;
|
||||
if (innerFunction.DelegateType?.Name.StartsWith("VB$AnonymousDelegate_", StringComparison.Ordinal) != true)
|
||||
return false;
|
||||
innerFunction.DelegateType = delegateType;
|
||||
unwrappedFunction = innerFunction;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TryUnwrapVisualBasicDelegateWrapper(ILFunction function, IMethod wrapperMethod, out IMethod unwrappedMethod)
|
||||
{
|
||||
unwrappedMethod = null;
|
||||
if (!wrapperMethod.Name.Contains("$__R", StringComparison.Ordinal))
|
||||
return false;
|
||||
var thisVariable = function.Variables.SingleOrDefault(VariableKindExtensions.IsThis);
|
||||
if (thisVariable == null || function.Body is not BlockContainer container)
|
||||
return false;
|
||||
var block = container.EntryPoint;
|
||||
if (block.Instructions.Count != 2 || !block.Instructions[1].MatchLeave(container, out _))
|
||||
return false;
|
||||
if (block.Instructions[0] is Call call)
|
||||
{
|
||||
if (call.Arguments.Count != 1 || !call.Arguments[0].MatchLdLoc(thisVariable))
|
||||
return false;
|
||||
unwrappedMethod = call.Method;
|
||||
}
|
||||
else if (block.Instructions[0] is CallVirt callVirt)
|
||||
{
|
||||
if (callVirt.Method.Name != "Invoke" || callVirt.Method.DeclaringType.Kind != TypeKind.Delegate)
|
||||
return false;
|
||||
if (callVirt.Arguments.Count != 1 || !MatchDelegateConstruction(callVirt.Arguments[0], out unwrappedMethod, out var target, out _))
|
||||
return false;
|
||||
if (!target.MatchLdLoc(thisVariable))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!object.Equals(unwrappedMethod.DeclaringTypeDefinition, wrapperMethod.DeclaringTypeDefinition))
|
||||
return false;
|
||||
if (!IsAnonymousMethod(wrapperMethod.DeclaringTypeDefinition, unwrappedMethod))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ValidateDelegateTarget(ILInstruction inst)
|
||||
{
|
||||
switch (inst)
|
||||
@@ -238,6 +314,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
// TODO : ldfld chains must be validated more thoroughly, i.e., we should make sure
|
||||
// that the value of the field is never changed.
|
||||
ILInstruction target = ldobj;
|
||||
// Match delegate targets stored in nested display classes, e.g.
|
||||
// ldobj(ldflda field(ldobj(ldflda field(ldloc displayClass)))).
|
||||
while (target is LdObj || target is LdFlda)
|
||||
{
|
||||
if (target is LdObj o)
|
||||
@@ -297,6 +375,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
break;
|
||||
case LdObj lo:
|
||||
ILInstruction inner = lo.Target;
|
||||
// Match the same display-class field chain after ValidateDelegateTarget
|
||||
// accepted it: ldobj(ldflda field(ldflda field(ldloc displayClass))).
|
||||
while (inner is LdFlda ldf)
|
||||
{
|
||||
inner = ldf.Target;
|
||||
|
||||
@@ -466,7 +466,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
{
|
||||
try
|
||||
{
|
||||
if (method.Parameters.Count != 0)
|
||||
if (method.Parameters.Count > 1)
|
||||
return false;
|
||||
var handle = (MethodDefinitionHandle)method.MetadataToken;
|
||||
var module = (MetadataModule)method.ParentModule;
|
||||
@@ -489,20 +489,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
// IL_0000: ldarg.0
|
||||
// IL_0001: call instance void [mscorlib]System.Object::.ctor()
|
||||
// IL_0006: ret
|
||||
var opCode = DecodeOpCodeSkipNop(ref reader);
|
||||
switch (opCode)
|
||||
{
|
||||
case ILOpCode.Ldarg:
|
||||
case ILOpCode.Ldarg_s:
|
||||
if (reader.DecodeIndex(opCode) != 0)
|
||||
return false;
|
||||
break;
|
||||
case ILOpCode.Ldarg_0:
|
||||
// OK
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (!DecodeLdArg(ref reader, 0))
|
||||
return false;
|
||||
if (DecodeOpCodeSkipNop(ref reader) != ILOpCode.Call)
|
||||
return false;
|
||||
var baseCtorHandle = MetadataTokenHelpers.EntityHandleOrNil(reader.ReadInt32());
|
||||
@@ -513,7 +501,43 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
return false;
|
||||
if (!objectCtor.IsConstructor || objectCtor.Parameters.Count != 0)
|
||||
return false;
|
||||
return DecodeOpCodeSkipNop(ref reader) == ILOpCode.Ret;
|
||||
if (method.Parameters.Count == 0)
|
||||
return DecodeOpCodeSkipNop(ref reader) == ILOpCode.Ret;
|
||||
if (!object.Equals(method.Parameters[0].Type.GetDefinition(), method.DeclaringTypeDefinition))
|
||||
return false;
|
||||
if (!DecodeLdArg(ref reader, 1))
|
||||
return false;
|
||||
switch (DecodeOpCodeSkipNop(ref reader))
|
||||
{
|
||||
case ILOpCode.Brfalse:
|
||||
reader.ReadInt32();
|
||||
break;
|
||||
case ILOpCode.Brfalse_s:
|
||||
reader.ReadSByte();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
bool copiedField = false;
|
||||
while (true)
|
||||
{
|
||||
var opCode = DecodeOpCodeSkipNop(ref reader);
|
||||
if (opCode == ILOpCode.Ret)
|
||||
return copiedField;
|
||||
if (!DecodeLdArg(opCode, ref reader, 0))
|
||||
return false;
|
||||
if (!DecodeLdArg(ref reader, 1))
|
||||
return false;
|
||||
if (DecodeOpCodeSkipNop(ref reader) != ILOpCode.Ldfld)
|
||||
return false;
|
||||
var sourceField = MetadataTokenHelpers.EntityHandleOrNil(reader.ReadInt32());
|
||||
if (DecodeOpCodeSkipNop(ref reader) != ILOpCode.Stfld)
|
||||
return false;
|
||||
var targetField = MetadataTokenHelpers.EntityHandleOrNil(reader.ReadInt32());
|
||||
if (sourceField.IsNil || sourceField != targetField)
|
||||
return false;
|
||||
copiedField = true;
|
||||
}
|
||||
}
|
||||
catch (BadImageFormatException)
|
||||
{
|
||||
@@ -521,6 +545,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
|
||||
}
|
||||
}
|
||||
|
||||
static bool DecodeLdArg(ref BlobReader reader, int expectedIndex)
|
||||
{
|
||||
return DecodeLdArg(DecodeOpCodeSkipNop(ref reader), ref reader, expectedIndex);
|
||||
}
|
||||
|
||||
static bool DecodeLdArg(ILOpCode opCode, ref BlobReader reader, int expectedIndex)
|
||||
{
|
||||
switch (opCode)
|
||||
{
|
||||
case ILOpCode.Ldarg:
|
||||
case ILOpCode.Ldarg_s:
|
||||
return reader.DecodeIndex(opCode) == expectedIndex;
|
||||
case ILOpCode.Ldarg_0:
|
||||
return expectedIndex == 0;
|
||||
case ILOpCode.Ldarg_1:
|
||||
return expectedIndex == 1;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static ILOpCode DecodeOpCodeSkipNop(ref BlobReader reader)
|
||||
{
|
||||
ILOpCode code;
|
||||
|
||||
Reference in New Issue
Block a user