File: src\RoslynAnalyzers\Utilities\FlowAnalysis\FlowAnalysis\Analysis\PropertySetAnalysis\PropertySetCallbacks.cs
Web Access
Project: src\src\RoslynAnalyzers\Microsoft.CodeAnalysis.AnalyzerUtilities\Microsoft.CodeAnalysis.AnalyzerUtilities.csproj (Microsoft.CodeAnalysis.AnalyzerUtilities)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis;
 
namespace Analyzer.Utilities.FlowAnalysis.Analysis.PropertySetAnalysis
{
    /// <summary>
    /// Common callbacks for <see cref="PropertySetAnalysis"/>.
    /// </summary>
    internal static class PropertySetCallbacks
    {
        /// <summary>
        /// A <see cref="PropertyMapper.PointsToAbstractValueCallback"/> for flagging assigning null to a property.
        /// </summary>
        /// <param name="pointsToAbstractValue">Value assigned to the property.</param>
        /// <returns>Flagged if null, Unflagged if not null, MaybeFlagged otherwise.</returns>
        public static PropertySetAbstractValueKind FlagIfNull(PointsToAbstractValue pointsToAbstractValue)
        {
            return pointsToAbstractValue.NullState switch
            {
                NullAbstractValue.Null => PropertySetAbstractValueKind.Flagged,
 
                NullAbstractValue.NotNull => PropertySetAbstractValueKind.Unflagged,
 
                NullAbstractValue.MaybeNull => PropertySetAbstractValueKind.MaybeFlagged,
 
                _ => PropertySetAbstractValueKind.Unknown,
            };
        }
 
        /// <summary>
        /// Enumerates literal values to map to a property set abstract value.
        /// </summary>
        /// <param name="valueContentAbstractValue">Abstract value containing the literal values to examine.</param>
        /// <param name="badLiteralValuePredicate">Predicate function to determine if a literal value is bad.</param>
        /// <returns>Mapped kind.</returns>
        /// <remarks>
        /// All literal values are bad => Flagged
        /// Some but not all literal are bad => MaybeFlagged
        /// All literal values are known and none are bad => Unflagged
        /// Otherwise => Unknown
        /// </remarks>
        public static PropertySetAbstractValueKind EvaluateLiteralValues(
            ValueContentAbstractValue valueContentAbstractValue,
            Func<object?, bool> badLiteralValuePredicate)
        {
            switch (valueContentAbstractValue.NonLiteralState)
            {
                case ValueContainsNonLiteralState.No:
                    if (valueContentAbstractValue.LiteralValues.IsEmpty)
                    {
                        return PropertySetAbstractValueKind.Unflagged;
                    }
 
                    bool allValuesBad = true;
                    bool someValuesBad = false;
                    foreach (object? literalValue in valueContentAbstractValue.LiteralValues)
                    {
                        if (badLiteralValuePredicate(literalValue))
                        {
                            someValuesBad = true;
                        }
                        else
                        {
                            allValuesBad = false;
                        }
 
                        if (!allValuesBad && someValuesBad)
                        {
                            break;
                        }
                    }
 
                    if (allValuesBad)
                    {
                        // We know all values are bad, so we can say Flagged.
                        return PropertySetAbstractValueKind.Flagged;
                    }
                    else if (someValuesBad)
                    {
                        // We know all values but some values are bad, so we can say MaybeFlagged.
                        return PropertySetAbstractValueKind.MaybeFlagged;
                    }
                    else
                    {
                        // We know all values are good, so we can say Unflagged.
                        return PropertySetAbstractValueKind.Unflagged;
                    }
 
                case ValueContainsNonLiteralState.Maybe:
                    if (valueContentAbstractValue.LiteralValues.Any(badLiteralValuePredicate))
                    {
                        // We don't know all values but know some values are bad, so we can say MaybeFlagged.
                        return PropertySetAbstractValueKind.MaybeFlagged;
                    }
                    else
                    {
                        // We don't know all values but didn't find any bad value, so we can say who knows.
                        return PropertySetAbstractValueKind.Unknown;
                    }
 
                default:
                    return PropertySetAbstractValueKind.Unknown;
            }
        }
 
        /// <summary>
        /// A <see cref="HazardousUsageEvaluator.EvaluationCallback"/> for all properties flagged being hazardous, treating all
        /// unknown as maybe flagged.
        /// </summary>
        /// <param name="propertySetAbstractValue">PropertySetAbstract value.</param>
        /// <returns>If all properties are flagged, then flagged; if at least one property is unflagged, then unflagged;
        /// otherwise (including all unknown) maybe flagged.</returns>
        public static HazardousUsageEvaluationResult HazardousIfAllFlaggedOrAllUnknown(
            PropertySetAbstractValue propertySetAbstractValue)
        {
            return HazardousIfAllFlagged(propertySetAbstractValue, assumeAllUnknownInsecure: true);
        }
 
