public override void VisitMethodDeclaration(MethodDeclaration methodDeclaration)
            {
                base.VisitMethodDeclaration(methodDeclaration);

                // partial method
                if (methodDeclaration.Body.IsNull)
                {
                    return;
                }

                var cfg = cfgBuilder.BuildControlFlowGraph(methodDeclaration.Body, ctx.Resolver,
                                                           ctx.CancellationToken);
                var stack        = new Stack <ControlFlowNode> ();
                var visitedNodes = new HashSet <ControlFlowNode> ();

                stack.Push(cfg [0]);
                while (stack.Count > 0)
                {
                    var node = stack.Pop();

                    // reach method's end
                    if (node.PreviousStatement == methodDeclaration.Body)
                    {
                        return;
                    }
                    // reach a return statement
                    if (node.NextStatement is ReturnStatement ||
                        node.NextStatement is ThrowStatement)
                    {
                        return;
                    }

                    foreach (var edge in node.Outgoing)
                    {
                        if (visitedNodes.Add(edge.To))
                        {
                            stack.Push(edge.To);
                        }
                    }
                }

                AddIssue(methodDeclaration.NameToken,
                         ctx.TranslateString("Method never reaches its end or a 'return' statement."));
            }
            void CollectIssues(Environment env, string variableName)
            {
                IList <ControlFlowNode> cfg = null;
                IDictionary <Statement, IList <Node> > modifications = null;

                if (env.Body != null)
                {
                    try
                    {
                        cfg = cfgBuilder.BuildControlFlowGraph(env.Body);
                    }
                    catch (Exception)
                    {
                        return;
                    }
                    modifications = new Dictionary <Statement, IList <Node> >();
                    foreach (var node in env.Children)
                    {
                        if (node.Kind == NodeKind.Modification || node.Kind == NodeKind.ReferenceAndModification)
                        {
                            IList <Node> nodes;
                            if (!modifications.TryGetValue(node.ContainingStatement, out nodes))
                            {
                                modifications[node.ContainingStatement] = nodes = new List <Node>();
                            }
                            nodes.Add(node);
                        }
                    }
                }

                foreach (var child in env.GetChildEnvironments())
                {
                    if (!child.IssueCollected && cfg != null &&
                        CanReachModification(cfg, child, modifications))
                    {
                        CollectAllIssues(child, variableName);
                    }

                    CollectIssues(child, variableName);
                }
            }
 internal static ReachabilityAnalysis Create(Statement statement, Func<AstNode, CancellationToken, ResolveResult> resolver, CSharpTypeResolveContext typeResolveContext, CancellationToken cancellationToken)
 {
     var cfgBuilder = new ControlFlowGraphBuilder();
     var cfg = cfgBuilder.BuildControlFlowGraph(statement, resolver, typeResolveContext, cancellationToken);
     return Create(cfg, cancellationToken);
 }
 public static ReachabilityAnalysis Create(Statement statement, CSharpAstResolver resolver = null, CancellationToken cancellationToken = default(CancellationToken))
 {
     var cfgBuilder = new ControlFlowGraphBuilder();
     var cfg = cfgBuilder.BuildControlFlowGraph(statement, resolver, cancellationToken);
     return Create(cfg, cancellationToken);
 }
