Browse Source

Merge pull request #3440 from icsharpcode/bugfix/constrained-call-targets

Fix changes due to eval order fix
pull/3456/head
Siegfried Pammer 3 months ago committed by GitHub
parent
commit
dba836c41e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 9
      .github/workflows/build-ilspy.yml
  2. 8
      ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
  3. 3
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  4. 143
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/NonGenericConstrainedCallVirt.il
  5. 54
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs
  6. 2
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  7. 11
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  8. 18
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  9. 8
      ICSharpCode.Decompiler/DecompilerSettings.cs
  10. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  11. 29
      ICSharpCode.Decompiler/IL/ILReader.cs
  12. 138
      ICSharpCode.Decompiler/IL/Instructions.cs
  13. 15
      ICSharpCode.Decompiler/IL/Instructions.tt
  14. 13
      ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
  15. 12
      ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
  16. 22
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  17. 1
      ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs
  18. 49
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  19. 2
      ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs
  20. 6
      ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
  21. 2
      ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs
  22. 145
      ICSharpCode.Decompiler/IL/Transforms/RemoveUnconstrainedGenericReferenceTypeCheck.cs
  23. 3
      ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
  24. 20
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
  25. 2
      ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
  26. 7
      ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
  27. 1
      ILSpy/Languages/ILAstLanguage.cs

9
.github/workflows/build-ilspy.yml

@ -60,7 +60,7 @@ jobs: @@ -60,7 +60,7 @@ jobs:
run: msbuild ILSpy.sln /p:Configuration=${{ matrix.configuration }} /p:Platform=$env:BuildPlatform /m
- name: Execute unit tests
run: dotnet test --logger "junit;LogFileName=${{ matrix.configuration }}.xml" --results-directory test-results $env:Tests1 $env:Tests2 $env:Tests3
run: dotnet test --logger "trx;LogFileName=${{ matrix.configuration }}.trx" --results-directory test-results $env:Tests1 $env:Tests2 $env:Tests3
env:
Tests1: ICSharpCode.Decompiler.Tests\bin\${{ matrix.configuration }}\net8.0-windows\win-x64\ICSharpCode.Decompiler.Tests.dll
Tests2: ILSpy.Tests\bin\${{ matrix.configuration }}\net8.0-windows\ILSpy.Tests.dll
@ -71,13 +71,14 @@ jobs: @@ -71,13 +71,14 @@ jobs:
if: success() || failure()
with:
name: test-results-${{ matrix.configuration }}
path: 'test-results/${{ matrix.configuration }}.xml'
path: 'test-results/${{ matrix.configuration }}.trx'
- name: Create Test Report
uses: test-summary/action@v2
uses: icsharpcode/test-summary-action@dist
if: always()
with:
paths: "test-results/${{ matrix.configuration }}.xml"
paths: "test-results/${{ matrix.configuration }}.trx"
folded: true
- name: Format check
run: dotnet-format whitespace --verify-no-changes --verbosity detailed ILSpy.sln

8
ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs

@ -16,8 +16,6 @@ @@ -16,8 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
@ -313,6 +311,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -313,6 +311,12 @@ namespace ICSharpCode.Decompiler.Tests
await RunIL("Jmp.il");
}
[Test]
public async Task NonGenericConstrainedCallVirt()
{
await RunIL("NonGenericConstrainedCallVirt.il", CompilerOptions.UseRoslynLatest);
}
[Test]
public async Task StackTests()
{

3
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<NoWarn>1701;1702;1705,67,169,1058,728,1720,649,168,251,660,661,675;1998;162;8632;626;8618;8714;8602;8981</NoWarn>
<DefineConstants>ROSLYN;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120</DefineConstants>
<DefineConstants>ROSLYN;ROSLYN2;ROSLYN3;ROSLYN4;NET60;CS60;CS70;CS71;CS72;CS73;CS80;CS90;CS100;CS110;CS120</DefineConstants>
<GenerateAssemblyVersionAttribute>False</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>False</GenerateAssemblyFileVersionAttribute>
@ -95,6 +95,7 @@ @@ -95,6 +95,7 @@
<None Include="TestCases\ILPretty\Issue2260SwitchString.il" />
<None Include="TestCases\ILPretty\Issue3442.il" />
<None Include="TestCases\ILPretty\MonoFixed.il" />
<None Include="TestCases\Correctness\NonGenericConstrainedCallVirt.il" />
<None Include="TestCases\ILPretty\UnknownTypes.cs" />
<None Include="TestCases\ILPretty\UnknownTypes.il" />
<None Include="TestCases\ILPretty\EvalOrder.cs" />

143
ICSharpCode.Decompiler.Tests/TestCases/Correctness/NonGenericConstrainedCallVirt.il

@ -0,0 +1,143 @@ @@ -0,0 +1,143 @@
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly _
{
.hash algorithm 0x00008004 // SHA1
.ver 0:0:0:0
}
.class public auto ansi beforefieldinit NonGenericConstrainedCallVirt
extends [mscorlib]System.Object
{
// Methods
.method public hidebysig static
void Main () cil managed
{
// Method begins at RVA 0x2050
// Code size 16 (0x10)
.entrypoint
.maxstack 8
IL_0000: ldstr "A"
IL_0005: newobj instance void C::.ctor(string)
IL_000a: call void NonGenericConstrainedCallVirt::Foo(class C)
IL_000f: ret
} // end of method NonGenericConstrainedCallVirt::Main
.method private hidebysig static
void Foo (
class C arg
) cil managed
{
// Method begins at RVA 0x2064
// Code size 25 (0x19)
.maxstack 8
IL_0000: ldarga arg
IL_0004: ldarga arg
IL_0008: call int32 NonGenericConstrainedCallVirt::Bar(class C&)
IL_000d: constrained. C
IL_0013: callvirt instance void C::Baz(int32)
IL_0018: ret
} // end of method NonGenericConstrainedCallVirt::Foo
.method private hidebysig static
int32 Bar (
class C& o
) cil managed
{
// Method begins at RVA 0x2080
// Code size 14 (0xe)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldstr "B"
IL_0006: newobj instance void C::.ctor(string)
IL_000b: stind.ref
IL_000c: ldc.i4.0
IL_000d: ret
} // end of method NonGenericConstrainedCallVirt::Bar
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2090
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method NonGenericConstrainedCallVirt::.ctor
} // end of class NonGenericConstrainedCallVirt
.class public auto ansi beforefieldinit C
extends [mscorlib]System.Object
{
// Fields
.field private initonly string '<Name>k__BackingField'
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
string n
) cil managed
{
// Method begins at RVA 0x2098
// Code size 14 (0xe)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ldarg.0
IL_0007: ldarg.1
IL_0008: stfld string C::'<Name>k__BackingField'
IL_000d: ret
} // end of method C::.ctor
.method public hidebysig
instance void Baz (
int32 arg
) cil managed
{
// Method begins at RVA 0x20a8
// Code size 12 (0xc)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance string C::get_Name()
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: ret
} // end of method C::Baz
.method public hidebysig specialname
instance string get_Name () cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x20b8
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld string C::'<Name>k__BackingField'
IL_0006: ret
} // end of method C::get_Name
// Properties
.property instance string Name()
{
.get instance string C::get_Name()
}
} // end of class C

