Browse Source

Prevent inlining of call arguments when doing so would change order of evaluation with regards to the implicit ldobj performed by a constrained.callvirt.

pull/3440/head
Siegfried Pammer 3 months ago
parent
commit
9f77f8a919
  1. 8
      ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
  2. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  3. 143
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/NonGenericConstrainedCallVirt.il
  4. 6
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs
  5. 11
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  6. 18
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  7. 27
      ICSharpCode.Decompiler/IL/ILReader.cs
  8. 138
      ICSharpCode.Decompiler/IL/Instructions.cs
  9. 15
      ICSharpCode.Decompiler/IL/Instructions.tt
  10. 13
      ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
  11. 12
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  12. 4
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  13. 2
      ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs
  14. 6
      ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
  15. 2
      ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs
  16. 3
      ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
  17. 20
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
  18. 2
      ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
  19. 7
      ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

8
ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs

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

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -95,6 +95,7 @@
<None Include="TestCases\ILPretty\Issue2260SwitchString.il" /> <None Include="TestCases\ILPretty\Issue2260SwitchString.il" />
<None Include="TestCases\ILPretty\Issue3442.il" /> <None Include="TestCases\ILPretty\Issue3442.il" />
<None Include="TestCases\ILPretty\MonoFixed.il" /> <None Include="TestCases\ILPretty\MonoFixed.il" />
<None Include="TestCases\Correctness\NonGenericConstrainedCallVirt.il" />
<None Include="TestCases\ILPretty\UnknownTypes.cs" /> <None Include="TestCases\ILPretty\UnknownTypes.cs" />
<None Include="TestCases\ILPretty\UnknownTypes.il" /> <None Include="TestCases\ILPretty\UnknownTypes.il" />
<None Include="TestCases\ILPretty\EvalOrder.cs" /> <None Include="TestCases\ILPretty\EvalOrder.cs" />

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

@ -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

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

@ -296,5 +296,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
return default(T).ToString(); return default(T).ToString();
} }
public static void ConstrainedCall<T>(T x, ref T y) where T : IDisposable
{
x.Dispose();
y.Dispose();
}
} }
} }

11
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

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

18
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2614,7 +2614,7 @@ namespace ICSharpCode.Decompiler.CSharp
} }
internal TranslatedExpression TranslateTarget(ILInstruction target, bool nonVirtualInvocation, 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. // If references are missing member.IsStatic might not be set correctly.
// Additionally check target for null, in order to avoid a crash. // Additionally check target for null, in order to avoid a crash.
@ -2630,8 +2630,8 @@ namespace ICSharpCode.Decompiler.CSharp
} }
else else
{ {
IType targetTypeHint = memberDeclaringType; IType targetTypeHint = constrainedTo ?? memberDeclaringType;
if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType) == StackType.Ref) if (CallInstruction.ExpectedTypeForThisPointer(memberDeclaringType, constrainedTo) == StackType.Ref)
{ {
if (target.ResultType == StackType.Ref) if (target.ResultType == StackType.Ref)
{ {
@ -2643,13 +2643,13 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
var translatedTarget = Translate(target, targetTypeHint); 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, // 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) // 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) if (translatedTarget.Expression is DirectionExpression)
@ -2675,9 +2675,9 @@ namespace ICSharpCode.Decompiler.CSharp
} }
else else
{ {
return new TypeReferenceExpression(ConvertType(memberDeclaringType)) return new TypeReferenceExpression(ConvertType(constrainedTo ?? memberDeclaringType))
.WithoutILInstruction() .WithoutILInstruction()
.WithRR(new TypeResolveResult(memberDeclaringType)); .WithRR(new TypeResolveResult(constrainedTo ?? memberDeclaringType));
} }
bool ShouldUseBaseReference() bool ShouldUseBaseReference()
@ -2686,7 +2686,7 @@ namespace ICSharpCode.Decompiler.CSharp
return false; return false;
if (!MatchLdThis(target)) if (!MatchLdThis(target))
return false; return false;
if (memberDeclaringType.GetDefinition() == resolver.CurrentTypeDefinition) if ((constrainedTo ?? memberDeclaringType).GetDefinition() == resolver.CurrentTypeDefinition)
return false; return false;
return true; return true;
} }

27
ICSharpCode.Decompiler/IL/ILReader.cs

