mirror of https://github.com/icsharpcode/ILSpy.git
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
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 |
|
} |
|
}
|
|
|