Browse Source

#2182: Move "Transform call to struct constructor" from EarlyExpressionTransforms to ILReader.

This fixes the evaluation order for such calls (and an assertion in StObj.CheckTargetSlot).
pull/2157/head
Daniel Grunwald 5 years ago
parent
commit
80cf72e498
  1. 2
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
  3. 21
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EvalOrder.cs
  4. 69
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EvalOrder.il
  5. 16
      ICSharpCode.Decompiler/IL/ILReader.cs
  6. 37
      ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs
  7. 14
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

2
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -65,6 +65,8 @@ @@ -65,6 +65,8 @@
<None Include="TestCases\Correctness\StackTests.il" />
<None Include="TestCases\Correctness\StackTypes.il" />
<None Include="TestCases\Correctness\Uninit.vb" />
<None Include="TestCases\ILPretty\EvalOrder.cs" />
<None Include="TestCases\ILPretty\EvalOrder.il" />
<None Include="TestCases\ILPretty\CallIndirect.il" />
<None Include="TestCases\ILPretty\Issue1454.il" />
<None Include="TestCases\ILPretty\Issue1681.il" />

6
ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs

@ -128,6 +128,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -128,6 +128,12 @@ namespace ICSharpCode.Decompiler.Tests
Run();
}
[Test]
public void EvalOrder()
{
Run();
}
[Test]
public void CS1xSwitch_Debug()
{

21
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EvalOrder.cs

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
using System;
using System.Runtime.InteropServices;
internal class EvalOrder
{
private SimpleStruct field;
public static void Test(EvalOrder p)
{
// ldflda (and potential NRE) before MyStruct ctor call
ref SimpleStruct reference = ref p.field;
reference = new SimpleStruct(1);
}
}
[StructLayout(LayoutKind.Sequential, Size = 1)]
internal struct SimpleStruct
{
public SimpleStruct(int val)
{
Console.WriteLine(val);
}
}

69
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/EvalOrder.il

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
#define CORE_ASSEMBLY "System.Runtime"
.assembly extern CORE_ASSEMBLY
{
.publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....:
.ver 4:0:0:0
}
.class private auto ansi beforefieldinit EvalOrder
extends [System.Private.CoreLib]System.Object
{
.field private valuetype SimpleStruct 'field'
// Methods
.method public hidebysig static
void Test (
class EvalOrder p
) cil managed
{
// Method begins at RVA 0x20f0
// Code size 14 (0xe)
.maxstack 8
ldarg.0
ldflda valuetype SimpleStruct EvalOrder::'field'
ldc.i4.1
call instance void SimpleStruct::.ctor(int32)
ret
} // end of method EvalOrder::Test
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x206e
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Example::.ctor
} // end of class Example
.class private sequential ansi sealed beforefieldinit SimpleStruct
extends [System.Runtime]System.ValueType
{
.pack 0
.size 1
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
int32 val
) cil managed
{
// Method begins at RVA 0x20f9
// Code size 9 (0x9)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.1
IL_0002: call void [System.Console]System.Console::WriteLine(int32)
IL_0007: nop
IL_0008: ret
} // end of method SimpleStruct::.ctor
} // end of class SimpleStruct

16
ICSharpCode.Decompiler/IL/ILReader.cs

@ -1528,6 +1528,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1528,6 +1528,7 @@ namespace ICSharpCode.Decompiler.IL
switch (method.DeclaringType.Kind)
{
case TypeKind.Array:
{
var elementType = ((ArrayType)method.DeclaringType).ElementType;
if (opCode == OpCode.NewObj)
return Push(new NewArr(elementType, arguments));
@ -1552,6 +1553,21 @@ namespace ICSharpCode.Decompiler.IL @@ -1552,6 +1553,21 @@ namespace ICSharpCode.Decompiler.IL
}
Warn("Unknown method called on array type: " + method.Name);
goto default;
}
case TypeKind.Struct when method.IsConstructor && !method.IsStatic && opCode == OpCode.Call
&& method.ReturnType.Kind == TypeKind.Void:
{
// "call Struct.ctor(target, ...)" doesn't exist in C#,
// the next best equivalent is an assignment `*target = new Struct(...);`.
// So we represent this call as "stobj Struct(target, newobj Struct.ctor(...))".
// This needs to happen early (not as a transform) because the StObj.TargetSlot has
// restricted inlining (doesn't accept ldflda when exceptions aren't delayed).
var newobj = new NewObj(method);
newobj.ILStackWasEmpty = currentStack.IsEmpty;
newobj.ConstrainedTo = constrainedPrefix;
newobj.Arguments.AddRange(arguments.Skip(1));
return new StObj(arguments[0], newobj, method.DeclaringType);
}
default:
var call = CallInstruction.Create(opCode, method);
call.ILStackWasEmpty = currentStack.IsEmpty;

37
ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs

@ -16,11 +16,7 @@ @@ -16,11 +16,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using ICSharpCode.Decompiler.TypeSystem;
@ -109,38 +105,5 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -109,38 +105,5 @@ namespace ICSharpCode.Decompiler.IL.Transforms
temp.ReplaceWith(replacement);
}
}
protected internal override void VisitCall(Call inst)
{
var expr = HandleCall(inst, context);
if (expr != null)
{
// The resulting expression may trigger further rules, so continue visiting the replacement:
expr.AcceptVisitor(this);
}
else
{
base.VisitCall(inst);
}
}
internal static ILInstruction HandleCall(Call inst, ILTransformContext context)
{
if (inst.Method.IsConstructor && !inst.Method.IsStatic && inst.Method.DeclaringType.Kind == TypeKind.Struct)
{
Debug.Assert(inst.Arguments.Count == inst.Method.Parameters.Count + 1);
context.Step("Transform call to struct constructor", inst);
// call(ref, ...)
// => stobj(ref, newobj(...))
var newObj = new NewObj(inst.Method);
newObj.AddILRange(inst);
newObj.Arguments.AddRange(inst.Arguments.Skip(1));
newObj.ILStackWasEmpty = inst.ILStackWasEmpty;
var expr = new StObj(inst.Arguments[0], newObj, inst.Method.DeclaringType);
inst.ReplaceWith(expr);
return expr;
}
return null;
}
}
}

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

@ -23,7 +23,6 @@ using System.Linq; @@ -23,7 +23,6 @@ using System.Linq;
using System.Linq.Expressions;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms
{
@ -314,17 +313,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -314,17 +313,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
protected internal override void VisitCall(Call inst)
{
var expr = EarlyExpressionTransforms.HandleCall(inst, context);
if (expr != null)
{
// The resulting expression may trigger further rules, so continue visiting the replacement:
expr.AcceptVisitor(this);
}
else
{
base.VisitCall(inst);
TransformAssignment.HandleCompoundAssign(inst, context);
}
base.VisitCall(inst);
TransformAssignment.HandleCompoundAssign(inst, context);
}
protected internal override void VisitCallVirt(CallVirt inst)

Loading…
Cancel
Save