mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Prevent inlining of call arguments when doing so would change order of evaluation with regards to the implicit ldobj performed by a constrained.callvirt.
This commit is contained in:
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -296,5 +296,11 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
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
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
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("/// <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("}");
|
||||
|
||||
@@ -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
|
||||
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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user