mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
Verify that the stack sizes are consistent - ie at a given location the stack size is statically known and constant. Keep track of instruction which pushed a given value on the stack. (Not handling the potential problem at control merge points - this problem does not occur in the quick sort algorithm) The state of the stack is outputted as a comment in the source codepull/1/head^2
4 changed files with 293 additions and 31 deletions
@ -0,0 +1,191 @@
@@ -0,0 +1,191 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
|
||||
using Ast = ICSharpCode.NRefactory.Ast; |
||||
using ICSharpCode.NRefactory.Ast; |
||||
|
||||
using Cecil = Mono.Cecil; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace Decompiler |
||||
{ |
||||
// Imutable
|
||||
public struct CilStackSlot { |
||||
Instruction allocadedBy; |
||||
|
||||
public Instruction AllocadedBy { |
||||
get { return allocadedBy; } |
||||
} |
||||
|
||||
public CilStackSlot(Instruction allocadedBy) |
||||
{ |
||||
this.allocadedBy = allocadedBy; |
||||
} |
||||
|
||||
public override int GetHashCode() |
||||
{ |
||||
int hashCode = 0; |
||||
if (allocadedBy != null) hashCode ^= allocadedBy.GetHashCode(); |
||||
return hashCode; |
||||
} |
||||
|
||||
public override bool Equals(object obj) |
||||
{ |
||||
if (!(obj is CilStackSlot)) return false; |
||||
CilStackSlot myCilStackSlot = (CilStackSlot)obj; |
||||
return object.Equals(this.allocadedBy, myCilStackSlot.allocadedBy); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
if (allocadedBy == null) { |
||||
return "<??>"; |
||||
} else { |
||||
return string.Format("expr{0:X2}", this.allocadedBy.Offset); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <remarks> The tail of the list is the top of the stack </remarks>
|
||||
public class CilStack: List<CilStackSlot> { |
||||
public static CilStack Empty = new CilStack(); |
||||
|
||||
public CilStack Clone() |
||||
{ |
||||
return new CilStack(this); |
||||
} |
||||
|
||||
public void PopCount(int count) |
||||
{ |
||||
this.RemoveRange(this.Count - count, count); |
||||
} |
||||
|
||||
public void Push(CilStackSlot slot) |
||||
{ |
||||
this.Add(slot); |
||||
} |
||||
|
||||
public CilStack(): base() |
||||
{ |
||||
|
||||
} |
||||
|
||||
public CilStack(IEnumerable<CilStackSlot> slotEnum): base(slotEnum) |
||||
{ |
||||
|
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
string ret = "Stack: {"; |
||||
bool first = true; |
||||
foreach(CilStackSlot slot in this) { |
||||
if (!first) ret += ", "; |
||||
ret += slot.ToString(); |
||||
first = false; |
||||
} |
||||
ret += "}"; |
||||
return ret; |
||||
} |
||||
} |
||||
|
||||
public class StackAnalysis { |
||||
MethodDefinition methodDef; |
||||
Dictionary<Instruction, CilStack> stackBefore = new Dictionary<Instruction, CilStack>(); |
||||
Dictionary<Instruction, CilStack> stackAfter = new Dictionary<Instruction, CilStack>(); |
||||
|
||||
public Dictionary<Instruction, CilStack> StackBefore { |
||||
get { return stackBefore; } |
||||
} |
||||
|
||||
public Dictionary<Instruction, CilStack> StackAfter { |
||||
get { return stackAfter; } |
||||
} |
||||
|
||||
public StackAnalysis(MethodDefinition methodDef) { |
||||
this.methodDef = methodDef; |
||||
|
||||
foreach(Instruction inst in methodDef.Body.Instructions) { |
||||
stackBefore[inst] = null; |
||||
stackAfter[inst] = null; |
||||
} |
||||
|
||||
if (methodDef.Body.Instructions.Count > 0) { |
||||
Instruction firstInst = methodDef.Body.Instructions[0]; |
||||
stackBefore[firstInst] = CilStack.Empty; |
||||
ProcessInstructionRec(firstInst); |
||||
} |
||||
} |
||||
|
||||
void ProcessInstructionRec(Instruction inst) |
||||
{ |
||||
stackAfter[inst] = ChangeStack(stackBefore[inst], inst); |
||||
|
||||
switch(inst.OpCode.FlowControl) { |
||||
case FlowControl.Branch: |
||||
CopyStack(inst, ((Instruction)inst.Operand)); |
||||
break; |
||||
case FlowControl.Cond_Branch: |
||||
CopyStack(inst, inst.Next); |
||||
CopyStack(inst, ((Instruction)inst.Operand)); |
||||
break; |
||||
case FlowControl.Next: |
||||
case FlowControl.Call: |
||||
CopyStack(inst, inst.Next); |
||||
break; |
||||
case FlowControl.Return: |
||||
if (stackAfter[inst].Count > 0) throw new Exception("Non-empty stack at the end"); |
||||
break; |
||||
default: throw new NotImplementedException(); |
||||
} |
||||
} |
||||
|
||||
CilStack ChangeStack(CilStack oldStack, Instruction inst) |
||||
{ |
||||
CilStack newStack = oldStack.Clone(); |
||||
newStack.PopCount(Util.GetNumberOfInputs(methodDef, inst)); |
||||
for (int i = 0; i < Util.GetNumberOfOutputs(methodDef, inst); i++) { |
||||
newStack.Push(new CilStackSlot(inst)); |
||||
} |
||||
return newStack; |
||||
} |
||||
|
||||
void CopyStack(Instruction instFrom, Instruction instTo) |
||||
{ |
||||
CilStack mergedStack; |
||||
if (!Merge(stackAfter[instFrom], stackBefore[instTo], out mergedStack)) { |
||||
stackBefore[instTo] = mergedStack; |
||||
ProcessInstructionRec(instTo); |
||||
} |
||||
} |
||||
|
||||
bool Merge(CilStack stack1, CilStack stack2, out CilStack merged) |
||||
{ |
||||
// Both null
|
||||
if (stack1 == null && stack2 == null) { |
||||
throw new Exception("Both stacks are null"); |
||||
} |
||||
// One of stacks null, one is not
|
||||
if (stack1 == null || stack2 == null) { |
||||
merged = stack1 ?? stack2; |
||||
return false; |
||||
} |
||||
// Both are non-null
|
||||
if (stack1.Count != stack2.Count) { |
||||
throw new Exception("Stack merge error: different sizes"); |
||||
} |
||||
|
||||
bool same = true; |
||||
int count = stack1.Count; |
||||
merged = stack1.Clone(); |
||||
for (int i = 0; i < count; i++) { |
||||
if (!stack1[i].Equals(stack2[i])) { |
||||
merged[i] = new CilStackSlot(null); // Merge slots
|
||||
same = false; |
||||
} |
||||
} |
||||
return same; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
using System; |
||||
|
||||
using Ast = ICSharpCode.NRefactory.Ast; |
||||
using ICSharpCode.NRefactory.Ast; |
||||
|
||||
using Cecil = Mono.Cecil; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace Decompiler |
||||
{ |
||||
public static class Util |
||||
{ |
||||
public static int GetNumberOfInputs(MethodDefinition methodDef, Instruction inst) |
||||
{ |
||||
switch(inst.OpCode.StackBehaviourPop) { |
||||
case StackBehaviour.Pop0: return 0; |
||||
case StackBehaviour.Pop1: return 1; |
||||
case StackBehaviour.Popi: return 1; |
||||
case StackBehaviour.Popref: return 1; |
||||
case StackBehaviour.Pop1_pop1: return 2; |
||||
case StackBehaviour.Popi_pop1: return 2; |
||||
case StackBehaviour.Popi_popi: return 2; |
||||
case StackBehaviour.Popi_popi8: return 2; |
||||
case StackBehaviour.Popi_popr4: return 2; |
||||
case StackBehaviour.Popi_popr8: return 2; |
||||
case StackBehaviour.Popref_pop1: return 2; |
||||
case StackBehaviour.Popref_popi: return 2; |
||||
case StackBehaviour.Popi_popi_popi: return 3; |
||||
case StackBehaviour.Popref_popi_popi: return 3; |
||||
case StackBehaviour.Popref_popi_popi8: return 3; |
||||
case StackBehaviour.Popref_popi_popr4: return 3; |
||||
case StackBehaviour.Popref_popi_popr8: return 3; |
||||
case StackBehaviour.Popref_popi_popref: return 3; |
||||
case StackBehaviour.PopAll: throw new Exception("PopAll"); |
||||
case StackBehaviour.Varpop: |
||||
switch(inst.OpCode.Code) { |
||||
case Code.Call: |
||||
Cecil.MethodReference cecilMethod = ((MethodReference)inst.Operand); |
||||
if (cecilMethod.HasThis) { |
||||
return cecilMethod.Parameters.Count + 1 /* this */; |
||||
} else { |
||||
return cecilMethod.Parameters.Count; |
||||
} |
||||
case Code.Calli: throw new NotImplementedException(); |
||||
case Code.Callvirt: throw new NotImplementedException(); |
||||
case Code.Ret: |
||||
if (methodDef.ReturnType.ReturnType.FullName == Constants.Void) { |
||||
return 0; |
||||
} else { |
||||
return 1; |
||||
} |
||||
case Code.Newobj: throw new NotImplementedException(); |
||||
default: throw new Exception("Unknown Varpop opcode"); |
||||
} |
||||
default: throw new Exception("Unknown pop behaviour: " + inst.OpCode.StackBehaviourPop); |
||||
} |
||||
} |
||||
|
||||
public static int GetNumberOfOutputs(MethodDefinition methodDef, Instruction inst) |
||||
{ |
||||
switch(inst.OpCode.StackBehaviourPush) { |
||||
case StackBehaviour.Push0: return 0; |
||||
case StackBehaviour.Push1: return 1; |
||||
case StackBehaviour.Push1_push1: return 2; |
||||
case StackBehaviour.Pushi: return 1; |
||||
case StackBehaviour.Pushi8: return 1; |
||||
case StackBehaviour.Pushr4: return 1; |
||||
case StackBehaviour.Pushr8: return 1; |
||||
case StackBehaviour.Pushref: return 1; |
||||
case StackBehaviour.Varpush: // Happens only for calls
|
||||
switch(inst.OpCode.Code) { |
||||
case Code.Call: |
||||
Cecil.MethodReference cecilMethod = ((MethodReference)inst.Operand); |
||||
if (cecilMethod.ReturnType.ReturnType.FullName == Constants.Void) { |
||||
return 0; |
||||
} else { |
||||
return 1; |
||||
} |
||||
case Code.Calli: throw new NotImplementedException(); |
||||
case Code.Callvirt: throw new NotImplementedException(); |
||||
default: throw new Exception("Unknown Varpush opcode"); |
||||
} |
||||
default: throw new Exception("Unknown push behaviour: " + inst.OpCode.StackBehaviourPush); |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue