Browse Source

Preserve delegate during decomp

pull/4126/head
Marcus Mikelic 1 week ago committed by Siegfried Pammer
parent
commit
701df21df6
  1. 23
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  2. 48
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/AnonymousTypeMethodGroups.cs
  3. 12
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateCaching.cs
  4. 39
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateCachingWithExplicitConversions.cs
  5. 45
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  6. 28
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  7. 22
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  8. 18
      ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
  9. 110
      ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs
  10. 2
      global.json

23
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -338,12 +338,25 @@ namespace ICSharpCode.Decompiler.Tests @@ -338,12 +338,25 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions);
}
[Test]
public async Task DelegateCachingWithExplicitConversions([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.UseImplicitMethodGroupConversion = false);
}
[Test]
public async Task AnonymousTypes([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
}
[Test]
public async Task AnonymousTypeMethodGroups([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions)
{
// Older compilers emit uncached method groups even when decompiling to a language version that supports caching.
await RunForLibrary(cscOptions: cscOptions, settings: new DecompilerSettings { FileScopedNamespaces = false });
}
[Test]
public async Task StringConcatenation([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions)
{
@ -679,6 +692,8 @@ namespace ICSharpCode.Decompiler.Tests @@ -679,6 +692,8 @@ namespace ICSharpCode.Decompiler.Tests
public async Task FunctionPointers([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
// Disabling implicit delegate conversions must not introduce function-pointer casts.
await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.UseImplicitMethodGroupConversion = false);
}
[Test]
@ -1070,12 +1085,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -1070,12 +1085,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunForLibrary(cscOptions: cscOptions);
}
async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null, DecompilerSettings settings = null)
{
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler);
await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler, settings);
}
async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null)
async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action<DecompilerSettings> configureDecompiler = null, DecompilerSettings settings = null)
{
var csFile = Path.Combine(TestCasePath, testName + ".cs");
var exeFile = TestsAssemblyOutput.GetFilePath(TestCasePath, testName, Tester.GetSuffix(cscOptions) + ".exe");
@ -1097,7 +1112,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -1097,7 +1112,7 @@ namespace ICSharpCode.Decompiler.Tests
}
// 2. Decompile
var settings = Tester.GetSettings(cscOptions);
settings ??= Tester.GetSettings(cscOptions);
configureDecompiler?.Invoke(settings);
var decompiled = await Tester.DecompileCSharp(exeFile, settings).ConfigureAwait(false);

48
ICSharpCode.Decompiler.Tests/TestCases/Pretty/AnonymousTypeMethodGroups.cs

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
// Copyright (c) 2026 marcusmalloc
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Linq;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal static class AnonymousTypeMethodGroups
{
public static object AnonymousTypeArgument()
{
return new[] {
new {
X = 1
}
}.Select(Identity).Single();
}
public static object AnonymousArrayTypeArgument()
{
return new[] { new[] {
new {
X = 1
}
} }.Select(Identity).Single();
}
private static T Identity<T>(T value)
{
return value;
}
}
}

12
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateCaching.cs

@ -22,6 +22,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -22,6 +22,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal static class DelegateCaching
{
public delegate void CustomDelegate();
// Issue #3921: explicit construction must preserve a fresh delegate on each call.
public static Action FreshDelegate()
{
@ -33,6 +35,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -33,6 +35,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return M;
}
public static object FreshDelegateAsObject()
{
return new CustomDelegate(M);
}
public static object CachedDelegateAsObject()
{
return (CustomDelegate)M;
}
private static void M()
{
}

39
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateCachingWithExplicitConversions.cs

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
// Copyright (c) 2026 marcusmalloc
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal static class DelegateCachingWithExplicitConversions
{
public static Action FreshDelegate()
{
return new Action(M);
}
public static Action CachedDelegate()
{
return (Action)M;
}
private static void M()
{
}
}
}