@ -1766,15 +1766,36 @@ namespace ICSharpCode.Decompiler.IL
{ {
int firstArgument = (opCode != OpCode.NewObj && !method.IsStatic) ? 1 : 0; int firstArgument = (opCode != OpCode.NewObj && !method.IsStatic) ? 1 : 0;
var arguments = new ILInstruction[firstArgument + method.Parameters.Count]; 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
&& expectedStackType == StackType.Ref && typeOfThis.IsReferenceType != false;
for (int i = method.Parameters.Count - 1; i >= 0; i--) for (int i = method.Parameters.Count - 1; i >= 0; i--)
{ {
if (requiresLdObjIfRef)
{
FlushExpressionStack();
}
arguments[firstArgument + i] = Pop(method.Parameters[i].Type.GetStackType()); arguments[firstArgument + i] = Pop(method.Parameters[i].Type.GetStackType());
} }
if (firstArgument == 1) if (firstArgument == 1)
{ {
arguments[0] = firstArgumentIsStObjTarget ILInstruction firstArgumentInstruction;
? PopStObjTarget() if (firstArgumentIsStObjTarget)
: Pop(CallInstruction.ExpectedTypeForThisPointer(constrainedPrefix ?? method.DeclaringType)); {
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 in reverse order of the Pop calls, thus
// arguments is now in the correct evaluation order. // arguments is now in the correct evaluation order.

138
ICSharpCode.Decompiler/IL/Instructions.cs

@ -164,6 +164,8 @@ namespace ICSharpCode.Decompiler.IL
IsInst, IsInst,
/// <summary>Indirect load (ref/pointer dereference).</summary> /// <summary>Indirect load (ref/pointer dereference).</summary>
LdObj, 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). /// <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> /// 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, StObj,
@ -4085,6 +4087,116 @@ namespace ICSharpCode.Decompiler.IL
} }
} }
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). /// <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> /// 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
{ {
Default(inst); Default(inst);
} }
protected internal virtual void VisitLdObjIfRef(LdObjIfRef inst)
{
Default(inst);
}
protected internal virtual void VisitStObj(StObj inst) protected internal virtual void VisitStObj(StObj inst)
{ {
Default(inst); Default(inst);
@ -7473,6 +7589,10 @@ namespace ICSharpCode.Decompiler.IL
{ {
return Default(inst); return Default(inst);
} }
protected internal virtual T VisitLdObjIfRef(LdObjIfRef inst)
{
return Default(inst);
}
protected internal virtual T VisitStObj(StObj inst) protected internal virtual T VisitStObj(StObj inst)
{ {
return Default(inst); return Default(inst);
@ -7875,6 +7995,10 @@ namespace ICSharpCode.Decompiler.IL
{ {
return Default(inst, context); 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) protected internal virtual T VisitStObj(StObj inst, C context)
{ {
return Default(inst, context); return Default(inst, context);
@ -8086,6 +8210,7 @@ namespace ICSharpCode.Decompiler.IL
"castclass", "castclass",
"isinst", "isinst",
"ldobj", "ldobj",
"ldobj.if.ref",
"stobj", "stobj",
"box", "box",
"unbox", "unbox",
@ -8591,6 +8716,19 @@ namespace ICSharpCode.Decompiler.IL
type = default(IType); type = default(IType);
return false; 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) public bool MatchStObj([NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out ILInstruction? value, [NotNullWhen(true)] out IType? type)
{ {
var inst = this as StObj; var inst = this as StObj;

15
ICSharpCode.Decompiler/IL/Instructions.tt

@ -255,6 +255,9 @@
new OpCode("ldobj", "Indirect load (ref/pointer dereference).", new OpCode("ldobj", "Indirect load (ref/pointer dereference).",
CustomClassName("LdObj"), CustomArguments(("target", new[] { "Ref", "I" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal, CustomClassName("LdObj"), CustomArguments(("target", new[] { "Ref", "I" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal,
SupportsVolatilePrefix, SupportsUnalignedPrefix, MayThrow, ResultType("type.GetStackType()")), 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 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())", + "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, CustomClassName("StObj"), CustomArguments(("target", new[] { "Ref", "I" }), ("value", new[] { "type.GetStackType()" })), HasTypeOperand, MemoryAccess, CustomWriteToButKeepOriginal,
@ -916,7 +919,8 @@ namespace ICSharpCode.Decompiler.IL
b = new StringBuilder(); b = new StringBuilder();
b.AppendLine("protected sealed override ILInstruction GetChild(int index)"); b.AppendLine("protected sealed override ILInstruction GetChild(int index)");
b.AppendLine("{"); b.AppendLine("{");
b.AppendLine("\tswitch (index) {"); b.AppendLine("\tswitch (index)");
b.AppendLine("\t{");
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
b.AppendLine("\t\tcase " + i + ":"); b.AppendLine("\t\tcase " + i + ":");
b.AppendLine("\t\t\treturn this." + children[i].Name + ";"); b.AppendLine("\t\t\treturn this." + children[i].Name + ";");
@ -933,7 +937,8 @@ namespace ICSharpCode.Decompiler.IL
b = new StringBuilder(); b = new StringBuilder();
b.AppendLine("protected sealed override void SetChild(int index, ILInstruction value)"); b.AppendLine("protected sealed override void SetChild(int index, ILInstruction value)");
b.AppendLine("{"); b.AppendLine("{");
b.AppendLine("\tswitch (index) {"); b.AppendLine("\tswitch (index)");
b.AppendLine("\t{");
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
b.AppendLine("\t\tcase " + i + ":"); b.AppendLine("\t\tcase " + i + ":");
b.AppendLine("\t\t\tthis." + children[i].PropertyName + " = value;"); b.AppendLine("\t\t\tthis." + children[i].PropertyName + " = value;");
@ -953,7 +958,8 @@ namespace ICSharpCode.Decompiler.IL
b = new StringBuilder(); b = new StringBuilder();
b.AppendLine("protected sealed override SlotInfo GetChildSlot(int index)"); b.AppendLine("protected sealed override SlotInfo GetChildSlot(int index)");
b.AppendLine("{"); b.AppendLine("{");
b.AppendLine("\tswitch (index) {"); b.AppendLine("\tswitch (index)");
b.AppendLine("\t{");
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
b.AppendLine("\t\tcase " + i + ":"); b.AppendLine("\t\tcase " + i + ":");
b.AppendLine("\t\t\treturn " + children[i].SlotName + ";"); b.AppendLine("\t\t\treturn " + children[i].SlotName + ";");
@ -1113,7 +1119,8 @@ protected override void Disconnected()
opCode.Members.Add("/// <summary>Returns the method operand.</summary>" + Environment.NewLine opCode.Members.Add("/// <summary>Returns the method operand.</summary>" + Environment.NewLine
+ $"public IMethod{n} Method => method;"); + $"public IMethod{n} Method => method;");
opCode.GenerateWriteTo = true; 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("\toutput.Write(' ');");
opCode.WriteOperand.Add("\tmethod.WriteTo(output);"); opCode.WriteOperand.Add("\tmethod.WriteTo(output);");
opCode.WriteOperand.Add("}"); opCode.WriteOperand.Add("}");

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

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

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

@ -475,6 +475,18 @@ 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;
}
}
protected internal override void VisitStObj(StObj inst) protected internal override void VisitStObj(StObj inst)
{ {
base.VisitStObj(inst); base.VisitStObj(inst);

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

@ -394,6 +394,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (ldloca.Variable.Type.IsReferenceType ?? false) if (ldloca.Variable.Type.IsReferenceType ?? false)
return false; return false;
ILInstruction inst = ldloca; ILInstruction inst = ldloca;
if (inst.Parent is LdObjIfRef)
{
inst = inst.Parent;
}
while (inst.Parent is LdFlda ldflda) while (inst.Parent is LdFlda ldflda)
{ {
inst = ldflda; inst = ldflda;

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

@ -99,7 +99,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (call.IsInstanceCall) if (call.IsInstanceCall)
{ {
IType thisVarType = call.ConstrainedTo ?? call.Method.DeclaringType; 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); thisVarType = new ByReferenceType(thisVarType);
} }

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

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

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

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

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

@ -107,6 +107,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case LdObj _: case LdObj _:
case StObj stobj when stobj.Target == addressLoadingInstruction: case StObj stobj when stobj.Target == addressLoadingInstruction:
return AddressUse.Immediate; 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: case LdFlda ldflda:
return DetermineAddressUse(ldflda, targetVar); return DetermineAddressUse(ldflda, targetVar);
case Await await: case Await await:

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

@ -318,6 +318,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (resolveContext != null && !IsMethodApplicable(method, call.Arguments, rootType, resolveContext, settings)) if (resolveContext != null && !IsMethodApplicable(method, call.Arguments, rootType, resolveContext, settings))
goto default; goto default;
inst = call.Arguments[0]; inst = call.Arguments[0];
if (inst is LdObjIfRef ldObjIfRef)
{
inst = ldObjIfRef.Target;
}
if (method.IsAccessor) if (method.IsAccessor)
{ {
if (method.AccessorOwner is IProperty property && if (method.AccessorOwner is IProperty property &&
@ -371,6 +375,22 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
goto default; 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: case StObj stobj:
{ {
if (stobj.Target is LdFlda ldflda) if (stobj.Target is LdFlda ldflda)

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

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

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

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

Loading…
Cancel
Save