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); }
List<ILNode> ConvertBody(List<ILNode> body, int startPos, int bodyLength, LabelRangeMapping mapping) { List<ILNode> newBody = new List<ILNode>(); // Copy all instructions from the old body to newBody. for (int pos = startPos; pos < bodyLength; pos++) { ILTryCatchBlock tryCatchBlock = body[pos] as ILTryCatchBlock; ILExpression expr = body[pos] as ILExpression; if (expr != null && expr.Code == ILCode.Leave && expr.Operand == exitLabel) { ILVariable awaiterVar; FieldDef awaiterField; int targetStateID; HandleAwait(newBody, out awaiterVar, out awaiterField, out targetStateID); MarkAsGeneratedVariable(awaiterVar); newBody.Add(new ILExpression(ILCode.Await, null, new ILExpression(ILCode.Ldloca, awaiterVar))); newBody.Add(MakeGoTo(mapping, targetStateID)); } else if (tryCatchBlock != null) { ILTryCatchBlock newTryCatchBlock = new ILTryCatchBlock(); var tryBody = tryCatchBlock.TryBlock.Body; if (tryBody.Count == 0) throw new SymbolicAnalysisFailedException(); StateRangeAnalysis rangeAnalysis = new StateRangeAnalysis(tryBody[0], StateRangeAnalysisMode.AsyncMoveNext, stateField, cachedStateVar); int tryBodyLength = tryBody.Count; int posInTryBody = rangeAnalysis.AssignStateRanges(tryBody, tryBodyLength); rangeAnalysis.EnsureLabelAtPos(tryBody, ref posInTryBody, ref tryBodyLength); var mappingInTryBlock = rangeAnalysis.CreateLabelRangeMapping(tryBody, posInTryBody, tryBodyLength); var newTryBody = ConvertBody(tryBody, posInTryBody, tryBodyLength, mappingInTryBlock); newTryBody.Insert(0, MakeGoTo(mappingInTryBlock, initialState)); // If there's a label at the beginning of the state dispatcher, copy that if (posInTryBody > 0 && tryBody.FirstOrDefault() is ILLabel) newTryBody.Insert(0, tryBody.First()); newTryCatchBlock.TryBlock = new ILBlock(newTryBody); newTryCatchBlock.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>(tryCatchBlock.CatchBlocks); newTryCatchBlock.FaultBlock = tryCatchBlock.FaultBlock; if (tryCatchBlock.FinallyBlock != null) newTryCatchBlock.FinallyBlock = new ILBlock(ConvertFinally(tryCatchBlock.FinallyBlock.Body)); newBody.Add(newTryCatchBlock); } else { newBody.Add(body[pos]); } } return newBody; }
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; }
void AnalyzeStateMachine(ILBlock block) { var body = block.Body; if (body.Count == 0) throw new SymbolicAnalysisFailedException(); if (DetectDoFinallyBodies(body)) { body.RemoveAt(0); if (body.Count == 0) throw new SymbolicAnalysisFailedException(); } StateRangeAnalysis rangeAnalysis = new StateRangeAnalysis(body[0], StateRangeAnalysisMode.AsyncMoveNext, stateField, cachedStateVar); int bodyLength = block.Body.Count; int pos = rangeAnalysis.AssignStateRanges(body, bodyLength); rangeAnalysis.EnsureLabelAtPos(body, ref pos, ref bodyLength); var labelStateRangeMapping = rangeAnalysis.CreateLabelRangeMapping(body, pos, bodyLength); newTopLevelBody = ConvertBody(body, pos, bodyLength, labelStateRangeMapping); newTopLevelBody.Insert(0, MakeGoTo(labelStateRangeMapping, initialState)); newTopLevelBody.Add(setResultAndExitLabel); if (methodType == AsyncMethodType.TaskOfT) { newTopLevelBody.Add(new ILExpression(ILCode.Ret, null, resultExpr)); } else { newTopLevelBody.Add(new ILExpression(ILCode.Ret, null)); } }