45
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -2037,7 +2037,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2037,7 +2037,12 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
private bool CanUseDelegateConstruction(IMethod targetMethod, ILInstruction thisArg, IMethod invokeMethod)
static bool IsBoundExtensionMethod(IMethod method, IMethod? invokeMethod)
{
return method.IsExtensionMethod && method.Parameters.Count - 1 == invokeMethod?.Parameters.Count;
}
private static bool CanUseDelegateConstruction(IMethod targetMethod, ILInstruction thisArg, IMethod? invokeMethod)
{
// Accessors cannot be directly referenced as method group in C#
// see https://github.com/icsharpcode/ILSpy/issues/1741#issuecomment-540179101
@ -2045,36 +2050,17 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2045,36 +2050,17 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (targetMethod.IsStatic)
{
// If the invoke method is known, we can compare the parameter counts to figure out whether the
// delegate is static or binds the first argument
if (invokeMethod != null)
{
if (invokeMethod.Parameters.Count == targetMethod.Parameters.Count)
{
return thisArg.MatchLdNull();
}
else if (targetMethod.IsExtensionMethod && invokeMethod.Parameters.Count == targetMethod.Parameters.Count - 1)
{
return true;
}
else
{
return false;
}
}
else
if (invokeMethod == null)
{
// delegate type unknown:
// Delegate type unknown.
return thisArg.MatchLdNull() || targetMethod.IsExtensionMethod;
}
// An unbound static delegate supplies every method parameter through Invoke.
if (invokeMethod.Parameters.Count == targetMethod.Parameters.Count)
return thisArg.MatchLdNull();
return IsBoundExtensionMethod(targetMethod, invokeMethod);
}
else
{
// targetMethod is instance method
if (invokeMethod != null && invokeMethod.Parameters.Count != targetMethod.Parameters.Count)
return false;
return true;
}
return invokeMethod == null || invokeMethod.Parameters.Count == targetMethod.Parameters.Count;
}
internal TranslatedExpression Build(LdVirtDelegate inst)
@ -2124,7 +2110,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2124,7 +2110,7 @@ namespace ICSharpCode.Decompiler.CSharp
Debug.Assert(localFunction != null);
return (default, addTypeArguments: true, localFunction.Name!, ToMethodGroup(method, localFunction));
}
if (method.IsExtensionMethod && method.Parameters.Count - 1 == invokeMethod?.Parameters.Count)
if (IsBoundExtensionMethod(method, invokeMethod))
{
IType targetType = method.Parameters[0].Type;
if (targetType.Kind == TypeKind.ByReference && thisArg is Box thisArgBox)
@ -2242,13 +2228,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2242,13 +2228,14 @@ namespace ICSharpCode.Decompiler.CSharp
TranslatedExpression HandleDelegateConstruction(IType delegateType, IMethod method, ExpectedTargetDetails expectedTargetDetails, ILInstruction thisArg, ILInstruction inst)
{
var invokeMethod = delegateType.GetDelegateInvokeMethod();
bool capturesFirstArgument = !method.IsStatic || IsBoundExtensionMethod(method, invokeMethod);
var targetExpression = BuildDelegateReference(method, invokeMethod, expectedTargetDetails, thisArg);
var oce = new ObjectCreateExpression(expressionBuilder.ConvertType(delegateType), targetExpression)
.WithILInstruction(inst)
.WithRR(new ConversionResolveResult(
delegateType,
targetExpression.ResolveResult,
Conversion.MethodGroupConversion(method, expectedTargetDetails.CallOpCode == OpCode.CallVirt, false)));
Conversion.MethodGroupConversion(method, expectedTargetDetails.CallOpCode == OpCode.CallVirt, capturesFirstArgument)));
return oce;
}