Example #5
0
            void AddIssueFor(AstNode currentFunction)
            {
                if (IsAsync(currentFunction))
                {
                    return;
                }

                //Only suggest modifying functions that return void, Task or Task<T>.
                IType returnType = GetReturnType(ctx, currentFunction);

                if (returnType == null)
                {
                    return;
                }

                bool  isVoid     = false;
                IType resultType = null;

                switch (returnType.FullName)
                {
                case "System.Void":
                    isVoid = true;
                    break;

                case "System.Threading.Tasks.Task":
                    resultType = returnType.IsParameterized ? returnType.TypeArguments.FirstOrDefault() : null;
                    break;

                default:
                    return;
                }

                var functionBody     = currentFunction.GetChildByRole(Roles.Body);
                var statements       = GetStatements(functionBody).ToList();
                var returnStatements = statements.OfType <ReturnStatement>().ToList();

                var invocations = new List <InvocationExpression>();
                var nextInChain = new Dictionary <InvocationExpression, InvocationExpression>();

                foreach (var invocation in currentFunction.Descendants.OfType <InvocationExpression>())
                {
                    if (invocation.Arguments.Count != 1)
                    {
                        continue;
                    }

                    var       lambdaOrDelegate = invocation.Arguments.Single();
                    Statement lambdaBody;
                    if (lambdaOrDelegate is LambdaExpression)
                    {
                        lambdaBody = lambdaOrDelegate.GetChildByRole(LambdaExpression.BodyRole) as BlockStatement;
                        if (lambdaBody == null)
                        {
                            continue;
                        }
                    }
                    else if (lambdaOrDelegate is AnonymousMethodExpression)
                    {
                        lambdaBody = lambdaOrDelegate.GetChildByRole(Roles.Body);
                    }
                    else
                    {
                        continue;
                    }

                    var resolveResult = ctx.Resolve(invocation) as MemberResolveResult;
                    if (resolveResult == null)
                    {
                        continue;
                    }
                    if (resolveResult.Member.FullName != "System.Threading.Tasks.Task.ContinueWith")
                    {
                        continue;
                    }

                    var parentExpression = invocation.Parent as Expression;
                    if (parentExpression != null)
                    {
                        var mreParent = parentExpression as MemberReferenceExpression;
                        if (mreParent == null || mreParent.MemberName != "ContinueWith")
                        {
                            continue;
                        }

                        var parentInvocation = mreParent.Parent as InvocationExpression;
                        if (parentInvocation == null || parentInvocation.Arguments.Count != 1)
                        {
                            continue;
                        }

                        nextInChain[invocation] = parentInvocation;
                    }

                    invocations.Add(invocation);
                }

                if (isVoid && invocations.Count == 0)
                {
                    //Prevent functions like void Foo() {} from being accepted
                    return;
                }

                string taskCompletionSourceIdentifier     = null;
                InvocationExpression returnedContinuation = null;

                if (isVoid)
                {
                    if (returnStatements.Any())
                    {
                        return;
                    }
                }
                else if (!isVoid)
                {
                    if (returnStatements.Count() != 1)
                    {
                        return;
                    }

                    var returnStatement = returnStatements.Single();
                    if (functionBody.Statements.Last() != returnStatement)
                    {
                        return;
                    }

                    var match = ReturnTaskCompletionSourcePattern.Match(returnStatement);
                    if (match.Success)
                    {
                        var taskCompletionSource = match.Get <IdentifierExpression>("target").Single();
                        var taskCompletionSourceResolveResult = ctx.Resolve(taskCompletionSource);

                        //Make sure the TaskCompletionSource is a local variable
                        if (!(taskCompletionSourceResolveResult is LocalResolveResult) ||
                            taskCompletionSourceResolveResult.Type.FullName != "System.Threading.Tasks.TaskCompletionSource")
                        {
                            return;
                        }
                        taskCompletionSourceIdentifier = taskCompletionSource.Identifier;

                        var cfgBuilder = new ControlFlowGraphBuilder();
                        var cachedControlFlowGraphs = new Dictionary <BlockStatement, IList <ControlFlowNode> >();

                        //Make sure there are no unsupported uses of the task completion source
                        foreach (var identifier in functionBody.Descendants.OfType <Identifier>())
                        {
                            if (identifier.Name != taskCompletionSourceIdentifier)
                            {
                                continue;
                            }

                            var statement         = identifier.GetParent <Statement>();
                            var variableStatement = statement as VariableDeclarationStatement;
                            if (variableStatement != null)
                            {
                                if (functionBody.Statements.First() != variableStatement || variableStatement.Variables.Count != 1)
                                {
                                    //This may actually be valid, but it would add even more complexity to this action
                                    return;
                                }
                                var initializer = variableStatement.Variables.First().Initializer as ObjectCreateExpression;
                                if (initializer == null || initializer.Arguments.Count != 0 || !initializer.Initializer.IsNull)
                                {
                                    return;
                                }

                                var constructedType = ctx.ResolveType(initializer.Type);
                                if (constructedType.FullName != "System.Threading.Tasks.TaskCompletionSource")
                                {
                                    return;
                                }

                                continue;
                            }

                            if (statement == returnStatement)
                            {
                                continue;
                            }

                            if (identifier.Parent is MemberReferenceExpression)
                            {
                                //Right side of the member.
                                //We don't care about this case since it's not a reference to the variable.
                                continue;
                            }

                            //The method's taskCompletionSource can only be used on the left side of a member
                            //reference expression (specifically tcs.SetResult).
                            var identifierExpressionParent = identifier.Parent as IdentifierExpression;
                            if (identifierExpressionParent == null)
                            {
                                return;
                            }
                            var memberReferenceExpression = identifierExpressionParent.Parent as MemberReferenceExpression;
                            if (memberReferenceExpression == null)
                            {
                                return;
                            }

                            if (memberReferenceExpression.MemberName != "SetResult")
                            {
                                //Aside from the final return statement, the only member of task completion source
                                //that can be used is SetResult.
                                //Perhaps future versions could also include SetException and SetCancelled.
                                return;
                            }

                            //We found a SetResult -- we will now find out if it is in a proper context
                            AstNode node = memberReferenceExpression;
                            for (;;)
                            {
                                node = node.Parent;

                                if (node == null)
                                {
                                    //Abort since this is unexpected (it should never happen)
                                    return;
                                }

                                if (node is MethodDeclaration)
                                {
                                    //Ok -- tcs.SetResult is in method declaration
                                    break;
                                }

                                if (node is LambdaExpression || node is AnonymousMethodExpression)
                                {
                                    //It's time to verify if the lambda is supported
                                    var lambdaParent = node.Parent as InvocationExpression;
                                    if (lambdaParent == null || !invocations.Contains(lambdaParent))
                                    {
                                        return;
                                    }
                                    break;
                                }
                            }

                            var containingContinueWith = node.Parent as InvocationExpression;
                            if (containingContinueWith != null)
                            {
                                if (nextInChain.ContainsKey(containingContinueWith))
                                {
                                    //Unsupported: ContinueWith has a SetResult
                                    //but it's not the last in the chain
                                    return;
                                }
                            }

                            var containingFunctionBlock = node is LambdaExpression ? (BlockStatement)node.GetChildByRole(LambdaExpression.BodyRole) : node.GetChildByRole(Roles.Body);

                            //Finally, tcs.SetResult must be at the end of its method
                            IList <ControlFlowNode> nodes;
                            if (!cachedControlFlowGraphs.TryGetValue(containingFunctionBlock, out nodes))
                            {
                                nodes = cfgBuilder.BuildControlFlowGraph(containingFunctionBlock, ctx.CancellationToken);
                                cachedControlFlowGraphs[containingFunctionBlock] = nodes;
                            }

                            var setResultNode = nodes.FirstOrDefault(candidateNode => candidateNode.PreviousStatement == statement);
                            if (setResultNode != null && HasReachableNonReturnNodes(setResultNode))
                            {
                                //The only allowed outgoing nodes are return statements
                                return;
                            }
                        }
                    }
                    else
                    {
                        //Not TaskCompletionSource-based
                        //Perhaps it is return Task.ContinueWith(foo);

                        if (!invocations.Any())
                        {
                            return;
                        }

                        var outerMostInvocations = new List <InvocationExpression>();
                        InvocationExpression currentInvocation = invocations.First();
                        do
                        {
                            outerMostInvocations.Add(currentInvocation);
                        } while (nextInChain.TryGetValue(currentInvocation, out currentInvocation));

                        var lastInvocation = outerMostInvocations.Last();
                        if (returnStatement.Expression != lastInvocation)
                        {
                            return;
                        }

                        //Found return <1>.ContinueWith(<2>);
                        returnedContinuation = lastInvocation;
                    }
                }

                //We do not support "return expr" in continuations
                //The only exception is when the outer method returns that continuation.
                invocations.RemoveAll(invocation => invocation != returnedContinuation &&
                                      invocation.Arguments.First().Children.OfType <Statement>().First().DescendantNodesAndSelf(node => node is Statement).OfType <ReturnStatement>().Any(returnStatement => !returnStatement.Expression.IsNull));

                AddIssue(new CodeIssue(GetFunctionToken(currentFunction),
                                       ctx.TranslateString("Function can be converted to C# 5-style async function"),
                                       ctx.TranslateString("Convert to C# 5-style async function"),
                                       script => {
                    AddOriginalNodeAnnotations(currentFunction);
                    var newFunction = currentFunction.Clone();
                    RemoveOriginalNodeAnnotations(currentFunction);

                    //Set async
                    var lambda = newFunction as LambdaExpression;
                    if (lambda != null)
                    {
                        lambda.IsAsync = true;
                    }
                    var anonymousMethod = newFunction as AnonymousMethodExpression;
                    if (anonymousMethod != null)
                    {
                        anonymousMethod.IsAsync = true;
                    }
                    var methodDeclaration = newFunction as MethodDeclaration;
                    if (methodDeclaration != null)
                    {
                        methodDeclaration.Modifiers |= Modifiers.Async;
                    }

                    TransformBody(invocations, isVoid, resultType != null, returnedContinuation, taskCompletionSourceIdentifier, newFunction.GetChildByRole(Roles.Body));

                    script.Replace(currentFunction, newFunction);
                }));
            }