Browse Source

Fix #938: add support for I4->I stack type adjustments.

With this fix we still only propagate type information along forward branches, so some of the issues in #901 remain.
pull/976/head v3.0-beta2
Daniel Grunwald 8 years ago
parent
commit
f155ca4ef6
  1. 31
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il
  2. 7
      ICSharpCode.Decompiler/IL/BlockBuilder.cs
  3. 134
      ICSharpCode.Decompiler/IL/ILReader.cs
  4. 2
      ICSharpCode.Decompiler/IL/Instructions.cs
  5. 10
      ICSharpCode.Decompiler/IL/Instructions.tt
  6. 22
      ICSharpCode.Decompiler/IL/StackType.cs

31
ICSharpCode.Decompiler.Tests/TestCases/Correctness/StackTypes.il

@ -21,7 +21,7 @@ extends [mscorlib]System.Object
.entrypoint .entrypoint
call void Program::InlineAssignByte() call void Program::InlineAssignByte()
//call void Program::Int32OrNativeTests() call void Program::Int32OrNativeTests()
ret ret
} // end of method Main } // end of method Main
@ -48,7 +48,6 @@ pointless:
ret ret
} }
/*
.method public static void Int32OrNativeTests() .method public static void Int32OrNativeTests()
{ {
ldstr "Int32OrNative(0x7fffffff, false) = {0}" ldstr "Int32OrNative(0x7fffffff, false) = {0}"
@ -79,6 +78,7 @@ pointless:
box native int box native int
call void [mscorlib]System.Console::WriteLine(string, object) call void [mscorlib]System.Console::WriteLine(string, object)
/*
ldstr "Int32OrNativeLoopStyle(0x7fffffff):" ldstr "Int32OrNativeLoopStyle(0x7fffffff):"
call void [mscorlib]System.Console::WriteLine(string) call void [mscorlib]System.Console::WriteLine(string)
ldc.i4 0x7fffffff ldc.i4 0x7fffffff
@ -100,6 +100,7 @@ pointless:
call native int Program::Int32OrNativeDeadCode(int32) call native int Program::Int32OrNativeDeadCode(int32)
box native int box native int
call void [mscorlib]System.Console::WriteLine(string, object) call void [mscorlib]System.Console::WriteLine(string, object)
*/
ldc.i4 0x7fffffff ldc.i4 0x7fffffff
call void Program::RunInt32OrNativeMultiUse(int32) call void Program::RunInt32OrNativeMultiUse(int32)
@ -115,6 +116,26 @@ pointless:
use_i4: use_i4:
ldarg.0 ldarg.0
br after_if br after_if
use_native_int:
ldarg.0
conv.u
br after_if
after_if:
ldc.i4.1
add
ret
}
/*
.method public static native int Int32OrNativeReordered(int32 val, bool use_native)
{
// The spec is ambiguous whether the addition will be in 32-bits or native size.
// The microsoft runtime picks native size.
ldarg.1
brtrue use_native_int
use_i4:
ldarg.0
br after_if
after_if: after_if:
ldc.i4.1 ldc.i4.1
add add
@ -127,6 +148,8 @@ pointless:
.method public static void Int32OrNativeLoopStyle(int32 val) .method public static void Int32OrNativeLoopStyle(int32 val)
{ {
// The spec is ambiguous whether the addition will be in 32-bits or native size.
// The microsoft runtime picks native size.
.locals init ( .locals init (
int32 i int32 i
) )
@ -149,6 +172,8 @@ pointless:
.method public static native int Int32OrNativeDeadCode(int32 val) .method public static native int Int32OrNativeDeadCode(int32 val)
{ {
// The spec is ambiguous whether the addition will be in 32-bits or native size.
// The microsoft runtime picks 32-bits.
use_i4: use_i4:
ldarg.0 ldarg.0
br after_if br after_if
@ -161,6 +186,7 @@ pointless:
conv.u conv.u
br after_if br after_if
} }
*/
.method public static void RunInt32OrNativeMultiUse(int32 val) .method public static void RunInt32OrNativeMultiUse(int32 val)
{ {
@ -229,7 +255,6 @@ pointless:
add add
ret ret
} }
*/
.method public static void Print(native int val) .method public static void Print(native int val)
{ {

7
ICSharpCode.Decompiler/IL/BlockBuilder.cs

@ -117,7 +117,7 @@ namespace ICSharpCode.Decompiler.IL
foreach (var inst in instructions) { foreach (var inst in instructions) {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
int start = inst.ILRange.Start; int start = inst.ILRange.Start;
if (currentBlock == null || incomingBranches[start]) { if (currentBlock == null || (incomingBranches[start] && !IsStackAdjustment(inst))) {
// Finish up the previous block // Finish up the previous block
FinalizeCurrentBlock(start, fallthrough: true); FinalizeCurrentBlock(start, fallthrough: true);
// Leave nested containers if necessary // Leave nested containers if necessary
@ -158,6 +158,11 @@ namespace ICSharpCode.Decompiler.IL
ConnectBranches(mainContainer, cancellationToken); ConnectBranches(mainContainer, cancellationToken);
} }
static bool IsStackAdjustment(ILInstruction inst)
{
return inst is StLoc stloc && stloc.IsStackAdjustment;
}
private void FinalizeCurrentBlock(int currentILOffset, bool fallthrough) private void FinalizeCurrentBlock(int currentILOffset, bool fallthrough)
{ {
if (currentBlock == null) if (currentBlock == null)

134
ICSharpCode.Decompiler/IL/ILReader.cs

@ -64,6 +64,7 @@ namespace ICSharpCode.Decompiler.IL
Dictionary<int, ImmutableStack<ILVariable>> stackByOffset; Dictionary<int, ImmutableStack<ILVariable>> stackByOffset;
Dictionary<Cil.ExceptionHandler, ILVariable> variableByExceptionHandler; Dictionary<Cil.ExceptionHandler, ILVariable> variableByExceptionHandler;
UnionFind<ILVariable> unionFind; UnionFind<ILVariable> unionFind;
List<(ILVariable, ILVariable)> stackMismatchPairs;
IEnumerable<ILVariable> stackVariables; IEnumerable<ILVariable> stackVariables;
void Init(Cil.MethodBody body) void Init(Cil.MethodBody body)
@ -74,8 +75,9 @@ namespace ICSharpCode.Decompiler.IL
this.debugInfo = body.Method.DebugInformation; this.debugInfo = body.Method.DebugInformation;
this.currentInstruction = null; this.currentInstruction = null;
this.nextInstructionIndex = 0; this.nextInstructionIndex = 0;
this.currentStack = System.Collections.Immutable.ImmutableStack<ILVariable>.Empty; this.currentStack = ImmutableStack<ILVariable>.Empty;
this.unionFind = new UnionFind<ILVariable>(); this.unionFind = new UnionFind<ILVariable>();
this.stackMismatchPairs = new List<(ILVariable, ILVariable)>();
this.methodReturnStackType = typeSystem.Resolve(body.Method.ReturnType).GetStackType(); this.methodReturnStackType = typeSystem.Resolve(body.Method.ReturnType).GetStackType();
InitParameterVariables(); InitParameterVariables();
this.localVariables = body.Variables.SelectArray(CreateILVariable); this.localVariables = body.Variables.SelectArray(CreateILVariable);
@ -84,7 +86,7 @@ namespace ICSharpCode.Decompiler.IL
v.HasInitialValue = true; v.HasInitialValue = true;
} }
} }
mainContainer = new BlockContainer(methodReturnStackType); this.mainContainer = new BlockContainer(methodReturnStackType);
this.instructionBuilder = new List<ILInstruction>(); this.instructionBuilder = new List<ILInstruction>();
this.isBranchTarget = new BitArray(body.CodeSize); this.isBranchTarget = new BitArray(body.CodeSize);
this.stackByOffset = new Dictionary<int, ImmutableStack<ILVariable>>(); this.stackByOffset = new Dictionary<int, ImmutableStack<ILVariable>>();
@ -186,30 +188,82 @@ namespace ICSharpCode.Decompiler.IL
Warnings.Add(string.Format("IL_{0:x4}: {1}", currentInstruction.Offset, message)); Warnings.Add(string.Format("IL_{0:x4}: {1}", currentInstruction.Offset, message));
} }
void MergeStacks(ImmutableStack<ILVariable> a, ImmutableStack<ILVariable> b) ImmutableStack<ILVariable> MergeStacks(ImmutableStack<ILVariable> a, ImmutableStack<ILVariable> b)
{ {
var enum1 = a.GetEnumerator(); if (CheckStackCompatibleWithoutAdjustments(a, b)) {
var enum2 = b.GetEnumerator(); // We only need to union the input variables, but can
bool ok1 = enum1.MoveNext(); // otherwise re-use the existing stack.
bool ok2 = enum2.MoveNext(); ImmutableStack<ILVariable> output = a;
while (ok1 && ok2) { while (!a.IsEmpty && !b.IsEmpty) {
if (enum1.Current.StackType != enum2.Current.StackType) { Debug.Assert(a.Peek().StackType == b.Peek().StackType);
Warn("Incompatible stack types: " + enum1.Current.StackType + " vs " + enum2.Current.StackType); unionFind.Merge(a.Peek(), b.Peek());
a = a.Pop();
b = b.Pop();
} }
unionFind.Merge(enum1.Current, enum2.Current); return output;
ok1 = enum1.MoveNext(); } else if (a.Count() != b.Count()) {
ok2 = enum2.MoveNext(); // Let's not try to merge mismatched stacks.
}
if (ok1 || ok2) {
Warn("Incompatible stack heights: " + a.Count() + " vs " + b.Count()); Warn("Incompatible stack heights: " + a.Count() + " vs " + b.Count());
return a;
} else {
// The more complex case where the stacks don't match exactly.
var output = new List<ILVariable>();
while (!a.IsEmpty && !b.IsEmpty) {
var varA = a.Peek();
var varB = b.Peek();
if (varA.StackType == varB.StackType) {
unionFind.Merge(varA, varB);
output.Add(varA);
} else {
if (!IsValidTypeStackTypeMerge(varA.StackType, varB.StackType)) {
Warn("Incompatible stack types: " + varA.StackType + " vs " + varB.StackType);
}
if (varA.StackType > varB.StackType) {
output.Add(varA);
// every store to varB should also store to varA
stackMismatchPairs.Add((varB, varA));
} else {
output.Add(varB);
// every store to varA should also store to varB
stackMismatchPairs.Add((varA, varB));
}
}
a = a.Pop();
b = b.Pop();
}
// because we built up output by popping from the input stacks, we need to reverse it to get back the original order
output.Reverse();
return ImmutableStack.CreateRange(output);
}
}
static bool CheckStackCompatibleWithoutAdjustments(ImmutableStack<ILVariable> a, ImmutableStack<ILVariable> b)
{
while (!a.IsEmpty && !b.IsEmpty) {
if (a.Peek().StackType != b.Peek().StackType)
return false;
a = a.Pop();
b = b.Pop();
} }
return a.IsEmpty && b.IsEmpty;
}
private bool IsValidTypeStackTypeMerge(StackType stackType1, StackType stackType2)
{
if (stackType1 == StackType.I && stackType2 == StackType.I4)
return true;
if (stackType1 == StackType.I4 && stackType2 == StackType.I)
return true;
// allow merging unknown type with any other type
return stackType1 == StackType.Unknown || stackType2 == StackType.Unknown;
} }
void StoreStackForOffset(int offset, ImmutableStack<ILVariable> stack) void StoreStackForOffset(int offset, ImmutableStack<ILVariable> stack)
{ {
ImmutableStack<ILVariable> existing; if (stackByOffset.TryGetValue(offset, out var existing)) {
if (stackByOffset.TryGetValue(offset, out existing)) { var newStack = MergeStacks(existing, stack);
MergeStacks(existing, stack); if (newStack != existing)
stackByOffset[offset] = newStack;
} else { } else {
stackByOffset.Add(offset, stack); stackByOffset.Add(offset, stack);
} }
@ -272,6 +326,44 @@ namespace ICSharpCode.Decompiler.IL
instructionBuilder[i] = instructionBuilder[i].AcceptVisitor(visitor); instructionBuilder[i] = instructionBuilder[i].AcceptVisitor(visitor);
} }
stackVariables = visitor.variables; stackVariables = visitor.variables;
InsertStackAdjustments();
}
void InsertStackAdjustments()
{
if (stackMismatchPairs.Count == 0)
return;
var dict = new MultiDictionary<ILVariable, ILVariable>();
foreach (var (origA, origB) in stackMismatchPairs) {
var a = unionFind.Find(origA);
var b = unionFind.Find(origB);
Debug.Assert(a.StackType < b.StackType);
// For every store to a, insert a converting store to b.
if (!dict[a].Contains(b))
dict.Add(a, b);
}
var newInstructions = new List<ILInstruction>();
foreach (var inst in instructionBuilder) {
newInstructions.Add(inst);
if (inst is StLoc store) {
foreach (var additionalVar in dict[store.Variable]) {
ILInstruction value = new LdLoc(store.Variable);
switch (additionalVar.StackType) {
case StackType.I:
value = new Conv(value, PrimitiveType.I, false, Sign.None);
break;
case StackType.I8:
value = new Conv(value, PrimitiveType.I8, false, Sign.None);
break;
}
newInstructions.Add(new StLoc(additionalVar, value) {
IsStackAdjustment = true,
ILRange = inst.ILRange
});
}
}
}
instructionBuilder = newInstructions;
} }
/// <summary> /// <summary>
@ -282,6 +374,12 @@ namespace ICSharpCode.Decompiler.IL
Init(body); Init(body);
ReadInstructions(cancellationToken); ReadInstructions(cancellationToken);
foreach (var inst in instructionBuilder) { foreach (var inst in instructionBuilder) {
if (inst is StLoc stloc && stloc.IsStackAdjustment) {
output.Write(" ");
inst.WriteTo(output, new ILAstWritingOptions());
output.WriteLine();
continue;
}
output.Write(" ["); output.Write(" [");
bool isFirstElement = true; bool isFirstElement = true;
foreach (var element in stackByOffset[inst.ILRange.Start]) { foreach (var element in stackByOffset[inst.ILRange.Start]) {

2
ICSharpCode.Decompiler/IL/Instructions.cs

@ -2317,6 +2317,8 @@ namespace ICSharpCode.Decompiler.IL
clone.Value = this.value.Clone(); clone.Value = this.value.Clone();
return clone; return clone;
} }
/// <summary>If true, this stloc represents a stack type adjustment. This field is only used in ILReader and BlockBuilder, and should be ignored by ILAst transforms.</summary>
internal bool IsStackAdjustment;
public override StackType ResultType { get { return variable.StackType; } } public override StackType ResultType { get { return variable.StackType; } }
protected override InstructionFlags ComputeFlags() protected override InstructionFlags ComputeFlags()
{ {

10
ICSharpCode.Decompiler/IL/Instructions.tt

@ -153,6 +153,9 @@
new OpCode("stloc", "Stores a value into a local variable. (IL: starg/stloc)" + Environment.NewLine new OpCode("stloc", "Stores a value into a local variable. (IL: starg/stloc)" + Environment.NewLine
+ "Evaluates to the value that was stored (for byte/short variables: evaluates to the truncated value, sign/zero extended back to I4 based on variable.Type.GetSign())", + "Evaluates to the value that was stored (for byte/short variables: evaluates to the truncated value, sign/zero extended back to I4 based on variable.Type.GetSign())",
CustomClassName("StLoc"), HasVariableOperand("Store"), CustomArguments("value"), CustomClassName("StLoc"), HasVariableOperand("Store"), CustomArguments("value"),
AdditionalMember(
"/// <summary>If true, this stloc represents a stack type adjustment. This field is only used in ILReader and BlockBuilder, and should be ignored by ILAst transforms.</summary>" + Environment.NewLine
+ "internal bool IsStackAdjustment;"),
ResultType("variable.StackType")), ResultType("variable.StackType")),
new OpCode("addressof", "Stores the value into an anonymous temporary variable, and returns the address of that variable.", new OpCode("addressof", "Stores the value into an anonymous temporary variable, and returns the address of that variable.",
CustomClassName("AddressOf"), CustomArguments("value"), ResultType("Ref")), CustomClassName("AddressOf"), CustomArguments("value"), ResultType("Ref")),
@ -571,6 +574,13 @@ namespace ICSharpCode.Decompiler.IL
}; };
} }
static Action<OpCode> AdditionalMember(string declaration)
{
return opCode => {
opCode.Members.Add(declaration);
};
}
// ResultType trait: the instruction has the specified result type. // ResultType trait: the instruction has the specified result type.
static Action<OpCode> ResultType(string type) static Action<OpCode> ResultType(string type)
{ {

22
ICSharpCode.Decompiler/IL/StackType.cs

@ -24,6 +24,15 @@ namespace ICSharpCode.Decompiler.IL
/// </summary> /// </summary>
public enum StackType : byte public enum StackType : byte
{ {
// Note: the numeric of these enum members is relevant for ILReader.MergeStacks:
// when two branches meet where a stack slot has different types, the type after
// the branch is the one with the higher numeric value.
/// <summary>
/// The stack type is unknown; for example a call returning an unknown type
/// because an assembly reference isn't loaded.
/// Can also occur with invalid IL.
/// </summary>
Unknown, Unknown,
/// <summary>32-bit integer</summary> /// <summary>32-bit integer</summary>
/// <remarks> /// <remarks>
@ -33,23 +42,24 @@ namespace ICSharpCode.Decompiler.IL
/// and any enums with one of the above as underlying type. /// and any enums with one of the above as underlying type.
/// </remarks> /// </remarks>
I4, I4,
/// <summary>native-size integer, or unmanaged pointer</summary>
/// <remarks>
/// Used for C# <c>IntPtr</c>, <c>UIntPtr</c> and any native pointer types (<c>void*</c> etc.)
/// Also used for IL function pointer types.
/// </remarks>
I,
/// <summary>64-bit integer</summary> /// <summary>64-bit integer</summary>
/// <remarks> /// <remarks>
/// Used for C# <c>long</c>, <c>ulong</c>, /// Used for C# <c>long</c>, <c>ulong</c>,
/// and any enums with one of the above as underlying type. /// and any enums with one of the above as underlying type.
/// </remarks> /// </remarks>
I8, I8,
/// <summary>native-size integer, or unmanaged pointer</summary>
/// <remarks>
/// Used for C# <c>IntPtr</c>, <c>UIntPtr</c> and any native pointer types (<c>void*</c> etc.)
/// </remarks>
I,
/// <summary>Floating point number</summary> /// <summary>Floating point number</summary>
/// <remarks> /// <remarks>
/// Used for C# <c>float</c> and <c>double</c>. /// Used for C# <c>float</c> and <c>double</c>.
/// </remarks> /// </remarks>
F, F,
/// <summary>Another stack type. Includes objects, value types, function pointers, ...</summary> /// <summary>Another stack type. Includes objects, value types, ...</summary>
O, O,
/// <summary>A managed pointer</summary> /// <summary>A managed pointer</summary>
Ref, Ref,

Loading…
Cancel
Save