File: Microsoft.NetCore.Analyzers\Security\SourceTriggeredTaintedDataAnalyzerBase.cs
Web Access
Project: 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.Concurrent;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Analyzer.Utilities.FlowAnalysis.Analysis.TaintedDataAnalysis;
using Analyzer.Utilities.PooledObjects;
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.NetCore.Analyzers.Security
{
    using ValueContentAnalysisResult = DataFlowAnalysisResult<ValueContentBlockAnalysisResult, ValueContentAbstractValue>;

    /// <summary>
    /// Base class to aid in implementing tainted data analyzers.
    /// </summary>
    public abstract class SourceTriggeredTaintedDataAnalyzerBase : DiagnosticAnalyzer
    {
        // The engine defaults to three nested method calls; a higher prepass cap avoids unbounded recursion
        // without pruning any path that its configurable analysis could follow.
        private const int MaxPrepassCallDepth = 32;

        /// <summary>
        /// <see cref="DiagnosticDescriptor"/> for when tainted data enters a sink.
        /// </summary>
        /// <remarks>Format string arguments are:
        /// 0. Sink symbol.
        /// 1. Method name containing the code where the tainted data enters the sink.
        /// 2. Source symbol.
        /// 3. Method name containing the code where the tainted data came from the source.
        /// </remarks>
        protected abstract DiagnosticDescriptor TaintedDataEnteringSinkDescriptor { get; }

        /// <summary>
        /// Kind of tainted data sink.
        /// </summary>
        protected abstract SinkKind SinkKind { get; }

        protected virtual bool RequiresReachableSink => true;

        // Per-analyzer observer keeps performance assertions isolated across concurrent tests.
        internal Action? DataflowAnalysisStarted { get; set; }
        internal Action? ValueContentAnalysisStarted { get; set; }

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(TaintedDataEnteringSinkDescriptor);

        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);

            context.RegisterCompilationStartAction(
                (CompilationStartAnalysisContext compilationContext) =>
                {
                    Compilation compilation = compilationContext.Compilation;
                    TaintedDataConfig taintedDataConfig = TaintedDataConfig.GetOrCreate(compilation);
                    TaintedDataSymbolMap<SourceInfo> sourceInfoSymbolMap = taintedDataConfig.GetSourceSymbolMap(this.SinkKind);
                    if (sourceInfoSymbolMap.IsEmpty)
                    {
                        return;
                    }

                    TaintedDataSymbolMap<SinkInfo> sinkInfoSymbolMap = taintedDataConfig.GetSinkSymbolMap(this.SinkKind);
                    if (sinkInfoSymbolMap.IsEmpty)
                    {
                        return;
                    }

                    ConcurrentDictionary<IMethodSymbol, bool>? sinkReachabilityCache = RequiresReachableSink
                        ? new ConcurrentDictionary<IMethodSymbol, bool>(SymbolEqualityComparer.Default)
                        : null;
                    ConcurrentDictionary<IMethodSymbol, bool>? valueContentAnalysisCache = RequiresReachableSink && sourceInfoSymbolMap.RequiresValueContentAnalysis
                        ? new ConcurrentDictionary<IMethodSymbol, bool>(SymbolEqualityComparer.Default)
                        : null;
                    compilationContext.RegisterOperationBlockStartAction(
                        operationBlockStartContext =>
                        {
                            ISymbol owningSymbol = operationBlockStartContext.OwningSymbol;
                            AnalyzerOptions options = operationBlockStartContext.Options;
                            CancellationToken cancellationToken = operationBlockStartContext.CancellationToken;
                            if (options.IsConfiguredToSkipAnalysis(TaintedDataEnteringSinkDescriptor, owningSymbol, compilation))
                            {
                                return;
                            }

                            Lazy<bool>? mayReachSink = sinkReachabilityCache is not null ? new Lazy<bool>(() =>
                                HasReachableSink(operationBlockStartContext.OperationBlocks, compilation, sinkInfoSymbolMap,
                                    this.SinkKind, sinkReachabilityCache, cancellationToken)) : null;

                            WellKnownTypeProvider wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(compilation);
                            Lazy<ControlFlowGraph?> controlFlowGraphFactory = new Lazy<ControlFlowGraph?>(
                                () => operationBlockStartContext.OperationBlocks.GetControlFlowGraph());
                            Lazy<PointsToAnalysisResult?> pointsToFactory = new Lazy<PointsToAnalysisResult?>(
                                () =>
                                {
                                    if (controlFlowGraphFactory.Value == null)
                                    {
                                        return null;
                                    }

                                    InterproceduralAnalysisConfiguration interproceduralAnalysisConfiguration = InterproceduralAnalysisConfiguration.Create(
                                                                    options,
                                                                    SupportedDiagnostics,
                                                                    controlFlowGraphFactory.Value,
                                                                    operationBlockStartContext.Compilation,
                                                                    defaultInterproceduralAnalysisKind: InterproceduralAnalysisKind.ContextSensitive);
                                    return PointsToAnalysis.TryGetOrComputeResult(
                                                                controlFlowGraphFactory.Value,
                                                                owningSymbol,
                                                                options,
                                                                wellKnownTypeProvider,
                                                                PointsToAnalysisKind.Complete,
                                                                interproceduralAnalysisConfiguration,
                                                                interproceduralAnalysisPredicate: null);
                                });
                            Lazy<(PointsToAnalysisResult?, ValueContentAnalysisResult?)> valueContentFactory = new Lazy<(PointsToAnalysisResult?, ValueContentAnalysisResult?)>(
                                () =>
                                {
                                    if (controlFlowGraphFactory.Value == null)
                                    {
                                        return (null, null);
                                    }

                                    InterproceduralAnalysisConfiguration interproceduralAnalysisConfiguration = InterproceduralAnalysisConfiguration.Create(
                                                                    options,
                                                                    SupportedDiagnostics,
                                                                    controlFlowGraphFactory.Value,
                                                                    operationBlockStartContext.Compilation,
                                                                    defaultInterproceduralAnalysisKind: InterproceduralAnalysisKind.ContextSensitive);
                                    ValueContentAnalysisResult? valuecontentAnalysisResult = ValueContentAnalysis.TryGetOrComputeResult(
                                                                    controlFlowGraphFactory.Value,
                                                                    owningSymbol,
                                                                    options,
                                                                    wellKnownTypeProvider,
                                                                    PointsToAnalysisKind.Complete,
                                                                    interproceduralAnalysisConfiguration,
                                                                    out _,
                                                                    out PointsToAnalysisResult? p);

                                    return (p, valuecontentAnalysisResult);
                                });

                            int hasSource = 0;

                            operationBlockStartContext.RegisterOperationAction(
                                operationAnalysisContext =>
                                {
                                    IPropertyReferenceOperation propertyReferenceOperation = (IPropertyReferenceOperation)operationAnalysisContext.Operation;
                                    if (sourceInfoSymbolMap.IsSourceProperty(propertyReferenceOperation.Property))
                                    {
                                        Interlocked.Exchange(ref hasSource, 1);
                                    }
                                },
                                OperationKind.PropertyReference);

                            if (sourceInfoSymbolMap.RequiresParameterReferenceAnalysis)
                            {
                                operationBlockStartContext.RegisterOperationAction(
                                    operationAnalysisContext =>
                                    {
                                        IParameterReferenceOperation parameterReferenceOperation = (IParameterReferenceOperation)operationAnalysisContext.Operation;
                                        if (sourceInfoSymbolMap.IsSourceParameter(parameterReferenceOperation.Parameter, wellKnownTypeProvider))
                                        {
                                            Interlocked.Exchange(ref hasSource, 1);
                                        }
                                    },
                                    OperationKind.ParameterReference);
                            }

                            operationBlockStartContext.RegisterOperationAction(
                                operationAnalysisContext =>
                                {
                                    IInvocationOperation invocationOperation = (IInvocationOperation)operationAnalysisContext.Operation;
                                    if (mayReachSink is not null &&
                                        sourceInfoSymbolMap.GetInfosForType(invocationOperation.TargetMethod.ContainingType).Any() &&
                                        !mayReachSink.Value)
                                    {
                                        return;
                                    }

                                    if (sourceInfoSymbolMap.IsSourceMethod(
                                            invocationOperation.TargetMethod,
                                            invocationOperation.Arguments,
                                            pointsToFactory,
                                            valueContentFactory,
                                            out _))
                                    {
                                        Interlocked.Exchange(ref hasSource, 1);
                                    }
                                },
                                OperationKind.Invocation);

                            if (TaintedDataConfig.HasTaintArraySource(SinkKind))
                            {
                                operationBlockStartContext.RegisterOperationAction(
                                    operationAnalysisContext =>
                                    {
                                        IArrayInitializerOperation arrayInitializerOperation = (IArrayInitializerOperation)operationAnalysisContext.Operation;
                                        if (arrayInitializerOperation.GetAncestor<IArrayCreationOperation>(OperationKind.ArrayCreation)?.Type is IArrayTypeSymbol arrayTypeSymbol
                                            && sourceInfoSymbolMap.IsSourceConstantArrayOfType(arrayTypeSymbol, arrayInitializerOperation))
                                        {
                                            Interlocked.Exchange(ref hasSource, 1);
                                        }
                                    },
                                    OperationKind.ArrayInitializer);
                            }

                            operationBlockStartContext.RegisterOperationBlockEndAction(
                                operationBlockAnalysisContext =>
                                {
                                    if (Volatile.Read(ref hasSource) == 0 || mayReachSink is { Value: false })
                                    {
                                        return;
                                    }

                                    if (controlFlowGraphFactory.Value == null)
                                    {
                                        return;
                                    }

                                    TaintedDataSymbolMap<SanitizerInfo> sanitizerInfoSymbolMap = taintedDataConfig.GetSanitizerSymbolMap(this.SinkKind);
                                    // Computed source values and branch predicates may need value-content flow,
                                    // even when every literal array element is already known.
                                    bool performValueContentAnalysis = valueContentAnalysisCache is null ||
                                        sanitizerInfoSymbolMap.RequiresValueContentAnalysis ||
                                        sinkInfoSymbolMap.RequiresValueContentAnalysis ||
                                        (sourceInfoSymbolMap.RequiresValueContentAnalysis &&
                                            MayRequireValueContentAnalysis(operationBlockStartContext.OperationBlocks,
                                                compilation, sourceInfoSymbolMap, valueContentAnalysisCache,
                                                cancellationToken));
                                    DataflowAnalysisStarted?.Invoke();
                                    TaintedDataAnalysisResult? taintedDataAnalysisResult = TaintedDataAnalysis.TryGetOrComputeResult(
                                        controlFlowGraphFactory.Value,
                                        operationBlockAnalysisContext.Compilation,
                                        operationBlockAnalysisContext.OwningSymbol,
                                        operationBlockAnalysisContext.Options,
                                        TaintedDataEnteringSinkDescriptor,
                                        sourceInfoSymbolMap,
                                        sanitizerInfoSymbolMap,
                                        sinkInfoSymbolMap,
                                        performValueContentAnalysis,
                                        onValueContentAnalysis: ValueContentAnalysisStarted);
                                    if (taintedDataAnalysisResult == null)
                                    {
                                        return;
                                    }

                                    foreach (TaintedDataSourceSink sourceSink in taintedDataAnalysisResult.TaintedDataSourceSinks)
                                    {
                                        if (!sourceSink.SinkKinds.Contains(this.SinkKind))
                                        {
                                            continue;
                                        }

                                        foreach (SymbolAccess sourceOrigin in sourceSink.SourceOrigins)
                                        {
                                            // Something like:
                                            // CA3001: Potential SQL injection vulnerability was found where '{0}' in method '{1}' may be tainted by user-controlled data from '{2}' in method '{3}'.
                                            Diagnostic diagnostic = Diagnostic.Create(
                                                this.TaintedDataEnteringSinkDescriptor,
                                                sourceSink.Sink.Location,
                                                additionalLocations: new Location[] { sourceOrigin.Location },
                                                messageArgs: new object[] {
                                            sourceSink.Sink.Symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
                                            sourceSink.Sink.AccessingMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
                                            sourceOrigin.Symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
                                            sourceOrigin.AccessingMethod.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)});
                                            operationBlockAnalysisContext.ReportDiagnostic(diagnostic);
                                        }
                                    }
                                });
                        });
                });
        }

        private static bool HasReachableSink(
            ImmutableArray<IOperation> operationBlocks,
            Compilation compilation,
            TaintedDataSymbolMap<SinkInfo> sinkInfoSymbolMap,
            SinkKind sinkKind,
            ConcurrentDictionary<IMethodSymbol, bool> sinkReachabilityCache,
            CancellationToken cancellationToken)
        {
            using PooledHashSet<IMethodSymbol> visitingMethods = PooledHashSet<IMethodSymbol>.GetInstance(SymbolEqualityComparer.Default);
            foreach (IOperation operationBlock in operationBlocks)
            {
                if (HasReachableSink(operationBlock, compilation, sinkInfoSymbolMap, sinkKind, sinkReachabilityCache,
                    visitingMethods, cancellationToken))
                {
                    return true;
                }
            }

            return false;
        }

        private static bool HasReachableSink(
            IOperation root,
            Compilation compilation,
            TaintedDataSymbolMap<SinkInfo> sinkInfoSymbolMap,
            SinkKind sinkKind,
            ConcurrentDictionary<IMethodSymbol, bool> sinkReachabilityCache,
            PooledHashSet<IMethodSymbol> visitingMethods,
            CancellationToken cancellationToken)
        {
            foreach (IOperation operation in root.DescendantsAndSelf())
            {
                cancellationToken.ThrowIfCancellationRequested();
                switch (operation)
                {
                    case IInvocationOperation invocation:
                        if (IsSinkMethod(invocation.TargetMethod, sinkInfoSymbolMap, sinkKind) ||
                            MethodMayReachSink(invocation.TargetMethod, compilation, sinkInfoSymbolMap, sinkKind,
                                sinkReachabilityCache, visitingMethods, cancellationToken))
                        {
                            return true;
                        }

                        break;

                    case IObjectCreationOperation creation when creation.Constructor is IMethodSymbol constructor:
                        if (IsSinkMethod(constructor, sinkInfoSymbolMap, sinkKind) ||
                            MethodMayReachSink(constructor, compilation, sinkInfoSymbolMap, sinkKind,
                                sinkReachabilityCache, visitingMethods, cancellationToken))
                        {
                            return true;
                        }

                        break;

                    case IPropertyReferenceOperation property:
                        if (IsSinkProperty(property.Property, sinkInfoSymbolMap, sinkKind) ||
                            (property.Property.GetMethod is IMethodSymbol getter &&
                                MethodMayReachSink(getter, compilation, sinkInfoSymbolMap, sinkKind,
                                    sinkReachabilityCache, visitingMethods, cancellationToken)) ||
                            (property.Property.SetMethod is IMethodSymbol setter &&
                                MethodMayReachSink(setter, compilation, sinkInfoSymbolMap, sinkKind,
                                    sinkReachabilityCache, visitingMethods, cancellationToken)))
                        {
                            return true;
                        }

                        break;

                    case IPropertyInitializerOperation initializer:
                        if (initializer.InitializedProperties.Any(property => IsSinkProperty(property, sinkInfoSymbolMap, sinkKind)))
                        {
                            return true;
                        }

                        break;

                    case IMethodReferenceOperation reference:
                        if (IsSinkMethod(reference.Method, sinkInfoSymbolMap, sinkKind) ||
                            MethodMayReachSink(reference.Method, compilation, sinkInfoSymbolMap, sinkKind,
                                sinkReachabilityCache, visitingMethods, cancellationToken))
                        {
                            return true;
                        }

                        break;

                    case IDynamicInvocationOperation:
                    case IForEachLoopOperation:
                    case IUsingOperation:
                    case IUsingDeclarationOperation:
                    case IDeconstructionAssignmentOperation:
                    case ILockOperation lockOperation when lockOperation.LockedValue.Type is ITypeSymbol lockedType &&
                        SymbolEqualityComparer.Default.Equals(lockedType, compilation.GetTypeByMetadataName("System.Threading.Lock")):
                    case IConversionOperation { OperatorMethod: not null }:
                    case IBinaryOperation { OperatorMethod: not null }:
                    case IUnaryOperation { OperatorMethod: not null }:
                    case ICompoundAssignmentOperation { OperatorMethod: not null }:
                    case IIncrementOrDecrementOperation { OperatorMethod: not null }:
                        // These calls may dispatch to a sink even when there is no ordinary invocation operation.
                        return true;
                }
            }

            return false;
        }

        private static bool MethodMayReachSink(
            IMethodSymbol method,
            Compilation compilation,
            TaintedDataSymbolMap<SinkInfo> sinkInfoSymbolMap,
            SinkKind sinkKind,
            ConcurrentDictionary<IMethodSymbol, bool> sinkReachabilityCache,
            PooledHashSet<IMethodSymbol> visitingMethods,
            CancellationToken cancellationToken)
        {
            if (method.MethodKind == MethodKind.DelegateInvoke)
            {
                return true;
            }

            // The dataflow engine cannot analyze metadata bodies or dispatch to virtual implementations.
            if (!SymbolEqualityComparer.Default.Equals(method.ContainingAssembly, compilation.Assembly) ||
                method.IsAbstract || method.IsVirtual || method.IsOverride || method.IsImplicitlyDeclared)
            {
                return false;
            }

            method = method.OriginalDefinition;
            if (sinkReachabilityCache.TryGetValue(method, out bool canReachSink))
            {
                return canReachSink;
            }

            if (visitingMethods.Count >= MaxPrepassCallDepth || !visitingMethods.Add(method))
            {
                // Do not prune a recursive call chain without proving it cannot reach a sink.
                return true;
            }

            try
            {
                IBlockOperation? block = method.GetTopmostOperationBlock(compilation, cancellationToken);
                canReachSink = block is null ||
                    HasReachableSink(block.GetRoot(), compilation, sinkInfoSymbolMap, sinkKind, sinkReachabilityCache,
                        visitingMethods, cancellationToken);
                sinkReachabilityCache.TryAdd(method, canReachSink);
                return canReachSink;
            }
            finally
            {
                visitingMethods.Remove(method);
            }
        }

        private static bool IsSinkMethod(IMethodSymbol method, TaintedDataSymbolMap<SinkInfo> sinkInfoSymbolMap, SinkKind sinkKind)
        {
            foreach (SinkInfo sinkInfo in sinkInfoSymbolMap.GetInfosForType(method.ContainingType))
            {
                if (!sinkInfo.SinkKinds.Contains(sinkKind))
                {
                    continue;
                }

                if (method.MethodKind == MethodKind.Constructor &&
                    sinkInfo.IsAnyStringParameterInConstructorASink &&
                    method.Parameters.Any(parameter => parameter.Type.SpecialType == SpecialType.System_String))
                {
                    return true;
                }

                if (sinkInfo.SinkMethodParameters.TryGetValue(method.MetadataName, out ImmutableHashSet<string>? sinkParameters) &&
                    method.Parameters.Any(parameter => sinkParameters.Contains(parameter.MetadataName)))
                {
                    return true;
                }
            }

            return false;
        }

        private static bool IsSinkProperty(IPropertySymbol property, TaintedDataSymbolMap<SinkInfo> sinkInfoSymbolMap, SinkKind sinkKind)
        {
            return sinkInfoSymbolMap.GetInfosForType(property.ContainingType)
                .Any(sinkInfo => sinkInfo.SinkKinds.Contains(sinkKind) && sinkInfo.SinkProperties.Contains(property.MetadataName));
        }

        private static bool MayRequireValueContentAnalysis(
            ImmutableArray<IOperation> operationBlocks,
            Compilation compilation,
            TaintedDataSymbolMap<SourceInfo> sourceInfoSymbolMap,
            ConcurrentDictionary<IMethodSymbol, bool> cache,
            CancellationToken cancellationToken)
        {
            using PooledHashSet<IMethodSymbol> visitingMethods = PooledHashSet<IMethodSymbol>.GetInstance(SymbolEqualityComparer.Default);
            return operationBlocks.Any(block =>
                MayRequireValueContentAnalysis(block, compilation, sourceInfoSymbolMap, cache, visitingMethods, cancellationToken));
        }

        private static bool MayRequireValueContentAnalysis(
            IOperation root,
            Compilation compilation,
            TaintedDataSymbolMap<SourceInfo> sourceInfoSymbolMap,
            ConcurrentDictionary<IMethodSymbol, bool> cache,
            PooledHashSet<IMethodSymbol> visitingMethods,
            CancellationToken cancellationToken)
        {
            foreach (IOperation operation in root.DescendantsAndSelf())
            {
                cancellationToken.ThrowIfCancellationRequested();
                switch (operation)
                {
                    case IArrayInitializerOperation initializer
                        when initializer.GetAncestor<IArrayCreationOperation>(OperationKind.ArrayCreation)?.Type is IArrayTypeSymbol arrayType
                            && sourceInfoSymbolMap.IsSourceConstantArrayOfType(arrayType, initializer)
                            && initializer.ElementValues.Any(element => !element.ConstantValue.HasValue):
                        // Nonconstant elements can become literals through value propagation.
                        return true;

                    case IInvocationOperation invocation:
                        if (IsValueContentSource(invocation.TargetMethod, invocation.Arguments, sourceInfoSymbolMap) ||
                            MethodMayRequireValueContentAnalysis(invocation.TargetMethod, compilation, sourceInfoSymbolMap,
                                cache, visitingMethods, cancellationToken))
                        {
                            return true;
                        }

                        break;

                    case IObjectCreationOperation creation when creation.Constructor is IMethodSymbol constructor:
                        if (IsValueContentSource(constructor, creation.Arguments, sourceInfoSymbolMap) ||
                            MethodMayRequireValueContentAnalysis(constructor, compilation, sourceInfoSymbolMap,
                                cache, visitingMethods, cancellationToken))
                        {
                            return true;
                        }

                        break;

                    case IPropertyReferenceOperation property:
                        if ((property.Property.GetMethod is IMethodSymbol getter &&
                                MethodMayRequireValueContentAnalysis(getter, compilation, sourceInfoSymbolMap,
                                    cache, visitingMethods, cancellationToken)) ||
                            (property.Property.SetMethod is IMethodSymbol setter &&
                                MethodMayRequireValueContentAnalysis(setter, compilation, sourceInfoSymbolMap,
                                    cache, visitingMethods, cancellationToken)))
                        {
                            return true;
                        }

                        break;

                    case IMethodReferenceOperation reference:
                        if (sourceInfoSymbolMap.GetInfosForType(reference.Method.ContainingType)
                                .Any(source => source.TaintedMethodsNeedsValueContentAnalysis.Count > 0) ||
                            MethodMayRequireValueContentAnalysis(reference.Method, compilation, sourceInfoSymbolMap,
                                cache, visitingMethods, cancellationToken))
                        {
                            return true;
                        }

                        break;

                    case IDynamicInvocationOperation:
                    case IForEachLoopOperation:
                    case IUsingOperation:
                    case IUsingDeclarationOperation:
                    case IDeconstructionAssignmentOperation:
                    case ILockOperation lockOperation when lockOperation.LockedValue.Type is ITypeSymbol lockedType &&
                        SymbolEqualityComparer.Default.Equals(lockedType, compilation.GetTypeByMetadataName("System.Threading.Lock")):
                    case IConditionalOperation conditional when !conditional.Condition.ConstantValue.HasValue:
                    case ILoopOperation:
                    case ISwitchOperation:
                    case ISwitchExpressionOperation:
                    case IConditionalAccessOperation:
                    case ICoalesceOperation:
                    case IBinaryOperation { OperatorKind: BinaryOperatorKind.ConditionalAnd or BinaryOperatorKind.ConditionalOr }:
                    case IBinaryPatternOperation { OperatorKind: BinaryOperatorKind.And or BinaryOperatorKind.Or }:
                    case IConversionOperation { OperatorMethod: not null }:
                    case IBinaryOperation { OperatorMethod: not null }:
                    case IUnaryOperation { OperatorMethod: not null }:
                    case ICompoundAssignmentOperation { OperatorMethod: not null }:
                    case IIncrementOrDecrementOperation { OperatorMethod: not null }:
                        return true;
                }
            }

            return false;
        }

        private static bool IsValueContentSource(
            IMethodSymbol method,
            ImmutableArray<IArgumentOperation> arguments,
            TaintedDataSymbolMap<SourceInfo> sourceInfoSymbolMap)
        {
            return sourceInfoSymbolMap.GetInfosForType(method.ContainingType)
                .Any(source => source.TaintedMethodsNeedsValueContentAnalysis
                    .Any(entry => entry.MethodMatcher(method.Name, arguments)));
        }

        private static bool MethodMayRequireValueContentAnalysis(
            IMethodSymbol method,
            Compilation compilation,
            TaintedDataSymbolMap<SourceInfo> sourceInfoSymbolMap,
            ConcurrentDictionary<IMethodSymbol, bool> cache,
            PooledHashSet<IMethodSymbol> visitingMethods,
            CancellationToken cancellationToken)
        {
            if (method.MethodKind == MethodKind.DelegateInvoke)
            {
                return true;
            }

            if (!SymbolEqualityComparer.Default.Equals(method.ContainingAssembly, compilation.Assembly))
            {
                return false;
            }

            if (method.IsAbstract || method.IsVirtual || method.IsOverride)
            {
                return true;
            }

            if (method.IsImplicitlyDeclared)
            {
                return false;
            }

            method = method.OriginalDefinition;
            if (cache.TryGetValue(method, out bool requiresValueContent))
            {
                return requiresValueContent;
            }

            if (visitingMethods.Count >= MaxPrepassCallDepth || !visitingMethods.Add(method))
            {
                return true;
            }

            try
            {
                IBlockOperation? block = method.GetTopmostOperationBlock(compilation, cancellationToken);
                requiresValueContent = block is null ||
                    MayRequireValueContentAnalysis(block.GetRoot(), compilation, sourceInfoSymbolMap, cache,
                        visitingMethods, cancellationToken);
                cache.TryAdd(method, requiresValueContent);
                return requiresValueContent;
            }
            finally
            {
                visitingMethods.Remove(method);
            }
        }
    }
}