| File: Microsoft.CodeQuality.Analyzers\Maintainability\AvoidDeadConditionalCode.cs | Web Access |
| Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow; using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis; using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeQuality.Analyzers.Maintainability { using static MicrosoftCodeQualityAnalyzersResources; /// <summary> /// CA1508: <inheritdoc cref="AvoidDeadConditionalCodeTitle"/> /// /// Flags conditional expressions which are always true/false and null checks for operations that are always null/non-null based on predicate analysis. /// </summary> public abstract class AvoidDeadConditionalCode : DiagnosticAnalyzer { internal const string RuleId = "CA1508"; private static readonly LocalizableString s_localizableTitle = CreateLocalizableResourceString(nameof(AvoidDeadConditionalCodeTitle)); // https://github.com/dotnet/roslyn-analyzers/issues/2180 tracks enabling the rule by default internal static readonly DiagnosticDescriptor AlwaysTrueFalseOrNullRule = DiagnosticDescriptorHelper.Create( RuleId, s_localizableTitle, CreateLocalizableResourceString(nameof(AvoidDeadConditionalCodeAlwaysTruFalseOrNullMessage)), DiagnosticCategory.Maintainability, RuleLevel.Disabled, description: null, isPortedFxCopRule: false, isDataflowRule: true); internal static readonly DiagnosticDescriptor NeverNullRule = DiagnosticDescriptorHelper.Create( RuleId, s_localizableTitle, CreateLocalizableResourceString(nameof(AvoidDeadConditionalCodeNeverNullMessage)), DiagnosticCategory.Maintainability, RuleLevel.Disabled, description: null, isPortedFxCopRule: false, isDataflowRule: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(AlwaysTrueFalseOrNullRule, NeverNullRule); protected abstract bool IsSwitchArmExpressionWithWhenClause(SyntaxNode node); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(compilationContext => { var typeProvider = WellKnownTypeProvider.GetOrCreate(compilationContext.Compilation); var debugAssert = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDiagnosticsDebug); var debugAssertMethods = debugAssert?.GetMembers(nameof(Debug.Assert)).OfType<IMethodSymbol>().ToArray() ?? Array.Empty<IMethodSymbol>(); compilationContext.RegisterOperationBlockAction(operationBlockContext => { var owningSymbol = operationBlockContext.OwningSymbol; if (operationBlockContext.Options.IsConfiguredToSkipAnalysis(AlwaysTrueFalseOrNullRule, owningSymbol, operationBlockContext.Compilation)) { return; } var processedOperationRoots = new HashSet<IOperation>(); foreach (var operationRoot in operationBlockContext.OperationBlocks) { bool ShouldAnalyze(IOperation op) => ((op as IBinaryOperation)?.IsComparisonOperator() == true || op is IInvocationOperation { TargetMethod.ReturnType.SpecialType: SpecialType.System_Boolean } || op.Kind == OperationKind.Coalesce || op.Kind == OperationKind.ConditionalAccess || op.Kind == OperationKind.IsNull || op.Kind == OperationKind.IsPattern) && (op.Parent is not IArgumentOperation { Parent: IInvocationOperation invocation } || !debugAssertMethods.Contains(invocation.TargetMethod, SymbolEqualityComparer.Default)); if (operationRoot.HasAnyOperationDescendant(ShouldAnalyze)) { // Skip duplicate analysis from operation blocks for constructor initializer and body. if (!processedOperationRoots.Add(operationRoot.GetRoot())) { // Already processed. continue; } var cfg = operationBlockContext.GetControlFlowGraph(operationRoot); var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(operationBlockContext.Compilation); var valueContentAnalysisResult = ValueContentAnalysis.TryGetOrComputeResult(cfg, owningSymbol, wellKnownTypeProvider, operationBlockContext.Options, AlwaysTrueFalseOrNullRule, PointsToAnalysisKind.Complete, out var copyAnalysisResultOpt, out var pointsToAnalysisResult); if (valueContentAnalysisResult == null || pointsToAnalysisResult == null) { continue; } foreach (var block in cfg.Blocks) { foreach (var operation in block.DescendantOperations()) { // Skip implicit operations. // However, 'IsNull' operations are compiler generated operations corresponding to // non-implicit conditional access operations, so we should not skip them. if (operation.IsImplicit && operation.Kind != OperationKind.IsNull) { continue; } switch (operation.Kind) { case OperationKind.BinaryOperator: var binaryOperation = (IBinaryOperation)operation; PredicateValueKind predicateKind = GetPredicateKind(binaryOperation); if (predicateKind != PredicateValueKind.Unknown && (binaryOperation.LeftOperand is not IBinaryOperation leftBinary || GetPredicateKind(leftBinary) == PredicateValueKind.Unknown) && (binaryOperation.RightOperand is not IBinaryOperation rightBinary || GetPredicateKind(rightBinary) == PredicateValueKind.Unknown)) { ReportAlwaysTrueFalseOrNullDiagnostic(operation, predicateKind); } break; case OperationKind.Invocation: predicateKind = GetPredicateKind(operation); if (predicateKind != PredicateValueKind.Unknown) { ReportAlwaysTrueFalseOrNullDiagnostic(operation, predicateKind); } break; case OperationKind.IsPattern: predicateKind = GetPredicateKind(operation); // We bail out for lowered IsPatternOperation generated for a switch arm expression with a when clause. // We currently perform syntactic checks as the switch arm and when clause are split into // multiple basic blocks and control flow branches during CFG generation. // TODO: Consider if we can harden this validation without relying on syntactic checks. if (predicateKind != PredicateValueKind.Unknown && !IsSwitchArmExpressionWithWhenClause(operation.Syntax)) { ReportAlwaysTrueFalseOrNullDiagnostic(operation, predicateKind); } break; case OperationKind.IsNull: // '{0}' is always/never '{1}'. Remove or refactor the condition(s) to avoid dead code. predicateKind = GetPredicateKind(operation); DiagnosticDescriptor rule; switch (predicateKind) { case PredicateValueKind.AlwaysTrue: rule = AlwaysTrueFalseOrNullRule; break; case PredicateValueKind.AlwaysFalse: rule = NeverNullRule; break; default: continue; } // Skip IsNull operations in compiler generated finally region for using statement if (block.IsFirstBlockOfCompilerGeneratedFinally(cfg)) { continue; } var arg1 = operation.Syntax.ToString(); var arg2 = operation.Language == LanguageNames.VisualBasic ? "Nothing" : "null"; var diagnostic = operation.CreateDiagnostic(rule, arg1, arg2); operationBlockContext.ReportDiagnostic(diagnostic); break; } } } PredicateValueKind GetPredicateKind(IOperation operation) { Debug.Assert(operation.Kind is OperationKind.BinaryOperator or OperationKind.Invocation or OperationKind.IsNull or OperationKind.IsPattern); RoslynDebug.Assert(pointsToAnalysisResult != null); RoslynDebug.Assert(valueContentAnalysisResult != null); if (operation is IBinaryOperation binaryOperation && binaryOperation.IsComparisonOperator() || operation.Type?.SpecialType == SpecialType.System_Boolean) { PredicateValueKind predicateKind = pointsToAnalysisResult.GetPredicateKind(operation); if (predicateKind != PredicateValueKind.Unknown) { return predicateKind; } if (copyAnalysisResultOpt != null) { predicateKind = copyAnalysisResultOpt.GetPredicateKind(operation); if (predicateKind != PredicateValueKind.Unknown) { return predicateKind; } } predicateKind = valueContentAnalysisResult.GetPredicateKind(operation); if (predicateKind != PredicateValueKind.Unknown) { return predicateKind; } } return PredicateValueKind.Unknown; } void ReportAlwaysTrueFalseOrNullDiagnostic(IOperation operation, PredicateValueKind predicateKind) { Debug.Assert(predicateKind != PredicateValueKind.Unknown); // '{0}' is always '{1}'. Remove or refactor the condition(s) to avoid dead code. var arg1 = operation.Syntax.ToString(); var arg2 = predicateKind == PredicateValueKind.AlwaysTrue ? (operation.Language == LanguageNames.VisualBasic ? "True" : "true") : (operation.Language == LanguageNames.VisualBasic ? "False" : "false"); var diagnostic = operation.CreateDiagnostic(AlwaysTrueFalseOrNullRule, arg1, arg2); operationBlockContext.ReportDiagnostic(diagnostic); } } } }); }); } } }