54
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs

@ -84,6 +84,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -84,6 +84,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
void Method1<T>() where T : class;
void Method2<T>() where T : class;
void Method3(int a, string b, Type c);
#if CS72
void Method4(in int a);
#endif
}
public abstract class Base : IInterface
@ -95,6 +100,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -95,6 +100,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
void IInterface.Method2<T>()
{
}
void IInterface.Method3(int a, string b, Type c)
{
}
#if CS72
void IInterface.Method4(in int a)
{
}
#endif
}
public class Derived : Base
@ -296,5 +311,44 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -296,5 +311,44 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
return default(T).ToString();
}
public static void ConstrainedCall<T>(T x, ref T y) where T : IDisposable
{
x.Dispose();
y.Dispose();
}
// prior to C# 7.0 UseRefLocalsForAccurateOrderOfEvaluation is disabled, so we will inline.
// Roslyn 4 generates the explicit ldobj.if.ref pattern, so we can also inline.
// The versions in between, we don't inline, so the code doesn't look pretty.
#if ROSLYN4 || !CS70
public static int[] Issue3438<T>(T[] array)
{
List<int> list = new List<int>();
for (int i = 0; i < array.Length; i++)
{
if (!array[i].Equals(default(T)))
{
list.Add(i);
}
}
return list.ToArray();
}
public void Issue3438b<T>(T[] item1, T item2, int item3) where T : IInterface
{
item1[CastToInt(item2)].Method3(CastToInt(item2), CastToString(item2), TestTypeOf()[1]);
}
public void Issue3438c<T>(T item, T item2, int item3) where T : IInterface
{
CastFromInt<T>(item3).Method3(CastToInt(item2), CastToString(item2), TestTypeOf()[1]);
}
//#if CS72
// Disabled because ILInlining does not support inlining ldloca currently.
// public void Issue3438d<T>(T[] item1, T item2, int item3) where T : IInterface
// {
// item1[CastToInt(item2)].Method4(CastToInt(item2));
// }
//#endif
#endif
}
}

2
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -149,6 +149,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -149,6 +149,7 @@ namespace ICSharpCode.Decompiler.CSharp
new IndexRangeTransform(),
new DeconstructionTransform(),
new NamedArgumentTransform(),
new RemoveUnconstrainedGenericReferenceTypeCheck(),
new UserDefinedLogicTransform(),
new InterpolatedStringTransform()
),
@ -1717,6 +1718,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1717,6 +1718,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
var ilReader = new ILReader(typeSystem.MainModule) {
UseDebugSymbols = settings.UseDebugSymbols,
UseRefLocalsForAccurateOrderOfEvaluation = settings.UseRefLocalsForAccurateOrderOfEvaluation,
DebugInfo = DebugInfoProvider
};
var methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)method.MetadataToken);

