Esempio n. 1
0
        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;
        }