public static async Task ComputeRefactoringAsync(RefactoringContext context, MethodDeclarationSyntax methodDeclaration) { if (context.IsRefactoringEnabled(RefactoringIdentifiers.ChangeMethodReturnTypeToVoid) && methodDeclaration.ReturnType?.IsVoid() == false && methodDeclaration.Body?.Statements.Count > 0 && !methodDeclaration.IsIterator() && context.SupportsSemanticModel) { SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false); if (!IsAsyncMethodThatReturnsTask(methodDeclaration, semanticModel, context.CancellationToken)) { ControlFlowAnalysis analysis = semanticModel.AnalyzeControlFlow(methodDeclaration.Body); if (analysis.Succeeded && analysis.ReturnStatements.All(node => IsReturnStatementWithoutExpression(node))) { context.RegisterRefactoring( "Change return type to 'void'", cancellationToken => { return(ChangeTypeRefactoring.ChangeTypeAsync( context.Document, methodDeclaration.ReturnType, CSharpFactory.VoidType(), cancellationToken)); }); } } } }
private static IEnumerable <ITypeSymbol> AnalyzeTestCreationMethod(MethodDeclarationSyntax methodDeclaration, SemanticModel semanticModel) { var types = new HashSet <ITypeSymbol>(); if (methodDeclaration.Body is null) { if (methodDeclaration.ExpressionBody?.Expression is ObjectCreationExpressionSyntax oces) { var type = oces.GetTypeSymbol(semanticModel); types.Add(type); } } else { var controlFlow = semanticModel.AnalyzeControlFlow(methodDeclaration.Body); var returnStatements = controlFlow.ReturnStatements.OfType <ReturnStatementSyntax>(); foreach (var variable in methodDeclaration.DescendantNodes().OfType <VariableDeclarationSyntax>().SelectMany(_ => _.Variables)) { if (returnStatements.Any(_ => _.Expression is IdentifierNameSyntax ins && variable.GetName() == ins.GetName()) && variable.Initializer?.Value is ObjectCreationExpressionSyntax oces) { var type = oces.GetTypeSymbol(semanticModel); types.Add(type); } } } return(types); }
static public MethodBlockAnalysis FromSemanticModel(MethodDeclarationSyntax methodSyntax, SemanticModel semanticModel) { var block = SyntaxOperations.GetBodyOfMethod(methodSyntax).A; var controlFlow = semanticModel.AnalyzeControlFlow(block); var dataFlow = semanticModel.AnalyzeDataFlow(block); return(new MethodBlockAnalysis(methodSyntax, block, semanticModel, controlFlow, dataFlow)); }
private bool ContainsReturnStatementInSelectedCode(SemanticModel model) { Contract.ThrowIfTrue(SelectionResult.SelectionInExpression); var pair = GetFlowAnalysisNodeRange(); var controlFlowAnalysisData = model.AnalyzeControlFlow(pair.Item1, pair.Item2); return(ContainsReturnStatementInSelectedCode(controlFlowAnalysisData.ExitPoints)); }
private static ControlFlowAnalysis?GetControlFlowAnalysis(SyntaxGenerator generator, SemanticModel semanticModel, SyntaxNode node) { var statements = generator.GetStatements(node); if (statements.Count > 0) { return(semanticModel.AnalyzeControlFlow(statements[0], statements[statements.Count - 1])); } return(null); }
public static bool ReturnsNull(IEnumerable <StatementSyntax> statements, SemanticModel semanticModel) { var statementSyntaxes = statements as StatementSyntax[] ?? statements.ToArray(); var returnStatements = semanticModel.AnalyzeControlFlow( statementSyntaxes.First(), statementSyntaxes.Last()) .ReturnStatements; return(returnStatements.Any( stmt => stmt is ReturnStatementSyntax returnStatement && CanBeNull(returnStatement.Expression !, semanticModel))); }
private bool IsEndOfSelectionReachable(SemanticModel model) { if (SelectionResult.SelectionInExpression) { return(true); } var pair = GetFlowAnalysisNodeRange(); var analysis = model.AnalyzeControlFlow(pair.Item1, pair.Item2); return(analysis.EndPointIsReachable); }
private static void AnalyzeControlFlow( SemanticModel semanticModel, StatementRange statementRange, out bool endPointIsReachable, out SyntaxNode?singleExitPoint) { var flow = semanticModel.AnalyzeControlFlow( statementRange.FirstStatement, statementRange.LastStatement); endPointIsReachable = flow.EndPointIsReachable; singleExitPoint = flow.ExitPoints.Length == 1 ? flow.ExitPoints[0] : null; }
public static List <INamedType> GetActualReturnTypes(this IMethodSymbol method, List <NamedTypeBase> types, SemanticModel model) { List <INamedType> returntypes = new List <INamedType>(); foreach (var item in method.Locations) { if (item.IsInSource) { var ms = model.SyntaxTree.GetRoot().FindNode(item.SourceSpan) as MethodDeclarationSyntax; if (ms != null && ms.Body != null && ms.Body.Statements.Count != 0) { var cf = model.AnalyzeControlFlow(ms.Body.Statements.First(), ms.Body.Statements.Last()); foreach (var returnStatement in cf.ReturnStatements) { if (returnStatement is YieldStatementSyntax) { continue; } var ret = (ReturnStatementSyntax)returnStatement; //void methods if (ret.Expression != null && ret.Expression.CSharpKind() != SyntaxKind.NullLiteralExpression) { var type = model.GetTypeInfo(ret.Expression); if (type.Type == null) { logprovider.WriteToLog(string.Format("Cannot get semantinc type info for expression {0}", ret.Expression.ToString())); continue; } //hack if (type.Type.TypeKind == TypeKind.TypeParameter) { logprovider.WriteToLog(string.Format("Cannot get semantinc type info for expression {0}", ret.Expression.ToString())); continue; } var rtype = types.FirstOrDefault(t => t == ((ITypeSymbol)(type.Type)).ToCSharpNamedType(logprovider)); if (rtype != null) { returntypes.Add(rtype); } else { logprovider.WriteToLog(string.Format("Cannot find type for symbol {0}", type.Type.ToString())); } } } } } } return(returntypes); }
public static async Task ComputeRefactoringAsync(RefactoringContext context, MethodDeclarationSyntax methodDeclaration) { if (context.IsRefactoringEnabled(RefactoringIdentifiers.ChangeMethodReturnTypeToVoid)) { TypeSyntax returnType = methodDeclaration.ReturnType; if (returnType?.IsVoid() == false) { BlockSyntax body = methodDeclaration.Body; if (body != null) { SyntaxList <StatementSyntax> statements = body.Statements; if (statements.Any() && !ContainsOnlyThrowStatement(statements) && !methodDeclaration.ContainsYield()) { SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false); IMethodSymbol methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken); if (methodSymbol?.IsOverride == false && !methodSymbol.ImplementsInterfaceMember() && !IsAsyncMethodThatReturnsTask(methodSymbol, semanticModel)) { ControlFlowAnalysis analysis = semanticModel.AnalyzeControlFlow(body); if (analysis.Succeeded && analysis.ReturnStatements.All(IsReturnStatementWithoutExpression)) { context.RegisterRefactoring( "Change return type to 'void'", cancellationToken => { return(ChangeTypeRefactoring.ChangeTypeAsync( context.Document, returnType, CSharpFactory.VoidType(), cancellationToken)); }); } } } } } } }
private static bool IsEndPointReachable(SemanticModel semanticModel, StatementSyntax statementSyntax) { var result = semanticModel.AnalyzeControlFlow(statementSyntax); if (result == null || !result.Succeeded) { return(false); } if (!result.EndPointIsReachable) { return(false); } return(true); }
protected bool IsFinalSpanSemanticallyValidSpan( SemanticModel semanticModel, TextSpan textSpan, Tuple <SyntaxNode, SyntaxNode> range, CancellationToken cancellationToken) { Contract.ThrowIfNull(range); var controlFlowAnalysisData = semanticModel.AnalyzeControlFlow(range.Item1, range.Item2); // there must be no control in and out of given span if (controlFlowAnalysisData.EntryPoints.Any()) { return(false); } // check something like continue, break, yield break, yield return, and etc if (ContainsNonReturnExitPointsStatements(controlFlowAnalysisData.ExitPoints)) { return(false); } // okay, there is no branch out, check whether next statement can be executed normally var returnStatements = GetOuterReturnStatements(range.Item1.GetCommonRoot(range.Item2), controlFlowAnalysisData.ExitPoints); if (!returnStatements.Any()) { if (!controlFlowAnalysisData.EndPointIsReachable) { // REVIEW: should we just do extract method regardless or show some warning to user? // in dev10, looks like we went ahead and did the extract method even if selection contains // unreachable code. } return(true); } // okay, only branch was return. make sure we have all return in the selection. // check for special case, if end point is not reachable, we don't care the selection // actually contains all return statements. we just let extract method go through // and work like we did in dev10 if (!controlFlowAnalysisData.EndPointIsReachable) { return(true); } // there is a return statement, and current position is reachable. let's check whether this is a case where that is okay return(IsFinalSpanSemanticallyValidSpan(semanticModel.SyntaxTree.GetRoot(cancellationToken), textSpan, returnStatements, cancellationToken)); }
private static bool IsCalledFromAddSteps(InvocationExpressionSyntax invocationExpressionNode, SemanticModel semanticModel) { var containingMethodSyntax = invocationExpressionNode.FirstAncestorOrSelf<MemberDeclarationSyntax>(); var blockSyntax = (BlockSyntax)containingMethodSyntax.ChildNodes().Last(); var controlFlow = semanticModel.AnalyzeControlFlow(blockSyntax.Statements.First(), blockSyntax.Statements.Last()); if (controlFlow.Succeeded) { } var token = containingMethodSyntax.DescendantTokens().FirstOrDefault(f => f.IsKind(SyntaxKind.IdentifierToken)); return token.Text == "AddSteps"; }
public bool CatchClauseCatchesCoroutineStopped(CatchClauseSyntax catchClause, SemanticModel model, out bool rethrows) { rethrows = false; if (catchClause.Declaration != null) { INamedTypeSymbol exceptionSymbol = model.GetSymbolInfo(catchClause.Declaration.Type).Symbol as INamedTypeSymbol; if (exceptionSymbol == null) { return(false); } if (exceptionSymbol != ExceptionType && exceptionSymbol != CoroutineStoppedExceptionType) { return(false); } if (RethrowsCoroutineStoppedException(catchClause, model)) { rethrows = true; return(true); } } ControlFlowAnalysis controlFlowResult = model.AnalyzeControlFlow(catchClause.Block); // Check if this catch always rethrows if (controlFlowResult.Succeeded && !controlFlowResult.EndPointIsReachable && controlFlowResult.ExitPoints.Length == 0 && controlFlowResult.ReturnStatements.Length == 0) { rethrows = true; } return(true); }
private static void ComputeRefactoring( RefactoringContext context, IMethodSymbol methodSymbol, SemanticModel semanticModel, BlockSyntax body, TypeSyntax returnType) { if (methodSymbol?.IsOverride != false) { return; } if (methodSymbol.ImplementsInterfaceMember()) { return; } if (methodSymbol.IsAsync && methodSymbol.ReturnType.HasMetadataName(MetadataNames.System_Threading_Tasks_Task)) { return; } ControlFlowAnalysis analysis = semanticModel.AnalyzeControlFlow(body); if (!analysis.Succeeded) { return; } if (!analysis.ReturnStatements.All(f => (f as ReturnStatementSyntax)?.Expression == null)) { return; } Document document = context.Document; context.RegisterRefactoring( "Change return type to 'void'", ct => document.ReplaceNodeAsync(returnType, CSharpFactory.VoidType().WithTriviaFrom(returnType), ct), RefactoringDescriptors.ChangeMethodReturnTypeToVoid); }
static void CollectSwitchSectionStatements(List <StatementSyntax> result, SemanticModel context, StatementSyntax statement) { var blockStatement = statement as BlockSyntax; if (blockStatement != null) { result.AddRange(blockStatement.Statements); } else { result.Add(statement); } // add 'break;' at end if necessary var reachabilityAnalysis = context.AnalyzeControlFlow(statement); if (reachabilityAnalysis.EndPointIsReachable) { result.Add(SyntaxFactory.BreakStatement()); } }
public override SyntaxNode VisitBlock(BlockSyntax node) { SyntaxList <StatementSyntax> statements = node.Statements; // eliminate unreachable statements (cosmetic) List <int> remove = new List <int>(); for (int i = 0; i < statements.Count; i++) { try { ControlFlowAnalysis cfa = semanticModel.AnalyzeControlFlow(statements[i]); if (!cfa.StartPointIsReachable) { remove.Add(i); } } catch (ArgumentException) { // probably node not in tree (because we rewrote it) // do nothing and come back next time Debug.Assert(Changed); break; } } if (remove.Count != 0) { Changed = true; for (int i = remove.Count - 1; i >= 0; i--) { statements = statements.RemoveAt(remove[i]); } node = node.WithStatements(statements); } return(base.VisitBlock(node)); }
// Ensure that all usages of the pattern variable are // in scope and definitely assigned after replacement. private static bool CheckDefiniteAssignment( SemanticModel semanticModel, ISymbol localVariable, StatementSyntax declarationStatement, StatementSyntax targetStatement, BlockSyntax parentBlock, bool isNegativeNullCheck) { var statements = parentBlock.Statements; var targetIndex = statements.IndexOf(targetStatement); var declarationIndex = statements.IndexOf(declarationStatement); // Since we're going to remove this declaration-statement, // we need to first ensure that it's not used up to the target statement. if (declarationIndex + 1 < targetIndex) { var dataFlow = semanticModel.AnalyzeDataFlow( statements[declarationIndex + 1], statements[targetIndex - 1]); if (dataFlow.ReadInside.Contains(localVariable) || dataFlow.WrittenInside.Contains(localVariable)) { return(false); } } // In case of an if-statement, we need to check if the variable // is being accessed before assignment in the opposite branch. if (targetStatement.IsKind(SyntaxKind.IfStatement, out IfStatementSyntax ifStatement)) { var statement = isNegativeNullCheck ? ifStatement.Statement : ifStatement.Else?.Statement; if (statement != null) { var dataFlow = semanticModel.AnalyzeDataFlow(statement); if (dataFlow.DataFlowsIn.Contains(localVariable)) { // Access before assignment is not safe in the opposite branch // as the variable is not definitely assgined at this point. // For example: // // if (o is string x) { } // else { Use(x); } // return(false); } if (dataFlow.AlwaysAssigned.Contains(localVariable)) { // If the variable is always assigned here, we don't need to check // subsequent statements as it's definitely assigned afterwards. // For example: // // if (o is string x) { } // else { x = null; } // return(true); } } } // Make sure that no access is made to the variable before assignment in the subsequent statements if (targetIndex + 1 < statements.Count) { var dataFlow = semanticModel.AnalyzeDataFlow( statements[targetIndex + 1], statements[statements.Count - 1]); if (targetStatement.Kind() == SyntaxKind.WhileStatement) { // The scope of pattern variables in a while-statement does not leak out to // the enclosing block so we bail also if there is any assignments afterwards. if (dataFlow.ReadInside.Contains(localVariable) || dataFlow.WrittenInside.Contains(localVariable)) { return(false); } } else if (dataFlow.DataFlowsIn.Contains(localVariable)) { // Access before assignment here is only valid if we have a negative // pattern-matching in an if-statement with an unreachable endpoint. // For example: // // if (!(o is string x)) { // return; // } // // // The 'return' statement above ensures x is definitely assigned here // Console.WriteLine(x); // return(isNegativeNullCheck && ifStatement != null && !semanticModel.AnalyzeControlFlow(ifStatement.Statement).EndPointIsReachable); } } return(true); }
private bool RequiresFix(SemanticModel semanticModel, StatementSyntax statementSyntax) { var controlFlow = semanticModel.AnalyzeControlFlow(statementSyntax); return(controlFlow.ExitPoints.Length == 0); }
private bool ContainsReturnStatementInSelectedCode(SemanticModel model) { var dataFlowAnalysis = _semanticModel.AnalyzeControlFlow(_selectionResult.FirstStatement(), _selectionResult.LastStatement()); return(ContainsReturnStatementInSelectedCode(dataFlowAnalysis.ExitPoints)); }
// To convert a null-check to pattern-matching, we should make sure of a few things: // // (1) The pattern variable may not be used before the point of declaration. // // { // var use = t; // if (x is T t) {} // } // // (2) The pattern variable may not be used outside of the new scope which // is determined by the parent statement. // // { // if (x is T t) {} // } // // var use = t; // // (3) The pattern variable may not be used before assignment in opposite // branches, if any. // // { // if (x is T t) {} // var use = t; // } // // We walk up the tree from the point of null-check and see if any of the above is violated. private bool CanSafelyConvertToPatternMatching() { // Keep track of whether the pattern variable is definitely assigned when false/true. // We start by the null-check itself, if it's compared with '==', the pattern variable // will be definitely assigned when false, because we wrap the is-operator in a !-operator. var defAssignedWhenTrue = _comparison.Kind() == SyntaxKind.NotEqualsExpression; foreach (var current in _comparison.Ancestors()) { // Checking for any conditional statement or expression that could possibly // affect or determine the state of definite-assignment of the pattern variable. switch (current.Kind()) { case SyntaxKind.LogicalAndExpression when !defAssignedWhenTrue: case SyntaxKind.LogicalOrExpression when defAssignedWhenTrue: // Since the pattern variable is only definitely assigned if the pattern // succeeded, in the following cases it would not be safe to use pattern-matching. // For example: // // if ((x = o as string) == null && SomeExpression) // if ((x = o as string) != null || SomeExpression) // // Here, x would never be definitely assigned if pattern-matching were used. return(false); case SyntaxKind.LogicalAndExpression: case SyntaxKind.LogicalOrExpression: // Parentheses and cast expressions do not contribute to the flow analysis. case SyntaxKind.ParenthesizedExpression: case SyntaxKind.CastExpression: // Skip over declaration parts to get to the parenting statement // which might be a for-statement or a local declaration statement. case SyntaxKind.EqualsValueClause: case SyntaxKind.VariableDeclarator: case SyntaxKind.VariableDeclaration: continue; case SyntaxKind.LogicalNotExpression: // The !-operator negates the definitive assignment state. defAssignedWhenTrue = !defAssignedWhenTrue; continue; case SyntaxKind.ConditionalExpression: var conditionalExpression = (ConditionalExpressionSyntax)current; if (LocalFlowsIn(defAssignedWhenTrue ? conditionalExpression.WhenFalse : conditionalExpression.WhenTrue)) { // In a conditional expression, the pattern variable // would not be definitely assigned in the opposite branch. return(false); } return(CheckExpression(conditionalExpression)); case SyntaxKind.ForStatement: var forStatement = (ForStatementSyntax)current; if (!forStatement.Condition.Span.Contains(_comparison.Span)) { // In a for-statement, only the condition expression // can make this definitely assigned in the loop body. return(false); } return(CheckLoop(forStatement, forStatement.Statement, defAssignedWhenTrue)); case SyntaxKind.WhileStatement: var whileStatement = (WhileStatementSyntax)current; return(CheckLoop(whileStatement, whileStatement.Statement, defAssignedWhenTrue)); case SyntaxKind.IfStatement: var ifStatement = (IfStatementSyntax)current; var oppositeStatement = defAssignedWhenTrue ? ifStatement.Else?.Statement : ifStatement.Statement; if (oppositeStatement != null) { var dataFlow = _semanticModel.AnalyzeDataFlow(oppositeStatement); if (dataFlow.DataFlowsIn.Contains(_localSymbol)) { // Access before assignment is not safe in the opposite branch // as the variable is not definitely assgined at this point. // For example: // // if (o is string x) { } // else { Use(x); } // return(false); } if (dataFlow.AlwaysAssigned.Contains(_localSymbol)) { // If the variable is always assigned here, we don't need to check // subsequent statements as it's definitely assigned afterwards. // For example: // // if (o is string x) { } // else { x = null; } // return(true); } } if (!defAssignedWhenTrue && !_semanticModel.AnalyzeControlFlow(ifStatement.Statement).EndPointIsReachable) { // Access before assignment here is only valid if we have a negative // pattern-matching in an if-statement with an unreachable endpoint. // For example: // // if (!(o is string x)) { // return; // } // // // The 'return' statement above ensures x is definitely assigned here // Console.WriteLine(x); // return(true); } return(CheckStatement(ifStatement)); } switch (current) { case ExpressionSyntax expression: // If we reached here, it means we have a sub-expression that // does not garantee definite assignment. We should make sure that // the pattern variable is not used outside of the expression boundaries. return(CheckExpression(expression)); case StatementSyntax statement: // If we reached here, it means that the null-check is appeared in // a statement. In that case, the variable would be actually in the // scope in subsequent statements, but not definitely assigned. // Therefore, we should ensure that there is no use before assignment. return(CheckStatement(statement)); } // Bail out for error cases and unhandled cases. break; } return(false); }
public static async Task ComputeRefactoringAsync(RefactoringContext context, MethodDeclarationSyntax methodDeclaration) { TypeSyntax returnType = methodDeclaration.ReturnType; if (returnType?.IsVoid() != false) { return; } BlockSyntax body = methodDeclaration.Body; if (body == null) { return; } SyntaxList <StatementSyntax> statements = body.Statements; if (!statements.Any()) { return; } if (statements.SingleOrDefault(shouldThrow: false)?.Kind() == SyntaxKind.ThrowStatement) { return; } if (methodDeclaration.ContainsYield()) { return; } SemanticModel semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false); IMethodSymbol methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken); if (methodSymbol?.IsOverride != false) { return; } if (methodSymbol.ImplementsInterfaceMember()) { return; } if (IsAsyncMethodThatReturnsTask(methodSymbol, semanticModel)) { return; } ControlFlowAnalysis analysis = semanticModel.AnalyzeControlFlow(body); if (!analysis.Succeeded) { return; } if (!analysis.ReturnStatements.All(IsReturnStatementWithoutExpression)) { return; } context.RegisterRefactoring( "Change return type to 'void'", ct => ChangeTypeRefactoring.ChangeTypeAsync(context.Document, returnType, CSharpFactory.VoidType(), ct), RefactoringIdentifiers.ChangeMethodReturnTypeToVoid); }