summaryrefslogtreecommitdiff
path: root/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs
diff options
context:
space:
mode:
Diffstat (limited to 'ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs')
-rw-r--r--ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs635
1 files changed, 635 insertions, 0 deletions
diff --git a/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs
new file mode 100644
index 00000000..7a9a09ed
--- /dev/null
+++ b/ICSharpCode.Decompiler/ILAst/YieldReturnDecompiler.cs
@@ -0,0 +1,635 @@
+// 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.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Mono.Cecil;
+
+namespace ICSharpCode.Decompiler.ILAst
+{
+ internal class YieldReturnDecompiler
+ {
+ // For a description on the code generated by the C# compiler for yield return:
+ // http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx
+
+ // The idea here is:
+ // - Figure out whether the current method is instanciating an enumerator
+ // - Figure out which of the fields is the state field
+ // - Construct an exception table based on states. This allows us to determine, for each state, what the parent try block is.
+
+ // See http://community.sharpdevelop.net/blogs/danielgrunwald/archive/2011/03/06/ilspy-yield-return.aspx
+ // for a description of this step.
+
+ DecompilerContext context;
+ TypeDefinition enumeratorType;
+ MethodDefinition enumeratorCtor;
+ MethodDefinition disposeMethod;
+ FieldDefinition stateField;
+ FieldDefinition currentField;
+ Dictionary<FieldDefinition, ILVariable> fieldToParameterMap = new Dictionary<FieldDefinition, ILVariable>();
+ List<ILNode> newBody;
+
+ #region Run() method
+ public static void Run(DecompilerContext context, ILBlock method)
+ {
+ if (!context.Settings.YieldReturn)
+ return; // abort if enumerator decompilation is disabled
+ var yrd = new YieldReturnDecompiler();
+ yrd.context = context;
+ if (!yrd.MatchEnumeratorCreationPattern(method))
+ return;
+ yrd.enumeratorType = yrd.enumeratorCtor.DeclaringType;
+ #if DEBUG
+ if (Debugger.IsAttached) {
+ yrd.Run();
+ } else {
+ #endif
+ try {
+ yrd.Run();
+ } catch (SymbolicAnalysisFailedException) {
+ return;
+ }
+ #if DEBUG
+ }
+ #endif
+ method.Body.Clear();
+ method.EntryGoto = null;
+ method.Body.AddRange(yrd.newBody);
+
+ // Repeat the inlining/copy propagation optimization because the conversion of field access
+ // to local variables can open up additional inlining possibilities.
+ ILInlining inlining = new ILInlining(method);
+ inlining.InlineAllVariables();
+ inlining.CopyPropagation();
+ }
+
+ void Run()
+ {
+ AnalyzeCtor();
+ AnalyzeCurrentProperty();
+ ResolveIEnumerableIEnumeratorFieldMapping();
+ ConstructExceptionTable();
+ AnalyzeMoveNext();
+ TranslateFieldsToLocalAccess();
+ }
+ #endregion
+
+ #region Match the enumerator creation pattern
+ bool MatchEnumeratorCreationPattern(ILBlock method)
+ {
+ if (method.Body.Count == 0)
+ return false;
+ ILExpression newObj;
+ if (method.Body.Count == 1) {
+ // ret(newobj(...))
+ if (method.Body[0].Match(ILCode.Ret, out newObj))
+ return MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor);
+ else
+ return false;
+ }
+ // stloc(var_1, newobj(..)
+ ILVariable var1;
+ if (!method.Body[0].Match(ILCode.Stloc, out var1, out newObj))
+ return false;
+ if (!MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor))
+ return false;
+
+ int i;
+ for (i = 1; i < method.Body.Count; i++) {
+ // stfld(..., ldloc(var_1), ldloc(parameter))
+ FieldReference storedField;
+ ILExpression ldloc, loadParameter;
+ if (!method.Body[i].Match(ILCode.Stfld, out storedField, out ldloc, out loadParameter))
+ break;
+ ILVariable loadedVar, loadedArg;
+ if (!ldloc.Match(ILCode.Ldloc, out loadedVar) || !loadParameter.Match(ILCode.Ldloc, out loadedArg))
+ return false;
+ storedField = GetFieldDefinition(storedField);
+ if (loadedVar != var1 || storedField == null || !loadedArg.IsParameter)
+ return false;
+ fieldToParameterMap[(FieldDefinition)storedField] = loadedArg;
+ }
+ ILVariable var2;
+ ILExpression ldlocForStloc2;
+ if (i < method.Body.Count && method.Body[i].Match(ILCode.Stloc, out var2, out ldlocForStloc2)) {
+ // stloc(var_2, ldloc(var_1))
+ if (ldlocForStloc2.Code != ILCode.Ldloc || ldlocForStloc2.Operand != var1)
+ return false;
+ i++;
+ } else {
+ // the compiler might skip the above instruction in release builds; in that case, it directly returns stloc.Operand
+ var2 = var1;
+ }
+ ILExpression retArg;
+ if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out retArg)) {
+ // ret(ldloc(var_2))
+ if (retArg.Code == ILCode.Ldloc && retArg.Operand == var2) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static FieldDefinition GetFieldDefinition(FieldReference field)
+ {
+ return CecilExtensions.ResolveWithinSameModule(field);
+ }
+
+ static MethodDefinition GetMethodDefinition(MethodReference method)
+ {
+ return CecilExtensions.ResolveWithinSameModule(method);
+ }
+
+ bool MatchEnumeratorCreationNewObj(ILExpression expr, out MethodDefinition ctor)
+ {
+ // newobj(CurrentType/...::.ctor, ldc.i4(-2))
+ ctor = null;
+ if (expr.Code != ILCode.Newobj || expr.Arguments.Count != 1)
+ return false;
+ if (expr.Arguments[0].Code != ILCode.Ldc_I4)
+ return false;
+ int initialState = (int)expr.Arguments[0].Operand;
+ if (!(initialState == -2 || initialState == 0))
+ return false;
+ ctor = GetMethodDefinition(expr.Operand as MethodReference);
+ if (ctor == null || ctor.DeclaringType.DeclaringType != context.CurrentType)
+ return false;
+ return IsCompilerGeneratorEnumerator(ctor.DeclaringType);
+ }
+
+ public static bool IsCompilerGeneratorEnumerator(TypeDefinition type)
+ {
+ if (!(type.DeclaringType != null && type.IsCompilerGenerated()))
+ return false;
+ foreach (TypeReference i in type.Interfaces) {
+ if (i.Namespace == "System.Collections" && i.Name == "IEnumerator")
+ return true;
+ }
+ return false;
+ }
+ #endregion
+
+ #region Figure out what the 'state' field is (analysis of .ctor())
+ /// <summary>
+ /// Looks at the enumerator's ctor and figures out which of the fields holds the state.
+ /// </summary>
+ void AnalyzeCtor()
+ {
+ ILBlock method = CreateILAst(enumeratorCtor);
+
+ foreach (ILNode node in method.Body) {
+ FieldReference field;
+ ILExpression instExpr;
+ ILExpression stExpr;
+ ILVariable arg;
+ if (node.Match(ILCode.Stfld, out field, out instExpr, out stExpr) &&
+ instExpr.MatchThis() &&
+ stExpr.Match(ILCode.Ldloc, out arg) &&
+ arg.IsParameter && arg.OriginalParameter.Index == 0)
+ {
+ stateField = GetFieldDefinition(field);
+ }
+ }
+ if (stateField == null)
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ /// <summary>
+ /// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step.
+ /// </summary>
+ ILBlock CreateILAst(MethodDefinition method)
+ {
+ if (method == null || !method.HasBody)
+ throw new SymbolicAnalysisFailedException();
+
+ ILBlock ilMethod = new ILBlock();
+ ILAstBuilder astBuilder = new ILAstBuilder();
+ ilMethod.Body = astBuilder.Build(method, true, context);
+ ILAstOptimizer optimizer = new ILAstOptimizer();
+ optimizer.Optimize(context, ilMethod, ILAstOptimizationStep.YieldReturn);
+ return ilMethod;
+ }
+ #endregion
+
+ #region Figure out what the 'current' field is (analysis of get_Current())
+ /// <summary>
+ /// Looks at the enumerator's get_Current method and figures out which of the fields holds the current value.
+ /// </summary>
+ void AnalyzeCurrentProperty()
+ {
+ MethodDefinition getCurrentMethod = enumeratorType.Methods.FirstOrDefault(
+ m => m.Name.StartsWith("System.Collections.Generic.IEnumerator", StringComparison.Ordinal)
+ && m.Name.EndsWith(".get_Current", StringComparison.Ordinal));
+ ILBlock method = CreateILAst(getCurrentMethod);
+ if (method.Body.Count == 1) {
+ // release builds directly return the current field
+ ILExpression retExpr;
+ FieldReference field;
+ ILExpression ldFromObj;
+ if (method.Body[0].Match(ILCode.Ret, out retExpr) &&
+ retExpr.Match(ILCode.Ldfld, out field, out ldFromObj) &&
+ ldFromObj.MatchThis())
+ {
+ currentField = GetFieldDefinition(field);
+ }
+ } else if (method.Body.Count == 2) {
+ ILVariable v, v2;
+ ILExpression stExpr;
+ FieldReference field;
+ ILExpression ldFromObj;
+ ILExpression retExpr;
+ if (method.Body[0].Match(ILCode.Stloc, out v, out stExpr) &&
+ stExpr.Match(ILCode.Ldfld, out field, out ldFromObj) &&
+ ldFromObj.MatchThis() &&
+ method.Body[1].Match(ILCode.Ret, out retExpr) &&
+ retExpr.Match(ILCode.Ldloc, out v2) &&
+ v == v2)
+ {
+ currentField = GetFieldDefinition(field);
+ }
+ }
+ if (currentField == null)
+ throw new SymbolicAnalysisFailedException();
+ }
+ #endregion
+
+ #region Figure out the mapping of IEnumerable fields to IEnumerator fields (analysis of GetEnumerator())
+ void ResolveIEnumerableIEnumeratorFieldMapping()
+ {
+ MethodDefinition getEnumeratorMethod = enumeratorType.Methods.FirstOrDefault(
+ m => m.Name.StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal)
+ && m.Name.EndsWith(".GetEnumerator", StringComparison.Ordinal));
+ if (getEnumeratorMethod == null)
+ return; // no mappings (maybe it's just an IEnumerator implementation?)
+
+ ILBlock method = CreateILAst(getEnumeratorMethod);
+ foreach (ILNode node in method.Body) {
+ FieldReference stField;
+ ILExpression stToObj;
+ ILExpression stExpr;
+ FieldReference ldField;
+ ILExpression ldFromObj;
+ if (node.Match(ILCode.Stfld, out stField, out stToObj, out stExpr) &&
+ stExpr.Match(ILCode.Ldfld, out ldField, out ldFromObj) &&
+ ldFromObj.MatchThis())
+ {
+ FieldDefinition storedField = GetFieldDefinition(stField);
+ FieldDefinition loadedField = GetFieldDefinition(ldField);
+ if (storedField != null && loadedField != null) {
+ ILVariable mappedParameter;
+ if (fieldToParameterMap.TryGetValue(loadedField, out mappedParameter))
+ fieldToParameterMap[storedField] = mappedParameter;
+ }
+ }
+ }
+ }
+ #endregion
+
+ #region Construction of the exception table (analysis of Dispose())
+ // We construct the exception table by analyzing the enumerator's Dispose() method.
+
+ // Assumption: there are no loops/backward jumps
+ // We 'run' the code, with "state" being a symbolic variable
+ // so it can form expressions like "state + x" (when there's a sub instruction)
+ // For each instruction, we maintain a list of value ranges for state for which the instruction is reachable.
+ // This is (int.MinValue, int.MaxValue) for the first instruction.
+ // These ranges are propagated depending on the conditional jumps performed by the code.
+
+ Dictionary<MethodDefinition, StateRange> finallyMethodToStateRange;
+
+ void ConstructExceptionTable()
+ {
+ disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose");
+ ILBlock ilMethod = CreateILAst(disposeMethod);
+
+ var rangeAnalysis = new StateRangeAnalysis(ilMethod.Body[0], StateRangeAnalysisMode.IteratorDispose, stateField);
+ rangeAnalysis.AssignStateRanges(ilMethod.Body, ilMethod.Body.Count);
+ finallyMethodToStateRange = rangeAnalysis.finallyMethodToStateRange;
+
+ // Now look at the finally blocks:
+ foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive<ILTryCatchBlock>()) {
+ var range = rangeAnalysis.ranges[tryFinally.TryBlock.Body[0]];
+ var finallyBody = tryFinally.FinallyBlock.Body;
+ if (finallyBody.Count != 2)
+ throw new SymbolicAnalysisFailedException();
+ ILExpression call = finallyBody[0] as ILExpression;
+ if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1)
+ throw new SymbolicAnalysisFailedException();
+ if (!call.Arguments[0].MatchThis())
+ throw new SymbolicAnalysisFailedException();
+ if (!finallyBody[1].Match(ILCode.Endfinally))
+ throw new SymbolicAnalysisFailedException();
+
+ MethodDefinition mdef = GetMethodDefinition(call.Operand as MethodReference);
+ if (mdef == null || finallyMethodToStateRange.ContainsKey(mdef))
+ throw new SymbolicAnalysisFailedException();
+ finallyMethodToStateRange.Add(mdef, range);
+ }
+ rangeAnalysis = null;
+ }
+ #endregion
+
+ #region Analysis of MoveNext()
+ ILVariable returnVariable;
+ ILLabel returnLabel;
+ ILLabel returnFalseLabel;
+
+ void AnalyzeMoveNext()
+ {
+ MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext");
+ ILBlock ilMethod = CreateILAst(moveNextMethod);
+
+ if (ilMethod.Body.Count == 0)
+ throw new SymbolicAnalysisFailedException();
+ ILExpression lastReturnArg;
+ if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg))
+ throw new SymbolicAnalysisFailedException();
+
+ // There are two possibilities:
+ if (lastReturnArg.Code == ILCode.Ldloc) {
+ // a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks)
+ returnVariable = (ILVariable)lastReturnArg.Operand;
+ returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel;
+ if (returnLabel == null)
+ throw new SymbolicAnalysisFailedException();
+ } else {
+ // b) the compiler directly returns constants
+ returnVariable = null;
+ returnLabel = null;
+ // In this case, the last return must return false.
+ if (lastReturnArg.Code != ILCode.Ldc_I4 || (int)lastReturnArg.Operand != 0)
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ ILTryCatchBlock tryFaultBlock = ilMethod.Body[0] as ILTryCatchBlock;
+ List<ILNode> body;
+ int bodyLength;
+ if (tryFaultBlock != null) {
+ // there are try-finally blocks
+ if (returnVariable == null) // in this case, we must use a return variable
+ throw new SymbolicAnalysisFailedException();
+ // must be a try-fault block:
+ if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null)
+ throw new SymbolicAnalysisFailedException();
+
+ ILBlock faultBlock = tryFaultBlock.FaultBlock;
+ // Ensure the fault block contains the call to Dispose().
+ if (faultBlock.Body.Count != 2)
+ throw new SymbolicAnalysisFailedException();
+ MethodReference disposeMethodRef;
+ ILExpression disposeArg;
+ if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg))
+ throw new SymbolicAnalysisFailedException();
+ if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !disposeArg.MatchThis())
+ throw new SymbolicAnalysisFailedException();
+ if (!faultBlock.Body[1].Match(ILCode.Endfinally))
+ throw new SymbolicAnalysisFailedException();
+
+ body = tryFaultBlock.TryBlock.Body;
+ bodyLength = body.Count;
+ } else {
+ // no try-finally blocks
+ body = ilMethod.Body;
+ if (returnVariable == null)
+ bodyLength = body.Count - 1; // all except for the return statement
+ else
+ bodyLength = body.Count - 2; // all except for the return label and statement
+ }
+
+ // Now verify that the last instruction in the body is 'ret(false)'
+ if (returnVariable != null) {
+ // If we don't have a return variable, we already verified that above.
+ // If we do have one, check for 'stloc(returnVariable, ldc.i4(0))'
+
+ // Maybe might be a jump to the return label after the stloc:
+ ILExpression leave = body.ElementAtOrDefault(bodyLength - 1) as ILExpression;
+ if (leave != null && (leave.Code == ILCode.Br || leave.Code == ILCode.Leave) && leave.Operand == returnLabel)
+ bodyLength--;
+ ILExpression store0 = body.ElementAtOrDefault(bodyLength - 1) as ILExpression;
+ if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable)
+ throw new SymbolicAnalysisFailedException();
+ if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0)
+ throw new SymbolicAnalysisFailedException();
+
+ bodyLength--; // don't conside the stloc instruction to be part of the body
+ }
+ // The last element in the body usually is a label pointing to the 'ret(false)'
+ returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel;
+ // Note: in Roslyn-compiled code, returnFalseLabel may be null.
+
+ var rangeAnalysis = new StateRangeAnalysis(body[0], StateRangeAnalysisMode.IteratorMoveNext, stateField);
+ int pos = rangeAnalysis.AssignStateRanges(body, bodyLength);
+ rangeAnalysis.EnsureLabelAtPos(body, ref pos, ref bodyLength);
+
+ var labels = rangeAnalysis.CreateLabelRangeMapping(body, pos, bodyLength);
+ ConvertBody(body, pos, bodyLength, labels);
+ }
+ #endregion
+
+ #region ConvertBody
+ struct SetState
+ {
+ public readonly int NewBodyPos;
+ public readonly int NewState;
+
+ public SetState(int newBodyPos, int newState)
+ {
+ NewBodyPos = newBodyPos;
+ NewState = newState;
+ }
+ }
+
+ void ConvertBody(List<ILNode> body, int startPos, int bodyLength, List<KeyValuePair<ILLabel, StateRange>> labels)
+ {
+ newBody = new List<ILNode>();
+ newBody.Add(MakeGoTo(labels, 0));
+ List<SetState> stateChanges = new List<SetState>();
+ int currentState = -1;
+ // Copy all instructions from the old body to newBody.
+ for (int pos = startPos; pos < bodyLength; pos++) {
+ ILExpression expr = body[pos] as ILExpression;
+ if (expr != null && expr.Code == ILCode.Stfld && expr.Arguments[0].MatchThis()) {
+ // Handle stores to 'state' or 'current'
+ if (GetFieldDefinition(expr.Operand as FieldReference) == stateField) {
+ if (expr.Arguments[1].Code != ILCode.Ldc_I4)
+ throw new SymbolicAnalysisFailedException();
+ currentState = (int)expr.Arguments[1].Operand;
+ stateChanges.Add(new SetState(newBody.Count, currentState));
+ } else if (GetFieldDefinition(expr.Operand as FieldReference) == currentField) {
+ newBody.Add(new ILExpression(ILCode.YieldReturn, null, expr.Arguments[1]));
+ } else {
+ newBody.Add(body[pos]);
+ }
+ } else if (returnVariable != null && expr != null && expr.Code == ILCode.Stloc && expr.Operand == returnVariable) {
+ // handle store+branch to the returnVariable
+ ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression;
+ if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnLabel || expr.Arguments[0].Code != ILCode.Ldc_I4)
+ throw new SymbolicAnalysisFailedException();
+ int val = (int)expr.Arguments[0].Operand;
+ if (val == 0) {
+ newBody.Add(new ILExpression(ILCode.YieldBreak, null));
+ } else if (val == 1) {
+ newBody.Add(MakeGoTo(labels, currentState));
+ } else {
+ throw new SymbolicAnalysisFailedException();
+ }
+ } else if (expr != null && expr.Code == ILCode.Ret) {
+ if (expr.Arguments.Count != 1 || expr.Arguments[0].Code != ILCode.Ldc_I4)
+ throw new SymbolicAnalysisFailedException();
+ // handle direct return (e.g. in release builds)
+ int val = (int)expr.Arguments[0].Operand;
+ if (val == 0) {
+ newBody.Add(new ILExpression(ILCode.YieldBreak, null));
+ } else if (val == 1) {
+ newBody.Add(MakeGoTo(labels, currentState));
+ } else {
+ throw new SymbolicAnalysisFailedException();
+ }
+ } else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && expr.Arguments[0].MatchThis()) {
+ MethodDefinition method = GetMethodDefinition(expr.Operand as MethodReference);
+ if (method == null)
+ throw new SymbolicAnalysisFailedException();
+ StateRange stateRange;
+ if (method == disposeMethod) {
+ // Explicit call to dispose is used for "yield break;" within the method.
+ ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression;
+ if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnFalseLabel)
+ throw new SymbolicAnalysisFailedException();
+ newBody.Add(new ILExpression(ILCode.YieldBreak, null));
+ } else if (finallyMethodToStateRange.TryGetValue(method, out stateRange)) {
+ // Call to Finally-method
+ int index = stateChanges.FindIndex(ss => stateRange.Contains(ss.NewState));
+ if (index < 0)
+ throw new SymbolicAnalysisFailedException();
+
+ ILLabel label = new ILLabel();
+ label.Name = "JumpOutOfTryFinally" + stateChanges[index].NewState;
+ newBody.Add(new ILExpression(ILCode.Leave, label));
+
+ SetState stateChange = stateChanges[index];
+ // Move all instructions from stateChange.Pos to newBody.Count into a try-block
+ stateChanges.RemoveRange(index, stateChanges.Count - index); // remove all state changes up to the one we found
+ ILTryCatchBlock tryFinally = new ILTryCatchBlock();
+ tryFinally.TryBlock = new ILBlock(newBody.GetRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos));
+ newBody.RemoveRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos); // remove all nodes that we just moved into the try block
+ tryFinally.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>();
+ tryFinally.FinallyBlock = ConvertFinallyBlock(method);
+ newBody.Add(tryFinally);
+ newBody.Add(label);
+ }
+ } else {
+ newBody.Add(body[pos]);
+ }
+ }
+ newBody.Add(new ILExpression(ILCode.YieldBreak, null));
+ }
+
+ ILExpression MakeGoTo(ILLabel targetLabel)
+ {
+ Debug.Assert(targetLabel != null);
+ if (targetLabel == returnFalseLabel)
+ return new ILExpression(ILCode.YieldBreak, null);
+ else
+ return new ILExpression(ILCode.Br, targetLabel);
+ }
+
+ ILExpression MakeGoTo(List<KeyValuePair<ILLabel, StateRange>> labels, int state)
+ {
+ foreach (var pair in labels) {
+ if (pair.Value.Contains(state))
+ return MakeGoTo(pair.Key);
+ }
+ throw new SymbolicAnalysisFailedException();
+ }
+
+ ILBlock ConvertFinallyBlock(MethodDefinition finallyMethod)
+ {
+ ILBlock block = CreateILAst(finallyMethod);
+ // Get rid of assignment to state
+ FieldReference stfld;
+ List<ILExpression> args;
+ if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld, out args)) {
+ if (GetFieldDefinition(stfld) == stateField && args[0].MatchThis())
+ block.Body.RemoveAt(0);
+ }
+ // Convert ret to endfinally
+ foreach (ILExpression expr in block.GetSelfAndChildrenRecursive<ILExpression>()) {
+ if (expr.Code == ILCode.Ret)
+ expr.Code = ILCode.Endfinally;
+ }
+ return block;
+ }
+ #endregion
+
+ #region TranslateFieldsToLocalAccess
+ void TranslateFieldsToLocalAccess()
+ {
+ TranslateFieldsToLocalAccess(newBody, fieldToParameterMap);
+ }
+
+ internal static void TranslateFieldsToLocalAccess(List<ILNode> newBody, Dictionary<FieldDefinition, ILVariable> fieldToParameterMap)
+ {
+ var fieldToLocalMap = new DefaultDictionary<FieldDefinition, ILVariable>(f => new ILVariable { Name = f.Name, Type = f.FieldType });
+ foreach (ILNode node in newBody) {
+ foreach (ILExpression expr in node.GetSelfAndChildrenRecursive<ILExpression>()) {
+ FieldDefinition field = GetFieldDefinition(expr.Operand as FieldReference);
+ if (field != null) {
+ switch (expr.Code) {
+ case ILCode.Ldfld:
+ if (expr.Arguments[0].MatchThis()) {
+ expr.Code = ILCode.Ldloc;
+ if (fieldToParameterMap.ContainsKey(field)) {
+ expr.Operand = fieldToParameterMap[field];
+ } else {
+ expr.Operand = fieldToLocalMap[field];
+ }
+ expr.Arguments.Clear();
+ }
+ break;
+ case ILCode.Stfld:
+ if (expr.Arguments[0].MatchThis()) {
+ expr.Code = ILCode.Stloc;
+ if (fieldToParameterMap.ContainsKey(field)) {
+ expr.Operand = fieldToParameterMap[field];
+ } else {
+ expr.Operand = fieldToLocalMap[field];
+ }
+ expr.Arguments.RemoveAt(0);
+ }
+ break;
+ case ILCode.Ldflda:
+ if (expr.Arguments[0].MatchThis()) {
+ expr.Code = ILCode.Ldloca;
+ if (fieldToParameterMap.ContainsKey(field)) {
+ expr.Operand = fieldToParameterMap[field];
+ } else {
+ expr.Operand = fieldToLocalMap[field];
+ }
+ expr.Arguments.Clear();
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ #endregion
+ }
+}