diff --git a/Decompiler.csproj b/Decompiler.csproj
index d34828ecf..335c96556 100644
--- a/Decompiler.csproj
+++ b/Decompiler.csproj
@@ -42,6 +42,8 @@
+
+
MainForm.cs
diff --git a/src/AstMetodBodyBuilder.cs b/src/AstMetodBodyBuilder.cs
index 0b6791973..66dfc153e 100644
--- a/src/AstMetodBodyBuilder.cs
+++ b/src/AstMetodBodyBuilder.cs
@@ -18,10 +18,12 @@ namespace Decompiler
methodDef.Body.Simplify();
+ StackAnalysis stackAnalysis = new StackAnalysis(methodDef);
+
foreach(Instruction instr in methodDef.Body.Instructions) {
OpCode opCode = instr.OpCode;
string description =
- string.Format("/* {1, -22} # {2}->{3} {4} {5} */",
+ string.Format(" {1, -22} # {2}->{3} {4} {5}",
instr.Offset,
opCode + " " + FormatInstructionOperand(instr.Operand),
opCode.StackBehaviourPop,
@@ -46,7 +48,7 @@ namespace Decompiler
new Ast.IdentifierExpression("arg2"),
new Ast.IdentifierExpression("arg3"));
if (codeExpr is Ast.Expression) {
- if (GetNumberOfOutputs(methodDef, instr) == 1) {
+ if (Util.GetNumberOfOutputs(methodDef, instr) == 1) {
type = type ?? "object";
string name = string.Format("expr{0:X2}", instr.Offset);
Ast.LocalVariableDeclaration astLocal = new Ast.LocalVariableDeclaration(new Ast.TypeReference(type.ToString()));
@@ -59,15 +61,22 @@ namespace Decompiler
astStatement = (Ast.Statement)codeExpr;
}
} catch (NotImplementedException) {
- astStatement = new Ast.ExpressionStatement(new PrimitiveExpression(description, description));
+ astStatement = MakeComment(description);
}
astBlock.Children.Add(new Ast.LabelStatement(string.Format("IL_{0:X2}", instr.Offset)));
astBlock.Children.Add(astStatement);
+ astBlock.Children.Add(MakeComment(" " + stackAnalysis.StackAfter[instr].ToString()));
}
return astBlock;
}
+ static Ast.ExpressionStatement MakeComment(string text)
+ {
+ text = "/*" + text + "*/";
+ return new Ast.ExpressionStatement(new PrimitiveExpression(text, text));
+ }
+
static object FormatInstructionOperand(object operand)
{
if (operand == null) {
@@ -91,34 +100,6 @@ namespace Decompiler
}
}
- 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);
- }
- }
-
static object MakeCodeDomExpression(MethodDefinition methodDef, Instruction inst, params Ast.Expression[] args)
{
OpCode opCode = inst.OpCode;
diff --git a/src/StackAnalysis.cs b/src/StackAnalysis.cs
new file mode 100644
index 000000000..3551e529d
--- /dev/null
+++ b/src/StackAnalysis.cs
@@ -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);
+ }
+ }
+ }
+
+ /// The tail of the list is the top of the stack
+ public class CilStack: List {
+ 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 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 stackBefore = new Dictionary();
+ Dictionary stackAfter = new Dictionary();
+
+ public Dictionary StackBefore {
+ get { return stackBefore; }
+ }
+
+ public Dictionary 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;
+ }
+ }
+}
diff --git a/src/Util.cs b/src/Util.cs
new file mode 100644
index 000000000..5e1cbfa1b
--- /dev/null
+++ b/src/Util.cs
@@ -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);
+ }
+ }
+ }
+}