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.
635 lines
24 KiB
635 lines
24 KiB
// 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 |
|
{ |
|
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) |
|
{ |
|
this.NewBodyPos = newBodyPos; |
|
this.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 |
|
} |
|
}
|
|
|