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; }