private static void VerifyDataFlow(SemanticModel model, DeclarationExpressionSyntax decl, bool isDelegateCreation, bool isExecutableCode, IdentifierNameSyntax[] references, ISymbol symbol) { var dataFlowParent = decl.Parent.Parent.Parent as ExpressionSyntax; if (dataFlowParent == null) { Assert.IsAssignableFrom<ConstructorInitializerSyntax>(decl.Parent.Parent.Parent); return; } if (model.IsSpeculativeSemanticModel) { Assert.Throws<NotSupportedException>(() => model.AnalyzeDataFlow(dataFlowParent)); return; } var dataFlow = model.AnalyzeDataFlow(dataFlowParent); Assert.Equal(isExecutableCode, dataFlow.Succeeded); if (isExecutableCode) { Assert.True(dataFlow.VariablesDeclared.Contains(symbol, ReferenceEqualityComparer.Instance)); if (!isDelegateCreation) { Assert.True(dataFlow.AlwaysAssigned.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.True(dataFlow.WrittenInside.Contains(symbol, ReferenceEqualityComparer.Instance)); var flowsIn = FlowsIn(dataFlowParent, decl, references); Assert.Equal(flowsIn, dataFlow.DataFlowsIn.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.Equal(flowsIn, dataFlow.ReadInside.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.Equal(FlowsOut(dataFlowParent, decl, references), dataFlow.DataFlowsOut.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.Equal(ReadOutside(dataFlowParent, references), dataFlow.ReadOutside.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.Equal(WrittenOutside(dataFlowParent, references), dataFlow.WrittenOutside.Contains(symbol, ReferenceEqualityComparer.Instance)); } } }
protected static void VerifyModelForDeclarationField( SemanticModel model, SingleVariableDesignationSyntax designation, bool duplicate, params IdentifierNameSyntax[] references) { var symbol = model.GetDeclaredSymbol(designation); Assert.Equal(designation.Identifier.ValueText, symbol.Name); Assert.Equal(SymbolKind.Field, symbol.Kind); Assert.Equal(designation, symbol.DeclaringSyntaxReferences.Single().GetSyntax()); Assert.Same(symbol, model.GetDeclaredSymbol((SyntaxNode)designation)); var symbols = model.LookupSymbols(designation.SpanStart, name: designation.Identifier.ValueText); var names = model.LookupNames(designation.SpanStart); if (duplicate) { Assert.True(symbols.Count() > 1); Assert.Contains(symbol, symbols); } else { Assert.Same(symbol, symbols.Single()); } Assert.Contains(designation.Identifier.ValueText, names); var local = (IFieldSymbol)symbol; switch (designation.Parent) { case DeclarationPatternSyntax decl: var typeSyntax = decl.Type; Assert.True(SyntaxFacts.IsInNamespaceOrTypeContext(typeSyntax)); Assert.True(SyntaxFacts.IsInTypeOnlyContext(typeSyntax)); var type = local.Type; if (typeSyntax.IsVar && type.IsErrorType()) { Assert.Null(model.GetSymbolInfo(typeSyntax).Symbol); } else { Assert.Equal(type, model.GetSymbolInfo(typeSyntax).Symbol); } AssertTypeInfo(model, decl.Type, type); break; case var parent: Assert.True(parent is VarPatternSyntax); break; } var declarator = designation.Ancestors().OfType <VariableDeclaratorSyntax>().FirstOrDefault(); var inFieldDeclaratorArgumentlist = declarator != null && declarator.Parent.Parent.Kind() != SyntaxKind.LocalDeclarationStatement && (declarator.ArgumentList?.Contains(designation)).GetValueOrDefault(); // this is a declaration site, not a use site. Assert.Null(model.GetSymbolInfo(designation).Symbol); Assert.Null(model.GetSymbolInfo(designation).Symbol); foreach (var reference in references) { var referenceInfo = model.GetSymbolInfo(reference); symbols = model.LookupSymbols(reference.SpanStart, name: designation.Identifier.ValueText); if (duplicate) { Assert.Null(referenceInfo.Symbol); Assert.Contains(symbol, referenceInfo.CandidateSymbols); Assert.True(symbols.Count() > 1); Assert.Contains(symbol, symbols); } else { Assert.Same(symbol, referenceInfo.Symbol); Assert.Same(symbol, symbols.Single()); Assert.Equal(local.Type, model.GetTypeInfo(reference).Type); } Assert.True(model.LookupNames(reference.SpanStart).Contains(designation.Identifier.ValueText)); } if (!inFieldDeclaratorArgumentlist) { var dataFlowParent = designation.FirstAncestorOrSelf <ExpressionSyntax>(); if (model.IsSpeculativeSemanticModel) { Assert.Throws <NotSupportedException>(() => model.AnalyzeDataFlow(dataFlowParent)); } else { var dataFlow = model.AnalyzeDataFlow(dataFlowParent); if (dataFlow.Succeeded) { Assert.False(dataFlow.VariablesDeclared.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.AlwaysAssigned.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.WrittenInside.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.DataFlowsIn.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.ReadInside.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.DataFlowsOut.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.ReadOutside.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.WrittenOutside.Contains(symbol, ReferenceEqualityComparer.Instance)); } } } }
private static void VerifyModelForOutField( SemanticModel model, DeclarationExpressionSyntax decl, bool duplicate, params IdentifierNameSyntax[] references) { var variableDesignationSyntax = GetVariableDesignation(decl); var symbol = model.GetDeclaredSymbol(variableDesignationSyntax); Assert.Equal(decl.Identifier().ValueText, symbol.Name); Assert.Equal(SymbolKind.Field, symbol.Kind); Assert.Equal(variableDesignationSyntax, symbol.DeclaringSyntaxReferences.Single().GetSyntax()); Assert.Same(symbol, model.GetDeclaredSymbol((SyntaxNode)variableDesignationSyntax)); var symbols = model.LookupSymbols(decl.SpanStart, name: decl.Identifier().ValueText); var names = model.LookupNames(decl.SpanStart); if (duplicate) { Assert.True(symbols.Count() > 1); Assert.Contains(symbol, symbols); } else { Assert.Same(symbol, symbols.Single()); } Assert.Contains(decl.Identifier().ValueText, names); var local = (FieldSymbol)symbol; var typeSyntax = decl.Type(); Assert.True(SyntaxFacts.IsInNamespaceOrTypeContext(typeSyntax)); Assert.True(SyntaxFacts.IsInTypeOnlyContext(typeSyntax)); if (typeSyntax.IsVar && local.Type.IsErrorType()) { Assert.Null(model.GetSymbolInfo(typeSyntax).Symbol); } else { Assert.Equal(local.Type, model.GetSymbolInfo(typeSyntax).Symbol); } var declarator = decl.Ancestors().OfType<VariableDeclaratorSyntax>().FirstOrDefault(); var inFieldDeclaratorArgumentlist = declarator != null && declarator.Parent.Parent.Kind() != SyntaxKind.LocalDeclarationStatement && (declarator.ArgumentList?.Contains(decl)).GetValueOrDefault(); if (inFieldDeclaratorArgumentlist) { Assert.Null(model.GetSymbolInfo(decl).Symbol); Assert.Null(model.GetSymbolInfo(decl).Symbol); } else { Assert.Same(symbol, model.GetSymbolInfo(decl).Symbol); Assert.Same(symbol, model.GetSymbolInfo(decl).Symbol); } Assert.Null(model.GetDeclaredSymbol(decl)); foreach (var reference in references) { var referenceInfo = model.GetSymbolInfo(reference); symbols = model.LookupSymbols(reference.SpanStart, name: decl.Identifier().ValueText); if (duplicate) { Assert.Null(referenceInfo.Symbol); Assert.Contains(symbol, referenceInfo.CandidateSymbols); Assert.True(symbols.Count() > 1); Assert.Contains(symbol, symbols); } else { Assert.Same(symbol, referenceInfo.Symbol); Assert.Same(symbol, symbols.Single()); Assert.Equal(local.Type, model.GetTypeInfo(reference).Type); } Assert.True(model.LookupNames(reference.SpanStart).Contains(decl.Identifier().ValueText)); } if (!inFieldDeclaratorArgumentlist) { var dataFlowParent = (ExpressionSyntax)decl.Parent.Parent.Parent; if (model.IsSpeculativeSemanticModel) { Assert.Throws<NotSupportedException>(() => model.AnalyzeDataFlow(dataFlowParent)); } else { var dataFlow = model.AnalyzeDataFlow(dataFlowParent); if (dataFlow.Succeeded) { Assert.False(dataFlow.VariablesDeclared.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.AlwaysAssigned.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.WrittenInside.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.DataFlowsIn.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.ReadInside.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.DataFlowsOut.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.ReadOutside.Contains(symbol, ReferenceEqualityComparer.Instance)); Assert.False(dataFlow.WrittenOutside.Contains(symbol, ReferenceEqualityComparer.Instance)); } } } }
public static DataFlowAnalysis AnalyzeDataFlow(this SemanticModel semanticModel, MethodDeclarationSyntax methodDeclarationSyntax) { return(semanticModel.AnalyzeDataFlow(methodDeclarationSyntax.Body)); }
private ExpressionSyntax TryVisitInvocationExpression(InvocationExpressionSyntax node, ForEachStatementSyntax containingForEach) { var memberAccess = node.Expression as MemberAccessExpressionSyntax; if (memberAccess != null) { var symbol = semantic.GetSymbolInfo(memberAccess).Symbol as IMethodSymbol; var owner = node.AncestorsAndSelf().FirstOrDefault(x => x is MethodDeclarationSyntax); if (owner == null) { return(null); } currentMethodIsStatic = semantic.GetDeclaredSymbol((MethodDeclarationSyntax)owner).IsStatic; currentMethodName = ((MethodDeclarationSyntax)owner).Identifier.ValueText; currentMethodTypeParameters = ((MethodDeclarationSyntax)owner).TypeParameterList; currentMethodConstraintClauses = ((MethodDeclarationSyntax)owner).ConstraintClauses; if (IsSupportedMethod(GetMethodFullName(node))) { var chain = new List <LinqStep>(); chain.Add(new LinqStep(GetMethodFullName(node), node.ArgumentList.Arguments.Select(x => x.Expression).ToList(), node)); var c = node; var lastNode = node; while (c.Expression is MemberAccessExpressionSyntax) { c = ((MemberAccessExpressionSyntax)c.Expression).Expression as InvocationExpressionSyntax; if (c != null && IsSupportedMethod(GetMethodFullName(c))) { chain.Add(new LinqStep(GetMethodFullName(c), c.ArgumentList.Arguments.Select(x => x.Expression).ToList(), c)); lastNode = c; } else { break; } } if (containingForEach != null) { chain.Insert(0, new LinqStep(IEnumerableForEachMethod, new[] { SyntaxFactory.SimpleLambdaExpression(SyntaxFactory.Parameter(containingForEach.Identifier), containingForEach.Statement) }) { Lambda = new Lambda(containingForEach.Statement, new[] { CreateParameter(containingForEach.Identifier, semantic.GetTypeInfo(containingForEach.Type).ConvertedType) }) }); } if (!chain.Any(x => x.Arguments.Any(y => y is AnonymousFunctionExpressionSyntax))) { return(null); } if (chain.Count == 1 && RootMethodsThatRequireYieldReturn.Contains(chain[0].MethodName)) { return(null); } if (currentMethodName == "SetBlogConfigAsync") { Debugger.Break(); } var flowsIn = new List <ISymbol>(); var flowsOut = new List <ISymbol>(); foreach (var item in chain) { foreach (var arg in item.Arguments) { if (item.Lambda != null) { var dataFlow = semantic.AnalyzeDataFlow(item.Lambda.Body); var pname = item.Lambda.Parameters.Single().Identifier.ValueText; foreach (var k in dataFlow.DataFlowsIn) { if (k.Name == pname) { continue; } if (!flowsIn.Contains(k)) { flowsIn.Add(k); } } foreach (var k in dataFlow.DataFlowsOut) { if (k.Name == pname) { continue; } if (!flowsOut.Contains(k)) { flowsOut.Add(k); } } } else { var dataFlow = semantic.AnalyzeDataFlow(arg); foreach (var k in dataFlow.DataFlowsIn) { if (!flowsIn.Contains(k)) { flowsIn.Add(k); } } foreach (var k in dataFlow.DataFlowsOut) { if (!flowsOut.Contains(k)) { flowsOut.Add(k); } } } } } currentFlow = flowsIn .Union(flowsOut) .Where(x => (x as IParameterSymbol)?.IsThis != true) .Select(x => new VariableCapture(x, flowsOut.Contains(x))) ?? Enumerable.Empty <VariableCapture>(); var collection = ((MemberAccessExpressionSyntax)lastNode.Expression).Expression; if (IsAnonymousType(semantic.GetTypeInfo(collection).Type)) { return(null); } var semanticReturnType = semantic.GetTypeInfo(node).Type; if (IsAnonymousType(semanticReturnType) || currentFlow.Any(x => IsAnonymousType(GetSymbolType(x.Symbol)))) { return(null); } return(TryRewrite(chain.First().MethodName, collection, semanticReturnType, chain, node) .WithLeadingTrivia(((CSharpSyntaxNode)containingForEach ?? node).GetLeadingTrivia()) .WithTrailingTrivia(((CSharpSyntaxNode)containingForEach ?? node).GetTrailingTrivia())); } } return(null); }
private static void Analyze( SyntaxNodeAnalysisContext context, SyntaxNode declaration, ParameterListSyntax parameterList, CSharpSyntaxNode bodyOrExpressionBody) { if (parameterList == null) { return; } if (bodyOrExpressionBody == null) { return; } if (!parameterList.Parameters.Any()) { return; } SemanticModel semanticModel = context.SemanticModel; CancellationToken cancellationToken = context.CancellationToken; var methodSymbol = (IMethodSymbol)semanticModel.GetDeclaredSymbol(declaration, cancellationToken); SyntaxWalker walker = null; foreach (IParameterSymbol parameter in methodSymbol.Parameters) { cancellationToken.ThrowIfCancellationRequested(); ITypeSymbol type = parameter.Type; if (type.Kind == SymbolKind.ErrorType) { continue; } if (CSharpFacts.IsSimpleType(type.SpecialType)) { continue; } if (!type.IsReadOnlyStruct()) { if (parameter.RefKind == RefKind.In && type.TypeKind == TypeKind.Struct) { var parameterSyntax = (ParameterSyntax)parameter.GetSyntax(cancellationToken); Debug.Assert(parameterSyntax.Modifiers.Contains(SyntaxKind.InKeyword), ""); DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.DoNotPassNonReadOnlyStructByReadOnlyReference, parameterSyntax.Identifier); } continue; } if (parameter.RefKind != RefKind.None) { continue; } if (walker == null) { if (methodSymbol.ImplementsInterfaceMember(allInterfaces: true)) { break; } walker = SyntaxWalker.GetInstance(); } else if (walker.Parameters.ContainsKey(parameter.Name)) { walker.Parameters.Clear(); break; } walker.Parameters.Add(parameter.Name, parameter); } if (walker == null) { return; } try { if (walker.Parameters.Count > 0) { walker.SemanticModel = semanticModel; walker.CancellationToken = cancellationToken; if (bodyOrExpressionBody.IsKind(SyntaxKind.Block)) { walker.VisitBlock((BlockSyntax)bodyOrExpressionBody); } else { walker.VisitArrowExpressionClause((ArrowExpressionClauseSyntax)bodyOrExpressionBody); } if (walker.Parameters.Count > 0) { DataFlowAnalysis analysis = (bodyOrExpressionBody.IsKind(SyntaxKind.Block)) ? semanticModel.AnalyzeDataFlow((BlockSyntax)bodyOrExpressionBody) : semanticModel.AnalyzeDataFlow(((ArrowExpressionClauseSyntax)bodyOrExpressionBody).Expression); bool?isReferencedAsMethodGroup = null; foreach (KeyValuePair <string, IParameterSymbol> kvp in walker.Parameters) { var isAssigned = false; foreach (ISymbol assignedSymbol in analysis.AlwaysAssigned) { if (SymbolEqualityComparer.Default.Equals(assignedSymbol, kvp.Value)) { isAssigned = true; break; } } if (isAssigned) { continue; } if (isReferencedAsMethodGroup ??= IsReferencedAsMethodGroup()) { break; } if (kvp.Value.GetSyntaxOrDefault(cancellationToken) is ParameterSyntax parameter) { DiagnosticHelpers.ReportDiagnostic( context, DiagnosticRules.MakeParameterRefReadOnly, parameter.Identifier); } } } } } finally { SyntaxWalker.Free(walker); } bool IsReferencedAsMethodGroup() { switch (declaration.Kind()) { case SyntaxKind.MethodDeclaration: return(MethodReferencedAsMethodGroupWalker.IsReferencedAsMethodGroup(declaration.Parent, methodSymbol, semanticModel, cancellationToken)); case SyntaxKind.LocalFunctionStatement: return(MethodReferencedAsMethodGroupWalker.IsReferencedAsMethodGroup(declaration.FirstAncestor <MemberDeclarationSyntax>(), methodSymbol, semanticModel, cancellationToken)); default: return(false); } } }
// 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); }
private static void ExtractVariableDeclarationsBeforeUsing(SemanticModel semanticModel, List <StatementSyntax> beforeUsing, List <StatementSyntax> insideUsing, List <SyntaxNode> nodesToRemove, CancellationToken ct) { var inUsingDataFlow = semanticModel.AnalyzeDataFlow(insideUsing.First(), insideUsing.Last()); var declaredVariablesUsedOutside = inUsingDataFlow.ReadOutside.Union(inUsingDataFlow.WrittenOutside) .Distinct() .Intersect(inUsingDataFlow.VariablesDeclared) .Select(x => new { Symbol = x, Declarator = (VariableDeclaratorSyntax)x.DeclaringSyntaxReferences[0].GetSyntax(ct) }) .ToArray(); for (var i = 0; i < insideUsing.Count; i++) { var stmt = insideUsing[i]; var localDeclarationStmt = stmt as LocalDeclarationStatementSyntax; if (localDeclarationStmt == null) { continue; } nodesToRemove.Add(localDeclarationStmt); var declaredVariables = localDeclarationStmt.Declaration .Variables.Select(x => new { Symbol = semanticModel.GetDeclaredSymbol(x), Declarator = x }) .ToArray(); var variablesToMove = declaredVariables .Intersect(declaredVariablesUsedOutside) .ToArray(); if (!variablesToMove.Any()) { continue; } var reducedLocalDeclaration = localDeclarationStmt.RemoveNodes(variablesToMove.Select(x => x.Declarator), SyntaxRemoveOptions.AddElasticMarker); if (reducedLocalDeclaration.Declaration.Variables.Any()) { insideUsing[i] = reducedLocalDeclaration; } else { insideUsing.RemoveAt(i); i--; } foreach (var needAssignment in variablesToMove.Where(x => x.Declarator.Initializer != null)) { insideUsing.Insert(i + 1, needAssignment.Declarator.InitializerAsAssignment()); } beforeUsing.Add(SyntaxFactory.LocalDeclarationStatement( SyntaxFactory.VariableDeclaration( localDeclarationStmt.Declaration.Type, SyntaxFactory.SeparatedList(variablesToMove.Select(x => x.Declarator.WithInitializer(null))) ) ) .WithAdditionalAnnotations(Formatter.Annotation) ); } }
private static bool CanBeMadeConst(LocalDeclarationStatementSyntax localDeclaration, SemanticModel semanticModel) { // already const? if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword)) { return(false); } // Ensure that all variables in the local declaration have initializers that // are assigned with constant values. foreach (var variable in localDeclaration.Declaration.Variables) { var initializer = variable.Initializer; if (initializer == null) { return(false); } var constantValue = semanticModel.GetConstantValue(initializer.Value); if (!constantValue.HasValue) { return(false); } var variableTypeName = localDeclaration.Declaration.Type; var variableType = semanticModel.GetTypeInfo(variableTypeName).ConvertedType; // Ensure that the initializer value can be converted to the type of the // local declaration without a user-defined conversion. var conversion = semanticModel.ClassifyConversion(initializer.Value, variableType); if (!conversion.Exists || conversion.IsUserDefined) { return(false); } // Special cases: // * If the constant value is a string, the type of the local declaration // must be System.String. // * If the constant value is null, the type of the local declaration must // be a reference type. if (constantValue.Value is string) { if (variableType.SpecialType != SpecialType.System_String) { return(false); } } else if (variableType.IsReferenceType && constantValue.Value != null) { return(false); } } // Perform data flow analysis on the local declaration. var dataFlowAnalysis = semanticModel.AnalyzeDataFlow(localDeclaration); // Retrieve the local symbol for each variable in the local declaration // and ensure that it is not written outside of the data flow analysis region. foreach (var variable in localDeclaration.Declaration.Variables) { var variableSymbol = semanticModel.GetDeclaredSymbol(variable); if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol)) { return(false); } } return(true); }
static bool ContainsLocalReferences(SemanticModel context, SyntaxNode expr, SyntaxNode body) { var dataFlowAnalysis = context.AnalyzeDataFlow(expr); return(dataFlowAnalysis.Captured.Any()); }
private static bool TryGetDataFlowAnalysis(LocalFunctionStatementSyntax localFunction, SemanticModel semanticModel, [NotNullWhen(returnValue: true)] out DataFlowAnalysis?dataFlow) { dataFlow = semanticModel.AnalyzeDataFlow(localFunction); return(dataFlow is { Succeeded : true });
private Diagnostic AnalyzeAwaitedOrReturnedExpression(ExpressionSyntax expressionSyntax, SemanticModel semanticModel, CancellationToken cancellationToken) { if (expressionSyntax == null) { return(null); } if (semanticModel == null) { throw new ArgumentNullException(nameof(semanticModel)); } SymbolInfo symbolToConsider = semanticModel.GetSymbolInfo(expressionSyntax, cancellationToken); if (CommonInterest.TaskConfigureAwait.Any(configureAwait => configureAwait.IsMatch(symbolToConsider.Symbol))) { if (((InvocationExpressionSyntax)expressionSyntax).Expression is MemberAccessExpressionSyntax memberAccessExpression) { symbolToConsider = semanticModel.GetSymbolInfo(memberAccessExpression.Expression, cancellationToken); } } ITypeSymbol symbolType; bool dataflowAnalysisCompatibleVariable = false; switch (symbolToConsider.Symbol) { case ILocalSymbol localSymbol: symbolType = localSymbol.Type; dataflowAnalysisCompatibleVariable = true; break; case IParameterSymbol parameterSymbol: symbolType = parameterSymbol.Type; dataflowAnalysisCompatibleVariable = true; break; case IFieldSymbol fieldSymbol: symbolType = fieldSymbol.Type; break; case IMethodSymbol methodSymbol: if (Utils.IsTask(methodSymbol.ReturnType) && expressionSyntax is InvocationExpressionSyntax invocationExpressionSyntax) { // Consider all arguments var expressionsToConsider = invocationExpressionSyntax.ArgumentList.Arguments.Select(a => a.Expression); // Consider the implicit first argument when this method is invoked as an extension method. if (methodSymbol.IsExtensionMethod && invocationExpressionSyntax.Expression is MemberAccessExpressionSyntax invokedMember) { if (!methodSymbol.ContainingType.Equals(semanticModel.GetSymbolInfo(invokedMember.Expression, cancellationToken).Symbol)) { expressionsToConsider = new ExpressionSyntax[] { invokedMember.Expression }.Concat(expressionsToConsider); } } return(expressionsToConsider.Select(e => this.AnalyzeAwaitedOrReturnedExpression(e, semanticModel, cancellationToken)).FirstOrDefault(r => r != null)); } return(null); default: return(null); } if (symbolType?.Name != nameof(Task) || !symbolType.BelongsToNamespace(Namespaces.SystemThreadingTasks)) { return(null); } // Report warning if the task was not initialized within the current delegate or lambda expression var containingFunc = Utils.GetContainingFunction(expressionSyntax); if (containingFunc.BlockOrExpression is BlockSyntax delegateBlock) { if (dataflowAnalysisCompatibleVariable) { // Run data flow analysis to understand where the task was defined DataFlowAnalysis dataFlowAnalysis; // When possible (await is direct child of the block and not a field), execute data flow analysis by passing first and last statement to capture only what happens before the await // Check if the await is direct child of the code block (first parent is ExpressionStantement, second parent is the block itself) if (delegateBlock.Equals(expressionSyntax.Parent.Parent?.Parent)) { dataFlowAnalysis = semanticModel.AnalyzeDataFlow(delegateBlock.ChildNodes().First(), expressionSyntax.Parent.Parent); } else { // Otherwise analyze the data flow for the entire block. One caveat: it doesn't distinguish if the initalization happens after the await. dataFlowAnalysis = semanticModel.AnalyzeDataFlow(delegateBlock); } if (!dataFlowAnalysis.WrittenInside.Contains(symbolToConsider.Symbol)) { return(Diagnostic.Create(Descriptor, expressionSyntax.GetLocation())); } } else { // Do the best we can searching for assignment statements. if (!Utils.IsAssignedWithin(containingFunc.BlockOrExpression, semanticModel, symbolToConsider.Symbol, cancellationToken)) { return(Diagnostic.Create(Descriptor, expressionSyntax.GetLocation())); } } } else { // It's not a block, it's just a lambda expression, so the variable must be external. return(Diagnostic.Create(Descriptor, expressionSyntax.GetLocation())); } return(null); }