File: Microsoft.NetCore.Analyzers\InteropServices\PlatformCompatibilityAnalyzer.Value.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.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Analyzer.Utilities.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.GlobalFlowStateAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.NetCore.Analyzers.InteropServices
{
    using ValueContentAnalysisResult = DataFlowAnalysisResult<ValueContentBlockAnalysisResult, ValueContentAbstractValue>;

    public sealed partial class PlatformCompatibilityAnalyzer
    {
        private readonly struct PlatformMethodValue : IAbstractAnalysisValue, IEquatable<PlatformMethodValue>
        {
            internal PlatformMethodValue(string platformPropertyName, Version version, bool negated)
            {
                PlatformName = platformPropertyName ?? throw new ArgumentNullException(nameof(platformPropertyName));
                Version = version ?? throw new ArgumentNullException(nameof(version));
                Negated = negated;
            }

            public string PlatformName { get; }
            public Version Version { get; }
            public bool Negated { get; }

            public IAbstractAnalysisValue GetNegatedValue()
                => new PlatformMethodValue(PlatformName, Version, !Negated);

            public static bool TryDecode(
                IMethodSymbol invokedPlatformCheckMethod,
                ImmutableArray<IArgumentOperation> arguments,
                ValueContentAnalysisResult? valueContentAnalysisResult,
                INamedTypeSymbol? osPlatformType,
                ArrayBuilder<PlatformMethodValue> infosBuilder)
            {
                // Accelerators like OperatingSystem.IsPlatformName()
                if (arguments.IsEmpty)
                {
                    if (TryExtractPlatformName(invokedPlatformCheckMethod.Name, out var platformName))
                    {
                        var info = new PlatformMethodValue(platformName, EmptyVersion, negated: false);
                        infosBuilder.Add(info);
                        return true;
                    }
                }
                else
                {
                    using var osPlatformNamesBuilder = ArrayBuilder<string>.GetInstance();
                    if (TryDecodeRuntimeInformationIsOSPlatform(arguments[0].Value, osPlatformType, valueContentAnalysisResult, osPlatformNamesBuilder))
                    {
                        Debug.Assert(osPlatformNamesBuilder.Count > 0);
                        for (var i = 0; i < osPlatformNamesBuilder.Count; i++)
                        {
                            var info = new PlatformMethodValue(osPlatformNamesBuilder[i], EmptyVersion, negated: false);
                            infosBuilder.Add(info);
                        }

                        return true;
                    }

                    if (arguments.GetArgumentForParameterAtIndex(0).Value is ILiteralOperation literal)
                    {
                        if (literal.Type?.SpecialType == SpecialType.System_String &&
                            literal.ConstantValue.HasValue &&
                            literal.ConstantValue.Value is { } value)
                        {
                            // OperatingSystem.IsOSPlatform(string platform)
                            if (invokedPlatformCheckMethod.Name == IsOSPlatform &&
                                TryParsePlatformNameAndVersion(value.ToString(), out string platformName, out Version? version))
                            {
                                var info = new PlatformMethodValue(platformName, version, negated: false);
                                infosBuilder.Add(info);
                                return true;
                            }
                            else if (TryDecodeOSVersion(arguments, valueContentAnalysisResult, out version, 1))
                            {
                                // OperatingSystem.IsOSPlatformVersionAtLeast(string platform, int major, int minor = 0, int build = 0, int revision = 0)
                                Debug.Assert(invokedPlatformCheckMethod.Name == "IsOSPlatformVersionAtLeast");
                                var info = new PlatformMethodValue(literal.ConstantValue.Value.ToString(), version, negated: false);
                                infosBuilder.Add(info);
                                return true;
                            }
                        }
                        else if (literal.Type?.SpecialType == SpecialType.System_Int32)
                        {
                            // Accelerators like OperatingSystem.IsPlatformNameVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0)
                            if (TryExtractPlatformName(invokedPlatformCheckMethod.Name, out var platformName) &&
                                TryDecodeOSVersion(arguments, valueContentAnalysisResult, out var version))
                            {
                                var info = new PlatformMethodValue(platformName, version, negated: false);
                                infosBuilder.Add(info);
                                return true;
                            }
                        }
                    }
                }

                return false;
            }

            private static bool TryDecodeRuntimeInformationIsOSPlatform(
                IOperation argumentValue,
                INamedTypeSymbol? osPlatformType,
                ValueContentAnalysisResult? valueContentAnalysisResult,
                ArrayBuilder<string> decodedOsPlatformNamesBuilder)
            {
                if (argumentValue.Type?.Equals(osPlatformType) != true)
                {
                    return false;
                }

                if ((argumentValue is IPropertyReferenceOperation propertyReference) &&
                    propertyReference.Property.ContainingType.Equals(osPlatformType))
                {
                    if (propertyReference.Property.Name.Equals(OSX, StringComparison.OrdinalIgnoreCase))
                    {
                        decodedOsPlatformNamesBuilder.Add(macOS);
                    }
                    else
                    {
                        decodedOsPlatformNamesBuilder.Add(propertyReference.Property.Name);
                    }

                    return true;
                }

                if (valueContentAnalysisResult != null)
                {
                    var valueContentValue = valueContentAnalysisResult[argumentValue];
                    if (valueContentValue.IsLiteralState)
                    {
                        decodedOsPlatformNamesBuilder.AddRange(valueContentValue.LiteralValues.OfType<string>());
                        return decodedOsPlatformNamesBuilder.Count > 0;
                    }
                }

                return false;
            }

            public override string ToString()
            {
                var result = $"{PlatformName};{Version}";
                if (Negated)
                {
                    result = $"!{result}";
                }

                return result;
            }

            public bool Equals(PlatformMethodValue other)
                => PlatformName.Equals(other.PlatformName, StringComparison.OrdinalIgnoreCase) &&
                   Version.Equals(other.Version) &&
                   Negated == other.Negated;

            public override bool Equals(object obj)
                => obj is PlatformMethodValue otherInfo && Equals(otherInfo);

            public override int GetHashCode()
            {
                return RoslynHashCode.Combine(
                    PlatformName.GetHashCode(),
                    Version.GetHashCode(),
                    Negated.GetHashCode());
            }

            bool IEquatable<IAbstractAnalysisValue>.Equals(IAbstractAnalysisValue other)
                => other is PlatformMethodValue otherInfo && Equals(otherInfo);

            public static bool operator ==(PlatformMethodValue left, PlatformMethodValue right)
            {
                return left.Equals(right);
            }

            public static bool operator !=(PlatformMethodValue left, PlatformMethodValue right)
            {
                return !(left == right);
            }
        }

        private static bool TryDecodeOSVersion(
                ImmutableArray<IArgumentOperation> arguments,
                ValueContentAnalysisResult? valueContentAnalysisResult,
                [NotNullWhen(returnValue: true)] out Version? osVersion,
                int skip = 0)
        {

            using var versionBuilder = ArrayBuilder<int>.GetInstance(4, fillWithValue: 0);
            var index = 0;

            foreach (var argument in arguments.GetArgumentsInParameterOrder().Skip(skip))
            {
                if (!TryDecodeOSVersionPart(argument, valueContentAnalysisResult, out var osVersionPart))
                {
                    osVersion = null;
                    return false;
                }

                versionBuilder[index++] = osVersionPart;
            }

            osVersion = CreateVersion(versionBuilder);
            return true;

            static bool TryDecodeOSVersionPart(IArgumentOperation argument, ValueContentAnalysisResult? valueContentAnalysisResult, out int osVersionPart)
            {
                if (argument.Value.ConstantValue.HasValue &&
                    argument.Value.ConstantValue.Value is int versionPart)
                {
                    osVersionPart = versionPart;
                    return true;
                }

                if (valueContentAnalysisResult != null)
                {
                    var valueContentValue = valueContentAnalysisResult[argument.Value];
                    if (valueContentValue.IsLiteralState &&
                        valueContentValue.LiteralValues.Count == 1 &&
                        valueContentValue.LiteralValues.Single() is int part)
                    {
                        osVersionPart = part;
                        return true;
                    }
                }

                osVersionPart = default;
                return false;
            }

            static Version CreateVersion(ArrayBuilder<int> versionBuilder) => versionBuilder switch
            {
                [int major, int minor, 0, 0] => new Version(major, minor),
                [int major, int minor, int build, 0] => new Version(major, minor, build),
                [int major, int minor, int build, int revision] => new Version(major, minor, build, revision),
                _ => EmptyVersion
            };
        }
    }
}