/// <summary> /// Copies the closure initialization into the contractinitializer and preambleBlock (if non-null) /// </summary> internal static int MovePastClosureInit(Method m, Block firstBlock, ContractNodes contractNodes, StatementList contractInitializer, Block preambleBlock, int currentIndex, ref StackDepthTracker dupStackTracker, out TypeNode closureType) { int indexForClosureCreationStatement = currentIndex; closureType = null; Local introducedClosureLocal = null; while (indexForClosureCreationStatement < firstBlock.Statements.Count) { var closureCreationCandidate = firstBlock.Statements[indexForClosureCreationStatement]; closureType = IsClosureCreation(m, closureCreationCandidate); if (closureType != null) break; if (contractNodes.IsContractOrValidatorOrAbbreviatorCall(closureCreationCandidate)) { // found contracts before closure creation, so get out return currentIndex; } if (closureCreationCandidate != null && closureCreationCandidate.SourceContext.IsValid) return currentIndex; indexForClosureCreationStatement++; } if (closureType != null && indexForClosureCreationStatement < firstBlock.Statements.Count) { // then there is a set of statements to add to the preamble block // up to and including "local := new ClosureClass();" for (int i = currentIndex; i <= indexForClosureCreationStatement; i++) { if (firstBlock.Statements[i] == null) continue; if (preambleBlock != null) { preambleBlock.Statements.Add(firstBlock.Statements[i]); } Local existingClosureLocal; if (IsClosureCreation(m, firstBlock.Statements[i], out existingClosureLocal)) { if (existingClosureLocal == null) { // introduce one ExpressionStatement estmt = firstBlock.Statements[i] as ExpressionStatement; if (estmt != null) { introducedClosureLocal = new Local(closureType); contractInitializer.Add( new AssignmentStatement(introducedClosureLocal, (Expression) estmt.Expression.Clone())); } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } if (preambleBlock != null) { firstBlock.Statements[i] = null; // need to null them out so search below can be done starting at beginning of m's body } } // Some number of assignment statements of the form "local.f := f;" where "f" is a parameter // that is captured by the closure. // // Roslyn generates code as follows: // new Closure() // dup // ldarg f // stfld f // dup // ldarg q // stfld q // dup... // int endOfAssignmentsToClosureFields = indexForClosureCreationStatement + 1; for (; endOfAssignmentsToClosureFields < firstBlock.Statements.Count; endOfAssignmentsToClosureFields++) { Statement s = firstBlock.Statements[endOfAssignmentsToClosureFields]; if (s == null) continue; if (s.NodeType == NodeType.Nop) continue; if (s.SourceContext.IsValid) break; // end of closure if (s is Return) break; // Return is also an ExpressionStatement ExpressionStatement exprSt = s as ExpressionStatement; if (exprSt != null && exprSt.Expression.NodeType == NodeType.Dup) { // dup of closure node continue; } AssignmentStatement assign = s as AssignmentStatement; if (assign == null) break; MemberBinding mb = assign.Target as MemberBinding; if (mb == null) break; if (mb.TargetObject == null || (mb.TargetObject.Type != closureType && mb.TargetObject.NodeType != NodeType.Pop)) break; } if (endOfAssignmentsToClosureFields - 1 < firstBlock.Statements.Count && endOfAssignmentsToClosureFields >= 1 && IsDup(firstBlock.Statements[endOfAssignmentsToClosureFields - 1])) { endOfAssignmentsToClosureFields--; // last dup is not part of closure init } dupStackTracker = new StackDepthTracker(introducedClosureLocal); for (int i = indexForClosureCreationStatement + 1; i < endOfAssignmentsToClosureFields; i++) { var stmt = firstBlock.Statements[i]; if (stmt == null) continue; if (preambleBlock != null) { preambleBlock.Statements.Add(stmt); } if (stmt.NodeType != NodeType.Nop) { // don't add nop's to contract initializer if (dupStackTracker.IsValid) { contractInitializer.Add(dupStackTracker.Visit(stmt)); } else { contractInitializer.Add((Statement) stmt.Clone()); } } if (preambleBlock != null) { firstBlock.Statements[i] = null; // need to null them out so search below can be done starting at beginning of m's body } } currentIndex = endOfAssignmentsToClosureFields; } return currentIndex; }
/// <summary> /// Copies the closure initialization into the contractinitializer and preambleBlock (if non-null) /// </summary> internal static int MovePastClosureInit(Method m, Block firstBlock, ContractNodes contractNodes, StatementList contractInitializer, Block preambleBlock, int currentIndex, ref StackDepthTracker dupStackTracker, out TypeNode closureType) { int indexForClosureCreationStatement = currentIndex; closureType = null; Local introducedClosureLocal = null; while (indexForClosureCreationStatement < firstBlock.Statements.Count) { var closureCreationCandidate = firstBlock.Statements[indexForClosureCreationStatement]; closureType = IsClosureCreation(m, closureCreationCandidate); if (closureType != null) break; if (contractNodes.IsContractOrValidatorOrAbbreviatorCall(closureCreationCandidate)) { // found contracts before closure creation, so get out return currentIndex; } // Following code was changed from simple check that closureCreationCandidate is not null and sourceContext is valid // to method call IsBaseConstructorCall. // Here is a history of this fix. // Original code led to NullReferenceException for constructor with capturing lambda // and field-like initializer compiled with VS2015. // // With VS2015 order of initialization was changed. Consider constructor initialization: // VS2013: // 1. Field-like initialization // 2. Closure creation // 3. Base class constructor call // 4. Constructor body // VS2015 // 1. Closure creation // 2. Field-like initialization // 3. Base class constructor call // 4. Constructor body // // Previous version of the code worked incorrectly with VS2015 compiler. // Because field-like initializer satisfy original criteria the function // was unable to find closure initializer properly. if (IsChainConstructorCall(closureCreationCandidate)) { return currentIndex; } indexForClosureCreationStatement++; } if (closureType != null && indexForClosureCreationStatement < firstBlock.Statements.Count) { // then there is a set of statements to add to the preamble block // up to and including "local := new ClosureClass();" for (int i = currentIndex; i <= indexForClosureCreationStatement; i++) { if (firstBlock.Statements[i] == null) continue; if (preambleBlock != null) { preambleBlock.Statements.Add(firstBlock.Statements[i]); } Local existingClosureLocal; if (IsClosureCreation(m, firstBlock.Statements[i], out existingClosureLocal)) { if (existingClosureLocal == null) { // introduce one ExpressionStatement estmt = firstBlock.Statements[i] as ExpressionStatement; if (estmt != null) { introducedClosureLocal = new Local(closureType); contractInitializer.Add( new AssignmentStatement(introducedClosureLocal, (Expression) estmt.Expression.Clone())); } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } } else { contractInitializer.Add((Statement) firstBlock.Statements[i].Clone()); } if (preambleBlock != null) { firstBlock.Statements[i] = null; // need to null them out so search below can be done starting at beginning of m's body } } // Some number of assignment statements of the form "local.f := f;" where "f" is a parameter // that is captured by the closure. // // Roslyn generates code as follows: // new Closure() // dup // ldarg f // stfld f // dup // ldarg q // stfld q // dup... // int endOfAssignmentsToClosureFields = indexForClosureCreationStatement + 1; for (; endOfAssignmentsToClosureFields < firstBlock.Statements.Count; endOfAssignmentsToClosureFields++) { Statement s = firstBlock.Statements[endOfAssignmentsToClosureFields]; if (s == null) continue; if (s.NodeType == NodeType.Nop) continue; if (s.SourceContext.IsValid) break; // end of closure if (s is Return) break; // Return is also an ExpressionStatement ExpressionStatement exprSt = s as ExpressionStatement; if (exprSt != null && exprSt.Expression.NodeType == NodeType.Dup) { // dup of closure node continue; } AssignmentStatement assign = s as AssignmentStatement; if (assign == null) break; MemberBinding mb = assign.Target as MemberBinding; if (mb == null) break; if (mb.TargetObject == null || (mb.TargetObject.Type != closureType && mb.TargetObject.NodeType != NodeType.Pop)) break; } if (endOfAssignmentsToClosureFields - 1 < firstBlock.Statements.Count && endOfAssignmentsToClosureFields >= 1 && IsDup(firstBlock.Statements[endOfAssignmentsToClosureFields - 1])) { endOfAssignmentsToClosureFields--; // last dup is not part of closure init } dupStackTracker = new StackDepthTracker(introducedClosureLocal); for (int i = indexForClosureCreationStatement + 1; i < endOfAssignmentsToClosureFields; i++) { var stmt = firstBlock.Statements[i]; if (stmt == null) continue; if (preambleBlock != null) { preambleBlock.Statements.Add(stmt); } if (stmt.NodeType != NodeType.Nop) { // don't add nop's to contract initializer if (dupStackTracker.IsValid) { contractInitializer.Add(dupStackTracker.Visit(stmt)); } else { contractInitializer.Add((Statement) stmt.Clone()); } } if (preambleBlock != null) { firstBlock.Statements[i] = null; // need to null them out so search below can be done starting at beginning of m's body } } currentIndex = endOfAssignmentsToClosureFields; } return currentIndex; }
private static int ContractStartInMoveNext(ContractNodes contractNodes, Method moveNext, out int statementIndex, Method origMethod) { Contract.Requires(contractNodes != null); Contract.Requires(moveNext != null); Contract.Requires(moveNext.Body != null); Contract.Requires(moveNext.Body.Statements != null); Contract.Ensures(Contract.ValueAtReturn(out statementIndex) >= 0); Contract.Ensures(Contract.Result<int>() >= -1); Contract.Ensures(Contract.Result<int>() < moveNext.Body.Statements.Count); Block body = moveNext.Body; bool isAsync = moveNext.IsAsync; statementIndex = 0; var blocks = body.Statements; bool stateIsRead = false; var fieldScanner = new HelperMethods.ExpressionScanner((field, isStore) => { if (isStore) return; if (field != null && field.Name != null && field.Name.Name.Contains("<>") && field.Name.Name.Contains("__state")) { stateIsRead = true; } }); fieldScanner.Visit(body.Statements); // Dictionary tracks local variable values and a bit saying if the value is the state (in that case it is 0). Dictionary<Variable, Pair<int, EvalKind>> env = new Dictionary<Variable, Pair<int, EvalKind>>(); int currentBlockIndex = 0; bool seenFinalCompare = false; bool lastBranchNonConditional = false; while (currentBlockIndex >= 0 && currentBlockIndex < blocks.Count) { var block = blocks[currentBlockIndex] as Block; if (block == null || block.Statements == null) continue; for (int i = 0; i < block.Statements.Count; i++) { var stmt = block.Statements[i]; if (stmt == null) continue; if (contractNodes.IsContractOrValidatorOrAbbreviatorCall(stmt)) { statementIndex = i; return currentBlockIndex; } Branch branch = stmt as Branch; if (branch != null) { if (branch.Condition == null) { currentBlockIndex = FindTargetBlock(blocks, branch.Target, currentBlockIndex); lastBranchNonConditional = true; goto OuterLoop; } var value = EvaluateExpression(branch.Condition, env, seenFinalCompare, isAsync); if (value.Two == EvalKind.IsDisposingTest) { if (value.One != 0) { return FindTargetBlock(blocks, branch.Target, currentBlockIndex); } if (i + 1 < block.Statements.Count) { statementIndex = i + 1; return currentBlockIndex; } if (currentBlockIndex + 1 < body.Statements.Count) { return currentBlockIndex + 1; } return -1; } if (seenFinalCompare) { // must be the end. statementIndex = i; return currentBlockIndex; } seenFinalCompare = (value.Two == EvalKind.IsFinalCompare || value.Two == EvalKind.IsStateValue); if (value.One != 0) { currentBlockIndex = FindTargetBlock(blocks, branch.Target, currentBlockIndex); goto OuterLoop; } continue; } if (isAsync && lastBranchNonConditional) { // not a branch and last one was non-conditional // must be the end. statementIndex = i; return currentBlockIndex; } var swtch = stmt as SwitchInstruction; if (swtch != null) { if (seenFinalCompare) { // must be the end. statementIndex = i; return currentBlockIndex; } var value = EvaluateExpression(swtch.Expression, env, seenFinalCompare, isAsync); if (value.One < 0 || swtch.Targets == null || value.One >= swtch.Targets.Count) { // fall through if (isAsync) { seenFinalCompare = true; } continue; } currentBlockIndex = FindTargetBlock(blocks, swtch.Targets[value.One], currentBlockIndex); // assume seen final compare seenFinalCompare = true; goto OuterLoop; } if (HelperMethods.IsClosureCreation(origMethod, stmt) != null) { // end of trace statementIndex = i; return currentBlockIndex; } AssignmentStatement assign = stmt as AssignmentStatement; if (assign != null) { if (assign.Source is ConstructArray) { // treat as beginning of contract (typically a params array) statementIndex = i; return currentBlockIndex; } var value = EvaluateExpression(assign.Source, env, seenFinalCompare, isAsync); if (IsThisDotState(assign.Target)) { // end of trace if (i + 1 < block.Statements.Count) { statementIndex = i + 1; return currentBlockIndex; } if (currentBlockIndex + 1 < body.Statements.Count) { return currentBlockIndex + 1; } return -1; } if (IsDoFinallyBodies(assign.Target) && !stateIsRead) { // end of trace if (i + 1 < block.Statements.Count) { statementIndex = i + 1; return currentBlockIndex; } if (currentBlockIndex + 1 < body.Statements.Count) { return currentBlockIndex + 1; } return -1; } var target = assign.Target as Variable; if (target != null) env[target] = value; if (seenFinalCompare && !(value.Two == EvalKind.IsDisposingTest)) { // must be the end. statementIndex = i; return currentBlockIndex; } continue; } if (seenFinalCompare) { // must be the end. statementIndex = i; return currentBlockIndex; } switch (stmt.NodeType) { case NodeType.Nop: case NodeType.ExpressionStatement: // skip: C# compiler emits funky pushes then pops break; default: Contract.Assume(false, string.Format("Unexpected node type '{0}'", stmt.NodeType)); return -1; } } // next block in body currentBlockIndex++; OuterLoop: ; } return -1; }
private static int ContractStartInMoveNext(ContractNodes contractNodes, Method moveNext, out int statementIndex, Method origMethod) { Contract.Requires(contractNodes != null); Contract.Requires(moveNext != null); Contract.Requires(moveNext.Body != null); Contract.Requires(moveNext.Body.Statements != null); Contract.Ensures(Contract.ValueAtReturn(out statementIndex) >= 0); Contract.Ensures(Contract.Result<int>() >= -1); Contract.Ensures(Contract.Result<int>() < moveNext.Body.Statements.Count); Block body = moveNext.Body; bool isAsync = moveNext.IsAsync; statementIndex = 0; var blocks = body.Statements; bool stateIsRead = false; var fieldScanner = new HelperMethods.ExpressionScanner((field, isStore) => { if (isStore) return; if (field != null && field.Name != null && field.Name.Name.Contains("<>") && field.Name.Name.Contains("__state")) { stateIsRead = true; } }); fieldScanner.Visit(body.Statements); // Dictionary tracks local variable values and a bit saying if the value is the state (in that case it is 0). Dictionary<Variable, Pair<int, EvalKind>> env = new Dictionary<Variable, Pair<int, EvalKind>>(); int currentBlockIndex = 0; bool seenFinalCompare = false; bool lastBranchNonConditional = false; while (currentBlockIndex >= 0 && currentBlockIndex < blocks.Count) { var block = blocks[currentBlockIndex] as Block; if (block == null || block.Statements == null) continue; for (int i = 0; i < block.Statements.Count; i++) { var stmt = block.Statements[i]; if (stmt == null) continue; if (contractNodes.IsContractOrValidatorOrAbbreviatorCall(stmt)) { statementIndex = i; return currentBlockIndex; } Branch branch = stmt as Branch; if (branch != null) { if (branch.Condition == null) { currentBlockIndex = FindTargetBlock(blocks, branch.Target, currentBlockIndex); lastBranchNonConditional = true; goto OuterLoop; } // Roslyn-based compiler introduced new pattern for async methods with 2 await statements. // Current implementation sets seenFinalCompare to true when `if (num != 0)` code was found in the MoveNext method. // This usually meant that async preamble is finished and next statement could be a contract statement // (or legacy contract statement, doesn't matter). // But VS2015 compiler changes the behavior and right after `if (num != 0)` there another check that should be skipped. // Please see additional comments at IsRoslynStateCheckForSecondFinishedAwaiter if (seenFinalCompare && isAsync && IsRoslynStateCheckForSecondFinishedAwaiter(branch.Condition, env, ignoreUnknown: true)) { // just skipping current statement! continue; } var value = EvaluateExpression(branch.Condition, env, seenFinalCompare, isAsync); if (value.Two == EvalKind.IsDisposingTest) { if (value.One != 0) { return FindTargetBlock(blocks, branch.Target, currentBlockIndex); } if (i + 1 < block.Statements.Count) { statementIndex = i + 1; return currentBlockIndex; } if (currentBlockIndex + 1 < body.Statements.Count) { return currentBlockIndex + 1; } return -1; } if (seenFinalCompare) { // must be the end. statementIndex = i; return currentBlockIndex; } seenFinalCompare = (value.Two == EvalKind.IsFinalCompare || value.Two == EvalKind.IsStateValue); if (value.One != 0) { currentBlockIndex = FindTargetBlock(blocks, branch.Target, currentBlockIndex); goto OuterLoop; } continue; } if (isAsync && lastBranchNonConditional) { // not a branch and last one was non-conditional // must be the end. statementIndex = i; return currentBlockIndex; } var swtch = stmt as SwitchInstruction; if (swtch != null) { if (seenFinalCompare) { // must be the end. statementIndex = i; return currentBlockIndex; } var value = EvaluateExpression(swtch.Expression, env, seenFinalCompare, isAsync); if (value.One < 0 || swtch.Targets == null || value.One >= swtch.Targets.Count) { // fall through if (isAsync) { seenFinalCompare = true; } continue; } currentBlockIndex = FindTargetBlock(blocks, swtch.Targets[value.One], currentBlockIndex); // assume seen final compare seenFinalCompare = true; goto OuterLoop; } if (HelperMethods.IsClosureCreation(origMethod, stmt) != null) { // end of trace statementIndex = i; return currentBlockIndex; } AssignmentStatement assign = stmt as AssignmentStatement; if (assign != null) { if (assign.Source is ConstructArray) { // treat as beginning of contract (typically a params array) statementIndex = i; return currentBlockIndex; } // Roslyn-based compiler will generate AssignmentStatement with // Pop as a source node (any complex if-statement will do this). // This type of node will lead to // InvalidOperationException in the EvaluateExpression method. // This if statement avoids the failure and fixes #172. if (assign.Source.NodeType == NodeType.Pop) continue; var value = EvaluateExpression(assign.Source, env, seenFinalCompare, isAsync); if (IsThisDotState(assign.Target)) { // end of trace if (i + 1 < block.Statements.Count) { statementIndex = i + 1; return currentBlockIndex; } if (currentBlockIndex + 1 < body.Statements.Count) { return currentBlockIndex + 1; } return -1; } if (IsDoFinallyBodies(assign.Target) && !stateIsRead) { // end of trace if (i + 1 < block.Statements.Count) { statementIndex = i + 1; return currentBlockIndex; } if (currentBlockIndex + 1 < body.Statements.Count) { return currentBlockIndex + 1; } return -1; } var target = assign.Target as Variable; if (target != null) env[target] = value; if (seenFinalCompare && !(value.Two == EvalKind.IsDisposingTest)) { // must be the end. statementIndex = i; return currentBlockIndex; } continue; } if (seenFinalCompare) { // must be the end. statementIndex = i; return currentBlockIndex; } switch (stmt.NodeType) { case NodeType.Nop: case NodeType.ExpressionStatement: // skip: C# compiler emits funky pushes then pops break; default: Contract.Assume(false, string.Format("Unexpected node type '{0}'", stmt.NodeType)); return -1; } } // next block in body currentBlockIndex++; OuterLoop: ; } return -1; }