maui-linux/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs

840 строки
30 KiB
C#
Исходник Ответственный История

// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// 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.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Cecil = Mono.Cecil;
namespace ICSharpCode.Decompiler.ILAst
{
/// <summary>
/// Converts stack-based bytecode to variable-based bytecode by calculating use-define chains
/// </summary>
public class ILAstBuilder
{
/// <summary> Immutable </summary>
struct StackSlot
{
public readonly ByteCode[] Definitions; // Reaching definitions of this stack slot
public readonly ILVariable LoadFrom; // Variable used for storage of the value
public StackSlot(ByteCode[] definitions, ILVariable loadFrom)
{
Definitions = definitions;
LoadFrom = loadFrom;
}
public static StackSlot[] ModifyStack(StackSlot[] stack, int popCount, int pushCount, ByteCode pushDefinition)
{
StackSlot[] newStack = new StackSlot[stack.Length - popCount + pushCount];
Array.Copy(stack, newStack, stack.Length - popCount);
for (int i = stack.Length - popCount; i < newStack.Length; i++) {
newStack[i] = new StackSlot(new [] { pushDefinition }, null);
}
return newStack;
}
}
/// <summary> Immutable </summary>
struct VariableSlot
{
public readonly ByteCode[] Definitions; // Reaching deinitions of this variable
public readonly bool UnknownDefinition; // Used for initial state and exceptional control flow
static readonly VariableSlot UnknownInstance = new VariableSlot(new ByteCode[0], true);
public VariableSlot(ByteCode[] definitions, bool unknownDefinition)
{
Definitions = definitions;
UnknownDefinition = unknownDefinition;
}
public static VariableSlot[] CloneVariableState(VariableSlot[] state)
{
VariableSlot[] clone = new VariableSlot[state.Length];
Array.Copy(state, clone, state.Length);
return clone;
}
public static VariableSlot[] MakeUknownState(int varCount)
{
VariableSlot[] unknownVariableState = new VariableSlot[varCount];
for (int i = 0; i < unknownVariableState.Length; i++) {
unknownVariableState[i] = UnknownInstance;
}
return unknownVariableState;
}
}
sealed class ByteCode
{
public ILLabel Label; // Non-null only if needed
public int Offset;
public int EndOffset;
public ILCode Code;
public object Operand;
public int? PopCount; // Null means pop all
public int PushCount;
public string Name { get { return "IL_" + Offset.ToString("X2"); } }
public ByteCode Next;
public Instruction[] Prefixes; // Non-null only if needed
public StackSlot[] StackBefore; // Unique per bytecode; not shared
public VariableSlot[] VariablesBefore; // Unique per bytecode; not shared
public List<ILVariable> StoreTo; // Store result of instruction to those AST variables
public bool IsVariableDefinition {
get {
return (Code == ILCode.Stloc) || (Code == ILCode.Ldloca && Next != null && Next.Code == ILCode.Initobj);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
// Label
sb.Append(Name);
sb.Append(':');
if (Label != null)
sb.Append('*');
// Name
sb.Append(' ');
if (Prefixes != null) {
foreach (var prefix in Prefixes) {
sb.Append(prefix.OpCode.Name);
sb.Append(' ');
}
}
sb.Append(Code.GetName());
if (Operand != null) {
sb.Append(' ');
if (Operand is Instruction) {
sb.Append("IL_" + ((Instruction)Operand).Offset.ToString("X2"));
} else if (Operand is Instruction[]) {
foreach(Instruction inst in (Instruction[])Operand) {
sb.Append("IL_" + inst.Offset.ToString("X2"));
sb.Append(" ");
}
} else if (Operand is ILLabel) {
sb.Append(((ILLabel)Operand).Name);
} else if (Operand is ILLabel[]) {
foreach(ILLabel label in (ILLabel[])Operand) {
sb.Append(label.Name);
sb.Append(" ");
}
} else {
sb.Append(Operand.ToString());
}
}
if (StackBefore != null) {
sb.Append(" StackBefore={");
bool first = true;
foreach (StackSlot slot in StackBefore) {
if (!first) sb.Append(",");
bool first2 = true;
foreach(ByteCode defs in slot.Definitions) {
if (!first2) sb.Append("|");
sb.AppendFormat("IL_{0:X2}", defs.Offset);
first2 = false;
}
first = false;
}
sb.Append("}");
}
if (StoreTo != null && StoreTo.Count > 0) {
sb.Append(" StoreTo={");
bool first = true;
foreach (ILVariable stackVar in StoreTo) {
if (!first) sb.Append(",");
sb.Append(stackVar.Name);
first = false;
}
sb.Append("}");
}
if (VariablesBefore != null) {
sb.Append(" VarsBefore={");
bool first = true;
foreach (VariableSlot varSlot in VariablesBefore) {
if (!first) sb.Append(",");
if (varSlot.UnknownDefinition) {
sb.Append("?");
} else {
bool first2 = true;
foreach (ByteCode storedBy in varSlot.Definitions) {
if (!first2) sb.Append("|");
sb.AppendFormat("IL_{0:X2}", storedBy.Offset);
first2 = false;
}
}
first = false;
}
sb.Append("}");
}
return sb.ToString();
}
}
MethodDefinition methodDef;
bool optimize;
// Virtual instructions to load exception on stack
Dictionary<ExceptionHandler, ByteCode> ldexceptions = new Dictionary<ExceptionHandler, ByteCode>();
DecompilerContext context;
public List<ILNode> Build(MethodDefinition methodDef, bool optimize, DecompilerContext context)
{
this.methodDef = methodDef;
this.optimize = optimize;
this.context = context;
if (methodDef.Body.Instructions.Count == 0) return new List<ILNode>();
List<ByteCode> body = StackAnalysis(methodDef);
List<ILNode> ast = ConvertToAst(body, new HashSet<ExceptionHandler>(methodDef.Body.ExceptionHandlers));
return ast;
}
static int offsetIdx;
List<ByteCode> StackAnalysis(MethodDefinition methodDef)
{
Dictionary<Instruction, ByteCode> instrToByteCode = new Dictionary<Instruction, ByteCode>();
// Create temporary structure for the stack analysis
List<ByteCode> body = new List<ByteCode>(methodDef.Body.Instructions.Count);
List<Instruction> prefixes = null;
foreach(Instruction inst in methodDef.Body.Instructions) {
if (inst.OpCode.OpCodeType == OpCodeType.Prefix) {
if (prefixes == null)
prefixes = new List<Instruction>(1);
prefixes.Add(inst);
continue;
}
ILCode code = (ILCode)inst.OpCode.Code;
object operand = inst.Operand;
ILCodeUtil.ExpandMacro(ref code, ref operand, methodDef.Body);
ByteCode byteCode = new ByteCode() {
Offset = inst.Offset,
EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize,
Code = code,
Operand = operand,
PopCount = inst.GetPopDelta(methodDef),
PushCount = inst.GetPushDelta()
};
if (prefixes != null) {
instrToByteCode[prefixes[0]] = byteCode;
byteCode.Offset = prefixes[0].Offset;
byteCode.Prefixes = prefixes.ToArray();
prefixes = null;
} else {
instrToByteCode[inst] = byteCode;
}
body.Add(byteCode);
}
for (int i = 0; i < body.Count - 1; i++) {
body[i].Next = body[i + 1];
}
Stack<ByteCode> agenda = new Stack<ByteCode>();
int varCount = methodDef.Body.Variables.Count;
var exceptionHandlerStarts = new HashSet<ByteCode>(methodDef.Body.ExceptionHandlers.Select(eh => instrToByteCode[eh.HandlerStart]));
// Add known states
if(methodDef.Body.HasExceptionHandlers) {
foreach(ExceptionHandler ex in methodDef.Body.ExceptionHandlers) {
ByteCode handlerStart = instrToByteCode[ex.HandlerStart];
handlerStart.StackBefore = new StackSlot[0];
handlerStart.VariablesBefore = VariableSlot.MakeUknownState(varCount);
if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) {
// Catch and Filter handlers start with the exeption on the stack
ByteCode ldexception = new ByteCode() {
Code = ILCode.Ldexception,
Operand = ex.CatchType,
PopCount = 0,
PushCount = 1
};
ldexceptions[ex] = ldexception;
handlerStart.StackBefore = new StackSlot[] { new StackSlot(new [] { ldexception }, null) };
}
agenda.Push(handlerStart);
if (ex.HandlerType == ExceptionHandlerType.Filter)
{
ByteCode filterStart = instrToByteCode[ex.FilterStart];
ByteCode ldexception = new ByteCode() {
Code = ILCode.Ldexception,
Operand = ex.CatchType,
PopCount = 0,
PushCount = 1
};
// TODO: ldexceptions[ex] = ldexception;
filterStart.StackBefore = new StackSlot[] { new StackSlot(new [] { ldexception }, null) };
filterStart.VariablesBefore = VariableSlot.MakeUknownState(varCount);
agenda.Push(filterStart);
}
}
}
body[0].StackBefore = new StackSlot[0];
body[0].VariablesBefore = VariableSlot.MakeUknownState(varCount);
agenda.Push(body[0]);
// Process agenda
while(agenda.Count > 0) {
ByteCode byteCode = agenda.Pop();
// Calculate new stack
StackSlot[] newStack = StackSlot.ModifyStack(byteCode.StackBefore, byteCode.PopCount ?? byteCode.StackBefore.Length, byteCode.PushCount, byteCode);
// Calculate new variable state
VariableSlot[] newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore);
if (byteCode.IsVariableDefinition) {
newVariableState[((VariableReference)byteCode.Operand).Index] = new VariableSlot(new [] { byteCode }, false);
}
// After the leave, finally block might have touched the variables
if (byteCode.Code == ILCode.Leave) {
newVariableState = VariableSlot.MakeUknownState(varCount);
}
// Find all successors
List<ByteCode> branchTargets = new List<ByteCode>();
if (!byteCode.Code.IsUnconditionalControlFlow()) {
if (exceptionHandlerStarts.Contains(byteCode.Next)) {
// Do not fall though down to exception handler
// It is invalid IL as per ECMA-335 <20>12.4.2.8.1, but some obfuscators produce it
} else {
branchTargets.Add(byteCode.Next);
}
}
if (byteCode.Operand is Instruction[]) {
foreach(Instruction inst in (Instruction[])byteCode.Operand) {
ByteCode target = instrToByteCode[inst];
branchTargets.Add(target);
// The target of a branch must have label
if (target.Label == null) {
target.Label = new ILLabel() { Name = target.Name };
}
}
} else if (byteCode.Operand is Instruction) {
ByteCode target = instrToByteCode[(Instruction)byteCode.Operand];
branchTargets.Add(target);
// The target of a branch must have label
if (target.Label == null) {
target.Label = new ILLabel() { Name = target.Name };
}
}
// Apply the state to successors
foreach (ByteCode branchTarget in branchTargets) {
if (branchTarget.StackBefore == null && branchTarget.VariablesBefore == null) {
if (branchTargets.Count == 1) {
branchTarget.StackBefore = newStack;
branchTarget.VariablesBefore = newVariableState;
} else {
// Do not share data for several bytecodes
branchTarget.StackBefore = StackSlot.ModifyStack(newStack, 0, 0, null);
branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState);
}
agenda.Push(branchTarget);
} else {
if (branchTarget.StackBefore.Length != newStack.Length) {
throw new Exception("Inconsistent stack size at " + byteCode.Name);
}
// Be careful not to change our new data - it might be reused for several branch targets.
// In general, be careful that two bytecodes never share data structures.
bool modified = false;
// Merge stacks - modify the target
for (int i = 0; i < newStack.Length; i++) {
ByteCode[] oldDefs = branchTarget.StackBefore[i].Definitions;
ByteCode[] newDefs = oldDefs.Union(newStack[i].Definitions);
if (newDefs.Length > oldDefs.Length) {
branchTarget.StackBefore[i] = new StackSlot(newDefs, null);
modified = true;
}
}
// Merge variables - modify the target
for (int i = 0; i < newVariableState.Length; i++) {
VariableSlot oldSlot = branchTarget.VariablesBefore[i];
VariableSlot newSlot = newVariableState[i];
if (!oldSlot.UnknownDefinition) {
if (newSlot.UnknownDefinition) {
branchTarget.VariablesBefore[i] = newSlot;
modified = true;
} else {
ByteCode[] oldDefs = oldSlot.Definitions;
ByteCode[] newDefs = oldDefs.Union(newSlot.Definitions);
if (newDefs.Length > oldDefs.Length) {
branchTarget.VariablesBefore[i] = new VariableSlot(newDefs, false);
modified = true;
}
}
}
}
if (modified) {
agenda.Push(branchTarget);
}
}
}
}
// Occasionally the compilers or obfuscators generate unreachable code (which might be intentionally invalid)
// I believe it is safe to just remove it
body.RemoveAll(b => b.StackBefore == null);
// Generate temporary variables to replace stack
foreach(ByteCode byteCode in body) {
int argIdx = 0;
int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length;
for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) {
var offset = byteCode.Offset != 0 ? byteCode.Offset : offsetIdx++;
ILVariable tmpVar = new ILVariable() { Name = string.Format("arg_{0:X2}_{1}", offset, argIdx), IsGenerated = true };
byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar);
foreach(ByteCode pushedBy in byteCode.StackBefore[i].Definitions) {
if (pushedBy.StoreTo == null) {
pushedBy.StoreTo = new List<ILVariable>(1);
}
pushedBy.StoreTo.Add(tmpVar);
}
argIdx++;
}
}
// Try to use single temporary variable insted of several if possilbe (especially useful for dup)
// This has to be done after all temporary variables are assigned so we know about all loads
foreach(ByteCode byteCode in body) {
if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) {
var locVars = byteCode.StoreTo;
// For each of the variables, find the location where it is loaded - there should be preciesly one
var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList();
// We now know that all the variables have a single load,
// Let's make sure that they have also a single store - us
if (loadedBy.All(slot => slot.Definitions.Length == 1 && slot.Definitions[0] == byteCode)) {
// Great - we can reduce everything into single variable
var offset = byteCode.Offset != 0 ? byteCode.Offset : offsetIdx++;
ILVariable tmpVar = new ILVariable() { Name = string.Format("expr_{0:X2}", offset), IsGenerated = true };
byteCode.StoreTo = new List<ILVariable>() { tmpVar };
foreach(ByteCode bc in body) {
for (int i = 0; i < bc.StackBefore.Length; i++) {
// Is it one of the variable to be merged?
if (locVars.Contains(bc.StackBefore[i].LoadFrom)) {
// Replace with the new temp variable
bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar);
}
}
}
}
}
}
// Split and convert the normal local variables
ConvertLocalVariables(body);
// Convert branch targets to labels
foreach(ByteCode byteCode in body) {
if (byteCode.Operand is Instruction[]) {
List<ILLabel> newOperand = new List<ILLabel>();
foreach(Instruction target in (Instruction[])byteCode.Operand) {
newOperand.Add(instrToByteCode[target].Label);
}
byteCode.Operand = newOperand.ToArray();
} else if (byteCode.Operand is Instruction) {
byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label;
}
}
// Convert parameters to ILVariables
ConvertParameters(body);
return body;
}
static bool IsDeterministicLdloca(ByteCode b)
{
var v = b.Operand;
b = b.Next;
if (b.Code == ILCode.Initobj) return true;
// instance method calls on value types use the variable ref deterministically
int stack = 1;
while (true) {
if (b.PopCount == null) return false;
stack -= b.PopCount.GetValueOrDefault();
if (stack == 0) break;
if (stack < 0) return false;
if (b.Code.IsConditionalControlFlow() || b.Code.IsUnconditionalControlFlow()) return false;
switch (b.Code) {
case ILCode.Ldloc:
case ILCode.Ldloca:
case ILCode.Stloc:
if (b.Operand == v) return false;
break;
}
stack += b.PushCount;
b = b.Next;
if (b == null) return false;
}
if (b.Code == ILCode.Ldfld || b.Code == ILCode.Stfld)
return true;
return (b.Code == ILCode.Call || b.Code == ILCode.Callvirt) && ((MethodReference)b.Operand).HasThis;
}
sealed class VariableInfo
{
public ILVariable Variable;
public List<ByteCode> Defs;
public List<ByteCode> Uses;
}
/// <summary>
/// If possible, separates local variables into several independent variables.
/// It should undo any compilers merging.
/// </summary>
void ConvertLocalVariables(List<ByteCode> body)
{
foreach(VariableDefinition varDef in methodDef.Body.Variables) {
// Find all definitions and uses of this variable
var defs = body.Where(b => b.Operand == varDef && b.IsVariableDefinition).ToList();
var uses = body.Where(b => b.Operand == varDef && !b.IsVariableDefinition).ToList();
List<VariableInfo> newVars;
// If the variable is pinned, use single variable.
// If any of the uses is from unknown definition, use single variable
// If any of the uses is ldloca with a nondeterministic usage pattern, use single variable
if (!optimize || varDef.IsPinned || uses.Any(b => b.VariablesBefore[varDef.Index].UnknownDefinition || (b.Code == ILCode.Ldloca && !IsDeterministicLdloca(b)))) {
newVars = new List<VariableInfo>(1) { new VariableInfo() {
Variable = new ILVariable() {
Name = string.IsNullOrEmpty(varDef.Name) ? "var_" + varDef.Index : varDef.Name,
Type = varDef.IsPinned ? ((PinnedType)varDef.VariableType).ElementType : varDef.VariableType,
OriginalVariable = varDef
},
Defs = defs,
Uses = uses
}};
} else {
// Create a new variable for each definition
newVars = defs.Select(def => new VariableInfo() {
Variable = new ILVariable() {
Name = (string.IsNullOrEmpty(varDef.Name) ? "var_" + varDef.Index : varDef.Name) + "_" + def.Offset.ToString("X2"),
Type = varDef.VariableType,
OriginalVariable = varDef
},
Defs = new List<ByteCode>() { def },
Uses = new List<ByteCode>()
}).ToList();
// VB.NET uses the 'init' to allow use of uninitialized variables.
// We do not really care about them too much - if the original variable
// was uninitialized at that point it means that no store was called and
// thus all our new variables must be uninitialized as well.
// So it does not matter which one we load.
// TODO: We should add explicit initialization so that C# code compiles.
// Remember to handle cases where one path inits the variable, but other does not.
// Add loads to the data structure; merge variables if necessary
foreach(ByteCode use in uses) {
ByteCode[] useDefs = use.VariablesBefore[varDef.Index].Definitions;
if (useDefs.Length == 1) {
VariableInfo newVar = newVars.Single(v => v.Defs.Contains(useDefs[0]));
newVar.Uses.Add(use);
} else {
List<VariableInfo> mergeVars = newVars.Where(v => v.Defs.Intersect(useDefs).Any()).ToList();
VariableInfo mergedVar = new VariableInfo() {
Variable = mergeVars[0].Variable,
Defs = mergeVars.SelectMany(v => v.Defs).ToList(),
Uses = mergeVars.SelectMany(v => v.Uses).ToList()
};
mergedVar.Uses.Add(use);
newVars = newVars.Except(mergeVars).ToList();
newVars.Add(mergedVar);
}
}
}
// Set bytecode operands
foreach(VariableInfo newVar in newVars) {
foreach(ByteCode def in newVar.Defs) {
def.Operand = newVar.Variable;
}
foreach(ByteCode use in newVar.Uses) {
use.Operand = newVar.Variable;
}
}
}
}
public List<ILVariable> Parameters = new List<ILVariable>();
void ConvertParameters(List<ByteCode> body)
{
ILVariable thisParameter = null;
if (methodDef.HasThis) {
TypeReference type = methodDef.DeclaringType;
thisParameter = new ILVariable();
thisParameter.Type = type.IsValueType ? new ByReferenceType(type) : type;
thisParameter.Name = "this";
thisParameter.OriginalParameter = methodDef.Body.ThisParameter;
}
foreach (ParameterDefinition p in methodDef.Parameters) {
Parameters.Add(new ILVariable { Type = p.ParameterType, Name = p.Name, OriginalParameter = p });
}
if (Parameters.Count > 0 && (methodDef.IsSetter || methodDef.IsAddOn || methodDef.IsRemoveOn)) {
// last parameter must be 'value', so rename it
Parameters.Last().Name = "value";
}
foreach (ByteCode byteCode in body) {
ParameterDefinition p;
switch (byteCode.Code) {
case ILCode.__Ldarg:
p = (ParameterDefinition)byteCode.Operand;
byteCode.Code = ILCode.Ldloc;
byteCode.Operand = p.Index < 0 ? thisParameter : Parameters[p.Index];
break;
case ILCode.__Starg:
p = (ParameterDefinition)byteCode.Operand;
byteCode.Code = ILCode.Stloc;
byteCode.Operand = p.Index < 0 ? thisParameter : Parameters[p.Index];
break;
case ILCode.__Ldarga:
p = (ParameterDefinition)byteCode.Operand;
byteCode.Code = ILCode.Ldloca;
byteCode.Operand = p.Index < 0 ? thisParameter : Parameters[p.Index];
break;
}
}
if (thisParameter != null)
Parameters.Add(thisParameter);
}
List<ILNode> ConvertToAst(List<ByteCode> body, HashSet<ExceptionHandler> ehs)
{
List<ILNode> ast = new List<ILNode>();
while (ehs.Any()) {
ILTryCatchBlock tryCatchBlock = new ILTryCatchBlock();
// Find the first and widest scope
int tryStart = ehs.Min(eh => eh.TryStart.Offset);
int tryEnd = ehs.Where(eh => eh.TryStart.Offset == tryStart).Max(eh => eh.TryEnd.Offset);
var handlers = ehs.Where(eh => eh.TryStart.Offset == tryStart && eh.TryEnd.Offset == tryEnd).ToList();
// Remember that any part of the body migt have been removed due to unreachability
// Cut all instructions up to the try block
{
int tryStartIdx = 0;
while (tryStartIdx < body.Count && body[tryStartIdx].Offset < tryStart) tryStartIdx++;
ast.AddRange(ConvertToAst(body.CutRange(0, tryStartIdx)));
}
// Cut the try block
{
HashSet<ExceptionHandler> nestedEHs = new HashSet<ExceptionHandler>(ehs.Where(eh => (tryStart <= eh.TryStart.Offset && eh.TryEnd.Offset < tryEnd) || (tryStart < eh.TryStart.Offset && eh.TryEnd.Offset <= tryEnd)));
ehs.ExceptWith(nestedEHs);
int tryEndIdx = 0;
while (tryEndIdx < body.Count && body[tryEndIdx].Offset < tryEnd) tryEndIdx++;
tryCatchBlock.TryBlock = new ILBlock(ConvertToAst(body.CutRange(0, tryEndIdx), nestedEHs));
}
// Cut all handlers
tryCatchBlock.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>();
foreach(ExceptionHandler eh in handlers) {
int handlerEndOffset = eh.HandlerEnd == null ? methodDef.Body.CodeSize : eh.HandlerEnd.Offset;
int startIdx = 0;
while (startIdx < body.Count && body[startIdx].Offset < eh.HandlerStart.Offset) startIdx++;
int endIdx = 0;
while (endIdx < body.Count && body[endIdx].Offset < handlerEndOffset) endIdx++;
HashSet<ExceptionHandler> nestedEHs = new HashSet<ExceptionHandler>(ehs.Where(e => (eh.HandlerStart.Offset <= e.TryStart.Offset && e.TryEnd.Offset < handlerEndOffset) || (eh.HandlerStart.Offset < e.TryStart.Offset && e.TryEnd.Offset <= handlerEndOffset)));
ehs.ExceptWith(nestedEHs);
List<ILNode> handlerAst = ConvertToAst(body.CutRange(startIdx, endIdx - startIdx), nestedEHs);
if (eh.HandlerType == ExceptionHandlerType.Catch) {
ILTryCatchBlock.CatchBlock catchBlock = new ILTryCatchBlock.CatchBlock() {
ExceptionType = eh.CatchType,
Body = handlerAst
};
// Handle the automatically pushed exception on the stack
ByteCode ldexception = ldexceptions[eh];
if (ldexception.StoreTo == null || ldexception.StoreTo.Count == 0) {
// Exception is not used
catchBlock.ExceptionVariable = null;
} else if (ldexception.StoreTo.Count == 1) {
ILExpression first = catchBlock.Body[0] as ILExpression;
if (first != null &&
first.Code == ILCode.Pop &&
first.Arguments[0].Code == ILCode.Ldloc &&
first.Arguments[0].Operand == ldexception.StoreTo[0])
{
// The exception is just poped - optimize it all away;
if (context.Settings.AlwaysGenerateExceptionVariableForCatchBlocks)
catchBlock.ExceptionVariable = new ILVariable() { Name = "ex_" + eh.HandlerStart.Offset.ToString("X2"), IsGenerated = true };
else
catchBlock.ExceptionVariable = null;
catchBlock.Body.RemoveAt(0);
} else {
catchBlock.ExceptionVariable = ldexception.StoreTo[0];
}
} else {
ILVariable exTemp = new ILVariable() { Name = "ex_" + eh.HandlerStart.Offset.ToString("X2"), IsGenerated = true };
catchBlock.ExceptionVariable = exTemp;
foreach(ILVariable storeTo in ldexception.StoreTo) {
catchBlock.Body.Insert(0, new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, exTemp)));
}
}
tryCatchBlock.CatchBlocks.Add(catchBlock);
} else if (eh.HandlerType == ExceptionHandlerType.Finally) {
tryCatchBlock.FinallyBlock = new ILBlock(handlerAst);
} else if (eh.HandlerType == ExceptionHandlerType.Fault) {
tryCatchBlock.FaultBlock = new ILBlock(handlerAst);
} else {
// TODO: ExceptionHandlerType.Filter
}
}
ehs.ExceptWith(handlers);
ast.Add(tryCatchBlock);
}
// Add whatever is left
ast.AddRange(ConvertToAst(body));
return ast;
}
List<ILNode> ConvertToAst(List<ByteCode> body)
{
List<ILNode> ast = new List<ILNode>();
// Convert stack-based IL code to ILAst tree
foreach(ByteCode byteCode in body) {
ILRange ilRange = new ILRange(byteCode.Offset, byteCode.EndOffset);
if (byteCode.StackBefore == null) {
// Unreachable code
continue;
}
ILExpression expr = new ILExpression(byteCode.Code, byteCode.Operand);
expr.ILRanges.Add(ilRange);
if (byteCode.Prefixes != null && byteCode.Prefixes.Length > 0) {
ILExpressionPrefix[] prefixes = new ILExpressionPrefix[byteCode.Prefixes.Length];
for (int i = 0; i < prefixes.Length; i++) {
prefixes[i] = new ILExpressionPrefix((ILCode)byteCode.Prefixes[i].OpCode.Code, byteCode.Prefixes[i].Operand);
}
expr.Prefixes = prefixes;
}
// Label for this instruction
if (byteCode.Label != null) {
ast.Add(byteCode.Label);
}
// Reference arguments using temporary variables
int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length;
for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) {
StackSlot slot = byteCode.StackBefore[i];
expr.Arguments.Add(new ILExpression(ILCode.Ldloc, slot.LoadFrom));
}
// Store the result to temporary variable(s) if needed
if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) {
ast.Add(expr);
} else if (byteCode.StoreTo.Count == 1) {
ast.Add(new ILExpression(ILCode.Stloc, byteCode.StoreTo[0], expr));
} else {
var offset = byteCode.Offset != 0 ? byteCode.Offset : offsetIdx++;
ILVariable tmpVar = new ILVariable() { Name = "expr_" + offset.ToString("X2"), IsGenerated = true };
ast.Add(new ILExpression(ILCode.Stloc, tmpVar, expr));
foreach(ILVariable storeTo in byteCode.StoreTo.AsEnumerable().Reverse()) {
ast.Add(new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, tmpVar)));
}
}
}
return ast;
}
}
public static class ILAstBuilderExtensionMethods
{
public static List<T> CutRange<T>(this List<T> list, int start, int count)
{
List<T> ret = new List<T>(count);
for (int i = 0; i < count; i++) {
ret.Add(list[start + i]);
}
list.RemoveRange(start, count);
return ret;
}
public static T[] Union<T>(this T[] a, T b)
{
if (a.Length == 0)
return new[] { b };
if (Array.IndexOf(a, b) >= 0)
return a;
var res = new T[a.Length + 1];
Array.Copy(a, 0, res, 0, a.Length);
res[res.Length - 1] = b;
return res;
}
public static T[] Union<T>(this T[] a, T[] b)
{
if (a == b)
return a;
if (a.Length == 0)
return b;
if (b.Length == 0)
return a;
if (a.Length == 1) {
if (b.Length == 1)
return a[0].Equals(b[0]) ? a : new[] { a[0], b[0] };
return b.Union(a[0]);
}
if (b.Length == 1)
return a.Union(b[0]);
return Enumerable.Union(a, b).ToArray();
}
}
}