        /// <summary>
        /// A <see cref="HazardousUsageEvaluator.EvaluationCallback"/> for all properties flagged being hazardous, treating all
        /// unknown as unflagged.
        /// </summary>
        /// <param name="propertySetAbstractValue">PropertySetAbstract value.</param>
        /// <returns>If all properties are flagged, then flagged; if at least one property is unflagged, then unflagged;
        /// otherwise (excluding all unknown) maybe flagged.</returns>
        public static HazardousUsageEvaluationResult HazardousIfAllFlaggedAndAtLeastOneKnown(
            PropertySetAbstractValue propertySetAbstractValue)
        {
            return HazardousIfAllFlagged(propertySetAbstractValue, assumeAllUnknownInsecure: false);
        }
 
        /// <summary>
        /// A <see cref="HazardousUsageEvaluator.InvocationEvaluationCallback"/> for all properties flagged being hazardous,
        /// treating all unknown as maybe flagged.
        /// </summary>
        /// <param name="methodSymbol">Ignored.  If your scenario cares about the method, don't use this.</param>
        /// <param name="propertySetAbstractValue">PropertySetAbstract value.</param>
        /// <returns>If all properties are flagged, then flagged; if all properties are unflagged, then unflagged; otherwise
        /// (including all unknown) maybe flagged.</returns>
        [SuppressMessage("Usage", "CA1801", Justification = "Intentionally ignored; have to match delegate signature")]
        [SuppressMessage("Usage", "IDE0060", Justification = "Intentionally ignored; have to match delegate signature")]
        public static HazardousUsageEvaluationResult HazardousIfAllFlaggedOrAllUnknown(
            IMethodSymbol methodSymbol,
            PropertySetAbstractValue propertySetAbstractValue)
        {
            return HazardousIfAllFlagged(propertySetAbstractValue, assumeAllUnknownInsecure: true);
        }
 
        /// <summary>
        /// A <see cref="HazardousUsageEvaluator.InvocationEvaluationCallback"/> for all properties flagged being hazardous,
        /// treating all unknown as unflagged
        /// </summary>
        /// <param name="methodSymbol">Ignored.  If your scenario cares about the method, don't use this.</param>
        /// <param name="propertySetAbstractValue">PropertySetAbstract value.</param>
        /// <returns>If all properties are flagged, then flagged; if all properties are unflagged, then unflagged; otherwise
        /// (excluding all unknown) maybe flagged.</returns>
        [SuppressMessage("Usage", "CA1801", Justification = "Intentionally ignored; have to match delegate signature")]
        [SuppressMessage("Usage", "IDE0060", Justification = "Intentionally ignored; have to match delegate signature")]
        public static HazardousUsageEvaluationResult HazardousIfAllFlaggedAndAtLeastOneKnown(
            IMethodSymbol methodSymbol,
            PropertySetAbstractValue propertySetAbstractValue)
        {
            return HazardousIfAllFlagged(propertySetAbstractValue, assumeAllUnknownInsecure: false);
        }
 
        /// <summary>
        /// A <see cref="HazardousUsageEvaluator.EvaluationCallback"/> for all properties flagged being hazardous.
        /// </summary>
        /// <param name="propertySetAbstractValue">PropertySetAbstract value.</param>
        /// <returns>If all properties are flagged, then flagged; if at least one property is unflagged, then unflagged; otherwise (including all unknown) maybe flagged.</returns>
        private static HazardousUsageEvaluationResult HazardousIfAllFlagged(
            PropertySetAbstractValue propertySetAbstractValue,
            bool assumeAllUnknownInsecure)
        {
            if (propertySetAbstractValue.KnownValuesCount == 0)
            {
                // No known values implies all properties are PropertySetAbstractValueKind.Unknown.
                return assumeAllUnknownInsecure
                    ? HazardousUsageEvaluationResult.MaybeFlagged
                    : HazardousUsageEvaluationResult.Unflagged;
            }
 
            bool allFlagged = true;
            bool atLeastOneUnflagged = false;
            for (int i = 0; i < propertySetAbstractValue.KnownValuesCount; i++)
            {
                if (propertySetAbstractValue[i] != PropertySetAbstractValueKind.Flagged)
                {
                    allFlagged = false;
                }
 
                if (propertySetAbstractValue[i] == PropertySetAbstractValueKind.Unflagged)
                {
                    atLeastOneUnflagged = true;
                    break;
                }
            }
 
            if (allFlagged)
            {
                return HazardousUsageEvaluationResult.Flagged;
            }
            else if (atLeastOneUnflagged)
            {
                return HazardousUsageEvaluationResult.Unflagged;
            }
            else
            {
                // Mix of flagged and unknown.
                return HazardousUsageEvaluationResult.MaybeFlagged;
            }
        }
    }
}