diff options
Diffstat (limited to 'ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs')
-rw-r--r-- | ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs | 839 |
1 files changed, 839 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs new file mode 100644 index 00000000..21b2b150 --- /dev/null +++ b/ICSharpCode.Decompiler/ILAst/ILAstBuilder.cs @@ -0,0 +1,839 @@ +// 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 §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(); + } + } +} |