.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

810 lines
29 KiB

// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Mono.Cecil;
namespace ICSharpCode.Decompiler.ILAst
{
public 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.
/// <summary>
/// This exception is thrown when we find something else than we expect from the C# compiler.
/// This aborts the analysis and makes the whole transform fail.
/// </summary>
class YieldAnalysisFailedException : Exception {}
DecompilerContext context;
TypeDefinition enumeratorType;
MethodDefinition enumeratorCtor;
MethodDefinition disposeMethod;
FieldDefinition stateField;
FieldDefinition currentField;
Dictionary<FieldDefinition, ParameterDefinition> fieldToParameterMap;
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
try {
#endif
yrd.AnalyzeCtor();
yrd.AnalyzeCurrentProperty();
yrd.ResolveIEnumerableIEnumeratorFieldMapping();
yrd.ConstructExceptionTable();
yrd.AnalyzeMoveNext();
yrd.TranslateFieldsToLocalAccess();
#if !DEBUG
} catch (YieldAnalysisFailedException) {
return;
}
#endif
method.Body.Clear();
method.EntryGoto = null;
method.Body.AddRange(yrd.newBody);
}
#endregion
#region Match the enumerator creation pattern
bool MatchEnumeratorCreationPattern(ILBlock method)
{
if (method.Body.Count == 0)
return false;
ILExpression ret;
if (method.Body.Count == 1) {
// ret(newobj(...))
if (method.Body[0].Match(ILCode.Ret, out ret) && ret.Arguments.Count == 1)
return MatchEnumeratorCreationNewObj(ret.Arguments[0], out enumeratorCtor);
else
return false;
}
// stloc(var_1, newobj(..)
ILExpression stloc;
if (!method.Body[0].Match(ILCode.Stloc, out stloc))
return false;
if (!MatchEnumeratorCreationNewObj(stloc.Arguments[0], out enumeratorCtor))
return false;
fieldToParameterMap = new Dictionary<FieldDefinition, ParameterDefinition>();
int i = 1;
ILExpression stfld;
while (i < method.Body.Count && method.Body[i].Match(ILCode.Stfld, out stfld)) {
// stfld(..., ldloc(var_1), ldarg(...))
ILExpression ldloc, ldarg;
if (!(stfld.Arguments[0].Match(ILCode.Ldloc, out ldloc) && stfld.Arguments[1].Match(ILCode.Ldarg, out ldarg)))
return false;
if (ldloc.Operand != stloc.Operand || !(stfld.Operand is FieldDefinition))
return false;
fieldToParameterMap[(FieldDefinition)stfld.Operand] = (ParameterDefinition)ldarg.Operand;
i++;
}
ILExpression stloc2;
if (i < method.Body.Count && method.Body[i].Match(ILCode.Stloc, out stloc2)) {
// stloc(var_2, ldloc(var_1))
if (stloc2.Arguments[0].Code != ILCode.Ldloc || stloc2.Arguments[0].Operand != stloc.Operand)
return false;
i++;
} else {
// the compiler might skip the above instruction in release builds; in that case, it directly returns stloc.Operand
stloc2 = stloc;
}
if (!SkipDummyBr(method, ref i))
return false;
if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out ret)) {
if (ret.Arguments[0].Code == ILCode.Ldloc && ret.Arguments[0].Operand == stloc2.Operand) {
return true;
}
}
return false;
}
bool SkipDummyBr(ILBlock method, ref int i)
{
ILExpression br;
if (i + 1 < method.Body.Count && method.Body[i].Match(ILCode.Br, out br)) {
if (br.Operand != method.Body[i + 1])
return false;
i += 2;
}
return true;
}
bool MatchEnumeratorCreationNewObj(ILExpression expr, out MethodDefinition ctor)
{
// newobj(CurrentType/...::.ctor, ldc.i4(-2))
ctor = expr.Operand as MethodDefinition;
if (expr.Code != ILCode.Newobj || expr.Arguments.Count != 1)
return false;
if (expr.Arguments[0].Code != ILCode.Ldc_I4 || (int)expr.Arguments[0].Operand != -2)
return false;
if (ctor == null || !ctor.DeclaringType.IsCompilerGenerated())
return false;
if (ctor.DeclaringType.DeclaringType != context.CurrentType)
return false;
foreach (TypeReference i in ctor.DeclaringType.Interfaces) {
if (i.Namespace == "System.Collections" && i.Name == "IEnumerator")
return true;
}
return false;
}
#endregion
#region Figure out what the 'state' field is
/// <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) {
ILExpression stfld;
if (node.Match(ILCode.Stfld, out stfld)
&& stfld.Arguments[0].Code == ILCode.Ldarg && ((ParameterDefinition)stfld.Arguments[0].Operand).Index < 0
&& stfld.Arguments[1].Code == ILCode.Ldarg && ((ParameterDefinition)stfld.Arguments[1].Operand).Index == 0)
{
stateField = stfld.Operand as FieldDefinition;
}
}
if (stateField == null)
throw new YieldAnalysisFailedException();
}
/// <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 YieldAnalysisFailedException();
ILBlock ilMethod = new ILBlock();
ILAstBuilder astBuilder = new ILAstBuilder();
ilMethod.Body = astBuilder.Build(method, true);
ILAstOptimizer optimizer = new ILAstOptimizer();
optimizer.Optimize(context, ilMethod, ILAstOptimizationStep.YieldReturn);
return ilMethod;
}
#endregion
#region Figure out what the 'current' field is
static readonly ILExpression returnFieldFromThisPattern = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromThis.Instance));
/// <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
if (returnFieldFromThisPattern.Match(method.Body[0])) {
currentField = ((ILExpression)method.Body[0]).Arguments[0].Operand as FieldDefinition;
}
} else {
StoreToVariable v = new StoreToVariable(new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromThis.Instance));
if (v.Match(method.Body[0])) {
int i = 1;
if (SkipDummyBr(method, ref i) && i == method.Body.Count - 1) {
if (new ILExpression(ILCode.Ret, null, new LoadFromVariable(v)).Match(method.Body[i])) {
currentField = ((ILExpression)method.Body[0]).Arguments[0].Operand as FieldDefinition;
}
}
}
}
if (currentField == null)
throw new YieldAnalysisFailedException();
}
#endregion
#region Figure out the mapping of IEnumerable fields to IEnumerator fields
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?)
ILExpression mappingPattern = new ILExpression(
ILCode.Stfld, ILExpression.AnyOperand, new AnyILExpression(),
new ILExpression(ILCode.Ldfld, ILExpression.AnyOperand, LoadFromThis.Instance));
ILBlock method = CreateILAst(getEnumeratorMethod);
foreach (ILNode node in method.Body) {
if (mappingPattern.Match(node)) {
ILExpression stfld = (ILExpression)node;
FieldDefinition storedField = stfld.Operand as FieldDefinition;
FieldDefinition loadedField = stfld.Arguments[1].Operand as FieldDefinition;
if (storedField != null && loadedField != null) {
ParameterDefinition mappedParameter;
if (fieldToParameterMap.TryGetValue(loadedField, out mappedParameter))
fieldToParameterMap[storedField] = mappedParameter;
}
}
}
}
#endregion
#region Construction of the exception table
// 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.
#region struct Interval / class StateRange
struct Interval
{
public readonly int Start, End;
public Interval(int start, int end)
{
Debug.Assert(start <= end || (start == 0 && end == -1));
this.Start = start;
this.End = end;
}
public override string ToString()
{
return string.Format("({0} to {1})", Start, End);
}
}
class StateRange
{
readonly List<Interval> data = new List<Interval>();
public StateRange()
{
}
public StateRange(int start, int end)
{
this.data.Add(new Interval(start, end));
}
public void UnionWith(StateRange other)
{
data.AddRange(other.data);
}
/// <summary>
/// Unions this state range with (other intersect (minVal to maxVal))
/// </summary>
public void UnionWith(StateRange other, int minVal, int maxVal)
{
foreach (Interval v in other.data) {
int start = Math.Max(v.Start, minVal);
int end = Math.Min(v.End, maxVal);
if (start <= end)
data.Add(new Interval(start, end));
}
}
/// <summary>
/// Merges overlapping interval ranges.
/// </summary>
public void Simplify()
{
if (data.Count < 2)
return;
data.Sort((a, b) => a.Start.CompareTo(b.Start));
Interval prev = data[0];
int prevIndex = 0;
for (int i = 1; i < data.Count; i++) {
Interval next = data[i];
Debug.Assert(prev.Start <= next.Start);
if (next.Start <= prev.End + 1) { // intervals overlapping or touching
prev = new Interval(prev.Start, Math.Max(prev.End, next.End));
data[prevIndex] = prev;
data[i] = new Interval(0, -1); // mark as deleted
} else {
prev = next;
prevIndex = i;
}
}
data.RemoveAll(i => i.Start > i.End); // remove all entries that were marked as deleted
}
public override string ToString()
{
return string.Join(",", data);
}
public Interval ToInterval()
{
if (data.Count == 1)
return data[0];
else
throw new YieldAnalysisFailedException();
}
}
#endregion
DefaultDictionary<ILNode, StateRange> ranges;
Dictionary<MethodDefinition, Interval> finallyMethodToStateInterval = new Dictionary<MethodDefinition, Interval>();
void ConstructExceptionTable()
{
disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose");
ILBlock ilMethod = CreateILAst(disposeMethod);
ranges = new DefaultDictionary<ILNode, StateRange>(node => new StateRange());
ranges[ilMethod] = new StateRange(int.MinValue, int.MaxValue);
AssignStateRanges(ilMethod);
// Now look at the finally blocks:
foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive<ILTryCatchBlock>()) {
Interval interval = ranges[tryFinally.TryBlock.Body[0]].ToInterval();
var finallyBody = tryFinally.FinallyBlock.Body;
if (!(finallyBody.Count == 2 || finallyBody.Count == 3))
throw new YieldAnalysisFailedException();
ILExpression call = finallyBody[0] as ILExpression;
if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1)
throw new YieldAnalysisFailedException();
if (call.Arguments[0].Code != ILCode.Ldarg || ((ParameterDefinition)call.Arguments[0].Operand).Index >= 0)
throw new YieldAnalysisFailedException();
if (finallyBody.Count == 3 && !finallyBody[1].Match(ILCode.Nop))
throw new YieldAnalysisFailedException();
if (!finallyBody[finallyBody.Count - 1].Match(ILCode.Endfinally))
throw new YieldAnalysisFailedException();
MethodDefinition mdef = call.Operand as MethodDefinition;
if (mdef != null) {
if (finallyMethodToStateInterval.ContainsKey(mdef))
throw new YieldAnalysisFailedException();
finallyMethodToStateInterval.Add(mdef, interval);
}
}
ranges = null;
}
#region Assign StateRanges / Symbolic Execution
void AssignStateRanges(ILBlock block)
{
if (block.Body.Count == 0)
return;
ranges[block.Body[0]].UnionWith(ranges[block]);
for (int i = 0; i < block.Body.Count; i++) {
StateRange nodeRange = ranges[block.Body[i]];
nodeRange.Simplify();
ILLabel label = block.Body[i] as ILLabel;
if (label != null) {
ranges[block.Body[i + 1]].UnionWith(nodeRange);
continue;
}
ILTryCatchBlock tryFinally = block.Body[i] as ILTryCatchBlock;
if (tryFinally != null) {
if (tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null)
throw new YieldAnalysisFailedException();
ranges[tryFinally.TryBlock].UnionWith(nodeRange);
AssignStateRanges(tryFinally.TryBlock);
continue;
}
ILExpression expr = block.Body[i] as ILExpression;
if (expr == null)
throw new YieldAnalysisFailedException();
switch (expr.Code) {
case ILCode.Switch:
SymbolicValue val = Eval(expr.Arguments[0]);
if (val.Type != SymbolicValueType.State)
throw new YieldAnalysisFailedException();
ILLabel[] targetLabels = (ILLabel[])expr.Operand;
for (int j = 0; j < targetLabels.Length; j++) {
int state = j - val.Constant;
ranges[targetLabels[j]].UnionWith(nodeRange, state, state);
}
ranges[block.Body[i + 1]].UnionWith(nodeRange, int.MinValue, -1 - val.Constant);
ranges[block.Body[i + 1]].UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue);
break;
case ILCode.Br:
case ILCode.Leave:
ranges[(ILLabel)expr.Operand].UnionWith(nodeRange);
break;
case ILCode.Nop:
ranges[block.Body[i + 1]].UnionWith(nodeRange);
break;
case ILCode.Ret:
break;
default:
throw new YieldAnalysisFailedException();
}
}
}
enum SymbolicValueType
{
IntegerConstant,
State,
This
}
struct SymbolicValue
{
public readonly int Constant;
public readonly SymbolicValueType Type;
public SymbolicValue(SymbolicValueType type, int constant = 0)
{
this.Type = type;
this.Constant = constant;
}
public override string ToString()
{
return string.Format("[SymbolicValue {0}: {1}]", this.Type, this.Constant);
}
}
SymbolicValue Eval(ILExpression expr)
{
switch (expr.Code) {
case ILCode.Sub:
SymbolicValue left = Eval(expr.Arguments[0]);
SymbolicValue right = Eval(expr.Arguments[1]);
if (left.Type != SymbolicValueType.State && right.Type != SymbolicValueType.IntegerConstant)
throw new YieldAnalysisFailedException();
if (right.Type != SymbolicValueType.IntegerConstant)
throw new YieldAnalysisFailedException();
return new SymbolicValue(left.Type, unchecked ( left.Constant - right.Constant ));
case ILCode.Ldfld:
if (Eval(expr.Arguments[0]).Type != SymbolicValueType.This)
throw new YieldAnalysisFailedException();
if (expr.Operand != stateField)
throw new YieldAnalysisFailedException();
return new SymbolicValue(SymbolicValueType.State);
case ILCode.Ldarg:
if (((ParameterDefinition)expr.Operand).Index < 0)
return new SymbolicValue(SymbolicValueType.This);
else
throw new YieldAnalysisFailedException();
case ILCode.Ldc_I4:
return new SymbolicValue(SymbolicValueType.IntegerConstant, (int)expr.Operand);
default:
throw new YieldAnalysisFailedException();
}
}
#endregion
#endregion
#region Analysis and Transformation 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 YieldAnalysisFailedException();
ILExpression lastReturn;
if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturn))
throw new YieldAnalysisFailedException();
ilMethod.Body.RemoveAll(n => n.Match(ILCode.Nop)); // remove nops
// There are two possibilities:
if (lastReturn.Arguments[0].Code == ILCode.Ldloc) {
// a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks)
returnVariable = (ILVariable)lastReturn.Arguments[0].Operand;
returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel;
if (returnLabel == null)
throw new YieldAnalysisFailedException();
} else {
// b) the compiler directly returns constants
returnVariable = null;
returnLabel = null;
// In this case, the last return must return false.
if (lastReturn.Arguments[0].Code != ILCode.Ldc_I4 || (int)lastReturn.Arguments[0].Operand != 0)
throw new YieldAnalysisFailedException();
}
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 YieldAnalysisFailedException();
// must be a try-fault block:
if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null)
throw new YieldAnalysisFailedException();
VerifyFaultBlock(tryFaultBlock.FaultBlock);
body = tryFaultBlock.TryBlock.Body;
body.RemoveAll(n => n.Match(ILCode.Nop)); // remove nops
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 (bodyLength < 2)
throw new YieldAnalysisFailedException();
ILExpression leave = body[bodyLength - 1] as ILExpression;
if (leave == null || leave.Operand != returnLabel || !(leave.Code == ILCode.Br || leave.Code == ILCode.Leave))
throw new YieldAnalysisFailedException();
ILExpression store0 = body[bodyLength - 2] as ILExpression;
if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable)
throw new YieldAnalysisFailedException();
if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0)
throw new YieldAnalysisFailedException();
bodyLength -= 2; // don't conside the 'ret(false)' part of the body
}
returnFalseLabel = body.ElementAtOrDefault(--bodyLength) as ILLabel;
if (returnFalseLabel == null || bodyLength < 2)
throw new YieldAnalysisFailedException();
// Verify that the first instruction is a switch on this.state, and that the 2nd instruction is br
if (!new ILExpression(ILCode.Switch, ILExpression.AnyOperand, new ILExpression(ILCode.Ldfld, stateField, LoadFromThis.Instance)).Match(body[0]))
throw new YieldAnalysisFailedException();
if (!body[1].Match(ILCode.Br))
throw new YieldAnalysisFailedException();
SimplifySwitch(body, ref bodyLength);
// verify that the br (if no state is matched) leads to 'return false'
if (((ILExpression)body[1]).Operand != returnFalseLabel)
throw new YieldAnalysisFailedException();
ConvertBody(body, bodyLength);
}
/// <summary>
/// Ensure the fault block contains the call to Dispose().
/// </summary>
void VerifyFaultBlock(ILBlock faultBlock)
{
ILExpression call;
if (!(faultBlock.Body.Count == 2 || faultBlock.Body.Count == 3))
throw new YieldAnalysisFailedException();
if (!faultBlock.Body[0].Match(ILCode.Call, out call) && call.Operand == disposeMethod
&& call.Arguments.Count == 1 && LoadFromThis.Instance.Match(call.Arguments[0]))
throw new YieldAnalysisFailedException();
if (faultBlock.Body.Count == 3 && !faultBlock.Body[1].Match(ILCode.Nop))
throw new YieldAnalysisFailedException();
if (!faultBlock.Body[faultBlock.Body.Count - 1].Match(ILCode.Endfinally))
throw new YieldAnalysisFailedException();
}
/// <summary>
/// Simplifies the switch statement at body[0], and the branch at body[1].
/// </summary>
void SimplifySwitch(List<ILNode> body, ref int bodyLength)
{
HashSet<ILLabel> regularLabels = new HashSet<ILLabel>();
Dictionary<ILLabel, ILLabel> simplications = new Dictionary<ILLabel, ILLabel>();
for (int i = 2; i < bodyLength; i++) {
ILExpression expr = body[i] as ILExpression;
if (expr != null && expr.Operand is ILLabel)
regularLabels.Add((ILLabel)expr.Operand);
}
for (int i = 2; i + 1 < bodyLength; i += 2) {
ILLabel label = body[i] as ILLabel;
if (label == null || regularLabels.Contains(label))
break;
ILExpression expr = body[i + 1] as ILExpression;
if (expr != null && expr.Code == ILCode.Br) {
simplications.Add(label, (ILLabel)expr.Operand);
} else {
break;
}
}
ILExpression switchExpr = (ILExpression)body[0];
ILExpression brExpr = (ILExpression)body[1];
Debug.Assert(switchExpr.Code == ILCode.Switch);
Debug.Assert(brExpr.Code == ILCode.Br);
ILLabel targetLabel;
if (simplications.TryGetValue((ILLabel)brExpr.Operand, out targetLabel))
brExpr.Operand = targetLabel;
ILLabel[] labels = (ILLabel[])switchExpr.Operand;
for (int i = 0; i < labels.Length; i++) {
if (simplications.TryGetValue(labels[i], out targetLabel))
labels[i] = targetLabel;
}
// remove the labels that aren't used anymore
body.RemoveRange(2, simplications.Count * 2);
bodyLength -= simplications.Count * 2;
}
struct SetState
{
public readonly int NewBodyPos;
public readonly int NewState;
public SetState(int newBodyPos, int newState)
{
this.NewBodyPos = newBodyPos;
this.NewState = newState;
}
}
void ConvertBody(List<ILNode> body, int bodyLength)
{
newBody = new List<ILNode>();
ILLabel[] switchLabels = (ILLabel[])((ILExpression)body[0]).Operand;
newBody.Add(MakeGoTo(switchLabels[0]));
List<SetState> stateChanges = new List<SetState>();
int currentState = -1;
// Copy all instructions from the old body to newBody.
for (int pos = 2; pos < bodyLength; pos++) {
ILExpression expr = body[pos] as ILExpression;
if (expr != null && expr.Code == ILCode.Stfld && LoadFromThis.Instance.Match(expr.Arguments[0])) {
// Handle stores to 'state' or 'current'
if (expr.Operand == stateField) {
if (expr.Arguments[1].Code != ILCode.Ldc_I4)
throw new YieldAnalysisFailedException();
currentState = (int)expr.Arguments[1].Operand;
stateChanges.Add(new SetState(newBody.Count, currentState));
} else if (expr.Operand == 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 YieldAnalysisFailedException();
int val = (int)expr.Arguments[0].Operand;
if (val == 0) {
newBody.Add(MakeGoTo(returnFalseLabel));
} else if (val == 1) {
if (currentState >= 0 && currentState < switchLabels.Length)
newBody.Add(MakeGoTo(switchLabels[currentState]));
else
newBody.Add(MakeGoTo(returnFalseLabel));
} else {
throw new YieldAnalysisFailedException();
}
} else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && LoadFromThis.Instance.Match(expr.Arguments[0])) {
MethodDefinition method = expr.Operand as MethodDefinition;
if (method == null)
throw new YieldAnalysisFailedException();
Interval interval;
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 YieldAnalysisFailedException();
newBody.Add(MakeGoTo(returnFalseLabel));
} else if (finallyMethodToStateInterval.TryGetValue(method, out interval)) {
// Call to Finally-method
int index = stateChanges.FindIndex(ss => ss.NewState >= interval.Start && ss.NewState <= interval.End);
if (index < 0)
throw new YieldAnalysisFailedException();
SetState stateChange = stateChanges[index];
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);
}
} else {
newBody.Add(body[pos]);
}
}
newBody.Add(returnFalseLabel);
newBody.Add(new ILExpression(ILCode.YieldBreak, null));
}
ILExpression MakeGoTo(ILLabel targetLabel)
{
if (targetLabel == returnFalseLabel)
return new ILExpression(ILCode.YieldBreak, null);
else
return new ILExpression(ILCode.Br, targetLabel);
}
ILBlock ConvertFinallyBlock(MethodDefinition finallyMethod)
{
ILBlock block = CreateILAst(finallyMethod);
block.Body.RemoveAll(n => n.Match(ILCode.Nop));
// Get rid of assignment to state
ILExpression stfld;
if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld)) {
if (stfld.Operand == stateField && LoadFromThis.Instance.Match(stfld.Arguments[0]))
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()
{
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 = expr.Operand as FieldDefinition;
if (field != null) {
switch (expr.Code) {
case ILCode.Ldfld:
if (LoadFromThis.Instance.Match(expr.Arguments[0])) {
if (fieldToParameterMap.ContainsKey(field)) {
expr.Code = ILCode.Ldarg;
expr.Operand = fieldToParameterMap[field];
} else {
expr.Code = ILCode.Ldloc;
expr.Operand = fieldToLocalMap[field];
}
expr.Arguments.Clear();
}
break;
case ILCode.Stfld:
if (LoadFromThis.Instance.Match(expr.Arguments[0])) {
if (fieldToParameterMap.ContainsKey(field)) {
expr.Code = ILCode.Starg;
expr.Operand = fieldToParameterMap[field];
} else {
expr.Code = ILCode.Stloc;
expr.Operand = fieldToLocalMap[field];
}
expr.Arguments.RemoveAt(0);
}
break;
case ILCode.Ldflda:
if (fieldToParameterMap.ContainsKey(field)) {
expr.Code = ILCode.Ldarga;
expr.Operand = fieldToParameterMap[field];
} else {
expr.Code = ILCode.Ldloca;
expr.Operand = fieldToLocalMap[field];
}
expr.Arguments.Clear();
break;
}
}
}
}
}
#endregion
}
}