File: Kestrel\ListenOnIPv6AnyAnalyzer.cs
Web Access
Project: src\src\Framework\AspNetCoreAnalyzers\src\Analyzers\Microsoft.AspNetCore.App.Analyzers.csproj (Microsoft.AspNetCore.App.Analyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;
 
namespace Microsoft.AspNetCore.Analyzers.Kestrel;
 
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ListenOnIPv6AnyAnalyzer : DiagnosticAnalyzer
{
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [ DiagnosticDescriptors.KestrelShouldListenOnIPv6AnyInsteadOfIpAny ];
 
    public override void Initialize(AnalysisContext context)
    {
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
        context.EnableConcurrentExecution();
 
        context.RegisterSyntaxNodeAction(KestrelServerOptionsListenInvocation, SyntaxKind.InvocationExpression);
    }
 
    private void KestrelServerOptionsListenInvocation(SyntaxNodeAnalysisContext context)
    {
        // fail fast before accessing SemanticModel
        if (context.Node is not InvocationExpressionSyntax
            {
                Expression: MemberAccessExpressionSyntax
                {
                    Name: IdentifierNameSyntax { Identifier.ValueText: "Listen" }
                }
            } kestrelOptionsListenExpressionSyntax)
        {
            return;
        }
 
        var nodeOperation = context.SemanticModel.GetOperation(context.Node, context.CancellationToken);
        if (!IsKestrelServerOptionsType(nodeOperation, out var kestrelOptionsListenInvocation))
        {
            return;
        }
 
        var addressArgument = kestrelOptionsListenInvocation?.Arguments.FirstOrDefault();
        if (!IsIPAddressType(addressArgument?.Parameter))
        {
            return;
        }
 
        var args = kestrelOptionsListenExpressionSyntax.ArgumentList;
        var ipAddressArgumentSyntax = args.Arguments.FirstOrDefault();
        if (ipAddressArgumentSyntax is null)
        {
            return;
        }
 
        // explicit usage like `options.Listen(IPAddress.Any, ...)`
        if (ipAddressArgumentSyntax is ArgumentSyntax
        {
            Expression: MemberAccessExpressionSyntax
            {
                Name: IdentifierNameSyntax { Identifier.ValueText: "Any" }
            }
        })
        {
            context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.KestrelShouldListenOnIPv6AnyInsteadOfIpAny, ipAddressArgumentSyntax.GetLocation()));
        }
 
        // usage via local variable like
        // ```
        // var myIp = IPAddress.Any;
        // options.Listen(myIp, ...);
        // ```
        if (addressArgument!.Value is ILocalReferenceOperation localReferenceOperation)
        {
            var localVariableDeclaration = localReferenceOperation.Local.DeclaringSyntaxReferences.FirstOrDefault();
            if (localVariableDeclaration is null)
            {
                return;
            }
 
            var localVarSyntax = localVariableDeclaration.GetSyntax(context.CancellationToken);
            if (localVarSyntax is VariableDeclaratorSyntax
            {
                Initializer.Value: MemberAccessExpressionSyntax
                {
                    Name.Identifier.ValueText: "Any"
                }
            })
            {
                context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.KestrelShouldListenOnIPv6AnyInsteadOfIpAny, ipAddressArgumentSyntax.GetLocation()));
            }
        }
    }
 
    private static bool IsIPAddressType(IParameterSymbol? parameter) => parameter is 
    {
        Type: // searching type `System.Net.IPAddress`
        {
            Name: "IPAddress",
            ContainingNamespace: { Name: "Net", ContainingNamespace: { Name: "System", ContainingNamespace.IsGlobalNamespace: true } }
        }
    };
 
    private static bool IsKestrelServerOptionsType(IOperation? operation, out IInvocationOperation? kestrelOptionsListenInvocation)
    {
        var result = operation is IInvocationOperation // searching type `Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions`
        {
            TargetMethod: { Name: "Listen" },
            Instance.Type:
            {
                Name: "KestrelServerOptions",
                ContainingNamespace:
                {
                    Name: "Core",
                    ContainingNamespace:
                    {
                        Name: "Kestrel",
                        ContainingNamespace:
                        {
                            Name: "Server",
                            ContainingNamespace:
                            {
                                Name: "AspNetCore",
                                ContainingNamespace:
                                {
                                    Name: "Microsoft",
                                    ContainingNamespace.IsGlobalNamespace: true
                                }
                            }
                        }
                    }
                }
            }
        };
 
        kestrelOptionsListenInvocation = result ? (IInvocationOperation)operation! : null;
        return result;
    }
}