mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Handle legacy VB delegate wrappers
VB can lower event-handler closures through generated wrapper methods, closure copy constructors, and immediately invoked VB anonymous delegate helpers. Recognizing and unwrapping those shapes leaves the expected lambda form and lets captured locals be scalar-replaced instead of exposing generated closure artifacts. Assisted-by: OpenCode:openai/gpt-5.5:OpenCode
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)
|
||||
{
|
||||
@@ -216,7 +222,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,6 +478,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