int AssignStateRanges(List <ILNode> body, int bodyLength, bool forDispose) { if (bodyLength == 0) { return(0); } for (int i = 0; i < bodyLength; i++) { StateRange nodeRange = ranges[body[i]]; nodeRange.Simplify(); ILLabel label = body[i] as ILLabel; if (label != null) { ranges[body[i + 1]].UnionWith(nodeRange); continue; } ILTryCatchBlock tryFinally = body[i] as ILTryCatchBlock; if (tryFinally != null) { if (!forDispose || tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null) { throw new YieldAnalysisFailedException(); } ranges[tryFinally.TryBlock].UnionWith(nodeRange); AssignStateRanges(tryFinally.TryBlock.Body, tryFinally.TryBlock.Body.Count, forDispose); continue; } ILExpression expr = 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); } StateRange nextRange = ranges[body[i + 1]]; nextRange.UnionWith(nodeRange, int.MinValue, -1 - val.Constant); nextRange.UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue); break; } case ILCode.Br: case ILCode.Leave: ranges[(ILLabel)expr.Operand].UnionWith(nodeRange); break; case ILCode.Brtrue: { SymbolicValue val = Eval(expr.Arguments[0]); if (val.Type == SymbolicValueType.StateEquals) { ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant); StateRange nextRange = ranges[body[i + 1]]; nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); } else if (val.Type == SymbolicValueType.StateInEquals) { ranges[body[i + 1]].UnionWith(nodeRange, val.Constant, val.Constant); StateRange targetRange = ranges[(ILLabel)expr.Operand]; targetRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); targetRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); } else { throw new YieldAnalysisFailedException(); } break; } case ILCode.Nop: ranges[body[i + 1]].UnionWith(nodeRange); break; case ILCode.Ret: break; case ILCode.Stloc: { SymbolicValue val = Eval(expr.Arguments[0]); if (val.Type == SymbolicValueType.State && val.Constant == 0 && rangeAnalysisStateVariable == null) { rangeAnalysisStateVariable = (ILVariable)expr.Operand; } else { throw new YieldAnalysisFailedException(); } goto case ILCode.Nop; } case ILCode.Call: // in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks if (forDispose) { MethodDefinition mdef = GetMethodDefinition(expr.Operand as MethodReference); if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) { throw new YieldAnalysisFailedException(); } finallyMethodToStateInterval.Add(mdef, nodeRange.ToEnclosingInterval()); } else { throw new YieldAnalysisFailedException(); } break; default: if (forDispose) { throw new YieldAnalysisFailedException(); } else { return(i); } } } return(bodyLength); }
public bool SimplifyTernaryOperator(List <ILNode> body, ILBasicBlock head, int pos) { Debug.Assert(body.Contains(head)); ILExpression condExpr; ILLabel trueLabel; ILLabel falseLabel; ILVariable trueLocVar = null; ILExpression trueExpr; ILLabel trueFall; ILVariable falseLocVar = null; ILExpression falseExpr; ILLabel falseFall; object unused; if (head.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel) && labelGlobalRefCount[trueLabel] == 1 && labelGlobalRefCount[falseLabel] == 1 && ((labelToBasicBlock[trueLabel].MatchSingleAndBr(ILCode.Stloc, out trueLocVar, out trueExpr, out trueFall) && labelToBasicBlock[falseLabel].MatchSingleAndBr(ILCode.Stloc, out falseLocVar, out falseExpr, out falseFall) && trueLocVar == falseLocVar && trueFall == falseFall) || (labelToBasicBlock[trueLabel].MatchSingle(ILCode.Ret, out unused, out trueExpr) && labelToBasicBlock[falseLabel].MatchSingle(ILCode.Ret, out unused, out falseExpr))) && body.Contains(labelToBasicBlock[trueLabel]) && body.Contains(labelToBasicBlock[falseLabel]) ) { bool isStloc = trueLocVar != null; ILCode opCode = isStloc ? ILCode.Stloc : ILCode.Ret; TypeSig retType = isStloc ? trueLocVar.Type : this.context.CurrentMethod.ReturnType; bool retTypeIsBoolean = retType.GetElementType() == ElementType.Boolean; int leftBoolVal; int rightBoolVal; ILExpression newExpr; // a ? true:false is equivalent to a // a ? false:true is equivalent to !a // a ? true : b is equivalent to a || b // a ? b : true is equivalent to !a || b // a ? b : false is equivalent to a && b // a ? false : b is equivalent to !a && b if (retTypeIsBoolean && trueExpr.Match(ILCode.Ldc_I4, out leftBoolVal) && falseExpr.Match(ILCode.Ldc_I4, out rightBoolVal) && ((leftBoolVal != 0 && rightBoolVal == 0) || (leftBoolVal == 0 && rightBoolVal != 0)) ) { // It can be expressed as trivilal expression if (leftBoolVal != 0) { newExpr = condExpr; } else { newExpr = new ILExpression(ILCode.LogicNot, null, condExpr) { InferredType = corLib.Boolean }; } } else if ((retTypeIsBoolean || falseExpr.InferredType.GetElementType() == ElementType.Boolean) && trueExpr.Match(ILCode.Ldc_I4, out leftBoolVal) && (leftBoolVal == 0 || leftBoolVal == 1)) { // It can be expressed as logical expression if (leftBoolVal != 0) { newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, condExpr, falseExpr); } else { newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, new ILExpression(ILCode.LogicNot, null, condExpr), falseExpr); } } else if ((retTypeIsBoolean || trueExpr.InferredType.GetElementType() == ElementType.Boolean) && falseExpr.Match(ILCode.Ldc_I4, out rightBoolVal) && (rightBoolVal == 0 || rightBoolVal == 1)) { // It can be expressed as logical expression if (rightBoolVal != 0) { newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, new ILExpression(ILCode.LogicNot, null, condExpr), trueExpr); } else { newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, condExpr, trueExpr); } } else { // Ternary operator tends to create long complicated return statements if (opCode == ILCode.Ret) { return(false); } // Only simplify generated variables if (opCode == ILCode.Stloc && !trueLocVar.IsGenerated) { return(false); } // Create ternary expression newExpr = new ILExpression(ILCode.TernaryOp, null, condExpr, trueExpr, falseExpr); } var tail = head.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); var newExprNodes = newExpr.GetSelfAndChildrenRecursive <ILNode>().ToArray(); foreach (var node in labelToBasicBlock[trueLabel].GetSelfAndChildrenRecursive <ILNode>().Except(newExprNodes)) { newExpr.ILRanges.AddRange(node.AllILRanges); } foreach (var node in labelToBasicBlock[falseLabel].GetSelfAndChildrenRecursive <ILNode>().Except(newExprNodes)) { newExpr.ILRanges.AddRange(node.AllILRanges); } newExpr.ILRanges.AddRange(tail[0].ILRanges); newExpr.ILRanges.AddRange(tail[1].GetSelfAndChildrenRecursiveILRanges()); head.Body.Add(new ILExpression(opCode, trueLocVar, newExpr)); if (isStloc) { head.Body.Add(new ILExpression(ILCode.Br, trueFall)); } // Remove the old basic blocks body.RemoveOrThrow(labelToBasicBlock[trueLabel]); body.RemoveOrThrow(labelToBasicBlock[falseLabel]); return(true); } return(false); }
/// <summary> /// Initializes the state range logic: /// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue) /// </summary> void InitStateRanges(ILNode entryPoint) { ranges = new DefaultDictionary <ILNode, StateRange>(n => new StateRange()); ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue); rangeAnalysisStateVariable = null; }
public readonly ILVariable LoadFrom; // Variable used for storage of the value public StackSlot(ByteCode[] definitions, ILVariable loadFrom) { this.Definitions = definitions; this.LoadFrom = loadFrom; }
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); }
/// <summary> /// Is this a temporary variable generated by the C# compiler for instance method calls on value type values /// </summary> /// <param name="next">The next top-level expression</param> /// <param name="parent">The direct parent of the load within 'next'</param> /// <param name="pos">Index of the load within 'parent'</param> /// <param name="v">The variable being inlined.</param> /// <param name="inlinedExpression">The expression being inlined</param> bool IsGeneratedValueTypeTemporary(ILExpression next, ILExpression parent, int pos, ILVariable v, ILExpression inlinedExpression) { if (pos == 0 && v.Type != null && DnlibExtensions.IsValueType(v.Type)) { // Inlining a value type variable is allowed only if the resulting code will maintain the semantics // that the method is operating on a copy. // Thus, we have to disallow inlining of other locals, fields, array elements, dereferenced pointers switch (inlinedExpression.Code) { case ILCode.Ldloc: case ILCode.Stloc: case ILCode.CompoundAssignment: case ILCode.Ldelem: case ILCode.Ldelem_I: case ILCode.Ldelem_I1: case ILCode.Ldelem_I2: case ILCode.Ldelem_I4: case ILCode.Ldelem_I8: case ILCode.Ldelem_R4: case ILCode.Ldelem_R8: case ILCode.Ldelem_Ref: case ILCode.Ldelem_U1: case ILCode.Ldelem_U2: case ILCode.Ldelem_U4: case ILCode.Ldobj: case ILCode.Ldind_Ref: return(false); case ILCode.Ldfld: case ILCode.Stfld: case ILCode.Ldsfld: case ILCode.Stsfld: // allow inlining field access only if it's a readonly field FieldDef f = ((IField)inlinedExpression.Operand).Resolve(); if (!(f != null && f.IsInitOnly)) { return(false); } break; case ILCode.Call: case ILCode.CallGetter: // inlining runs both before and after IntroducePropertyAccessInstructions, // so we have to handle both 'call' and 'callgetter' IMethod mr = (IMethod)inlinedExpression.Operand; // ensure that it's not an multi-dimensional array getter TypeSig ts; if (mr.DeclaringType is TypeSpec && (ts = ((TypeSpec)mr.DeclaringType).TypeSig.RemovePinnedAndModifiers()) != null && ts.IsSingleOrMultiDimensionalArray) { return(false); } goto case ILCode.Callvirt; case ILCode.Callvirt: case ILCode.CallvirtGetter: // don't inline foreach loop variables: mr = (IMethod)inlinedExpression.Operand; if (mr.Name == "get_Current" && mr.MethodSig != null && mr.MethodSig.HasThis) { return(false); } break; case ILCode.Castclass: case ILCode.Unbox_Any: // These are valid, but might occur as part of a foreach loop variable. ILExpression arg = inlinedExpression.Arguments[0]; if (arg.Code == ILCode.CallGetter || arg.Code == ILCode.CallvirtGetter || arg.Code == ILCode.Call || arg.Code == ILCode.Callvirt) { mr = (IMethod)arg.Operand; if (mr.Name == "get_Current" && mr.MethodSig != null && mr.MethodSig.HasThis) { return(false); // looks like a foreach loop variable, so don't inline it } } break; } // inline the compiler-generated variable that are used when accessing a member on a value type: switch (parent.Code) { case ILCode.Call: case ILCode.CallGetter: case ILCode.CallSetter: case ILCode.Callvirt: case ILCode.CallvirtGetter: case ILCode.CallvirtSetter: IMethod mr = parent.Operand as IMethod; return(mr == null || mr.MethodSig == null ? false : mr.MethodSig.HasThis); case ILCode.Stfld: case ILCode.Ldfld: case ILCode.Ldflda: case ILCode.Await: return(true); } } return(false); }
List <ByteCode> StackAnalysis(MethodDef methodDef) { Dictionary <Instruction, ByteCode> instrToByteCode = new Dictionary <Instruction, ByteCode>(); // Create temporary structure for the stack analysis List <ByteCode> body = new List <ByteCode>(methodDef.Body.Instructions.Count); List <Instruction> prefixes = null; var instructions = methodDef.Body.Instructions; for (int i = 0; i < instructions.Count; i++) { var inst = instructions[i]; if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { if (prefixes == null) { prefixes = new List <Instruction>(1); } prefixes.Add(inst); continue; } ILCode code = ilCodeTranslation[inst.OpCode.Code]; object operand = inst.Operand; ILCodeUtil.ExpandMacro(ref code, ref operand, methodDef); var next = i + 1 < instructions.Count ? instructions[i + 1] : null; ByteCode byteCode = new ByteCode() { Offset = inst.Offset, EndOffset = next != null ? next.Offset : (uint)methodDef.Body.GetCodeSize(), Code = code, Operand = operand, PopCount = inst.GetPopDelta(methodDef), PushCount = inst.GetPushDelta(methodDef) }; if (prefixes != null) { instrToByteCode[prefixes[0]] = byteCode; byteCode.Offset = prefixes[0].Offset; byteCode.Prefixes = prefixes.ToArray(); prefixes = null; } else { instrToByteCode[inst] = byteCode; } body.Add(byteCode); } for (int i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } Stack <ByteCode> agenda = new Stack <ByteCode>(); int varCount = methodDef.Body.Variables.Count; var exceptionHandlerStarts = new HashSet <ByteCode>(methodDef.Body.ExceptionHandlers.Select(eh => eh.HandlerStart == null ? null : instrToByteCode[eh.HandlerStart])); exceptionHandlerStarts.Remove(null); // HACK: Some MS reference assemblies contain just a RET instruction. If the method // returns a value, the code below will eventually throw an exception in // StackSlot.ModifyStack(). if (body.Count == 1 && body[0].Code == ILCode.Ret) { body[0].PopCount = 0; } // Add known states if (methodDef.Body.HasExceptionHandlers) { foreach (ExceptionHandler ex in methodDef.Body.ExceptionHandlers) { if (ex.HandlerStart == null) { continue; } ByteCode handlerStart = instrToByteCode[ex.HandlerStart]; handlerStart.StackBefore = new StackSlot[0]; handlerStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { // Catch and Filter handlers start with the exeption on the stack ByteCode ldexception = new ByteCode() { Code = ILCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; ldexceptions[ex] = ldexception; handlerStart.StackBefore = new StackSlot[] { new StackSlot(new [] { ldexception }, null) }; } agenda.Push(handlerStart); if (ex.HandlerType == ExceptionHandlerType.Filter && ex.FilterStart != null) { ByteCode filterStart = instrToByteCode[ex.FilterStart]; ByteCode ldexception = new ByteCode() { Code = ILCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; ldfilters[ex] = ldexception; filterStart.StackBefore = new StackSlot[] { new StackSlot(new [] { ldexception }, null) }; filterStart.VariablesBefore = VariableSlot.MakeUknownState(varCount); agenda.Push(filterStart); } } } body[0].StackBefore = new StackSlot[0]; body[0].VariablesBefore = VariableSlot.MakeUknownState(varCount); agenda.Push(body[0]); // Process agenda while (agenda.Count > 0) { ByteCode byteCode = agenda.Pop(); // Calculate new stack StackSlot[] newStack = StackSlot.ModifyStack(byteCode.StackBefore, byteCode.PopCount ?? byteCode.StackBefore.Length, byteCode.PushCount, byteCode); // Calculate new variable state VariableSlot[] newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); if (byteCode.IsVariableDefinition && byteCode.Operand is Local) { newVariableState[((Local)byteCode.Operand).Index] = new VariableSlot(new [] { byteCode }, false); } // After the leave, finally block might have touched the variables if (byteCode.Code == ILCode.Leave) { newVariableState = VariableSlot.MakeUknownState(varCount); } // Find all successors List <ByteCode> branchTargets = new List <ByteCode>(); if (!byteCode.Code.IsUnconditionalControlFlow()) { if (exceptionHandlerStarts.Contains(byteCode.Next)) { // Do not fall though down to exception handler // It is invalid IL as per ECMA-335 §12.4.2.8.1, but some obfuscators produce it } else { branchTargets.Add(byteCode.Next); } } if (byteCode.Operand is IList <Instruction> ) { foreach (Instruction inst in (IList <Instruction>)byteCode.Operand) { ByteCode target = instrToByteCode[inst]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new ILLabel() { Name = target.Name }; } } } else if (byteCode.Operand is Instruction) { ByteCode target = instrToByteCode[(Instruction)byteCode.Operand]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new ILLabel() { Name = target.Name }; } } // Apply the state to successors foreach (ByteCode branchTarget in branchTargets) { if (branchTarget.StackBefore == null && branchTarget.VariablesBefore == null) { if (branchTargets.Count == 1) { branchTarget.StackBefore = newStack; branchTarget.VariablesBefore = newVariableState; } else { // Do not share data for several bytecodes branchTarget.StackBefore = StackSlot.ModifyStack(newStack, 0, 0, null); branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState); } agenda.Push(branchTarget); } else { if (branchTarget.StackBefore.Length != newStack.Length) { throw new Exception("Inconsistent stack size at " + byteCode.Name); } // Be careful not to change our new data - it might be reused for several branch targets. // In general, be careful that two bytecodes never share data structures. bool modified = false; // Merge stacks - modify the target for (int i = 0; i < newStack.Length; i++) { ByteCode[] oldDefs = branchTarget.StackBefore[i].Definitions; ByteCode[] newDefs = oldDefs.Union(newStack[i].Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.StackBefore[i] = new StackSlot(newDefs, null); modified = true; } } // Merge variables - modify the target for (int i = 0; i < newVariableState.Length; i++) { VariableSlot oldSlot = branchTarget.VariablesBefore[i]; VariableSlot newSlot = newVariableState[i]; if (!oldSlot.UnknownDefinition) { if (newSlot.UnknownDefinition) { branchTarget.VariablesBefore[i] = newSlot; modified = true; } else { ByteCode[] oldDefs = oldSlot.Definitions; ByteCode[] newDefs = oldDefs.Union(newSlot.Definitions); if (newDefs.Length > oldDefs.Length) { branchTarget.VariablesBefore[i] = new VariableSlot(newDefs, false); modified = true; } } } } if (modified) { agenda.Push(branchTarget); } } } } // Occasionally the compilers or obfuscators generate unreachable code (which might be intentionally invalid) // I believe it is safe to just remove it body.RemoveAll(b => b.StackBefore == null); // Generate temporary variables to replace stack foreach (ByteCode byteCode in body) { int argIdx = 0; int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { ILVariable tmpVar = new ILVariable() { Name = string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), IsGenerated = true }; byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].Definitions, tmpVar); foreach (ByteCode pushedBy in byteCode.StackBefore[i].Definitions) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List <ILVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } argIdx++; } } // Try to use single temporary variable insted of several if possilbe (especially useful for dup) // This has to be done after all temporary variables are assigned so we know about all loads foreach (ByteCode byteCode in body) { if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => slot.Definitions.Length == 1 && slot.Definitions[0] == byteCode)) { // Great - we can reduce everything into single variable ILVariable tmpVar = new ILVariable() { Name = string.Format("expr_{0:X2}", byteCode.Offset), IsGenerated = true }; byteCode.StoreTo = new List <ILVariable>() { tmpVar }; foreach (ByteCode bc in body) { for (int i = 0; i < bc.StackBefore.Length; i++) { // Is it one of the variable to be merged? if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { // Replace with the new temp variable bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].Definitions, tmpVar); } } } } } } // Split and convert the normal local variables ConvertLocalVariables(body); // Convert branch targets to labels foreach (ByteCode byteCode in body) { if (byteCode.Operand is IList <Instruction> ) { List <ILLabel> newOperand = new List <ILLabel>(); foreach (Instruction target in (IList <Instruction>)byteCode.Operand) { newOperand.Add(instrToByteCode[target].Label); } byteCode.Operand = newOperand.ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label; } } // Convert parameters to ILVariables ConvertParameters(body); return(body); }
public StackSlot(ByteCode pushedBy) { this.PushedBy = new[] { pushedBy }; this.LoadFrom = null; }
List <ILNode> ConvertToAst(List <ByteCode> body, HashSet <ExceptionHandler> ehs) { List <ILNode> ast = new List <ILNode>(); while (ehs.Any()) { ILTryCatchBlock tryCatchBlock = new ILTryCatchBlock(); // Find the first and widest scope int tryStart = ehs.Min(eh => eh.TryStart.Offset); int tryEnd = ehs.Where(eh => eh.TryStart.Offset == tryStart).Max(eh => eh.TryEnd.Offset); var handlers = ehs.Where(eh => eh.TryStart.Offset == tryStart && eh.TryEnd.Offset == tryEnd).ToList(); // Cut all instructions up to the try block { int tryStartIdx; for (tryStartIdx = 0; body[tryStartIdx].Offset != tryStart; tryStartIdx++) { ; } ast.AddRange(ConvertToAst(body.CutRange(0, tryStartIdx))); } // Cut the try block { HashSet <ExceptionHandler> nestedEHs = new HashSet <ExceptionHandler>(ehs.Where(eh => (tryStart <= eh.TryStart.Offset && eh.TryEnd.Offset < tryEnd) || (tryStart < eh.TryStart.Offset && eh.TryEnd.Offset <= tryEnd))); ehs.ExceptWith(nestedEHs); int tryEndIdx; for (tryEndIdx = 0; tryEndIdx < body.Count && body[tryEndIdx].Offset != tryEnd; tryEndIdx++) { ; } tryCatchBlock.TryBlock = new ILBlock(ConvertToAst(body.CutRange(0, tryEndIdx), nestedEHs)); } // Cut all handlers tryCatchBlock.CatchBlocks = new List <ILTryCatchBlock.CatchBlock>(); foreach (ExceptionHandler eh in handlers) { int startIndex; for (startIndex = 0; body[startIndex].Offset != eh.HandlerStart.Offset; startIndex++) { ; } int endInclusiveIndex; if (eh.HandlerEnd == null) { endInclusiveIndex = body.Count - 1; } // Note that the end(exclusive) instruction may not necessarly be in our body else { for (endInclusiveIndex = 0; body[endInclusiveIndex].Next.Offset != eh.HandlerEnd.Offset; endInclusiveIndex++) { ; } } int count = 1 + endInclusiveIndex - startIndex; HashSet <ExceptionHandler> nestedEHs = new HashSet <ExceptionHandler>(ehs.Where(e => (eh.HandlerStart.Offset <= e.TryStart.Offset && e.TryEnd.Offset < eh.HandlerEnd.Offset) || (eh.HandlerStart.Offset < e.TryStart.Offset && e.TryEnd.Offset <= eh.HandlerEnd.Offset))); ehs.ExceptWith(nestedEHs); List <ILNode> handlerAst = ConvertToAst(body.CutRange(startIndex, count), nestedEHs); if (eh.HandlerType == ExceptionHandlerType.Catch) { ILTryCatchBlock.CatchBlock catchBlock = new ILTryCatchBlock.CatchBlock() { ExceptionType = eh.CatchType, Body = handlerAst }; // Handle the automatically pushed exception on the stack ByteCode ldexception = ldexceptions[eh]; if (ldexception.StoreTo.Count == 0) { throw new Exception("Exception should be consumed by something"); } else if (ldexception.StoreTo.Count == 1) { ILExpression first = catchBlock.Body[0] as ILExpression; if (first != null && first.Code == ILCode.Pop && first.Arguments[0].Code == ILCode.Ldloc && first.Arguments[0].Operand == ldexception.StoreTo[0]) { // The exception is just poped - optimize it all away; catchBlock.ExceptionVariable = null; catchBlock.Body.RemoveAt(0); } else { catchBlock.ExceptionVariable = ldexception.StoreTo[0]; } } else { ILVariable exTemp = new ILVariable() { Name = "ex_" + eh.HandlerStart.Offset.ToString("X2"), IsGenerated = true }; catchBlock.ExceptionVariable = exTemp; foreach (ILVariable storeTo in ldexception.StoreTo) { catchBlock.Body.Insert(0, new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, exTemp))); } } tryCatchBlock.CatchBlocks.Add(catchBlock); } else if (eh.HandlerType == ExceptionHandlerType.Finally) { tryCatchBlock.FinallyBlock = new ILBlock(handlerAst); } else if (eh.HandlerType == ExceptionHandlerType.Fault) { tryCatchBlock.FaultBlock = new ILBlock(handlerAst); } else { // TODO: ExceptionHandlerType.Filter } } ehs.ExceptWith(handlers); ast.Add(tryCatchBlock); } // Add whatever is left ast.AddRange(ConvertToAst(body)); return(ast); }
List <ByteCode> StackAnalysis(MethodDefinition methodDef) { Dictionary <Instruction, ByteCode> instrToByteCode = new Dictionary <Instruction, ByteCode>(); // Create temporary structure for the stack analysis List <ByteCode> body = new List <ByteCode>(methodDef.Body.Instructions.Count); List <Instruction> prefixes = null; foreach (Instruction inst in methodDef.Body.Instructions) { if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { if (prefixes == null) { prefixes = new List <Instruction>(1); } prefixes.Add(inst); continue; } ILCode code = (ILCode)inst.OpCode.Code; object operand = inst.Operand; ILCodeUtil.ExpandMacro(ref code, ref operand, methodDef.Body); ByteCode byteCode = new ByteCode() { Offset = inst.Offset, EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize, Code = code, Operand = operand, PopCount = inst.GetPopDelta(), PushCount = inst.GetPushDelta() }; if (prefixes != null) { instrToByteCode[prefixes[0]] = byteCode; byteCode.Offset = prefixes[0].Offset; byteCode.Prefixes = prefixes.ToArray(); prefixes = null; } else { instrToByteCode[inst] = byteCode; } body.Add(byteCode); } for (int i = 0; i < body.Count - 1; i++) { body[i].Next = body[i + 1]; } Stack <ByteCode> agenda = new Stack <ByteCode>(); int varCount = methodDef.Body.Variables.Count; // Add known states if (methodDef.Body.HasExceptionHandlers) { foreach (ExceptionHandler ex in methodDef.Body.ExceptionHandlers) { ByteCode handlerStart = instrToByteCode[ex.HandlerStart]; handlerStart.StackBefore = new List <StackSlot>(); handlerStart.VariablesBefore = VariableSlot.MakeFullState(varCount); if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { // Catch and Filter handlers start with the exeption on the stack ByteCode ldexception = new ByteCode() { Code = ILCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; ldexceptions[ex] = ldexception; handlerStart.StackBefore.Add(new StackSlot(ldexception)); } agenda.Push(handlerStart); if (ex.HandlerType == ExceptionHandlerType.Filter) { ByteCode filterStart = instrToByteCode[ex.FilterStart]; filterStart.StackBefore = new List <StackSlot>(); filterStart.VariablesBefore = VariableSlot.MakeFullState(varCount); ByteCode ldexception = new ByteCode() { Code = ILCode.Ldexception, Operand = ex.CatchType, PopCount = 0, PushCount = 1 }; // TODO: ldexceptions[ex] = ldexception; filterStart.StackBefore.Add(new StackSlot(ldexception)); agenda.Push(filterStart); } } } body[0].StackBefore = new List <StackSlot>(); body[0].VariablesBefore = VariableSlot.MakeEmptyState(varCount); agenda.Push(body[0]); // Process agenda while (agenda.Count > 0) { ByteCode byteCode = agenda.Pop(); // Calculate new stack List <StackSlot> newStack = StackSlot.CloneStack(byteCode.StackBefore, byteCode.PopCount); for (int i = 0; i < byteCode.PushCount; i++) { newStack.Add(new StackSlot(byteCode)); } // Calculate new variable state VariableSlot[] newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); if (byteCode.Code == ILCode.Stloc) { int varIndex = ((VariableReference)byteCode.Operand).Index; newVariableState[varIndex] = new VariableSlot(byteCode); } // After the leave, finally block might have touched the variables if (byteCode.Code == ILCode.Leave) { newVariableState = VariableSlot.MakeFullState(varCount); } // Find all successors List <ByteCode> branchTargets = new List <ByteCode>(); if (!byteCode.Code.IsUnconditionalControlFlow()) { branchTargets.Add(byteCode.Next); } if (byteCode.Operand is Instruction[]) { foreach (Instruction inst in (Instruction[])byteCode.Operand) { ByteCode target = instrToByteCode[inst]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new ILLabel() { Name = target.Name }; } } } else if (byteCode.Operand is Instruction) { ByteCode target = instrToByteCode[(Instruction)byteCode.Operand]; branchTargets.Add(target); // The target of a branch must have label if (target.Label == null) { target.Label = new ILLabel() { Name = target.Name }; } } // Apply the state to successors foreach (ByteCode branchTarget in branchTargets) { if (branchTarget.StackBefore == null && branchTarget.VariablesBefore == null) { if (branchTargets.Count == 1) { branchTarget.StackBefore = newStack; branchTarget.VariablesBefore = newVariableState; } else { // Do not share data for several bytecodes branchTarget.StackBefore = StackSlot.CloneStack(newStack, 0); branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState); } agenda.Push(branchTarget); } else { if (branchTarget.StackBefore.Count != newStack.Count) { throw new Exception("Inconsistent stack size at " + byteCode.Name); } // Be careful not to change our new data - it might be reused for several branch targets. // In general, be careful that two bytecodes never share data structures. bool modified = false; // Merge stacks - modify the target for (int i = 0; i < newStack.Count; i++) { ByteCode[] oldPushedBy = branchTarget.StackBefore[i].PushedBy; ByteCode[] newPushedBy = oldPushedBy.Union(newStack[i].PushedBy); if (newPushedBy.Length > oldPushedBy.Length) { branchTarget.StackBefore[i] = new StackSlot(newPushedBy, null); modified = true; } } // Merge variables - modify the target for (int i = 0; i < newVariableState.Length; i++) { VariableSlot oldSlot = branchTarget.VariablesBefore[i]; VariableSlot newSlot = newVariableState[i]; // All can not be unioned further if (!oldSlot.StoredByAll) { if (newSlot.StoredByAll) { branchTarget.VariablesBefore[i] = newSlot; modified = true; } else { ByteCode[] oldStoredBy = oldSlot.StoredBy; ByteCode[] newStoredBy = oldStoredBy.Union(newSlot.StoredBy); if (newStoredBy.Length > oldStoredBy.Length) { branchTarget.VariablesBefore[i] = new VariableSlot(newStoredBy, false); modified = true; } } } } if (modified) { agenda.Push(branchTarget); } } } } // Occasionally the compiler generates unreachable code - it can be usually just ignored var reachableBody = body.Where(b => b.StackBefore != null); var unreachableBody = body.Where(b => b.StackBefore == null); // Genertate temporary variables to replace stack // Unrachable code does not need temporary variables - the values are never pushed on the stack for consuption foreach (ByteCode byteCode in reachableBody) { int argIdx = 0; int popCount = byteCode.PopCount ?? byteCode.StackBefore.Count; for (int i = byteCode.StackBefore.Count - popCount; i < byteCode.StackBefore.Count; i++) { ILVariable tmpVar = new ILVariable() { Name = string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), IsGenerated = true }; byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].PushedBy, tmpVar); foreach (ByteCode pushedBy in byteCode.StackBefore[i].PushedBy) { if (pushedBy.StoreTo == null) { pushedBy.StoreTo = new List <ILVariable>(1); } pushedBy.StoreTo.Add(tmpVar); } argIdx++; } } // Try to use single temporary variable insted of several if possilbe (especially useful for dup) // This has to be done after all temporary variables are assigned so we know about all loads // Unrachable code will not have any StoreTo foreach (ByteCode byteCode in reachableBody) { if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { var locVars = byteCode.StoreTo; // For each of the variables, find the location where it is loaded - there should be preciesly one var loadedBy = locVars.Select(locVar => reachableBody.SelectMany(bc => bc.StackBefore).Where(s => s.LoadFrom == locVar).Single()).ToList(); // We now know that all the variables have a single load, // Let's make sure that they have also a single store - us if (loadedBy.All(slot => slot.PushedBy.Length == 1 && slot.PushedBy[0] == byteCode)) { // Great - we can reduce everything into single variable ILVariable tmpVar = new ILVariable() { Name = string.Format("expr_{0:X2}", byteCode.Offset), IsGenerated = true }; byteCode.StoreTo = new List <ILVariable>() { tmpVar }; foreach (ByteCode bc in reachableBody) { for (int i = 0; i < bc.StackBefore.Count; i++) { // Is it one of the variable to be merged? if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { // Replace with the new temp variable bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].PushedBy, tmpVar); } } } } } } // Split and convert the normal local variables ConvertLocalVariables(body); // Convert branch targets to labels foreach (ByteCode byteCode in body) { if (byteCode.Operand is Instruction[]) { List <ILLabel> newOperand = new List <ILLabel>(); foreach (Instruction target in (Instruction[])byteCode.Operand) { newOperand.Add(instrToByteCode[target].Label); } byteCode.Operand = newOperand.ToArray(); } else if (byteCode.Operand is Instruction) { byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label; } } // Convert parameters to ILVariables ConvertParameters(body); return(body); }
public readonly ILVariable LoadFrom; // Where can we get the value from in AST public StackSlot(ByteCode[] pushedBy, ILVariable loadFrom) { this.PushedBy = pushedBy; this.LoadFrom = loadFrom; }
public static bool MatchStloc(this ILNode node, ILVariable expectedVar, out ILExpression expr) { ILVariable v; return(node.Match(ILCode.Stloc, out v, out expr) && v == expectedVar); }
public static bool MatchLdloca(this ILNode node, ILVariable expectedVar) { ILVariable v; return(node.Match(ILCode.Ldloca, out v) && v == expectedVar); }
void HandleAwait(List <ILNode> newBody, out ILVariable awaiterVar, out FieldDefinition awaiterField, out int targetStateID) { // Handle the instructions prior to the exit out of the method to detect what is being awaited. // (analyses the last instructions in newBody and removes the analyzed instructions from newBody) if (doFinallyBodies != null) { // stloc(<>t__doFinallyBodies, ldc.i4(0)) ILExpression dfbInitExpr; if (!newBody.LastOrDefault().MatchStloc(doFinallyBodies, out dfbInitExpr)) { throw new SymbolicAnalysisFailedException(); } int val; if (!(dfbInitExpr.Match(ILCode.Ldc_I4, out val) && val == 0)) { throw new SymbolicAnalysisFailedException(); } newBody.RemoveAt(newBody.Count - 1); // remove doFinallyBodies assignment } // call(AsyncTaskMethodBuilder::AwaitUnsafeOnCompleted, ldflda(StateMachine::<>t__builder, ldloc(this)), ldloca(CS$0$0001), ldloc(this)) ILExpression callAwaitUnsafeOnCompleted = newBody.LastOrDefault() as ILExpression; newBody.RemoveAt(newBody.Count - 1); // remove AwaitUnsafeOnCompleted call if (callAwaitUnsafeOnCompleted == null || callAwaitUnsafeOnCompleted.Code != ILCode.Call) { throw new SymbolicAnalysisFailedException(); } if (((MethodReference)callAwaitUnsafeOnCompleted.Operand).Name != "AwaitUnsafeOnCompleted") { throw new SymbolicAnalysisFailedException(); } if (callAwaitUnsafeOnCompleted.Arguments.Count != 3) { throw new SymbolicAnalysisFailedException(); } if (!callAwaitUnsafeOnCompleted.Arguments[1].Match(ILCode.Ldloca, out awaiterVar)) { throw new SymbolicAnalysisFailedException(); } // stfld(StateMachine::<>u__$awaiter6, ldloc(this), ldloc(CS$0$0001)) FieldReference awaiterFieldRef; ILExpression loadThis, loadAwaiterVar; if (!newBody.LastOrDefault().Match(ILCode.Stfld, out awaiterFieldRef, out loadThis, out loadAwaiterVar)) { throw new SymbolicAnalysisFailedException(); } newBody.RemoveAt(newBody.Count - 1); // remove awaiter field assignment awaiterField = awaiterFieldRef.ResolveWithinSameModule(); if (!(awaiterField != null && loadThis.MatchThis() && loadAwaiterVar.MatchLdloc(awaiterVar))) { throw new SymbolicAnalysisFailedException(); } // stfld(StateMachine::<>1__state, ldloc(this), ldc.i4(0)) if (!MatchStateAssignment(newBody.LastOrDefault(), out targetStateID)) { throw new SymbolicAnalysisFailedException(); } newBody.RemoveAt(newBody.Count - 1); // remove awaiter field assignment }
SymbolicValue Eval(ILExpression expr) { SymbolicValue left, right; switch (expr.Code) { case ILCode.Sub: left = Eval(expr.Arguments[0]); right = Eval(expr.Arguments[1]); if (left.Type != SymbolicValueType.State && left.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 (GetFieldDefinition(expr.Operand as FieldReference) != stateField) { throw new YieldAnalysisFailedException(); } return(new SymbolicValue(SymbolicValueType.State)); case ILCode.Ldloc: ILVariable loadedVariable = (ILVariable)expr.Operand; if (loadedVariable == rangeAnalysisStateVariable) { return(new SymbolicValue(SymbolicValueType.State)); } else if (loadedVariable.IsParameter && loadedVariable.OriginalParameter.Index < 0) { return(new SymbolicValue(SymbolicValueType.This)); } else { throw new YieldAnalysisFailedException(); } case ILCode.Ldc_I4: return(new SymbolicValue(SymbolicValueType.IntegerConstant, (int)expr.Operand)); case ILCode.Ceq: left = Eval(expr.Arguments[0]); right = Eval(expr.Arguments[1]); if (left.Type != SymbolicValueType.State || right.Type != SymbolicValueType.IntegerConstant) { throw new YieldAnalysisFailedException(); } // bool: (state + left.Constant == right.Constant) // bool: (state == right.Constant - left.Constant) return(new SymbolicValue(SymbolicValueType.StateEquals, unchecked (right.Constant - left.Constant))); case ILCode.LogicNot: SymbolicValue val = Eval(expr.Arguments[0]); if (val.Type == SymbolicValueType.StateEquals) { return(new SymbolicValue(SymbolicValueType.StateInEquals, val.Constant)); } else if (val.Type == SymbolicValueType.StateInEquals) { return(new SymbolicValue(SymbolicValueType.StateEquals, val.Constant)); } else { throw new YieldAnalysisFailedException(); } default: throw new YieldAnalysisFailedException(); } }
List <ILNode> ConvertToAst(List <ByteCode> body) { List <ILNode> ast = new List <ILNode>(); // Convert stack-based IL code to ILAst tree foreach (ByteCode byteCode in body) { ILRange ilRange = new ILRange(byteCode.Offset, byteCode.EndOffset); if (byteCode.StackBefore == null) { // Unreachable code continue; } ILExpression expr = new ILExpression(byteCode.Code, byteCode.Operand); expr.ILRanges.Add(ilRange); if (byteCode.Prefixes != null && byteCode.Prefixes.Length > 0) { ILExpressionPrefix[] prefixes = new ILExpressionPrefix[byteCode.Prefixes.Length]; for (int i = 0; i < prefixes.Length; i++) { prefixes[i] = new ILExpressionPrefix(ilCodeTranslation[byteCode.Prefixes[i].OpCode.Code], byteCode.Prefixes[i].Operand); } expr.Prefixes = prefixes; } // Label for this instruction if (byteCode.Label != null) { ast.Add(byteCode.Label); } // Reference arguments using temporary variables int popCount = byteCode.PopCount ?? byteCode.StackBefore.Length; for (int i = byteCode.StackBefore.Length - popCount; i < byteCode.StackBefore.Length; i++) { StackSlot slot = byteCode.StackBefore[i]; expr.Arguments.Add(new ILExpression(ILCode.Ldloc, slot.LoadFrom)); } // Store the result to temporary variable(s) if needed if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) { ast.Add(expr); } else if (byteCode.StoreTo.Count == 1) { ast.Add(new ILExpression(ILCode.Stloc, byteCode.StoreTo[0], expr)); } else { ILVariable tmpVar = new ILVariable() { Name = "expr_" + byteCode.Offset.ToString("X2"), IsGenerated = true }; ast.Add(new ILExpression(ILCode.Stloc, tmpVar, expr)); foreach (ILVariable storeTo in byteCode.StoreTo.AsEnumerable().Reverse()) { ast.Add(new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, tmpVar))); } } } return(ast); }
void AnalyzeMoveNext() { MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext"); ILBlock ilMethod = CreateILAst(moveNextMethod); if (ilMethod.Body.Count == 0) { throw new YieldAnalysisFailedException(); } ILExpression lastReturnArg; if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg)) { throw new YieldAnalysisFailedException(); } // 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 YieldAnalysisFailedException(); } } 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 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(); } ILBlock faultBlock = tryFaultBlock.FaultBlock; // Ensure the fault block contains the call to Dispose(). if (faultBlock.Body.Count != 2) { throw new YieldAnalysisFailedException(); } MethodReference disposeMethodRef; ILExpression disposeArg; if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg)) { throw new YieldAnalysisFailedException(); } if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !disposeArg.MatchThis()) { throw new YieldAnalysisFailedException(); } if (!faultBlock.Body[1].Match(ILCode.Endfinally)) { throw new YieldAnalysisFailedException(); } 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 YieldAnalysisFailedException(); } if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0) { throw new YieldAnalysisFailedException(); } bodyLength--; // don't conside the stloc instruction to be part of the body } // verify that the last element in the body is a label pointing to the 'ret(false)' returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel; if (returnFalseLabel == null) { throw new YieldAnalysisFailedException(); } InitStateRanges(body[0]); int pos = AssignStateRanges(body, bodyLength, forDispose: false); if (pos > 0 && body[pos - 1] is ILLabel) { pos--; } else { // ensure that the first element at body[pos] is a label: ILLabel newLabel = new ILLabel(); newLabel.Name = "YieldReturnEntryPoint"; ranges[newLabel] = ranges[body[pos]]; // give the label the range of the instruction at body[pos] body.Insert(pos, newLabel); bodyLength++; } List <KeyValuePair <ILLabel, StateRange> > labels = new List <KeyValuePair <ILLabel, StateRange> >(); for (int i = pos; i < bodyLength; i++) { ILLabel label = body[i] as ILLabel; if (label != null) { labels.Add(new KeyValuePair <ILLabel, StateRange>(label, ranges[label])); } } ConvertBody(body, pos, bodyLength, labels); }
static bool AdjustInitializerStack(List <ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection, bool isValueType) { // Argument is of the form 'getter(getter(...(v)))' // Unpack it into a list of getters: List <ILExpression> getters = new List <ILExpression>(); while (argument.Code == ILCode.CallvirtGetter || argument.Code == ILCode.CallGetter || argument.Code == ILCode.Ldfld) { getters.Add(argument); if (argument.Arguments.Count != 1) { return(false); } argument = argument.Arguments[0]; } // Ensure that the final argument is 'v' if (isValueType) { ILVariable loadedVar; if (!(argument.Match(ILCode.Ldloca, out loadedVar) && loadedVar == v)) { return(false); } } else { if (!argument.MatchLdloc(v)) { return(false); } } // Now compare the getters with those that are currently active on the initializer stack: int i; for (i = 1; i <= Math.Min(getters.Count, initializerStack.Count - 1); i++) { ILExpression g1 = initializerStack[i].Arguments[0]; // getter stored in initializer ILExpression g2 = getters[getters.Count - i]; // matching getter from argument if (g1.Operand != g2.Operand) { // operands differ, so we abort the comparison break; } } // Remove all initializers from the stack that were not matched with one from the argument: initializerStack.RemoveRange(i, initializerStack.Count - i); // Now create new initializers for the remaining arguments: for (; i <= getters.Count; i++) { ILExpression g = getters[getters.Count - i]; MemberReference mr = (MemberReference)g.Operand; TypeReference returnType; if (mr is FieldReference) { returnType = TypeAnalysis.GetFieldType((FieldReference)mr); } else { returnType = TypeAnalysis.SubstituteTypeArgs(((MethodReference)mr).ReturnType, mr); } ILExpression nestedInitializer = new ILExpression( IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject, null, g); // add new initializer to its parent: ILExpression parentInitializer = initializerStack[initializerStack.Count - 1]; if (parentInitializer.Code == ILCode.InitCollection) { // can't add children to collection initializer if (parentInitializer.Arguments.Count == 1) { // convert empty collection initializer to object initializer parentInitializer.Code = ILCode.InitObject; } else { return(false); } } parentInitializer.Arguments.Add(nestedInitializer); initializerStack.Add(nestedInitializer); } ILExpression lastInitializer = initializerStack[initializerStack.Count - 1]; if (isCollection) { return(lastInitializer.Code == ILCode.InitCollection); } else { if (lastInitializer.Code == ILCode.InitCollection) { if (lastInitializer.Arguments.Count == 1) { // convert empty collection initializer to object initializer lastInitializer.Code = ILCode.InitObject; return(true); } else { return(false); } } else { return(true); } } }