11
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -356,11 +356,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -356,11 +356,18 @@ namespace ICSharpCode.Decompiler.CSharp
}
else
{
var thisArg = callArguments.FirstOrDefault();
if (thisArg is LdObjIfRef ldObjIfRef)
{
Debug.Assert(constrainedTo != null);
thisArg = ldObjIfRef.Target;
}
target = expressionBuilder.TranslateTarget(
callArguments.FirstOrDefault(),
thisArg,
nonVirtualInvocation: callOpCode == OpCode.Call || method.IsConstructor,
memberStatic: method.IsStatic,
memberDeclaringType: constrainedTo ?? method.DeclaringType);
memberDeclaringType: method.DeclaringType,
constrainedTo: constrainedTo);
if (constrainedTo == null
&& target.Expression is CastExpression cast
&& target.ResolveResult is ConversionResolveResult conversion

18
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2614,7 +2614,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2614,7 +2614,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
internal TranslatedExpression TranslateTarget(ILInstruction target, bool nonVirtualInvocation,
bool memberStatic, IType memberDeclaringType)
bool memberStatic, IType memberDeclaringType, IType constrainedTo = null)
{
// If references are missing member.IsStatic might not be set correctly.
// Additionally check target for null, in order to avoid a crash.
@ -2630,8 +2630,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2630,8 +2630,8 @@ namespace ICSharpCode.Decompiler.CSharp
}
else
{
IType targetTypeHint = memberDeclaringType;
if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType) == StackType.Ref)
IType targetTypeHint = constrainedTo ?? memberDeclaringType;
if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType, constrainedTo) == StackType.Ref)
{
if (target.ResultType == StackType.Ref)
{
@ -2643,13 +2643,13 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2643,13 +2643,13 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
var translatedTarget = Translate(target, targetTypeHint);
if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType) == StackType.Ref)
if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType, constrainedTo) == StackType.Ref)
{
// When accessing members on value types, ensure we use a reference of the correct type,
// and not a pointer or a reference to a different type (issue #1333)
if (!(translatedTarget.Type is ByReferenceType brt && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(brt.ElementType, memberDeclaringType)))
if (!(translatedTarget.Type is ByReferenceType brt && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(brt.ElementType, constrainedTo ?? memberDeclaringType)))
{
translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(memberDeclaringType), this);
translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(constrainedTo ?? memberDeclaringType), this);
}
}
if (translatedTarget.Expression is DirectionExpression)
@ -2675,9 +2675,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2675,9 +2675,9 @@ namespace ICSharpCode.Decompiler.CSharp
}
else
{
return new TypeReferenceExpression(ConvertType(memberDeclaringType))
return new TypeReferenceExpression(ConvertType(constrainedTo ?? memberDeclaringType))
.WithoutILInstruction()
.WithRR(new TypeResolveResult(memberDeclaringType));
.WithRR(new TypeResolveResult(constrainedTo ?? memberDeclaringType));
}
bool ShouldUseBaseReference()
@ -2686,7 +2686,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2686,7 +2686,7 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (!MatchLdThis(target))
return false;
if (memberDeclaringType.GetDefinition() == resolver.CurrentTypeDefinition)
if ((constrainedTo ?? memberDeclaringType).GetDefinition() == resolver.CurrentTypeDefinition)
return false;
return true;
}

