| File: Kestrel\ListenOnIPv6AnyAnalyzer.cs | Web Access |
| Project: src\aspnetcore\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; } }