28
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -560,7 +560,33 @@ namespace ICSharpCode.Decompiler.CSharp @@ -560,7 +560,33 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitCachedDelegate(CachedDelegate inst, TranslationContext context)
{
return Translate(inst.Argument, context.TypeHint).WithILInstruction(inst);
var expression = Translate(inst.Argument, context.TypeHint);
if (expression.Expression is ObjectCreateExpression objectCreation && objectCreation.Arguments.Count == 1
&& expression.ResolveResult is ConversionResolveResult { Conversion.IsMethodGroupConversion: true })
{
// A method-group conversion allows caching; explicit construction would allocate.
// ConvertTo can remove the cast when the context supplies the delegate type.
var cast = new CastExpression(objectCreation.Type.Detach(), objectCreation.Arguments.Single().Detach())
.CopyAnnotationsFrom(objectCreation);
return new TranslatedExpression(cast, expression.ResolveResult).WithILInstruction(inst);
}
return expression.WithILInstruction(inst);
}
/// <summary>
/// Gets whether the C# compiler would cache a method-group conversion in the current context.
/// </summary>
internal bool MethodGroupConversionWouldBeCached(Conversion conversion)
{
if (settings.GetMinimumRequiredVersion() < LanguageVersion.CSharp11_0
|| currentFunction.Kind == ILFunctionKind.ExpressionTree
|| decompilationContext.CurrentMember is { SymbolKind: SymbolKind.Constructor, IsStatic: true })
{
return false;
}
// Local-function symbols report IsStatic even when the C# declaration cannot be static.
return !conversion.DelegateCapturesFirstArgument
&& conversion.Method is not LocalFunctionMethod { IsStaticLocalFunction: false };
}
protected internal override TranslatedExpression VisitLdVirtDelegate(LdVirtDelegate inst, TranslationContext context)