8
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -92,7 +92,6 @@ namespace ICSharpCode.Decompiler @@ -92,7 +92,6 @@ namespace ICSharpCode.Decompiler
stringInterpolation = false;
dictionaryInitializers = false;
extensionMethodsInCollectionInitializers = false;
useRefLocalsForAccurateOrderOfEvaluation = false;
getterOnlyAutomaticProperties = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp7)
@ -105,6 +104,7 @@ namespace ICSharpCode.Decompiler @@ -105,6 +104,7 @@ namespace ICSharpCode.Decompiler
localFunctions = false;
deconstruction = false;
patternMatching = false;
useRefLocalsForAccurateOrderOfEvaluation = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp7_2)
{
@ -190,11 +190,11 @@ namespace ICSharpCode.Decompiler @@ -190,11 +190,11 @@ namespace ICSharpCode.Decompiler
return CSharp.LanguageVersion.CSharp7_2;
// C# 7.1 missing
if (outVariables || throwExpressions || tupleTypes || tupleConversions
|| discards || localFunctions || deconstruction || patternMatching)
|| discards || localFunctions || deconstruction || patternMatching || useRefLocalsForAccurateOrderOfEvaluation)
return CSharp.LanguageVersion.CSharp7;
if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation
|| stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers
|| useRefLocalsForAccurateOrderOfEvaluation || getterOnlyAutomaticProperties)
|| getterOnlyAutomaticProperties)
return CSharp.LanguageVersion.CSharp6;
if (asyncAwait)
return CSharp.LanguageVersion.CSharp5;
@ -1126,7 +1126,7 @@ namespace ICSharpCode.Decompiler @@ -1126,7 +1126,7 @@ namespace ICSharpCode.Decompiler
/// order of evaluation.
/// See https://github.com/icsharpcode/ILSpy/issues/2050
/// </summary>
[Category("C# 6.0 / VS 2015")]
[Category("C# 7.0 / VS 2017")]
[Description("DecompilerSettings.UseRefLocalsForAccurateOrderOfEvaluation")]
public bool UseRefLocalsForAccurateOrderOfEvaluation {
get { return useRefLocalsForAccurateOrderOfEvaluation; }

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -107,6 +107,7 @@ @@ -107,6 +107,7 @@
<Compile Include="DecompilationProgress.cs" />
<Compile Include="Disassembler\IEntityProcessor.cs" />
<Compile Include="Disassembler\SortByNameProcessor.cs" />
<Compile Include="IL\Transforms\RemoveUnconstrainedGenericReferenceTypeCheck.cs" />
<Compile Include="Metadata\MetadataFile.cs" />
<Compile Include="Metadata\ModuleReferenceMetadata.cs" />
<Compile Include="NRTAttributes.cs" />

29
ICSharpCode.Decompiler/IL/ILReader.cs

@ -122,6 +122,7 @@ namespace ICSharpCode.Decompiler.IL @@ -122,6 +122,7 @@ namespace ICSharpCode.Decompiler.IL
readonly MetadataReader metadata;
public bool UseDebugSymbols { get; set; }
public bool UseRefLocalsForAccurateOrderOfEvaluation { get; set; }
public DebugInfo.IDebugInfoProvider? DebugInfo { get; set; }
public List<string> Warnings { get; } = new List<string>();
@ -1766,15 +1767,37 @@ namespace ICSharpCode.Decompiler.IL @@ -1766,15 +1767,37 @@ namespace ICSharpCode.Decompiler.IL
{
int firstArgument = (opCode != OpCode.NewObj && !method.IsStatic) ? 1 : 0;
var arguments = new ILInstruction[firstArgument + method.Parameters.Count];
IType typeOfThis = constrainedPrefix ?? method.DeclaringType;
StackType expectedStackType = CallInstruction.ExpectedTypeForThisPointer(method.DeclaringType, constrainedPrefix);
bool requiresLdObjIfRef = firstArgument == 1
&& !firstArgumentIsStObjTarget
&& UseRefLocalsForAccurateOrderOfEvaluation
&& expectedStackType == StackType.Ref && typeOfThis.IsReferenceType != false;
for (int i = method.Parameters.Count - 1; i >= 0; i--)
{
if (requiresLdObjIfRef)
{
FlushExpressionStack();
}
arguments[firstArgument + i] = Pop(method.Parameters[i].Type.GetStackType());
}
if (firstArgument == 1)
{
arguments[0] = firstArgumentIsStObjTarget
? PopStObjTarget()
: Pop(CallInstruction.ExpectedTypeForThisPointer(constrainedPrefix ?? method.DeclaringType));
ILInstruction firstArgumentInstruction;
if (firstArgumentIsStObjTarget)
{
firstArgumentInstruction = PopStObjTarget();
}
else
{
firstArgumentInstruction = Pop(expectedStackType);
if (requiresLdObjIfRef)
{
firstArgumentInstruction = new LdObjIfRef(firstArgumentInstruction, typeOfThis);
}
}
arguments[0] = firstArgumentInstruction;
}
// arguments is in reverse order of the Pop calls, thus
// arguments is now in the correct evaluation order.

138
ICSharpCode.Decompiler/IL/Instructions.cs

@ -164,6 +164,8 @@ namespace ICSharpCode.Decompiler.IL @@ -164,6 +164,8 @@ namespace ICSharpCode.Decompiler.IL
IsInst,
/// <summary>Indirect load (ref/pointer dereference).</summary>
LdObj,
/// <summary>If argument is a ref to a reference type, loads the object reference, stores it in a temporary, and evaluates to the address of that temporary (address.of(ldobj(arg))). Otherwise, returns the argument ref as-is.<para>This instruction represents the memory-load semantics of callvirt with a generic type as receiver (where the IL always takes a ref, but only methods on value types expect one, for method on reference types there's an implicit ldobj, which this instruction makes explicit in order to preserve the order-of-evaluation).</para></summary>
LdObjIfRef,
/// <summary>Indirect store (store to ref/pointer).
/// Evaluates to the value that was stored (when using type byte/short: evaluates to the truncated value, sign/zero extended back to I4 based on type.GetSign())</summary>
StObj,
@ -4085,6 +4087,116 @@ namespace ICSharpCode.Decompiler.IL @@ -4085,6 +4087,116 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>If argument is a ref to a reference type, loads the object reference, stores it in a temporary, and evaluates to the address of that temporary (address.of(ldobj(arg))). Otherwise, returns the argument ref as-is.<para>This instruction represents the memory-load semantics of callvirt with a generic type as receiver (where the IL always takes a ref, but only methods on value types expect one, for method on reference types there's an implicit ldobj, which this instruction makes explicit in order to preserve the order-of-evaluation).</para></summary>
public sealed partial class LdObjIfRef : ILInstruction
{
public LdObjIfRef(ILInstruction target, IType type) : base(OpCode.LdObjIfRef)
{
this.Target = target;
this.type = type;
}
public static readonly SlotInfo TargetSlot = new SlotInfo("Target", canInlineInto: true);
ILInstruction target = null!;
public ILInstruction Target {
get { return this.target; }
set {
ValidateChild(value);
SetChildInstruction(ref this.target, value, 0);
}
}
protected sealed override int GetChildCount()
{
return 1;
}
protected sealed override ILInstruction GetChild(int index)
{
switch (index)
{
case 0:
return this.target;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override void SetChild(int index, ILInstruction value)
{
switch (index)
{
case 0:
this.Target = value;
break;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index)
{
case 0:
return TargetSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (LdObjIfRef)ShallowClone();
clone.Target = this.target.Clone();
return clone;
}
IType type;
/// <summary>Returns the type operand.</summary>
public IType Type {
get { return type; }
set { type = value; InvalidateFlags(); }
}
public override StackType ResultType { get { return StackType.Ref; } }
protected override InstructionFlags ComputeFlags()
{
return target.Flags | InstructionFlags.SideEffect | InstructionFlags.MayThrow;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.SideEffect | InstructionFlags.MayThrow;
}
}
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
WriteILRange(output, options);
output.Write(OpCode);
output.Write(' ');
type.WriteTo(output);
output.Write('(');
this.target.WriteTo(output, options);
output.Write(')');
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitLdObjIfRef(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitLdObjIfRef(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{
return visitor.VisitLdObjIfRef(this, context);
}
protected internal override bool PerformMatch(ILInstruction? other, ref Patterns.Match match)
{
var o = other as LdObjIfRef;
return o != null && this.target.PerformMatch(o.target, ref match) && type.Equals(o.type);
}
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
DebugAssert(target.ResultType == StackType.Ref || target.ResultType == StackType.I);
}
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Indirect store (store to ref/pointer).
/// Evaluates to the value that was stored (when using type byte/short: evaluates to the truncated value, sign/zero extended back to I4 based on type.GetSign())</summary>
@ -7071,6 +7183,10 @@ namespace ICSharpCode.Decompiler.IL @@ -7071,6 +7183,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
protected internal virtual void VisitLdObjIfRef(LdObjIfRef inst)
{
Default(inst);
}
protected internal virtual void VisitStObj(StObj inst)
{
Default(inst);
@ -7473,6 +7589,10 @@ namespace ICSharpCode.Decompiler.IL @@ -7473,6 +7589,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
protected internal virtual T VisitLdObjIfRef(LdObjIfRef inst)
{
return Default(inst);
}
protected internal virtual T VisitStObj(StObj inst)
{
return Default(inst);
@ -7875,6 +7995,10 @@ namespace ICSharpCode.Decompiler.IL @@ -7875,6 +7995,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
protected internal virtual T VisitLdObjIfRef(LdObjIfRef inst, C context)
{
return Default(inst, context);
}
protected internal virtual T VisitStObj(StObj inst, C context)
{
return Default(inst, context);
@ -8086,6 +8210,7 @@ namespace ICSharpCode.Decompiler.IL @@ -8086,6 +8210,7 @@ namespace ICSharpCode.Decompiler.IL
"castclass",
"isinst",
"ldobj",
"ldobj.if.ref",
"stobj",
"box",
"unbox",
@ -8591,6 +8716,19 @@ namespace ICSharpCode.Decompiler.IL @@ -8591,6 +8716,19 @@ namespace ICSharpCode.Decompiler.IL
type = default(IType);
return false;
}
public bool MatchLdObjIfRef([NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out IType? type)
{
var inst = this as LdObjIfRef;
if (inst != null)
{
target = inst.Target;
type = inst.Type;
return true;
}
target = default(ILInstruction);
type = default(IType);
return false;
}
public bool MatchStObj([NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out ILInstruction? value, [NotNullWhen(true)] out IType? type)
{
var inst = this as StObj;

15
ICSharpCode.Decompiler/IL/Instructions.tt

@ -255,6 +255,9 @@ @@ -255,6 +255,9 @@
new OpCode("ldobj", "Indirect load (ref/pointer dereference).",
CustomClassName("LdObj"), CustomArguments(("target", new[] { "Ref", "I" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal,
SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()")),
new OpCode("ldobj.if.ref", "If argument is a ref to a reference type, loads the object reference, stores it in a temporary, and evaluates to the address of that temporary (address.of(ldobj(arg))). Otherwise, returns the argument ref as-is.<para>This instruction represents the memory-load semantics of callvirt with a generic type as receiver (where the IL always takes a ref, but only methods on value types expect one, for method on reference types there's an implicit ldobj, which this instruction makes explicit in order to preserve the order-of-evaluation).</para>",
CustomClassName("LdObjIfRef"), CustomArguments(("target", new[] { "Ref", "I" })), HasTypeOperand, MemoryAccess,
MayThrow, ResultType("Ref")),
new OpCode("stobj", "Indirect store (store to ref/pointer)." + Environment.NewLine
+ "Evaluates to the value that was stored (when using type byte/short: evaluates to the truncated value, sign/zero extended back to I4 based on type.GetSign())",
CustomClassName("StObj"), CustomArguments(("target", new[] { "Ref", "I" }), ("value", new[] { "type.GetStackType()" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal,
@ -916,7 +919,8 @@ namespace ICSharpCode.Decompiler.IL @@ -916,7 +919,8 @@ namespace ICSharpCode.Decompiler.IL
b = new StringBuilder();
b.AppendLine("protected sealed override ILInstruction GetChild(int index)");
b.AppendLine("{");
b.AppendLine("\tswitch (index) {");
b.AppendLine("\tswitch (index)");
b.AppendLine("\t{");
for (int i = 0; i < childCount; i++) {
b.AppendLine("\t\tcase " + i + ":");
b.AppendLine("\t\t\treturn this." + children[i].Name + ";");
@ -933,7 +937,8 @@ namespace ICSharpCode.Decompiler.IL @@ -933,7 +937,8 @@ namespace ICSharpCode.Decompiler.IL
b = new StringBuilder();
b.AppendLine("protected sealed override void SetChild(int index, ILInstruction value)");
b.AppendLine("{");
b.AppendLine("\tswitch (index) {");
b.AppendLine("\tswitch (index)");
b.AppendLine("\t{");
for (int i = 0; i < childCount; i++) {
b.AppendLine("\t\tcase " + i + ":");
b.AppendLine("\t\t\tthis." + children[i].PropertyName + " = value;");
@ -953,7 +958,8 @@ namespace ICSharpCode.Decompiler.IL @@ -953,7 +958,8 @@ namespace ICSharpCode.Decompiler.IL
b = new StringBuilder();
b.AppendLine("protected sealed override SlotInfo GetChildSlot(int index)");
b.AppendLine("{");
b.AppendLine("\tswitch (index) {");
b.AppendLine("\tswitch (index)");
b.AppendLine("\t{");
for (int i = 0; i < childCount; i++) {
b.AppendLine("\t\tcase " + i + ":");
b.AppendLine("\t\t\treturn " + children[i].SlotName + ";");
@ -1113,7 +1119,8 @@ protected override void Disconnected() @@ -1113,7 +1119,8 @@ protected override void Disconnected()
opCode.Members.Add("/// <summary>Returns the method operand.</summary>" + Environment.NewLine
+ $"public IMethod{n} Method => method;");
opCode.GenerateWriteTo = true;
opCode.WriteOperand.Add("if (method != null) {");
opCode.WriteOperand.Add("if (method != null)");
opCode.WriteOperand.Add("{");
opCode.WriteOperand.Add("\toutput.Write(' ');");
opCode.WriteOperand.Add("\tmethod.WriteTo(output);");
opCode.WriteOperand.Add("}");

13
ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs

@ -98,16 +98,19 @@ namespace ICSharpCode.Decompiler.IL @@ -98,16 +98,19 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// Gets the expected stack type for passing the this pointer in a method call.
/// Returns StackType.O for reference types (this pointer passed as object reference),
/// Returns StackType.Ref if constrainedTo is not null,
/// StackType.O for reference types (this pointer passed as object reference),
/// and StackType.Ref for type parameters and value types (this pointer passed as managed reference).
///
/// Returns StackType.Unknown if the input type is unknown.
/// </summary>
internal static StackType ExpectedTypeForThisPointer(IType type)
internal static StackType ExpectedTypeForThisPointer(IType declaringType, IType? constrainedTo)
{
if (type.Kind == TypeKind.TypeParameter)
if (constrainedTo != null)
return StackType.Ref;
switch (type.IsReferenceType)
if (declaringType.Kind == TypeKind.TypeParameter)
return StackType.Ref;
switch (declaringType.IsReferenceType)
{
case true:
return StackType.O;
@ -125,7 +128,7 @@ namespace ICSharpCode.Decompiler.IL @@ -125,7 +128,7 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(Method.Parameters.Count + firstArgument == Arguments.Count);
if (firstArgument == 1)
{
if (!(Arguments[0].ResultType == ExpectedTypeForThisPointer(ConstrainedTo ?? Method.DeclaringType)))
if (!(Arguments[0].ResultType == ExpectedTypeForThisPointer(Method.DeclaringType, ConstrainedTo)))
Debug.Fail($"Stack type mismatch in 'this' argument in call to {Method.Name}()");
}
for (int i = 0; i < Method.Parameters.Count; ++i)

12
ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs

@ -581,5 +581,17 @@ namespace ICSharpCode.Decompiler.IL @@ -581,5 +581,17 @@ namespace ICSharpCode.Decompiler.IL
{
return this;
}
public bool MatchLdObj([NotNullWhen(true)] out ILInstruction? target, IType type)
{
var inst = this as LdObj;
if (inst != null && inst.Type.Equals(type))
{
target = inst.Target;
return true;
}
target = default(ILInstruction);
return false;
}
}
}

22
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -475,6 +475,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -475,6 +475,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
protected internal override void VisitLdObjIfRef(LdObjIfRef inst)
{
base.VisitLdObjIfRef(inst);
if (inst.Target is AddressOf)
{
context.Step("ldobj.if.ref(addressof(...)) -> addressof(...)", inst);
// there already is a temporary, so the ldobj.if.ref is a no-op in both cases
inst.ReplaceWith(inst.Target);
return;
}
if (inst.Target.MatchLdLoc(out var s) && s.IsSingleDefinition && s.LoadCount == 1
&& s.StoreInstructions.SingleOrDefault() is StLoc {
Value: LdLoca { Variable: { AddressCount: 1, StoreCount: 1 } }
})
{
context.Step("Single use of ldobj.if.ref(ldloc v) -> ldloc v", inst);
// there already is a temporary, so the ldobj.if.ref is a no-op in both cases
inst.ReplaceWith(inst.Target);
return;
}
}
protected internal override void VisitStObj(StObj inst)
{
base.VisitStObj(inst);

1
ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs

@ -79,6 +79,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -79,6 +79,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
return new ILReader(TypeSystem.MainModule) {
UseDebugSymbols = Settings.UseDebugSymbols,
UseRefLocalsForAccurateOrderOfEvaluation = Settings.UseRefLocalsForAccurateOrderOfEvaluation,
DebugInfo = DebugInfo
};
}

49
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -170,14 +170,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -170,14 +170,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public static bool InlineOneIfPossible(Block block, int pos, InliningOptions options, ILTransformContext context)
{
context.CancellationToken.ThrowIfCancellationRequested();
StLoc stloc = block.Instructions[pos] as StLoc;
if (stloc == null || stloc.Variable.Kind == VariableKind.PinnedLocal)
if (block.Instructions[pos] is not StLoc stloc)
return false;
ILVariable v = stloc.Variable;
// ensure the variable is accessed only a single time
if (v.StoreCount != 1)
return false;
if (v.LoadCount > 1 || v.LoadCount + v.AddressCount != 1)
if (!VariableCanBeUsedForInlining(stloc.Variable))
return false;
// TODO: inlining of small integer types might be semantically incorrect,
// but we can't avoid it this easily without breaking lots of tests.
@ -186,6 +181,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -186,6 +181,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return InlineOne(stloc, options, context);
}
public static bool VariableCanBeUsedForInlining(ILVariable v)
{
if (v.Kind == VariableKind.PinnedLocal)
return false;
// ensure the variable is accessed only a single time
if (v.StoreCount != 1)
return false;
if (v.LoadCount + v.AddressCount != 1)
return false;
return true;
}
/// <summary>
/// Inlines the stloc instruction at block.Instructions[pos] into the next instruction.
///
@ -394,6 +401,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -394,6 +401,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (ldloca.Variable.Type.IsReferenceType ?? false)
return false;
ILInstruction inst = ldloca;
if (inst.Parent is LdObjIfRef)
{
inst = inst.Parent;
}
while (inst.Parent is LdFlda ldflda)
{
inst = ldflda;
@ -904,6 +915,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -904,6 +915,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
/// <summary>
/// Gets whether 'expressionBeingMoved' can be moved from somewhere before 'stmt' to become the replacement of 'targetLoad'.
/// </summary>
public static bool CanMoveIntoCallVirt(ILInstruction expressionBeingMoved, ILInstruction stmt, CallVirt nestedCallVirt, ILInstruction targetLoad)
{
Debug.Assert(targetLoad.IsDescendantOf(stmt) && nestedCallVirt.IsDescendantOf(stmt));
ILInstruction thisArg = nestedCallVirt.Arguments[0];
Debug.Assert(thisArg is LdObjIfRef);
for (ILInstruction inst = targetLoad; inst != stmt; inst = inst.Parent)
{
if (!inst.Parent.CanInlineIntoSlot(inst.ChildIndex, expressionBeingMoved))
return false;
// Check whether re-ordering with predecessors is valid:
int childIndex = inst.ChildIndex;
for (int i = 0; i < childIndex; ++i)
{
ILInstruction predecessor = inst.Parent.Children[i];
if (predecessor != thisArg && !IsSafeForInlineOver(predecessor, expressionBeingMoved))
return false;
}
}
return true;
}
/// <summary>
/// Gets whether arg can be un-inlined out of stmt.
/// </summary>

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

@ -99,7 +99,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -99,7 +99,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (call.IsInstanceCall)
{
IType thisVarType = call.ConstrainedTo ?? call.Method.DeclaringType;
if (CallInstruction.ExpectedTypeForThisPointer(thisVarType) == StackType.Ref)
if (CallInstruction.ExpectedTypeForThisPointer(call.Method.DeclaringType, call.ConstrainedTo) == StackType.Ref)
{
thisVarType = new ByReferenceType(thisVarType);
}

6
ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs

@ -283,6 +283,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -283,6 +283,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
inst = arg;
}
else if (inst is LdObjIfRef ldObjIfRef)
{
inst = ldObjIfRef.Target;
}
// ensure the access chain does not contain any 'nullable.unwrap' that aren't directly part of the chain
if (ArgumentsAfterFirstMayUnwrapNull(call.Arguments))
return false;
@ -362,7 +366,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -362,7 +366,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
&& arg.MatchLdLoc(testedVar);
case Mode.UnconstrainedType:
// unconstrained generic type (expect: ldloc(testedVar))
return inst.MatchLdLoc(testedVar);
return inst.MatchLdLoc(testedVar) || (inst.MatchLdObjIfRef(out var testedVarLoad, out _) && testedVarLoad.MatchLdLoc(testedVar));
default:
throw new ArgumentOutOfRangeException(nameof(mode));
}

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

@ -54,8 +54,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -54,8 +54,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
}
private bool DoTransform(Block block, ILTransformContext context)
{
if (!MatchBlock1(block, out var s, out int value, out var br))

145
ICSharpCode.Decompiler/IL/Transforms/RemoveUnconstrainedGenericReferenceTypeCheck.cs

@ -0,0 +1,145 @@ @@ -0,0 +1,145 @@
// Copyright (c) 2025 Siegfried Pammer
//
// 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.
#nullable enable
using System.Collections.Generic;
using System.Linq;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// [stloc address(...)]
/// stloc temp(default.value ``0)
/// if (comp.o(ldloc temp == ldnull)) Block IL_002a {
/// stloc temp(ldobj ``0(ldloc address))
/// stloc address(ldloca temp)
/// }
/// stloc V_i(expr_i)
/// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), ldloc V_i ...))...
///
///=>
///
/// [stloc address(...)]
/// stloc address(ldobj.if.ref(ldloc address))
/// stloc V_i(expr_i)
/// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), ldloc V_i ...))...
///
/// Then ldobj.if.ref in the call is redundant because any object reference was already loaded into an immutable temporary.
/// So we can removed and inlining of the arguments (V_i) becomes possible.
///
/// Finally the newly created ldobj.if.ref is inlined into the place where the old ldobj.if.ref was.
///
/// =>
///
/// [stloc address(...)]
/// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), expr_i ...))...
///
/// </summary>
class RemoveUnconstrainedGenericReferenceTypeCheck : IStatementTransform
{
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
{
int startPos = pos;
// stloc temp(default.value ``0)
if (!block.Instructions[pos].MatchStLoc(out var temp, out var defaultValue))
return;
if (!defaultValue.MatchDefaultValue(out var type))
return;
if (temp.StoreCount != 2 || temp.LoadCount != 1 || temp.AddressCount != 1)
return;
pos++;
// if (comp.o(ldloc temp == ldnull)) Block IL_002a {
// stloc temp(ldobj ``0(ldloc address))
// stloc address(ldloca temp)
// }
if (block.Instructions.ElementAtOrDefault(pos) is not IfInstruction ifInst)
return;
if (!ifInst.Condition.MatchCompEqualsNull(out var tempLoadForNullCheck))
return;
if (!tempLoadForNullCheck.MatchLdLoc(temp))
return;
if (ifInst.TrueInst is not Block dereferenceBlock)
return;
if (ifInst.FalseInst is not Nop)
return;
if (dereferenceBlock.Instructions is not [StLoc tempStore, StLoc addressReassign])
return;
if (tempStore.Variable != temp)
return;
if (!tempStore.Value.MatchLdObj(out var addressLoadForLdObj, type))
return;
if (!addressLoadForLdObj.MatchLdLoc(addressReassign.Variable))
return;
if (!addressReassign.Value.MatchLdLoca(temp))
return;
var address = addressReassign.Variable;
if (address.StoreCount != 2 || address.LoadCount != 2 || address.AddressCount != 0)
return;
pos++;
// pos now is the first store to V_i
// ...(constrained[``0].callvirt Method(ldobj.if.ref ``0(ldloc address), ldloc V_i ...))...
var callTarget = address.LoadInstructions.Single(l => addressLoadForLdObj != l);
if (callTarget.Parent is not LdObjIfRef { Parent: CallVirt call } ldobjIfRef)
return;
if (call.Arguments.Count == 0 || call.Arguments[0] != ldobjIfRef || !type.Equals(call.ConstrainedTo))
return;
ILInstruction containingStmt = call;
while (containingStmt.Parent != block)
{
if (containingStmt.Parent == null)
return;
containingStmt = containingStmt.Parent;
}
if (containingStmt.ChildIndex < pos)
return;
// check if we can inline all temporaries used in the call:
int temporaryInitIndex = containingStmt.ChildIndex - 1;
List<(ILInstruction, ILInstruction)> replacements = new();
for (int argIndex = call.Arguments.Count - 1; argIndex > 0 && temporaryInitIndex >= pos; argIndex--)
{
var argument = call.Arguments[argIndex];
switch (argument)
{
case LdLoc load:
if (block.Instructions[temporaryInitIndex].MatchStLoc(load.Variable, out var expr) && ILInlining.VariableCanBeUsedForInlining(load.Variable))
{
if (!ILInlining.CanMoveIntoCallVirt(expr, containingStmt, call, argument))
{
return;
}
replacements.Add((argument, expr));
temporaryInitIndex--;
}
break;
}
}
// all stores to V_i processed?
if (temporaryInitIndex != pos - 1)
{
return;
}
context.Step("RemoveUnconstrainedGenericReferenceTypeCheck", block.Instructions[startPos]);
foreach (var (argument, expr) in replacements)
{
argument.ReplaceWith(expr);
}
block.Instructions.RemoveRange(startPos, containingStmt.ChildIndex - startPos);
}
}
}

3
ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs

@ -107,6 +107,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -107,6 +107,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case LdObj _:
case StObj stobj when stobj.Target == addressLoadingInstruction:
return AddressUse.Immediate;
case LdObjIfRef:
// This is either an immediate use, or we need to check how the parent uses the address.
return DetermineAddressUse(addressLoadingInstruction.Parent, targetVar);
case LdFlda ldflda:
return DetermineAddressUse(ldflda, targetVar);
case Await await:

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

@ -318,6 +318,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -318,6 +318,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (resolveContext != null && !IsMethodApplicable(method, call.Arguments, rootType, resolveContext, settings))
goto default;
inst = call.Arguments[0];
if (inst is LdObjIfRef ldObjIfRef)
{
inst = ldObjIfRef.Target;
}
if (method.IsAccessor)
{
if (method.AccessorOwner is IProperty property &&
@ -371,6 +375,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -371,6 +375,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
goto default;
}
case LdObjIfRef ldobj:
{
if (ldobj.Target is LdFlda ldflda && (kind != AccessPathKind.Setter || !ldflda.Field.IsReadOnly))
{
path.Insert(0, new AccessPathElement(ldobj.OpCode, ldflda.Field));
inst = ldflda.Target;
break;
}
if (ldobj.Target is LdLoca ldloca)
{
target = ldloca.Variable;
inst = null;
break;
}
goto default;
}
case StObj stobj:
{
if (stobj.Target is LdFlda ldflda)

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

@ -643,7 +643,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -643,7 +643,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILInstruction PrepareCallTarget(IType expectedType, ILInstruction target, IType targetType)
{
ILInstruction result;
switch (CallInstruction.ExpectedTypeForThisPointer(expectedType))
switch (CallInstruction.ExpectedTypeForThisPointer(expectedType, null))
{
case StackType.Ref:
if (target.ResultType == StackType.Ref)

7
ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

@ -316,7 +316,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -316,7 +316,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// the null check of reference types might have been transformed into "objVar?.Dispose();"
if (!(rewrap.Argument is CallVirt cv))
return false;
if (!(cv.Arguments.FirstOrDefault() is NullableUnwrap unwrap))
target = cv.Arguments.FirstOrDefault();
if (target is LdObjIfRef ldObjIfRef)
target = ldObjIfRef.Target;
if (!(target is NullableUnwrap unwrap))
return false;
numObjVarLoadsInCheck = 1;
disposeCall = cv;
@ -342,6 +345,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -342,6 +345,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
target = cv.Arguments.FirstOrDefault();
if (target == null)
return false;
if (target is LdObjIfRef ldObjIfRef)
target = ldObjIfRef.Target;
if (target.MatchBox(out var newTarget, out var type) && type.Equals(objVar.Type))
target = newTarget;
else if (isInlinedIsInst && target.MatchIsInst(out newTarget, out type) && type.IsKnownType(disposeTypeCode))

1
ILSpy/Languages/ILAstLanguage.cs

@ -109,6 +109,7 @@ namespace ICSharpCode.ILSpy @@ -109,6 +109,7 @@ namespace ICSharpCode.ILSpy
var typeSystem = new DecompilerTypeSystem(module, assemblyResolver);
var reader = new ILReader(typeSystem.MainModule);
reader.UseDebugSymbols = options.DecompilerSettings.UseDebugSymbols;
reader.UseRefLocalsForAccurateOrderOfEvaluation = options.DecompilerSettings.UseRefLocalsForAccurateOrderOfEvaluation;
var methodBody = module.GetMethodBody(methodDef.RelativeVirtualAddress);
ILFunction il = reader.ReadIL((SRM.MethodDefinitionHandle)method.MetadataToken, methodBody, kind: ILFunctionKind.TopLevelFunction, cancellationToken: options.CancellationToken);
var decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings) { CancellationToken = options.CancellationToken };

Loading…
Cancel
Save