diff --git a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
index a0901f255..cf3974d2f 100644
--- a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
+++ b/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
// 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
await RunIL("Jmp.il");
}
+ [Test]
+ public async Task NonGenericConstrainedCallVirt()
+ {
+ await RunIL("NonGenericConstrainedCallVirt.il", CompilerOptions.UseRoslynLatest);
+ }
+
[Test]
public async Task StackTests()
{
diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index f19374fd5..b40bf5b35 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -95,6 +95,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NonGenericConstrainedCallVirt.il b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/NonGenericConstrainedCallVirt.il
new file mode 100644
index 000000000..2fd15600e
--- /dev/null
+++ b/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 '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::'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::'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
+
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs
index 6d9d1672a..82a81c43e 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Generics.cs
@@ -296,5 +296,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
return default(T).ToString();
}
+
+ public static void ConstrainedCall(T x, ref T y) where T : IDisposable
+ {
+ x.Dispose();
+ y.Dispose();
+ }
}
}
diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
index 5a5fe7802..b8b6680e5 100644
--- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
@@ -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
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 90df1890a..8398be47a 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -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
}
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
}
}
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
}
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
return false;
if (!MatchLdThis(target))
return false;
- if (memberDeclaringType.GetDefinition() == resolver.CurrentTypeDefinition)
+ if ((constrainedTo ?? memberDeclaringType).GetDefinition() == resolver.CurrentTypeDefinition)
return false;
return true;
}
diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs
index 226218e12..254eb068e 100644
--- a/ICSharpCode.Decompiler/IL/ILReader.cs
+++ b/ICSharpCode.Decompiler/IL/ILReader.cs
@@ -1766,15 +1766,36 @@ 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
+ && 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.
diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs
index c4ce2d8af..4f3ef843d 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions.cs
@@ -164,6 +164,8 @@ namespace ICSharpCode.Decompiler.IL
IsInst,
/// Indirect load (ref/pointer dereference).
LdObj,
+ /// 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.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).
+ LdObjIfRef,
/// 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())
StObj,
@@ -4085,6 +4087,116 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
+{
+ /// 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.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).
+ 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;
+ /// Returns the type operand.
+ 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(ILVisitor visitor)
+ {
+ return visitor.VisitLdObjIfRef(this);
+ }
+ public override T AcceptVisitor(ILVisitor 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
{
/// 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())
@@ -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
{
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
{
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
"castclass",
"isinst",
"ldobj",
+ "ldobj.if.ref",
"stobj",
"box",
"unbox",
@@ -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;
diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt
index 72da23a16..6f42205af 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.tt
+++ b/ICSharpCode.Decompiler/IL/Instructions.tt
@@ -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.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).",
+ 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
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
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
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()
opCode.Members.Add("/// Returns the method operand." + 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("}");
diff --git a/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
index ccdf1f5fa..ab61817eb 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
@@ -98,16 +98,19 @@ namespace ICSharpCode.Decompiler.IL
///
/// 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.
///
- 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
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)
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
index 1b8d09264..744008c7e 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
+++ b/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)
{
base.VisitStObj(inst);
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index 3e2c743c8..88b2b95c4 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
@@ -394,6 +394,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;
diff --git a/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs
index 41e30e598..4454bf75e 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/NamedArgumentTransform.cs
@@ -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);
}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
index cb7d2161c..7a4b18deb 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
@@ -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
&& 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));
}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs
index a337ea1ac..1a84016bf 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs
@@ -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))
diff --git a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
index 4f3bbd698..4699f9f57 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
@@ -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:
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
index c632e22a2..ea32c3972 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
+++ b/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))
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
}
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)
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
index 2f4121b2a..b60c1e86a 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs
@@ -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)
diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
index 020166ac0..26f8cbda2 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
+++ b/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();"
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
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))