22
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -27,7 +27,6 @@ using ICSharpCode.Decompiler.CSharp.Transforms; @@ -27,7 +27,6 @@ using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
#nullable enable
@ -255,7 +254,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -255,7 +254,7 @@ namespace ICSharpCode.Decompiler.CSharp
case ConversionResolveResult conversion:
{
if (Expression is CastExpression cast && CastCanBeMadeImplicit(
Resolver.CSharpConversions.Get(expressionBuilder.compilation),
expressionBuilder,
conversion.Conversion,
conversion.Input.Type,
type, targetType
@ -271,13 +270,10 @@ namespace ICSharpCode.Decompiler.CSharp @@ -271,13 +270,10 @@ namespace ICSharpCode.Decompiler.CSharp
else if (Expression is ObjectCreateExpression oce && conversion.Conversion.IsMethodGroupConversion
&& oce.Arguments.Count == 1 && expressionBuilder.settings.UseImplicitMethodGroupConversion)
{
// C# 11 caches static method groups. Keep explicit construction when the IL creates a fresh delegate.
if (conversion.Conversion.Method.IsStatic
&& conversion.Conversion.Method is not LocalFunctionMethod { IsStaticLocalFunction: false }
&& conversion.Conversion.Method.Parameters.Count == type.GetDelegateInvokeMethod()?.Parameters.Count
&& expressionBuilder.settings.GetMinimumRequiredVersion() >= LanguageVersion.CSharp11_0
&& expressionBuilder.currentFunction.Kind != ILFunctionKind.ExpressionTree
&& !ILInstructions.Any(i => i is CachedDelegate))
// Preserve explicit construction if a method-group conversion would introduce caching.
// Delegate types containing anonymous types must be inferred instead.
if (expressionBuilder.MethodGroupConversionWouldBeCached(conversion.Conversion)
&& (!expressionBuilder.settings.AnonymousTypes || !type.ContainsAnonymousType()))
{
return this;
}
@ -360,7 +356,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -360,7 +356,7 @@ namespace ICSharpCode.Decompiler.CSharp
var conversions = Resolver.CSharpConversions.Get(compilation);
if (ResolveResult is ConversionResolveResult conv && Expression is CastExpression cast2
&& !conv.Conversion.IsUserDefined
&& CastCanBeMadeImplicit(conversions, conv.Conversion, conv.Input.Type, type, targetType))
&& CastCanBeMadeImplicit(expressionBuilder, conv.Conversion, conv.Input.Type, type, targetType))
{
var unwrapped = Unwrapped(this.UnwrapChild(cast2.Expression));
if (allowImplicitConversion)
@ -690,8 +686,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -690,8 +686,12 @@ namespace ICSharpCode.Decompiler.CSharp
/// would have the same semantics as the existing cast from 'inputType' to 'oldTargetType'.
/// The existing cast is classified in 'conversion'.
/// </summary>
bool CastCanBeMadeImplicit(Resolver.CSharpConversions conversions, Conversion conversion, IType inputType, IType oldTargetType, IType newTargetType)
bool CastCanBeMadeImplicit(ExpressionBuilder expressionBuilder, Conversion conversion, IType inputType, IType oldTargetType, IType newTargetType)
{
if (conversion.IsMethodGroupConversion && oldTargetType.Kind == TypeKind.Delegate
&& !expressionBuilder.settings.UseImplicitMethodGroupConversion)
return false;
var conversions = Resolver.CSharpConversions.Get(expressionBuilder.compilation);
if (!conversion.IsImplicit)
{
// If the cast was required for the old conversion, avoid making it implicit.

18
ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs

@ -751,34 +751,30 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -751,34 +751,30 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
protected internal override void VisitNullCoalescingInstruction(NullCoalescingInstruction inst)
{
HandleBinaryWithOptionalEvaluation(inst, inst.ValueInst, inst.FallbackInst);
HandleOptionalEvaluation(inst, inst.FallbackInst, precedingArgument: inst.ValueInst);
}
protected internal override void VisitCachedDelegate(CachedDelegate inst)
{
DebugStartPoint(inst);
State cachedState = state.Clone();
inst.Argument.AcceptVisitor(this);
state.JoinWith(cachedState);
DebugEndPoint(inst);
HandleOptionalEvaluation(inst, inst.Argument);
}
protected internal override void VisitDynamicLogicOperatorInstruction(DynamicLogicOperatorInstruction inst)
{
HandleBinaryWithOptionalEvaluation(inst, inst.Left, inst.Right);
HandleOptionalEvaluation(inst, inst.Right, precedingArgument: inst.Left);
}
protected internal override void VisitUserDefinedLogicOperator(UserDefinedLogicOperator inst)
{
HandleBinaryWithOptionalEvaluation(inst, inst.Left, inst.Right);
HandleOptionalEvaluation(inst, inst.Right, precedingArgument: inst.Left);
}
void HandleBinaryWithOptionalEvaluation(ILInstruction parent, ILInstruction left, ILInstruction right)
void HandleOptionalEvaluation(ILInstruction parent, ILInstruction optionalArgument, ILInstruction precedingArgument = null)
{
DebugStartPoint(parent);
left.AcceptVisitor(this);
precedingArgument?.AcceptVisitor(this);
State branchState = state.Clone();
right.AcceptVisitor(this);
optionalArgument.AcceptVisitor(this);
state.JoinWith(branchState);
DebugEndPoint(parent);
}

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

@ -33,46 +33,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -33,46 +33,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return;
for (int i = context.IndexOfFirstAlreadyTransformedInstruction - 1; i >= 0; i--)
{
if (block.Instructions[i] is IfInstruction inst)
if (block.Instructions[i] is IfInstruction inst && TryTransform(block, i, inst))
{
if (CachedDelegateInitializationWithField(inst))
{
block.Instructions.RemoveAt(i);
context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count;
continue;
}
if (CachedDelegateInitializationWithLocal(inst))
{
ILInlining.InlineOneIfPossible(block, i, InliningOptions.Aggressive, context);
context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count;
continue;
}
if (CachedDelegateInitializationRoslynInStaticWithLocal(inst) || CachedDelegateInitializationRoslynWithLocal(inst))
{
block.Instructions.RemoveAt(i);
context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count;
continue;
}
if (CachedDelegateInitializationVB(inst))
{
context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count;
continue;
}
if (CachedDelegateInitializationVBWithReturn(inst))
{
block.Instructions.RemoveAt(i);
context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count;
continue;
}
if (CachedDelegateInitializationVBWithClosure(inst))
{
context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count;
continue;
}
context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count;
}
}
}
bool TryTransform(Block block, int i, IfInstruction inst)
{
if (CachedDelegateInitializationWithField(inst))
{
block.Instructions.RemoveAt(i);
return true;
}
if (CachedDelegateInitializationWithLocal(inst))
{
ILInlining.InlineOneIfPossible(block, i, InliningOptions.Aggressive, context);
return true;
}
if (CachedDelegateInitializationRoslynWithLocal(inst))
{
block.Instructions.RemoveAt(i);
return true;
}
if (CachedDelegateInitializationVB(inst))
return true;
if (CachedDelegateInitializationVBWithReturn(inst))
{
block.Instructions.RemoveAt(i);
return true;
}
return CachedDelegateInitializationVBWithClosure(inst);
}
/// <summary>
/// if (comp(ldsfld CachedAnonMethodDelegate == ldnull)) {
/// stsfld CachedAnonMethodDelegate(DelegateConstruction)
@ -147,38 +141,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -147,38 +141,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
/// <summary>
/// stloc s(ldobj(ldsflda(CachedAnonMethodDelegate))
/// if (comp(ldloc s == null)) {
/// stloc s(stobj(ldsflda(CachedAnonMethodDelegate), DelegateConstruction))
/// }
/// =>
/// stloc s(cached.delegate(DelegateConstruction))
/// </summary>
bool CachedDelegateInitializationRoslynInStaticWithLocal(IfInstruction inst)
{
Block trueInst = inst.TrueInst as Block;
if (trueInst == null || (trueInst.Instructions.Count != 1) || !inst.FalseInst.MatchNop())
return false;
if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !left.MatchLdLoc(out ILVariable s) || !right.MatchLdNull())
return false;
var storeInst = trueInst.Instructions.Last() as StLoc;
var storeBeforeIf = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex - 1) as StLoc;
if (storeBeforeIf == null || storeInst == null || storeBeforeIf.Variable != s || storeInst.Variable != s)
return false;
if (!(storeInst.Value is StObj stobj) || !(storeBeforeIf.Value is LdObj ldobj))
return false;
if (!(stobj.Value is NewObj))
return false;
if (!stobj.Target.MatchLdsFlda(out var field1) || !ldobj.Target.MatchLdsFlda(out var field2) || !field1.Equals(field2))
return false;
if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true))
return false;
context.Step("CachedDelegateInitializationRoslynInStaticWithLocal", inst);
storeBeforeIf.Value = new CachedDelegate(stobj.Value);
return true;
}
/// <summary>
/// stloc s(ldobj(ldflda(CachedAnonMethodDelegate))
/// if (comp(ldloc s == null)) {
@ -186,6 +148,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -186,6 +148,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// }
/// =>
/// stloc s(cached.delegate(DelegateConstruction))
/// The same pattern applies to static cache fields accessed with ldsflda.
/// </summary>
bool CachedDelegateInitializationRoslynWithLocal(IfInstruction inst)
{
@ -200,14 +163,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -200,14 +163,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!(storeInst.Value is StObj stobj) || !(storeBeforeIf.Value is LdObj ldobj))
return false;
if (!(stobj.Value is NewObj))
if (stobj.Value is not NewObj delegateConstruction)
return false;
if (!stobj.Target.MatchLdFlda(out var _, out var field1) || !ldobj.Target.MatchLdFlda(out var __, out var field2) || !field1.Equals(field2))
bool sameField = (stobj.Target, ldobj.Target) switch {
(LdsFlda first, LdsFlda second) => first.Field.Equals(second.Field),
(LdFlda first, LdFlda second) => first.Field.Equals(second.Field),
_ => false
};
if (!sameField)
return false;
if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true))
if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true))
return false;
context.Step("CachedDelegateInitializationRoslynWithLocal", inst);
storeBeforeIf.Value = new CachedDelegate(stobj.Value);
storeBeforeIf.Value = new CachedDelegate(delegateConstruction);
return true;
}

2
global.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"sdk": {
"version": "11.0.100-preview.7.26381.103",
"version": "11.0.0",
"rollForward": "major",
"allowPrerelease": true
},

Loading…
Cancel
Save