diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Shipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Shipped.md index e46ea924ed..257f87974e 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Shipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Shipped.md @@ -63,7 +63,7 @@ CA1308 | Globalization | Disabled | NormalizeStringsToUppercaseAnalyzer, [Docume CA1309 | Globalization | Hidden | UseOrdinalStringComparisonAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1309) CA1401 | Interoperability | Info | PInvokeDiagnosticAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1401) CA1414 | Interoperability | Disabled | MarkBooleanPInvokeArgumentsWithMarshalAsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1414) -CA1416 | Interoperability | Info | RuntimePlatformCheckAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1416) +CA1416 | Interoperability | Warning | PlatformCompatabilityAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1416) CA1417 | Interoperability | Warning | DoNotUseOutAttributeStringPInvokeParametersAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1417) CA1500 | Maintainability | Disabled | VariableNamesShouldNotMatchFieldNamesAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1500) CA1501 | Maintainability | Disabled | CodeMetricsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1501) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.Data.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.Data.cs new file mode 100644 index 0000000000..b7015d064d --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.Data.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.NetCore.Analyzers.InteropServices +{ + public sealed partial class PlatformCompatabilityAnalyzer + { + /// + /// Class used for keeping platform information of an API, all properties are optional. + /// + /// We need to keep only 2 values for [SupportedOSPlatform] attribute, first one will be the lowest version found, mostly for assembly level + /// attribute which denotes when the API first introduced, second one would keep new APIs added later and requries higher platform version + /// (if there is multiple version found in the API parents chain we will keep only highest version) + /// + /// Same for [UnsupportedOSPlatform] attribute, an API could be unsupported at first and then start supported from some version then eventually removed. + /// So we only keep at most 2 versions of [UnsupportedOSPlatform] first one will be the lowest version found, second one will be second lowest if there is any + /// + /// Properties: + /// - SupportedFirst - keeps lowest version of [SupportedOSPlatform] attribute found + /// - SupportedSecond - keeps the highest version of [SupportedOSPlatform] attribute if there is any + /// - UnsupportedFirst - keeps the lowest version of [UnsupportedOSPlatform] attribute found + /// - UnsupportedSecond - keeps the second lowest version of [UnsupportedOSPlatform] attribute found + /// + private class PlatformAttributes + { + public Version? SupportedFirst { get; set; } + public Version? SupportedSecond { get; set; } + public Version? UnsupportedFirst { get; set; } + public Version? UnsupportedSecond { get; set; } + public bool HasAttribute() => SupportedFirst != null || UnsupportedFirst != null || + SupportedSecond != null || UnsupportedSecond != null; + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzer.OperationVisitor.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.OperationVisitor.cs similarity index 56% rename from src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzer.OperationVisitor.cs rename to src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.OperationVisitor.cs index 08b1b0b6dd..f757236e0c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzer.OperationVisitor.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.OperationVisitor.cs @@ -7,7 +7,7 @@ namespace Microsoft.NetCore.Analyzers.InteropServices { - public sealed partial class RuntimePlatformCheckAnalyzer + public sealed partial class PlatformCompatabilityAnalyzer { private sealed class OperationVisitor : GlobalFlowStateDataFlowOperationVisitor { @@ -34,16 +34,39 @@ public override GlobalFlowStateAnalysisValueSet VisitInvocation_NonLambdaOrDeleg { var value = base.VisitInvocation_NonLambdaOrDelegateOrLocalFunction(method, visitedInstance, visitedArguments, invokedAsDelegate, originalOperation, defaultValue); - if (_platformCheckMethods.Contains(method.OriginalDefinition) && - !visitedArguments.IsEmpty) + if (_platformCheckMethods.Contains(method.OriginalDefinition)) { - return RuntimeOSPlatformInfo.TryDecode(method, visitedArguments, DataFlowAnalysisContext.ValueContentAnalysisResult, _osPlatformType, out var platformInfo) ? + return PlatformMethodValue.TryDecode(method, visitedArguments, DataFlowAnalysisContext.ValueContentAnalysisResult, _osPlatformType, out var platformInfo) ? new GlobalFlowStateAnalysisValueSet(platformInfo) : GlobalFlowStateAnalysisValueSet.Unknown; } return GetValueOrDefault(value); } + + public override GlobalFlowStateAnalysisValueSet VisitPropertyReference(IPropertyReferenceOperation operation, object? argument) + { + return GetValueOrDefault(base.VisitPropertyReference(operation, argument)); + } + + public override GlobalFlowStateAnalysisValueSet VisitFieldReference(IFieldReferenceOperation operation, object? argument) + { + return GetValueOrDefault(base.VisitFieldReference(operation, argument)); + } + + public override GlobalFlowStateAnalysisValueSet VisitObjectCreation(IObjectCreationOperation operation, object? argument) + { + return GetValueOrDefault(base.VisitObjectCreation(operation, argument)); + } + + public override GlobalFlowStateAnalysisValueSet VisitEventReference(IEventReferenceOperation operation, object? argument) + { + return GetValueOrDefault(base.VisitEventReference(operation, argument)); + } + public override GlobalFlowStateAnalysisValueSet VisitMethodReference(IMethodReferenceOperation operation, object? argument) + { + return GetValueOrDefault(base.VisitMethodReference(operation, argument)); + } } } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.Value.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.Value.cs new file mode 100644 index 0000000000..9fd08d8390 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.Value.cs @@ -0,0 +1,242 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +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; + + public sealed partial class PlatformCompatabilityAnalyzer + { + private readonly struct PlatformMethodValue : IAbstractAnalysisValue, IEquatable + { + private PlatformMethodValue(string invokedPlatformCheckMethodName, string platformPropertyName, Version version, bool negated) + { + InvokedMethodName = invokedPlatformCheckMethodName ?? throw new ArgumentNullException(nameof(invokedPlatformCheckMethodName)); + PlatformName = platformPropertyName ?? throw new ArgumentNullException(nameof(platformPropertyName)); + Version = version ?? throw new ArgumentNullException(nameof(version)); + Negated = negated; + } + + public string InvokedMethodName { get; } + public string PlatformName { get; } + public Version Version { get; } + public bool Negated { get; } + + public IAbstractAnalysisValue GetNegatedValue() + => new PlatformMethodValue(InvokedMethodName, PlatformName, Version, !Negated); + + public static bool TryDecode( + IMethodSymbol invokedPlatformCheckMethod, + ImmutableArray arguments, + ValueContentAnalysisResult? valueContentAnalysisResult, + INamedTypeSymbol osPlatformType, + [NotNullWhen(returnValue: true)] out PlatformMethodValue? info) + { + // Accelerators like OperatingSystem.IsPlatformName() + if (arguments.IsEmpty) + { + if (TryExtractPlatformName(invokedPlatformCheckMethod.Name, out var platformName)) + { + info = new PlatformMethodValue(invokedPlatformCheckMethod.Name, platformName, new Version(0, 0), negated: false); + return true; + } + } + else + { + if (TryDecodeRuntimeInformationIsOSPlatform(arguments[0].Value, osPlatformType, out string? osPlatformName)) + { + info = new PlatformMethodValue(invokedPlatformCheckMethod.Name, osPlatformName, new Version(0, 0), negated: false); + return true; + } + + if (arguments.GetArgumentForParameterAtIndex(0).Value is ILiteralOperation literal) + { + if (literal.Type?.SpecialType == SpecialType.System_String && + literal.ConstantValue.HasValue) + { + // OperatingSystem.IsOSPlatform(string platform) + if (invokedPlatformCheckMethod.Name == IsOSPlatform && + TryParsePlatformNameAndVersion(literal.ConstantValue.Value.ToString(), out string platformName, out Version? version)) + { + info = new PlatformMethodValue(invokedPlatformCheckMethod.Name, platformName, version, negated: false); + 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"); + info = new PlatformMethodValue(invokedPlatformCheckMethod.Name, literal.ConstantValue.Value.ToString(), version, negated: false); + 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)) + { + info = new PlatformMethodValue(invokedPlatformCheckMethod.Name, platformName, version, negated: false); + return true; + } + } + } + } + + info = default; + return false; + } + + private static bool TryExtractPlatformName(string methodName, [NotNullWhen(true)] out string? platformName) + { + if (!methodName.StartsWith(IsPrefix, StringComparison.Ordinal)) + { + platformName = null; + return false; + } + + if (methodName.EndsWith(OptionalSuffix, StringComparison.Ordinal)) + { + platformName = methodName.Substring(2, methodName.Length - 2 - OptionalSuffix.Length); + return true; + } + + platformName = methodName.Substring(2); + return true; + } + + private static bool TryDecodeRuntimeInformationIsOSPlatform( + IOperation argumentValue, + INamedTypeSymbol osPlatformType, + [NotNullWhen(returnValue: true)] out string? osPlatformName) + { + if ((argumentValue is IPropertyReferenceOperation propertyReference) && + propertyReference.Property.ContainingType.Equals(osPlatformType)) + { + osPlatformName = propertyReference.Property.Name; + return true; + } + + osPlatformName = null; + return false; + } + + private static bool TryDecodeOSVersion( + ImmutableArray arguments, + ValueContentAnalysisResult? valueContentAnalysisResult, + [NotNullWhen(returnValue: true)] out Version? osVersion, + int skip = 0) + { + + using var versionBuilder = ArrayBuilder.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 versionBuilder) + { + if (versionBuilder[3] == 0) + { + if (versionBuilder[2] == 0) + { + return new Version(versionBuilder[0], versionBuilder[1]); + } + else + { + return new Version(versionBuilder[0], versionBuilder[1], versionBuilder[2]); + } + } + else + { + return new Version(versionBuilder[0], versionBuilder[1], versionBuilder[2], versionBuilder[3]); + } + } + } + + public override string ToString() + { + var result = $"{InvokedMethodName};{PlatformName};{Version}"; + if (Negated) + { + result = $"!{result}"; + } + + return result; + } + + public bool Equals(PlatformMethodValue other) + => InvokedMethodName.Equals(other.InvokedMethodName, StringComparison.OrdinalIgnoreCase) && + 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() + => HashUtilities.Combine(InvokedMethodName.GetHashCode(), PlatformName.GetHashCode(), Version.GetHashCode(), Negated.GetHashCode()); + + bool IEquatable.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); + } + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.cs new file mode 100644 index 0000000000..d05b349d70 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzer.cs @@ -0,0 +1,1020 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Analyzer.Utilities.PooledObjects; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow; +using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.GlobalFlowStateAnalysis; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.InteropServices +{ + /// + /// CA1416: Analyzer that informs developers when they use platform-specific APIs from call sites where the API might not be available + /// + /// It finds usage of platform-specific or unsupported APIs and diagnoses if the + /// API is guarded by platform check or if it is annotated with corresponding platform specific attribute. + /// If using the platform-specific API is not safe it reports diagnostics. + /// + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed partial class PlatformCompatabilityAnalyzer : DiagnosticAnalyzer + { + internal const string RuleId = "CA1416"; + private static readonly ImmutableArray s_osPlatformAttributes = ImmutableArray.Create(SupportedOSPlatformAttribute, UnsupportedOSPlatformAttribute); + + private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.PlatformCompatabilityCheckTitle), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); + private static readonly LocalizableString s_localizableSupportedOsMessage = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.PlatformCompatibilityCheckSupportedOsMessage), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); + private static readonly LocalizableString s_localizableSupportedOsVersionMessage = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.PlatformCompatibilityCheckSupportedOsVersionMessage), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); + private static readonly LocalizableString s_localizableUnsupportedOsMessage = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.PlatformCompatabilityCheckUnsupportedOsMessage), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); + private static readonly LocalizableString s_localizableUnsupportedOsVersionMessage = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.PlatformCompatabilityCheckUnsupportedOsVersionMessage), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); + private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.PlatformCompatabilityCheckDescription), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); + + // We are adding the new attributes into older versions of .Net 5.0, so there could be multiple referenced assemblies each with their own + // version of internal attribute type which will cause ambiguity, to avoid that we are comparing the attributes by their name + private const string SupportedOSPlatformAttribute = nameof(SupportedOSPlatformAttribute); + private const string UnsupportedOSPlatformAttribute = nameof(UnsupportedOSPlatformAttribute); + + // Platform guard method name, prefix, suffix + private const string IsOSPlatform = nameof(IsOSPlatform); + private const string IsPrefix = "Is"; + private const string OptionalSuffix = "VersionAtLeast"; + + internal static DiagnosticDescriptor SupportedOsVersionRule = DiagnosticDescriptorHelper.Create(RuleId, + s_localizableTitle, + s_localizableSupportedOsVersionMessage, + DiagnosticCategory.Interoperability, + RuleLevel.BuildWarning, + description: s_localizableDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static DiagnosticDescriptor SupportedOsRule = DiagnosticDescriptorHelper.Create(RuleId, + s_localizableTitle, + s_localizableSupportedOsMessage, + DiagnosticCategory.Interoperability, + RuleLevel.BuildWarning, + description: s_localizableDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static DiagnosticDescriptor UnsupportedOsRule = DiagnosticDescriptorHelper.Create(RuleId, + s_localizableTitle, + s_localizableUnsupportedOsMessage, + DiagnosticCategory.Interoperability, + RuleLevel.BuildWarning, + description: s_localizableDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + internal static DiagnosticDescriptor UnsupportedOsVersionRule = DiagnosticDescriptorHelper.Create(RuleId, + s_localizableTitle, + s_localizableUnsupportedOsVersionMessage, + DiagnosticCategory.Interoperability, + RuleLevel.BuildWarning, + description: s_localizableDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( + SupportedOsRule, SupportedOsVersionRule, UnsupportedOsRule, UnsupportedOsVersionRule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + + context.RegisterCompilationStartAction(context => + { + var typeName = WellKnownTypeNames.SystemOperatingSystem; + + if (!context.Compilation.TryGetOrCreateTypeByMetadataName(typeName + "Helper", out var operatingSystemType)) + { + // TODO: remove 'typeName + "Helper"' after tests able to consume the real new APIs + operatingSystemType = context.Compilation.GetOrCreateTypeByMetadataName(typeName); + } + if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeInteropServicesOSPlatform, out var osPlatformType) || + !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeInteropServicesRuntimeInformation, out var runtimeInformationType)) + { + return; + } + + var msBuildPlatforms = GetSupportedPlatforms(context.Options, context.Compilation, context.CancellationToken); + var runtimeIsOSPlatformMethod = runtimeInformationType.GetMembers().OfType().Where(m => + IsOSPlatform == m.Name && + m.IsStatic && + m.ReturnType.SpecialType == SpecialType.System_Boolean && + m.Parameters.Length == 1 && + m.Parameters[0].Type.Equals(osPlatformType)).FirstOrDefault(); + + var guardMethods = GetOperatingSystemGuardMethods(runtimeIsOSPlatformMethod, operatingSystemType!); + var platformSpecificMembers = new ConcurrentDictionary?>(); + + context.RegisterOperationBlockStartAction(context => AnalyzeOperationBlock(context, guardMethods, osPlatformType, platformSpecificMembers, msBuildPlatforms)); + }); + + static ImmutableArray GetOperatingSystemGuardMethods(IMethodSymbol? runtimeIsOSPlatformMethod, INamedTypeSymbol operatingSystemType) + { + var methods = operatingSystemType.GetMembers().OfType().Where(m => + m.IsStatic && + m.ReturnType.SpecialType == SpecialType.System_Boolean && + (IsOSPlatform == m.Name) || NameAndParametersValid(m)). + ToImmutableArray(); + + if (runtimeIsOSPlatformMethod != null) + { + return methods.Add(runtimeIsOSPlatformMethod); + } + return methods; + } + + static ImmutableArray GetSupportedPlatforms(AnalyzerOptions options, Compilation compilation, CancellationToken cancellationToken) => + options.GetMSBuildItemMetadataValues(MSBuildItemOptionNames.SupportedPlatform, compilation, cancellationToken); + + static bool NameAndParametersValid(IMethodSymbol method) => method.Name.StartsWith(IsPrefix, StringComparison.Ordinal) && + (method.Parameters.Length == 0 || method.Name.EndsWith(OptionalSuffix, StringComparison.Ordinal)); + } + + private void AnalyzeOperationBlock( + OperationBlockStartAnalysisContext context, + ImmutableArray guardMethods, + INamedTypeSymbol osPlatformType, + ConcurrentDictionary?> platformSpecificMembers, + ImmutableArray msBuildPlatforms) + { + var platformSpecificOperations = PooledConcurrentDictionary>.GetInstance(); + + context.RegisterOperationAction(context => + { + AnalyzeOperation(context.Operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms); + }, + OperationKind.MethodReference, + OperationKind.EventReference, + OperationKind.FieldReference, + OperationKind.Invocation, + OperationKind.ObjectCreation, + OperationKind.PropertyReference); + + context.RegisterOperationBlockEndAction(context => + { + try + { + if (platformSpecificOperations.IsEmpty) + { + return; + } + + if (guardMethods.IsEmpty || !(context.OperationBlocks.GetControlFlowGraph(out var topmostBlock) is { } cfg)) + { + ReportDiagnosticsForAll(platformSpecificOperations, context); + return; + } + + var performValueContentAnalysis = ComputeNeedsValueContentAnalysis(topmostBlock!, guardMethods); + var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); + var analysisResult = GlobalFlowStateAnalysis.TryGetOrComputeResult( + cfg, context.OwningSymbol, CreateOperationVisitor, wellKnownTypeProvider, + context.Options, SupportedOsRule, performValueContentAnalysis, + context.CancellationToken, out var valueContentAnalysisResult); + + if (analysisResult == null) + { + return; + } + + foreach (var (platformSpecificOperation, attributes) in platformSpecificOperations) + { + var value = analysisResult[platformSpecificOperation.Kind, platformSpecificOperation.Syntax]; + + if ((value.Kind == GlobalFlowStateAnalysisValueSetKind.Known && IsKnownValueGuarded(attributes, value)) || + (value.Kind == GlobalFlowStateAnalysisValueSetKind.Unknown && HasInterproceduralResult(platformSpecificOperation, attributes, analysisResult))) + { + continue; + } + + ReportDiagnostics(platformSpecificOperation, attributes, context); + } + } + finally + { + // Workaround for https://github.com/dotnet/roslyn/issues/46859 + // Do not free in presence of cancellation. + if (!context.CancellationToken.IsCancellationRequested) + { + platformSpecificOperations.Free(); + } + } + + return; + + OperationVisitor CreateOperationVisitor(GlobalFlowStateAnalysisContext context) => new OperationVisitor(guardMethods, osPlatformType, context); + }); + } + + private static bool HasInterproceduralResult(IOperation platformSpecificOperation, SmallDictionary attributes, + DataFlowAnalysisResult analysisResult) + { + if (platformSpecificOperation.IsWithinLambdaOrLocalFunction()) + { + var results = analysisResult.TryGetInterproceduralResults(); + if (results != null) + { + foreach (var localResult in results) + { + var localValue = localResult[platformSpecificOperation.Kind, platformSpecificOperation.Syntax]; + if (localValue.Kind == GlobalFlowStateAnalysisValueSetKind.Known && IsKnownValueGuarded(attributes, localValue)) + { + return true; + } + } + } + } + + return false; + } + + private static bool ComputeNeedsValueContentAnalysis(IBlockOperation operationBlock, ImmutableArray guardMethods) + { + foreach (var operation in operationBlock.Descendants()) + { + if (operation is IInvocationOperation invocation && + guardMethods.Contains(invocation.TargetMethod)) + { + // Check if any integral parameter to guard method invocation has non-constant value. + foreach (var argument in invocation.Arguments) + { + if (argument.Parameter.Type.SpecialType == SpecialType.System_Int32 && + !argument.Value.ConstantValue.HasValue) + { + return true; + } + } + } + } + + return false; + } + + private static bool IsKnownValueGuarded(SmallDictionary attributes, GlobalFlowStateAnalysisValueSet value) + { + using var capturedVersions = PooledDictionary.GetInstance(StringComparer.OrdinalIgnoreCase); + return IsKnownValueGuarded(attributes, value, capturedVersions); + + static bool IsKnownValueGuarded( + SmallDictionary attributes, + GlobalFlowStateAnalysisValueSet value, + PooledDictionary capturedVersions) + { + // 'GlobalFlowStateAnalysisValueSet.AnalysisValues' represent the && of values. + foreach (var analysisValue in value.AnalysisValues) + { + if (analysisValue is PlatformMethodValue info) + { + if (attributes.TryGetValue(info.PlatformName, out var attribute)) + { + if (info.Negated) + { + if (attribute.UnsupportedFirst != null) + { + if (attribute.UnsupportedFirst >= info.Version) + { + if (DenyList(attribute)) + { + attribute.SupportedFirst = null; + attribute.SupportedSecond = null; + attribute.UnsupportedSecond = null; + } + attribute.UnsupportedFirst = null; + } + } + + if (attribute.UnsupportedSecond != null) + { + if (attribute.UnsupportedSecond <= info.Version) + { + attribute.UnsupportedSecond = null; + } + } + + if (!IsEmptyVersion(info.Version)) + { + capturedVersions[info.PlatformName] = info.Version; + } + } + else + { + if (capturedVersions.Any()) + { + if (attribute.UnsupportedFirst != null && + capturedVersions.TryGetValue(info.PlatformName, out var version) && + attribute.UnsupportedFirst >= version) + { + attribute.UnsupportedFirst = null; + } + + if (attribute.UnsupportedSecond != null && + capturedVersions.TryGetValue(info.PlatformName, out version) && + attribute.UnsupportedSecond <= version) + { + attribute.UnsupportedSecond = null; + } + } + + if (attribute.SupportedFirst != null && + attribute.SupportedFirst <= info.Version) + { + attribute.SupportedFirst = null; + RemoveUnsupportedWithLessVersion(info.Version, attribute); + RemoveOtherSupportsOnDifferentPlatforms(attributes, info.PlatformName); + } + + if (attribute.SupportedSecond != null && + attribute.SupportedSecond <= info.Version) + { + attribute.SupportedSecond = null; + RemoveUnsupportedWithLessVersion(info.Version, attribute); + RemoveOtherSupportsOnDifferentPlatforms(attributes, info.PlatformName); + } + + RemoveUnsupportsOnDifferentPlatforms(attributes, info.PlatformName); + } + } + else + { + if (!info.Negated) + { + // it is checking one exact platform, other unsupported should be suppressed + RemoveUnsupportsOnDifferentPlatforms(attributes, info.PlatformName); + } + } + } + } + + if (value.Parents.IsEmpty) + { + foreach (var attribute in attributes) + { + // if any of the attributes is not suppressed + if (attribute.Value.HasAttribute()) + { + return false; + } + } + } + else + { + // 'GlobalFlowStateAnalysisValueSet.Parents' represent || of values on different flow paths. + // We are guarded only if values are guarded on *all flow paths**. + foreach (var parent in value.Parents) + { + // NOTE: IsKnownValueGuarded mutates the input values, so we pass in cloned values + // to ensure that evaluation of each part of || is independent of evaluation of other parts. + var parentAttributes = CopyAttributes(attributes); + using var parentCapturedVersions = PooledDictionary.GetInstance(capturedVersions); + + if (!IsKnownValueGuarded(parentAttributes, parent, parentCapturedVersions)) + { + return false; + } + } + } + + return true; + } + + static void RemoveUnsupportsOnDifferentPlatforms(SmallDictionary attributes, string platformName) + { + foreach (var (name, attribute) in attributes) + { + if (!name.Equals(platformName, StringComparison.OrdinalIgnoreCase) && + DenyList(attribute)) + { + attribute.UnsupportedFirst = null; + attribute.UnsupportedSecond = null; + attribute.SupportedFirst = null; + attribute.SupportedSecond = null; + } + } + } + + static void RemoveUnsupportedWithLessVersion(Version supportedVersion, PlatformAttributes attribute) + { + if (attribute.UnsupportedFirst != null && + attribute.UnsupportedFirst <= supportedVersion) + { + attribute.UnsupportedFirst = null; + } + } + + static void RemoveOtherSupportsOnDifferentPlatforms(SmallDictionary attributes, string platformName) + { + foreach (var (name, attribute) in attributes) + { + if (!name.Equals(platformName, StringComparison.OrdinalIgnoreCase)) + { + attribute.SupportedFirst = null; + attribute.SupportedSecond = null; + } + } + } + } + + private static bool IsEmptyVersion(Version version) => version.Major == 0 && version.Minor == 0; + + private static void ReportDiagnosticsForAll(PooledConcurrentDictionary> platformSpecificOperations, OperationBlockAnalysisContext context) + { + foreach (var platformSpecificOperation in platformSpecificOperations) + { + ReportDiagnostics(platformSpecificOperation.Key, platformSpecificOperation.Value, context); + } + } + + private static void ReportDiagnostics(IOperation operation, SmallDictionary attributes, OperationBlockAnalysisContext context) + { + var symbol = operation is IObjectCreationOperation creation ? creation.Constructor.ContainingType : GetOperationSymbol(operation); + + if (symbol == null) + { + return; + } + + var operationName = symbol.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat); + + foreach (var platformName in attributes.Keys) + { + var attribute = attributes[platformName]; + + if (attribute.SupportedSecond != null) + { + ReportSupportedDiagnostic(operation, context, operationName, platformName, VersionToString(attribute.SupportedSecond)); + } + else if (attribute.SupportedFirst != null) + { + ReportSupportedDiagnostic(operation, context, operationName, platformName, VersionToString(attribute.SupportedFirst)); + } + + if (attribute.UnsupportedFirst != null) + { + ReportUnsupportedDiagnostic(operation, context, operationName, platformName, VersionToString(attribute.UnsupportedFirst)); + } + else if (attribute.UnsupportedSecond != null) + { + ReportUnsupportedDiagnostic(operation, context, operationName, platformName, VersionToString(attribute.UnsupportedSecond)); + } + } + + static void ReportSupportedDiagnostic(IOperation operation, OperationBlockAnalysisContext context, string name, string platformName, string? version = null) => + context.ReportDiagnostic(version == null ? operation.CreateDiagnostic(SupportedOsRule, name, platformName) : + operation.CreateDiagnostic(SupportedOsVersionRule, name, platformName, version)); + + static void ReportUnsupportedDiagnostic(IOperation operation, OperationBlockAnalysisContext context, string name, string platformName, string? version = null) => + context.ReportDiagnostic(version == null ? operation.CreateDiagnostic(UnsupportedOsRule, name, platformName) : + operation.CreateDiagnostic(UnsupportedOsVersionRule, name, platformName, version)); + } + + private static string? VersionToString(Version version) => IsEmptyVersion(version) ? null : version.ToString(); + + private static ISymbol? GetOperationSymbol(IOperation operation) + => operation switch + { + IInvocationOperation iOperation => iOperation.TargetMethod, + IObjectCreationOperation cOperation => cOperation.Constructor, + IFieldReferenceOperation fOperation => IsWithinConditionalOperation(fOperation) ? null : fOperation.Field, + IMemberReferenceOperation mOperation => mOperation.Member, + _ => null, + }; + + private static void AnalyzeOperation(IOperation operation, OperationAnalysisContext context, + PooledConcurrentDictionary> platformSpecificOperations, + ConcurrentDictionary?> platformSpecificMembers, ImmutableArray msBuildPlatforms) + { + var symbol = GetOperationSymbol(operation); + + if (symbol == null) + { + return; + } + + if (TryGetOrCreatePlatformAttributes(symbol, platformSpecificMembers, out var operationAttributes)) + { + if (TryGetOrCreatePlatformAttributes(context.ContainingSymbol, platformSpecificMembers, out var callSiteAttributes)) + { + if (IsNotSuppressedByCallSite(operationAttributes, callSiteAttributes, msBuildPlatforms, out var notSuppressedAttributes)) + { + platformSpecificOperations.TryAdd(operation, notSuppressedAttributes); + } + } + else + { + if (TryCopyAttributesNotSuppressedByMsBuild(operationAttributes, msBuildPlatforms, out var copiedAttributes)) + { + platformSpecificOperations.TryAdd(operation, copiedAttributes); + } + } + } + } + + private static bool TryCopyAttributesNotSuppressedByMsBuild(SmallDictionary operationAttributes, + ImmutableArray msBuildPlatforms, out SmallDictionary copiedAttributes) + { + copiedAttributes = new SmallDictionary(StringComparer.OrdinalIgnoreCase); + foreach (var (platformName, attributes) in operationAttributes) + { + if (AllowList(attributes) || msBuildPlatforms.IndexOf(platformName, 0, StringComparer.OrdinalIgnoreCase) != -1) + { + copiedAttributes.Add(platformName, CopyAllAttributes(new PlatformAttributes(), attributes)); + } + } + + return copiedAttributes.Any(); + } + + private static SmallDictionary CopyAttributes(SmallDictionary copyAttributes) + { + var copy = new SmallDictionary(StringComparer.OrdinalIgnoreCase); + foreach (var (platformName, attributes) in copyAttributes) + { + copy.Add(platformName, CopyAllAttributes(new PlatformAttributes(), attributes)); + } + + return copy; + } + + /// + /// The semantics of the platform specific attributes are : + /// - An API that doesn't have any of these attributes is considered supported by all platforms. + /// - If either [SupportedOSPlatform] or [UnsupportedOSPlatform] attributes are present, we group all attributes by OS platform identifier: + /// - Allow list.If the lowest version for each OS platform is a [SupportedOSPlatform] attribute, the API is considered to only be supported by the listed platforms and unsupported by all other platforms. + /// - Deny list. If the lowest version for each OS platform is a [UnsupportedOSPlatform] attribute, then the API is considered to only be unsupported by the listed platforms and supported by all other platforms. + /// - Inconsistent list. If for some platforms the lowest version attribute is [SupportedOSPlatform] while for others it is [UnsupportedOSPlatform], the analyzer will produce a warning on the API definition because the API is attributed inconsistently. + /// - Both attributes can be instantiated without version numbers. This means the version number is assumed to be 0.0. This simplifies guard clauses, see examples below for more details. + /// + /// Platform specific attributes applied to the invoked member + /// Platform specific attributes applied to the call site where the member invoked + /// true if all attributes applied to the operation is suppressed, false otherwise + + private static bool IsNotSuppressedByCallSite(SmallDictionary operationAttributes, + SmallDictionary callSiteAttributes, ImmutableArray msBuildPlatforms, + out SmallDictionary notSuppressedAttributes) + { + notSuppressedAttributes = new SmallDictionary(StringComparer.OrdinalIgnoreCase); + bool? supportedOnlyList = null; + bool mandatoryMatchFound = false; + using var supportedOnlyPlatforms = PooledHashSet.GetInstance(StringComparer.OrdinalIgnoreCase); + foreach (var (platformName, attribute) in operationAttributes) + { + var diagnosticAttribute = new PlatformAttributes(); + + if (attribute.SupportedFirst != null) + { + if (attribute.UnsupportedFirst == null || attribute.UnsupportedFirst > attribute.SupportedFirst) + { + // If only supported for current platform + if (supportedOnlyList.HasValue && !supportedOnlyList.Value) + { + return true; // invalid state, do not need to add this API to the list + } + + supportedOnlyPlatforms.Add(platformName); + supportedOnlyList = true; + + if (callSiteAttributes.TryGetValue(platformName, out var callSiteAttribute)) + { + var attributeToCheck = attribute.SupportedSecond ?? attribute.SupportedFirst; + if (MandatoryOsVersionsSuppressed(callSiteAttribute, attributeToCheck)) + { + mandatoryMatchFound = true; + } + else + { + diagnosticAttribute.SupportedSecond = (Version)attributeToCheck.Clone(); + } + + if (attribute.UnsupportedFirst != null && + !(SuppressedByCallSiteSupported(attribute, callSiteAttribute.SupportedFirst) || + SuppressedByCallSiteUnsupported(callSiteAttribute, attribute.UnsupportedFirst))) + { + diagnosticAttribute.UnsupportedFirst = (Version)attribute.UnsupportedFirst.Clone(); + } + } + } + else if (attribute.UnsupportedFirst != null) // also means Unsupported <= Supported, optional list + { + if (supportedOnlyList.HasValue && supportedOnlyList.Value) + { + return true; // do not need to add this API to the list + } + + supportedOnlyList = false; + + if (callSiteAttributes.TryGetValue(platformName, out var callSiteAttribute)) + { + if (callSiteAttribute.SupportedFirst != null) + { + if (!OptionalOsSupportSuppressed(callSiteAttribute, attribute)) + { + diagnosticAttribute.SupportedFirst = (Version)attribute.SupportedFirst.Clone(); + } + + if (!UnsupportedFirstSuppressed(attribute, callSiteAttribute)) + { + diagnosticAttribute.UnsupportedFirst = (Version)attribute.UnsupportedFirst.Clone(); + } + + if (attribute.UnsupportedSecond != null && + !UnsupportedSecondSuppressed(attribute, callSiteAttribute)) + { + diagnosticAttribute.UnsupportedSecond = (Version)attribute.UnsupportedSecond.Clone(); + } + } + } + else + { + // Call site has no attributes for this platform, check if MsBuild list has it, + // then if call site has deny list, it should support its later support + if (msBuildPlatforms.Contains(platformName) && + callSiteAttributes.Any(ca => DenyList(ca.Value))) + { + diagnosticAttribute.SupportedFirst = (Version)attribute.SupportedFirst.Clone(); + } + } + } + } + else + { + if (supportedOnlyList.HasValue && supportedOnlyList.Value) + { + return true; // do not need to add this API to the list + } + + supportedOnlyList = false; + + if (attribute.UnsupportedFirst != null) // Unsupported for this but supported all other + { + if (callSiteAttributes.TryGetValue(platformName, out var callSiteAttribute)) + { + if (callSiteAttribute.SupportedFirst != null) + { + if (!SuppressedByCallSiteUnsupported(callSiteAttribute, attribute.UnsupportedFirst)) + { + diagnosticAttribute.UnsupportedFirst = (Version)attribute.UnsupportedFirst.Clone(); + } + + if (attribute.UnsupportedSecond != null && + !SuppressedByCallSiteUnsupported(callSiteAttribute, attribute.UnsupportedSecond)) + { + diagnosticAttribute.UnsupportedSecond = (Version)attribute.UnsupportedSecond.Clone(); + } + } + } + else if (msBuildPlatforms.Contains(platformName) && + !callSiteAttributes.Values.Any(v => v.SupportedFirst != null)) + { + // if MsBuild list contain the platform and call site has no any other supported attribute it means global, so need to warn + diagnosticAttribute.UnsupportedFirst = (Version)attribute.UnsupportedFirst.Clone(); + } + } + } + + if (diagnosticAttribute.HasAttribute()) + { + notSuppressedAttributes[platformName] = diagnosticAttribute; + } + } + + if (supportedOnlyList.HasValue && supportedOnlyList.Value) + { + if (!mandatoryMatchFound) + { + foreach (var (name, attributes) in operationAttributes) + { + if (attributes.SupportedFirst != null) + { + if (!notSuppressedAttributes.TryGetValue(name, out var diagnosticAttribute)) + { + diagnosticAttribute = new PlatformAttributes(); + } + CopyAllAttributes(diagnosticAttribute, attributes); + notSuppressedAttributes[name] = diagnosticAttribute; + } + } + } + + // if supportedOnlyList then call site should not have any platform not listed in the support list + foreach (var (platform, csAttributes) in callSiteAttributes) + { + if (csAttributes.SupportedFirst != null && + !supportedOnlyPlatforms.Contains(platform)) + { + foreach (var (name, version) in operationAttributes) + { + AddOrUpdatedDiagnostic(operationAttributes[name], notSuppressedAttributes, name); + } + } + } + } + return notSuppressedAttributes.Any(); + + static void AddOrUpdatedDiagnostic(PlatformAttributes operationAttributes, + SmallDictionary notSuppressedAttributes, string name) + { + if (operationAttributes.SupportedFirst != null) + { + if (!notSuppressedAttributes.TryGetValue(name, out var diagnosticAttribute)) + { + diagnosticAttribute = new PlatformAttributes(); + } + diagnosticAttribute.SupportedFirst = (Version)operationAttributes.SupportedFirst.Clone(); + notSuppressedAttributes[name] = diagnosticAttribute; + } + } + + static bool UnsupportedSecondSuppressed(PlatformAttributes attribute, PlatformAttributes callSiteAttribute) => + SuppressedByCallSiteSupported(attribute, callSiteAttribute.SupportedFirst) || + SuppressedByCallSiteUnsupported(callSiteAttribute, attribute.UnsupportedSecond!); + + static bool SuppressedByCallSiteUnsupported(PlatformAttributes callSiteAttribute, Version unsupporteAttribute) => + callSiteAttribute.UnsupportedFirst != null && unsupporteAttribute >= callSiteAttribute.UnsupportedFirst || + callSiteAttribute.UnsupportedSecond != null && unsupporteAttribute >= callSiteAttribute.UnsupportedSecond; + + static bool SuppressedByCallSiteSupported(PlatformAttributes attribute, Version? callSiteSupportedFirst) => + callSiteSupportedFirst != null && callSiteSupportedFirst >= attribute.SupportedFirst! && + attribute.SupportedSecond != null && callSiteSupportedFirst >= attribute.SupportedSecond; + + static bool UnsupportedFirstSuppressed(PlatformAttributes attribute, PlatformAttributes callSiteAttribute) => + callSiteAttribute.SupportedFirst != null && callSiteAttribute.SupportedFirst >= attribute.SupportedFirst || + SuppressedByCallSiteUnsupported(callSiteAttribute, attribute.UnsupportedFirst!); + + // As optianal if call site supports that platform, their versions should match + static bool OptionalOsSupportSuppressed(PlatformAttributes callSiteAttribute, PlatformAttributes attribute) => + (callSiteAttribute.SupportedFirst == null || attribute.SupportedFirst <= callSiteAttribute.SupportedFirst) && + (callSiteAttribute.SupportedSecond == null || attribute.SupportedFirst <= callSiteAttribute.SupportedSecond); + + static bool MandatoryOsVersionsSuppressed(PlatformAttributes callSitePlatforms, Version checkingVersion) => + callSitePlatforms.SupportedFirst != null && checkingVersion <= callSitePlatforms.SupportedFirst || + callSitePlatforms.SupportedSecond != null && checkingVersion <= callSitePlatforms.SupportedSecond; + } + + private static PlatformAttributes CopyAllAttributes(PlatformAttributes copyTo, PlatformAttributes copyFrom) + { + copyTo.SupportedFirst = (Version?)copyFrom.SupportedFirst?.Clone(); + copyTo.SupportedSecond = (Version?)copyFrom.SupportedSecond?.Clone(); + copyTo.UnsupportedFirst = (Version?)copyFrom.UnsupportedFirst?.Clone(); + copyTo.UnsupportedSecond = (Version?)copyFrom.UnsupportedSecond?.Clone(); + return copyTo; + } + + // Do not warn if platform specific enum/field value is used in conditional check, like: 'if (value == FooEnum.WindowsOnlyValue)' + private static bool IsWithinConditionalOperation(IFieldReferenceOperation pOperation) => + pOperation.ConstantValue.HasValue && + pOperation.Parent is IBinaryOperation bo && + (bo.OperatorKind == BinaryOperatorKind.Equals || + bo.OperatorKind == BinaryOperatorKind.NotEquals || + bo.OperatorKind == BinaryOperatorKind.GreaterThan || + bo.OperatorKind == BinaryOperatorKind.LessThan || + bo.OperatorKind == BinaryOperatorKind.GreaterThanOrEqual || + bo.OperatorKind == BinaryOperatorKind.LessThanOrEqual); + + private static bool TryGetOrCreatePlatformAttributes( + ISymbol symbol, + ConcurrentDictionary?> platformSpecificMembers, + [NotNullWhen(true)] out SmallDictionary? attributes) + { + if (!platformSpecificMembers.TryGetValue(symbol, out attributes)) + { + var container = symbol.ContainingSymbol; + + // Namespaces do not have attributes + while (container is INamespaceSymbol) + { + container = container.ContainingSymbol; + } + + if (container != null && + TryGetOrCreatePlatformAttributes(container, platformSpecificMembers, out var containerAttributes)) + { + attributes = CopyAttributes(containerAttributes); + } + + AddPlatformAttributes(symbol.GetAttributes(), ref attributes); + + attributes = platformSpecificMembers.GetOrAdd(symbol, attributes); + } + + return attributes != null; + + static bool AddPlatformAttributes(ImmutableArray immediateAttributes, [NotNullWhen(true)] ref SmallDictionary? attributes) + { + foreach (AttributeData attribute in immediateAttributes) + { + if (s_osPlatformAttributes.Contains(attribute.AttributeClass.Name)) + { + TryAddValidAttribute(ref attributes, attribute); + } + } + return attributes != null; + } + } + + private static bool TryAddValidAttribute([NotNullWhen(true)] ref SmallDictionary? attributes, AttributeData attribute) + { + if (!attribute.ConstructorArguments.IsEmpty && + attribute.ConstructorArguments[0] is { } argument && + argument.Kind == TypedConstantKind.Primitive && + argument.Type.SpecialType == SpecialType.System_String && + !argument.IsNull && + !argument.Value.Equals(string.Empty) && + TryParsePlatformNameAndVersion(argument.Value.ToString(), out string platformName, out Version? version)) + { + attributes ??= new SmallDictionary(StringComparer.OrdinalIgnoreCase); + + if (!attributes.TryGetValue(platformName, out var _)) + { + attributes[platformName] = new PlatformAttributes(); + } + + AddAttribute(attribute.AttributeClass.Name, version, attributes, platformName); + return true; + } + + return false; + } + + private static bool TryParsePlatformNameAndVersion(string osString, out string osPlatformName, [NotNullWhen(true)] out Version? version) + { + version = null; + osPlatformName = string.Empty; + for (int i = 0; i < osString.Length; i++) + { + if (char.IsDigit(osString[i])) + { + if (i > 0 && Version.TryParse(osString.Substring(i), out Version? parsedVersion)) + { + osPlatformName = osString.Substring(0, i); + version = parsedVersion; + return true; + } + + return false; + } + } + + osPlatformName = osString; + version = new Version(0, 0); + return true; + } + + private static void AddAttribute(string name, Version version, SmallDictionary existingAttributes, string platformName) + { + if (name == SupportedOSPlatformAttribute) + { + AddOrUpdateSupportedAttribute(existingAttributes[platformName], version); + } + else + { + Debug.Assert(name == UnsupportedOSPlatformAttribute); + AddOrUpdateUnsupportedAttribute(platformName, existingAttributes[platformName], version, existingAttributes); + } + + static void AddOrUpdateUnsupportedAttribute(string name, PlatformAttributes attributes, + Version version, SmallDictionary existingAttributes) + { + if (attributes.UnsupportedFirst != null) + { + if (attributes.UnsupportedFirst > version) + { + if (attributes.UnsupportedSecond != null) + { + if (attributes.UnsupportedSecond > attributes.UnsupportedFirst) + { + attributes.UnsupportedSecond = attributes.UnsupportedFirst; + } + } + else + { + attributes.UnsupportedSecond = attributes.UnsupportedFirst; + } + + attributes.UnsupportedFirst = version; + } + else + { + if (attributes.UnsupportedSecond != null) + { + if (attributes.UnsupportedSecond > version) + { + attributes.UnsupportedSecond = version; + } + } + else + { + if (attributes.SupportedFirst != null && attributes.SupportedFirst < version) + { + // We should ignore second attribute in case like [UnsupportedOSPlatform(""windows""), + // [UnsupportedOSPlatform(""windows11.0"")] which doesn't have supported in between + attributes.UnsupportedSecond = version; + } + } + } + } + else + { + if (attributes.SupportedFirst != null && attributes.SupportedFirst >= version) + { + // Override needed + if (attributes.SupportedSecond != null) + { + attributes.SupportedFirst = attributes.SupportedSecond; + attributes.SupportedSecond = null; + } + else + { + attributes.SupportedFirst = null; + } + if (!HasAnySupportedOnlyAttribute(name, existingAttributes)) + { + attributes.UnsupportedFirst = version; + } + } + else + { + attributes.UnsupportedFirst = version; + } + } + + static bool HasAnySupportedOnlyAttribute(string name, SmallDictionary existingAttributes) => + existingAttributes.Any(a => !a.Key.Equals(name, StringComparison.OrdinalIgnoreCase) && + AllowList(a.Value)); + } + + static void AddOrUpdateSupportedAttribute(PlatformAttributes attributes, Version version) + { + if (attributes.SupportedFirst != null) + { + if (attributes.SupportedFirst > version) + { + if (attributes.SupportedSecond != null) + { + if (attributes.SupportedSecond < attributes.SupportedFirst) + { + attributes.SupportedSecond = attributes.SupportedFirst; + } + } + else + { + attributes.SupportedSecond = attributes.SupportedFirst; + } + + attributes.SupportedFirst = version; + } + else + { + if (attributes.SupportedSecond != null) + { + if (attributes.SupportedSecond < version) + { + attributes.SupportedSecond = version; + } + } + else + { + attributes.SupportedSecond = version; + } + } + } + else + { + attributes.SupportedFirst = version; + } + } + } + + /// + /// Determines if the attributes supported only for the platform (allow list) + /// + /// PlatformAttributes being checked + /// true if it is allow list + private static bool AllowList(PlatformAttributes attributes) => + attributes.SupportedFirst != null && + (attributes.UnsupportedFirst == null || attributes.SupportedFirst < attributes.UnsupportedFirst); + + /// + /// Determines if the attributes unsupported only for the platform (deny list) + /// + /// PlatformAttributes being checked + /// true if it is deny list + private static bool DenyList(PlatformAttributes attributes) => + attributes.UnsupportedFirst != null && + (attributes.SupportedFirst == null || attributes.UnsupportedFirst <= attributes.SupportedFirst); + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzer.RuntimeOSPlatformInfo.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzer.RuntimeOSPlatformInfo.cs deleted file mode 100644 index d66f0f1285..0000000000 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzer.RuntimeOSPlatformInfo.cs +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Analyzer.Utilities; -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; - - public sealed partial class RuntimePlatformCheckAnalyzer - { - private struct RuntimeOSPlatformInfo : IAbstractAnalysisValue, IEquatable - { - private RuntimeOSPlatformInfo(string invokedPlatformCheckMethodName, string platformPropertyName, Version version, bool negated) - { - InvokedPlatformCheckMethodName = invokedPlatformCheckMethodName ?? throw new ArgumentNullException(nameof(invokedPlatformCheckMethodName)); - PlatformPropertyName = platformPropertyName ?? throw new ArgumentNullException(nameof(platformPropertyName)); - Version = version ?? throw new ArgumentNullException(nameof(version)); - Negated = negated; - } - - public string InvokedPlatformCheckMethodName { get; } - public string PlatformPropertyName { get; } - public Version Version { get; } - public bool Negated { get; } - - public IAbstractAnalysisValue GetNegatedValue() - => new RuntimeOSPlatformInfo(InvokedPlatformCheckMethodName, PlatformPropertyName, Version, !Negated); - - public static bool TryDecode( - IMethodSymbol invokedPlatformCheckMethod, - ImmutableArray arguments, - ValueContentAnalysisResult? valueContentAnalysisResult, - INamedTypeSymbol osPlatformType, - [NotNullWhen(returnValue: true)] out RuntimeOSPlatformInfo? info) - { - if (!TryDecodeOSPlatform(arguments, osPlatformType, out var osPlatformProperty) || - !TryDecodeOSVersion(arguments, valueContentAnalysisResult, out var osVersion)) - { - // Bail out - info = default; - return false; - } - - info = new RuntimeOSPlatformInfo(invokedPlatformCheckMethod.Name, osPlatformProperty.Name, osVersion, negated: false); - return true; - } - - private static bool TryDecodeOSPlatform( - ImmutableArray arguments, - INamedTypeSymbol osPlatformType, - [NotNullWhen(returnValue: true)] out IPropertySymbol? osPlatformProperty) - { - Debug.Assert(!arguments.IsEmpty); - return TryDecodeOSPlatform(arguments[0].Value, osPlatformType, out osPlatformProperty); - } - - private static bool TryDecodeOSPlatform( - IOperation argumentValue, - INamedTypeSymbol osPlatformType, - [NotNullWhen(returnValue: true)] out IPropertySymbol? osPlatformProperty) - { - if ((argumentValue is IPropertyReferenceOperation propertyReference) && - propertyReference.Property.ContainingType.Equals(osPlatformType)) - { - osPlatformProperty = propertyReference.Property; - return true; - } - - osPlatformProperty = null; - return false; - } - - private static bool TryDecodeOSVersion( - ImmutableArray arguments, - ValueContentAnalysisResult? valueContentAnalysisResult, - [NotNullWhen(returnValue: true)] out Version? osVersion) - { - using var versionBuilder = ArrayBuilder.GetInstance(4, fillWithValue: 0); - var index = 0; - foreach (var argument in arguments.Skip(1)) - { - if (!TryDecodeOSVersionPart(argument, valueContentAnalysisResult, out var osVersionPart)) - { - osVersion = null; - return false; - } - - versionBuilder[index++] = osVersionPart; - } - - osVersion = new Version(versionBuilder[0], versionBuilder[1], versionBuilder[2], versionBuilder[3]); - 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; - } - } - - public override string ToString() - { - var versionStr = Version.ToString(fieldCount: GetVersionFieldCount(Version)); - var result = $"{InvokedPlatformCheckMethodName};{PlatformPropertyName};{versionStr}"; - if (Negated) - { - result = $"!{result}"; - } - - return result; - - static int GetVersionFieldCount(Version version) - { - if (version.Revision != 0) - { - return 4; - } - - if (version.Build != 0) - { - return 3; - } - - if (version.Minor != 0) - { - return 2; - } - - return 1; - } - } - - public bool Equals(RuntimeOSPlatformInfo other) - => InvokedPlatformCheckMethodName.Equals(other.InvokedPlatformCheckMethodName, StringComparison.OrdinalIgnoreCase) && - PlatformPropertyName.Equals(other.PlatformPropertyName, StringComparison.OrdinalIgnoreCase) && - Version.Equals(other.Version) && - Negated == other.Negated; - - public override bool Equals(object obj) - => obj is RuntimeOSPlatformInfo otherInfo && Equals(otherInfo); - - public override int GetHashCode() - => HashUtilities.Combine(InvokedPlatformCheckMethodName.GetHashCode(), PlatformPropertyName.GetHashCode(), Version.GetHashCode(), Negated.GetHashCode()); - - bool IEquatable.Equals(IAbstractAnalysisValue other) - => other is RuntimeOSPlatformInfo otherInfo && Equals(otherInfo); - - public static bool operator ==(RuntimeOSPlatformInfo left, RuntimeOSPlatformInfo right) - { - return left.Equals(right); - } - - public static bool operator !=(RuntimeOSPlatformInfo left, RuntimeOSPlatformInfo right) - { - return !(left == right); - } - } - } -} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzer.cs deleted file mode 100644 index 32f5177850..0000000000 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzer.cs +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using Analyzer.Utilities; -using Analyzer.Utilities.Extensions; -using Analyzer.Utilities.PooledObjects; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.GlobalFlowStateAnalysis; -using Microsoft.CodeAnalysis.Operations; - -namespace Microsoft.NetCore.Analyzers.InteropServices -{ -#pragma warning disable RS1001 // Missing diagnostic analyzer attribute - TODO: fix and enable analyzer. - public sealed partial class RuntimePlatformCheckAnalyzer : DiagnosticAnalyzer -#pragma warning restore RS1001 // Missing diagnostic analyzer attribute. - { - internal const string RuleId = "CA1416"; - private static readonly ImmutableArray s_platformCheckMethods = ImmutableArray.Create("IsOSPlatformOrLater", "IsOSPlatformEarlierThan"); - - // TODO: Define resource strings for title, message and description. - private static readonly LocalizableString s_localizableTitle = "RuntimePlatformCheckTitle"; //new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.RuntimePlatformCheckTitle), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); - private static readonly LocalizableString s_localizableMessage = "Platform checks:'{0}'"; //new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.RuntimePlatformCheckMessage), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); - private static readonly LocalizableString s_localizableDescription = "RuntimePlatformCheckDescription."; //new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.RuntimePlatformCheckMessage), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); - - internal static DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(RuleId, - s_localizableTitle, - s_localizableMessage, - DiagnosticCategory.Interoperability, - RuleLevel.IdeSuggestion, - description: s_localizableDescription, - isPortedFxCopRule: false, - isDataflowRule: false); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - - public override void Initialize(AnalysisContext context) - { - context.EnableConcurrentExecution(); - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - - context.RegisterCompilationStartAction(context => - { - // TODO: Remove the below temporary hack once new APIs are available. - var typeName = WellKnownTypeNames.SystemRuntimeInteropServicesRuntimeInformation + "Helper"; - - if (!context.Compilation.TryGetOrCreateTypeByMetadataName(typeName, out var runtimeInformationType) || - !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeInteropServicesOSPlatform, out var osPlatformType)) - { - return; - } - - var platformCheckMethods = GetPlatformCheckMethods(runtimeInformationType, osPlatformType); - if (platformCheckMethods.IsEmpty) - { - return; - } - - context.RegisterOperationBlockStartAction(context => AnalyzerOperationBlock(context, platformCheckMethods, osPlatformType)); - return; - - static ImmutableArray GetPlatformCheckMethods(INamedTypeSymbol runtimeInformationType, INamedTypeSymbol osPlatformType) - { - using var builder = ArrayBuilder.GetInstance(); - var methods = runtimeInformationType.GetMembers().OfType(); - foreach (var method in methods) - { - if (s_platformCheckMethods.Contains(method.Name) && - !method.Parameters.IsEmpty && - method.Parameters[0].Type.Equals(osPlatformType) && - method.Parameters.Skip(1).All(p => p.Type.SpecialType == SpecialType.System_Int32)) - { - builder.Add(method); - } - } - - return builder.ToImmutable(); - } - }); - } - - private static void AnalyzerOperationBlock( - OperationBlockStartAnalysisContext context, - ImmutableArray platformCheckMethods, - INamedTypeSymbol osPlatformType) - { -#pragma warning disable CA2000 // Dispose objects before losing scope - disposed in OperationBlockEndAction. - var platformSpecificOperations = PooledConcurrentSet.GetInstance(); -#pragma warning restore CA2000 // Dispose objects before losing scope - var needsValueContentAnalysis = false; - - context.RegisterOperationAction(context => - { - var invocation = (IInvocationOperation)context.Operation; - if (platformCheckMethods.Contains(invocation.TargetMethod)) - { - needsValueContentAnalysis = needsValueContentAnalysis || ComputeNeedsValueContentAnalysis(invocation); - } - else - { - // TODO: Add real platform specific operations that need runtime OS platform validation. - platformSpecificOperations.Add(invocation); - } - }, OperationKind.Invocation); - - context.RegisterOperationBlockEndAction(context => - { - try - { - if (platformSpecificOperations.IsEmpty || - !(context.OperationBlocks.GetControlFlowGraph() is { } cfg)) - { - return; - } - - var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); - var analysisResult = GlobalFlowStateAnalysis.TryGetOrComputeResult( - cfg, context.OwningSymbol, CreateOperationVisitor, - wellKnownTypeProvider, context.Options, Rule, - performValueContentAnalysis: needsValueContentAnalysis, context.CancellationToken, - out var valueContentAnalysisResult); - if (analysisResult == null) - { - return; - } - - Debug.Assert(valueContentAnalysisResult == null || needsValueContentAnalysis); - - foreach (var platformSpecificOperation in platformSpecificOperations) - { - var value = analysisResult[platformSpecificOperation.Kind, platformSpecificOperation.Syntax]; - if (value.Kind == GlobalFlowStateAnalysisValueSetKind.Unknown) - { - continue; - } - - // TODO: Add real checks. - - // TODO Platform checks:'{0}' - context.ReportDiagnostic(platformSpecificOperation.CreateDiagnostic(Rule, value)); - } - } - finally - { - platformSpecificOperations.Free(); - } - - return; - - OperationVisitor CreateOperationVisitor(GlobalFlowStateAnalysisContext context) - => new OperationVisitor(platformCheckMethods, osPlatformType, context); - }); - } - - private static bool ComputeNeedsValueContentAnalysis(IInvocationOperation invocation) - { - Debug.Assert(!invocation.Arguments.IsEmpty); - foreach (var argument in invocation.Arguments.Skip(1)) - { - if (!argument.Value.ConstantValue.HasValue) - { - return true; - } - } - - return false; - } - } -} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 1df3ac003e..3a2393de18 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1425,6 +1425,18 @@ Use `{0}` instead of Range-based indexers on an array + + Validate platform compatibility + + + Using platform dependent API on a component makes the code no longer work across all platforms. + + + '{0}' is supported on '{1}' {2} and later + + + '{0}' is unsupported on '{1}' {2} and later + String parameters passed by value with the 'OutAttribute' can destabilize the runtime if the string is an interned string. @@ -1452,4 +1464,10 @@ '{0}' will throw for assemblies embedded in a single-file app + + '{0}' is unsupported on '{1}' + + + '{0}' is supported on '{1}' + \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 68fb6e60cb..34b8ac9900 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -1512,6 +1512,36 @@ Metody P/Invoke nemají být viditelné + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. Zkontrolujte kód, který zpracovává nedůvěryhodná deserializovaná data pro zpracování neočekávaných cyklů odkazů. Neočekávaný cyklus odkazů by neměl způsobit, aby se kód zasekl do nekonečné smyčky. V opačném případě může neočekávaný cyklus odkazů při deserializaci nedůvěryhodných dat umožnit útočníkovi útok typu odepření služby (DOS) nebo vyčerpání paměti procesu. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index b34f6df262..03337c34de 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -1512,6 +1512,36 @@ P/Invokes dürfen nicht sichtbar sein + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. Überprüfen Sie, auf welche Weise der Code zum Verarbeiten von nicht vertrauenswürdigen deserialisierten Daten unerwartete Verweiszyklen behandelt. Ein unerwarteter Verweiszyklus sollte nicht dazu führen, dass der Code in eine Endlosschleife eintritt. Andernfalls kann ein unerwarteter Verweiszyklus durch einen Angreifer für einen DoS-Angriff oder eine Arbeitsspeicherüberlastung des Prozesses genutzt werden, während nicht vertrauenswürdige Daten deserialisiert werden. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 29c6946874..67cba7a62e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -1512,6 +1512,36 @@ Los elementos P/Invoke no deben estar visibles + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. Revise el código que procesa datos deserializados que no son de confianza para controlar ciclos de referencia inesperados. Un ciclo de referencia inesperado no debe hacer que el código entre en un bucle infinito. De lo contrario, este tipo de ciclo puede permitir un ataque a DOS o agotar la memoria del proceso al deserializar datos que no son de confianza. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index ed7224d70b..af384ee4a3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -1512,6 +1512,36 @@ Les P/Invoke ne doivent pas être visibles + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. Passez en revue le code qui traite les données désérialisées non approuvées pour gérer les cycles de référence inattendus. Un cycle de référence inattendu ne doit pas entraîner l'entrée du code dans une boucle infinie. Sinon, le cycle de référence inattendu risque de permettre à une personne de lancer une attaque DoS ou de saturer la mémoire du processus pendant la désérialisation des données non approuvées. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 7042d84ef3..f41044208d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -1512,6 +1512,36 @@ I metodi P/Invoke non devono essere visibili + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. Rivedere il codice che elabora dati deserializzati non attendibili per la gestione di cicli di riferimento imprevisti. Un ciclo di riferimento imprevisto non deve causare un ciclo infinito del codice. Un ciclo di riferimento imprevisto può invece consentire attacchi di tipo DoS da parte di un utente malintenzionato o l'esaurimento della memoria del processo durante la deserializzazione di dati non attendibili. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 077913c80c..c003dc6b47 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -1512,6 +1512,36 @@ P/Invokes は参照可能にすることはできません + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. 信頼されていない逆シリアル化データを処理するコードで、予期しない参照サイクルの処理を確認してください。予期しない参照サイクルが原因でコードが無限ループに入ることのないようにしてください。そうしないと、信頼されていないデータを逆シリアル化するときに、予期しない参照サイクルのせいで攻撃者が DoS 攻撃をしたり、プロセスのメモリを使い果たしたりできるようになりかねません。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 6f07b60653..91a241277c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -1512,6 +1512,36 @@ P/Invokes를 표시하지 않아야 합니다. + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. 신뢰할 수 없는 역직렬화된 데이터를 처리하는 코드를 검토하여 예기치 않은 참조 주기를 처리합니다. 예기치 않은 참조 주기로 인해 코드의 무한 루프가 시작되어서는 안 됩니다. 참조 주기를 처리하지 않으면 공격자가 신뢰할 수 없는 데이터를 역직렬화할 때 프로세스의 메모리를 고갈시키거나 DOS를 수행할 수 있습니다. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 0d5b616630..d76871845b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -1513,6 +1513,36 @@ Elementy P/Invoke nie powinny być widoczne + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. Przejrzyj kod, który przetwarza zdeserializowane niezaufane dane, pod kątem obsługi nieoczekiwanych cykli odwołań. Nieoczekiwany cykl odwołań nie powinien powodować wejścia kodu w nieskończoną pętlę. W przeciwnym razie nieoczekiwany cykl odwołań może pozwolić osobie atakującej na przeprowadzenie ataku DoS lub wyczerpanie pamięci procesu podczas deserializacji niezaufanych danych. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 6434bdd99c..b44005b6fe 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -1512,6 +1512,36 @@ P/Invokes não deve ser visível + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. Examine o código que processa dados desserializados não confiáveis para o tratamento de ciclos de referência inesperados. Um ciclo de referência inesperado não deve fazer com que o código entre em um loop infinito. Caso contrário, um ciclo de referência inesperado pode permitir um invasor do DOS ou esgotar a memória do processo ao desserializar dados não confiáveis. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 63bc5ba859..d4b7a45cd7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -1512,6 +1512,36 @@ Методы P/Invoke не должны быть видимыми + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. Проверьте код, который обрабатывает недоверенные десериализованные данные, на наличие обработки непредвиденного зацикливания ссылок. Непредвиденное зацикливание ссылок не должно приводить к переводу кода в бесконечный цикл. В противном случае непредвиденное зацикливание ссылок может позволить злоумышленнику провести атаку типа "отказ в обслуживании" или исчерпать память процесса при десериализации недоверенных данных. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index e2f810f296..cc0c4f17fb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -1512,6 +1512,36 @@ P/Invokes görünür olmamalıdır + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. Beklenmeyen başvuru döngülerinin işlenmesi için, seri durumdan çıkarılmış güvenilmeyen verileri işleyen kodu inceleyin. Beklenmeyen başvuru döngüsü, kodun sonsuz bir döngüye girmesine neden olmamalıdır. Buna neden olursa beklenmeyen başvuru döngüsü, güvenilmeyen veriler seri durumdan çıkarılırken saldırganın DOS'a erişmesine izin verebilir veya işlem belleğini tüketebilir. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 5d5cbce535..7ca32c0e15 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -1512,6 +1512,36 @@ P/Invokes 应该是不可见的 + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. 查看处理不受信任的反序列化数据(为了处理意外引用循环)的代码。意外引用循环不应导致代码进入无限循环。否则,当反序列化不受信任的数据时,意外的引用循环可能导致攻击者 DOS 或耗尽进程的内存。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index c392a04e44..8993b80679 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -1512,6 +1512,36 @@ 不應看得見 P/Invoke + + Using platform dependent API on a component makes the code no longer work across all platforms. + Using platform dependent API on a component makes the code no longer work across all platforms. + + + + Validate platform compatibility + Validate platform compatibility + + + + '{0}' is unsupported on '{1}' + '{0}' is unsupported on '{1}' + + + + '{0}' is unsupported on '{1}' {2} and later + '{0}' is unsupported on '{1}' {2} and later + + + + '{0}' is supported on '{1}' + '{0}' is supported on '{1}' + + + + '{0}' is supported on '{1}' {2} and later + '{0}' is supported on '{1}' {2} and later + + Review code that processes untrusted deserialized data for handling of unexpected reference cycles. An unexpected reference cycle should not cause the code to enter an infinite loop. Otherwise, an unexpected reference cycle can allow an attacker to DOS or exhaust the memory of the process when deserializing untrusted data. 檢閱會處理未受信任之還原序列化資料的程式碼,以處理未預期的參考迴圈。未預期的參考迴圈不應導致程式碼進入無限迴圈,否則,未預期的參考週期可能讓攻擊者得以 DOS,或在將未受信任的資料還原序列化時耗盡處理序的記憶體。 diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzerTests.GuardedCallsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzerTests.GuardedCallsTests.cs new file mode 100644 index 0000000000..bd9fdc5d72 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzerTests.GuardedCallsTests.cs @@ -0,0 +1,2595 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.InteropServices.PlatformCompatabilityAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.InteropServices.UnitTests +{ + public partial class PlatformCompatabilityAnalyzerTests + { + public static IEnumerable NamedArgumentsData() + { + yield return new object[] { "minor : 2, major : 12" }; + yield return new object[] { "build : 2, major : 12, revision : 556" }; + yield return new object[] { "build : 1, minor : 1, major : 12" }; + yield return new object[] { "revision : 555, major : 12, build : 2" }; + yield return new object[] { "major : 13, build : 3" }; + } + + [Theory] + [MemberData(nameof(NamedArgumentsData))] + public async Task GuardMethodWithNamedArgumentsTest(string arguments) + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + public void Api_Usage() + { + if (OperatingSystemHelper.IsAndroidVersionAtLeast(" + arguments + @")) + { + Api(); + } + [|Api()|]; + + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(" + arguments + @", platform : ""Android"")) + { + Api(); + } + else + { + [|Api()|]; + } + } + + [SupportedOSPlatform(""Android12.0.2.521"")] + void Api() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task SupportedUnsupportedRange_GuardedWithOr() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + public void Api_Usage() + { + if (!OperatingSystemHelper.IsWindows() || + OperatingSystemHelper.IsWindowsVersionAtLeast(10, 0, 19041)) + { + Api(); + } + + [|Api()|]; // Two diagnostics expected + } + + [UnsupportedOSPlatform(""windows"")] + [SupportedOSPlatform(""windows10.0.19041"")] + void Api() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms, VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(15, 9) + .WithMessage("'Test.Api()' is unsupported on 'windows'")); + } + + [Fact] + public async Task GuardsAroundSupported_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +namespace PlatformCompatDemo.Bugs.GuardsAroundSupported +{ + class Caller + { + public static void TestWithGuardMethods() + { + if (OperatingSystemHelper.IsWindows() || OperatingSystemHelper.IsBrowser()) + { + [|Target.SupportedOnWindows()|]; + [|Target.SupportedOnWindows10()|]; + } + } + } + + class Target + { + [SupportedOSPlatform(""windows"")] + public static void SupportedOnWindows() { } + + [SupportedOSPlatform(""windows10.0"")] + public static void SupportedOnWindows10() { } + + [SupportedOSPlatform(""windows""), SupportedOSPlatform(""browser"")] + public static void SupportedOnWindowsAndBrowser() { } + + [SupportedOSPlatform(""windows10.0""), SupportedOSPlatform(""browser"")] + public static void SupportedOnWindows10AndBrowser() { } + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardsAroundSupported() + { + var source = @" +using System.Runtime.Versioning; +using System; + +namespace PlatformCompatDemo.Bugs.GuardsAroundSupported +{ + class Caller + { + public static void TestWithGuardMethods() + { + if (!OperatingSystemHelper.IsWindows()) + { + [|Target.SupportedOnWindows()|]; + [|Target.SupportedOnWindows10()|]; + [|Target.SupportedOnWindowsAndBrowser()|]; // expected two diagnostics - supported on windows and browser + [|Target.SupportedOnWindows10AndBrowser()|]; // expected two diagnostics - supported on windows 10 and browser + } + + if (OperatingSystemHelper.IsWindows()) + { + Target.SupportedOnWindows(); + [|Target.SupportedOnWindows10()|]; + Target.SupportedOnWindowsAndBrowser(); // no diagnostic expected - the API is supported on windows, no need to warn for other platforms support + [|Target.SupportedOnWindows10AndBrowser()|]; // expected two diagnostics - supported on windows 10 and browser + } + + if (OperatingSystemHelper.IsWindowsVersionAtLeast(10)) + { + Target.SupportedOnWindows(); + Target.SupportedOnWindows10(); + Target.SupportedOnWindowsAndBrowser(); // no diagnostic expected - the API is supported on windows, no need to warn for other platforms support + Target.SupportedOnWindows10AndBrowser(); // the same, no diagnostic expected + } + + if (OperatingSystemHelper.IsBrowser()) + { + [|Target.SupportedOnWindows()|]; + [|Target.SupportedOnWindows10()|]; + Target.SupportedOnWindowsAndBrowser(); // No diagnostic expected - the API is supported on browser, no need to warn for other platforms support + Target.SupportedOnWindows10AndBrowser(); // The same, no diagnostic expected + } + + if (OperatingSystemHelper.IsWindows() || OperatingSystemHelper.IsBrowser()) + { + [|Target.SupportedOnWindows()|]; // No diagnostic expected because of it was windows + [|Target.SupportedOnWindows10()|]; + Target.SupportedOnWindowsAndBrowser(); + [|Target.SupportedOnWindows10AndBrowser()|]; // two diagnostic expected windows 10 and browser + } + + if (OperatingSystemHelper.IsWindowsVersionAtLeast(10) || OperatingSystemHelper.IsBrowser()) + { + [|Target.SupportedOnWindows()|]; + [|Target.SupportedOnWindows10()|]; + Target.SupportedOnWindowsAndBrowser(); + Target.SupportedOnWindows10AndBrowser(); + } + } + } + + class Target + { + [SupportedOSPlatform(""windows"")] + public static void SupportedOnWindows() { } + + [SupportedOSPlatform(""windows10.0"")] + public static void SupportedOnWindows10() { } + + [SupportedOSPlatform(""windows""), SupportedOSPlatform(""browser"")] + public static void SupportedOnWindowsAndBrowser() { } + + [SupportedOSPlatform(""windows10.0""), SupportedOSPlatform(""browser"")] + public static void SupportedOnWindows10AndBrowser() { } + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsRule).WithLocation(15, 17).WithMessage("'Target.SupportedOnWindowsAndBrowser()' is supported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(16, 17).WithMessage("'Target.SupportedOnWindows10AndBrowser()' is supported on 'windows' 10.0 and later"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(24, 17).WithMessage("'Target.SupportedOnWindows10AndBrowser()' is supported on 'browser'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(48, 17).WithMessage("'Target.SupportedOnWindows10AndBrowser()' is supported on 'browser'")); + } + + [Fact] + public async Task MoreGuardsAroundSupported() + { + var source = @" +using System.Runtime.Versioning; +using System; + +namespace PlatformCompatDemo.SupportedUnupported +{ + class Caller + { + public static void UnsupportedCombinations() + { + if (OperatingSystemHelper.IsBrowser()) + { + var withoutAttributes = new TypeWithoutAttributes(); + withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11(); + withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12(); + withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12SupportedOnWindows13(); + + var unsupportedOnWindows = new TypeUnsupportedOnWindows(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11UnsupportedOnWindows12(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11UnsupportedOnWindows12SupportedOnWindows13(); + + var unsupportedOnBrowser = [|new TypeUnsupportedOnBrowser()|]; + unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionSupportedOnBrowser(); + + var unsupportedOnWindowsSupportedOnWindows11 = new TypeUnsupportedOnWindowsSupportedOnWindows11(); + unsupportedOnWindowsSupportedOnWindows11.TypeUnsupportedOnWindowsSupportedOnWindows11_FunctionUnsupportedOnWindows12(); + unsupportedOnWindowsSupportedOnWindows11.TypeUnsupportedOnWindowsSupportedOnWindows11_FunctionUnsupportedOnWindows12SupportedOnWindows13(); + + var unsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12 = new TypeUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12(); + unsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12.TypeUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12_FunctionSupportedOnWindows13(); + } + + if (OperatingSystemHelper.IsWindows()) + { + var withoutAttributes = new TypeWithoutAttributes(); + [|withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11()|]; + [|withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12()|]; + [|withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12SupportedOnWindows13()|]; + + var unsupportedOnWindows = [|new TypeUnsupportedOnWindows()|]; + [|unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11()|]; + + var unsupportedOnBrowser = new TypeUnsupportedOnBrowser(); + unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionSupportedOnBrowser(); + + var unsupportedOnWindowsSupportedOnWindows11 = [|new TypeUnsupportedOnWindowsSupportedOnWindows11()|]; + [|unsupportedOnWindowsSupportedOnWindows11.TypeUnsupportedOnWindowsSupportedOnWindows11_FunctionUnsupportedOnWindows12SupportedOnWindows13()|]; + + var unsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12 = [|new TypeUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12()|]; + } + } + } +}" + TargetTypesForTest + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(37, 17).WithMessage("'TypeWithoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11()' is unsupported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(38, 17).WithMessage("'TypeWithoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12()' is unsupported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(39, 17).WithMessage("'TypeWithoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12SupportedOnWindows13()' is unsupported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(42, 17).WithMessage("'TypeUnsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11()' is unsupported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(47, 64).WithMessage("'TypeUnsupportedOnWindowsSupportedOnWindows11' is unsupported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(48, 17).WithMessage("'TypeUnsupportedOnWindowsSupportedOnWindows11.TypeUnsupportedOnWindowsSupportedOnWindows11_FunctionUnsupportedOnWindows12SupportedOnWindows13()' is unsupported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(50, 86).WithMessage("'TypeUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12' is unsupported on 'windows'")); + } + + [Fact] + public async Task MoreGuardsAroundUnSupported() + { + var source = @" +using System.Runtime.Versioning; +using System; + +namespace PlatformCompatDemo.SupportedUnupported +{ + class Caller + { + public static void UnsupportedSingleCondition() + { + if (!OperatingSystemHelper.IsWindowsVersionAtLeast(10)) + { + var unsupported = new TypeWithoutAttributes(); + [|unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindows()|]; + [|unsupported.TypeWithoutAttributes_FunctionUnsupportedOnBrowser()|]; + unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindows10(); + [|unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindowsAndBrowser()|]; + [|unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindows10AndBrowser()|]; + + var unsupportedOnWindows = [|new TypeUnsupportedOnWindows()|]; + [|unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionUnsupportedOnWindows11()|]; + + var unsupportedOnBrowser = [|new TypeUnsupportedOnBrowser()|]; + [|unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionUnsupportedOnWindows()|]; + [|unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionUnsupportedOnWindows10()|]; + + var unsupportedOnWindows10 = new TypeUnsupportedOnWindows10(); + [|unsupportedOnWindows10.TypeUnsupportedOnWindows10_FunctionUnsupportedOnBrowser()|]; + unsupportedOnWindows10.TypeUnsupportedOnWindows10_FunctionUnsupportedOnWindows11(); // We should ignore above version of unsupported if there is no supported in between + [|unsupportedOnWindows10.TypeUnsupportedOnWindows10_FunctionUnsupportedOnWindows11AndBrowser()|]; + + var unsupportedOnWindowsAndBrowser = [|new TypeUnsupportedOnWindowsAndBrowser()|]; + [|unsupportedOnWindowsAndBrowser.TypeUnsupportedOnWindowsAndBrowser_FunctionUnsupportedOnWindows11()|]; + + var unsupportedOnWindows10AndBrowser = [|new TypeUnsupportedOnWindows10AndBrowser()|]; + [|unsupportedOnWindows10AndBrowser.TypeUnsupportedOnWindows10AndBrowser_FunctionUnsupportedOnWindows11()|]; + } + } + + public static void UnsupportedWithAnd() + { + if (!OperatingSystemHelper.IsWindowsVersionAtLeast(10) && !OperatingSystemHelper.IsBrowser()) + { + var unsupported = new TypeWithoutAttributes(); + [|unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindows()|]; + unsupported.TypeWithoutAttributes_FunctionUnsupportedOnBrowser(); + unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindows10(); + [|unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindowsAndBrowser()|]; + unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindows10AndBrowser(); + + var unsupportedOnWindows = [|new TypeUnsupportedOnWindows()|]; + [|unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionUnsupportedOnBrowser()|]; + [|unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionUnsupportedOnWindows11()|]; + [|unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionUnsupportedOnWindows11AndBrowser()|]; + + var unsupportedOnBrowser = new TypeUnsupportedOnBrowser(); + [|unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionUnsupportedOnWindows()|]; + unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionUnsupportedOnWindows10(); + + var unsupportedOnWindows10 = new TypeUnsupportedOnWindows10(); + unsupportedOnWindows10.TypeUnsupportedOnWindows10_FunctionUnsupportedOnBrowser(); + unsupportedOnWindows10.TypeUnsupportedOnWindows10_FunctionUnsupportedOnWindows11(); + unsupportedOnWindows10.TypeUnsupportedOnWindows10_FunctionUnsupportedOnWindows11AndBrowser(); + + var unsupportedOnWindowsAndBrowser = [|new TypeUnsupportedOnWindowsAndBrowser()|]; + [|unsupportedOnWindowsAndBrowser.TypeUnsupportedOnWindowsAndBrowser_FunctionUnsupportedOnWindows11()|]; + + var unsupportedOnWindows10AndBrowser = new TypeUnsupportedOnWindows10AndBrowser(); + unsupportedOnWindows10AndBrowser.TypeUnsupportedOnWindows10AndBrowser_FunctionUnsupportedOnWindows11(); + + var unsupportedCombinations = new TypeUnsupportedOnBrowser(); + unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionSupportedOnBrowser(); + } + } + + public static void UnsupportedCombinations() + { + if (!OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsBrowser()) + { + var withoutAttributes = new TypeWithoutAttributes(); + withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11(); + withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12(); + withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12SupportedOnWindows13(); + + var unsupportedOnWindows = new TypeUnsupportedOnWindows(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11UnsupportedOnWindows12(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11UnsupportedOnWindows12SupportedOnWindows13(); + + var unsupportedOnBrowser = new TypeUnsupportedOnBrowser(); + unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionSupportedOnBrowser(); + + var unsupportedOnWindowsSupportedOnWindows11 = new TypeUnsupportedOnWindowsSupportedOnWindows11(); + unsupportedOnWindowsSupportedOnWindows11.TypeUnsupportedOnWindowsSupportedOnWindows11_FunctionUnsupportedOnWindows12(); + unsupportedOnWindowsSupportedOnWindows11.TypeUnsupportedOnWindowsSupportedOnWindows11_FunctionUnsupportedOnWindows12SupportedOnWindows13(); + + var unsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12 = new TypeUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12(); + unsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12.TypeUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12_FunctionSupportedOnWindows13(); + } + } + } +}" + TargetTypesForTest + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(17, 17).WithMessage("'TypeWithoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsAndBrowser()' is unsupported on 'browser'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(24, 17).WithMessage("'TypeUnsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionUnsupportedOnWindows()' is unsupported on 'browser'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(32, 54).WithMessage("'TypeUnsupportedOnWindowsAndBrowser' is unsupported on 'browser'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(33, 17).WithMessage("'TypeUnsupportedOnWindowsAndBrowser.TypeUnsupportedOnWindowsAndBrowser_FunctionUnsupportedOnWindows11()' is unsupported on 'browser'")); + } + + [Fact] + public async Task GuardsAroundUnsupported() + { + var source = @" +using System.Runtime.Versioning; +using System; + +namespace PlatformCompatDemo.Bugs.GuardsAroundUnsupported +{ + class Caller + { + public static void TestWithGuardMethods() + { + if (!OperatingSystemHelper.IsWindows()) + { + Target.UnsupportedInWindows(); + Target.UnsupportedInWindows10(); + [|Target.UnsupportedOnBrowser()|]; // row 15 expected diagnostic - browser unsupported + [|Target.UnsupportedOnWindowsAndBrowser()|]; // expected diagnostic - browser unsupported + [|Target.UnsupportedOnWindows10AndBrowser()|]; // expected diagnostic - browser unsupported + } + + if (!OperatingSystemHelper.IsWindowsVersionAtLeast(10)) + { + [|Target.UnsupportedInWindows()|]; // row 22 expected diagnostic - windows unsupported + Target.UnsupportedInWindows10(); + [|Target.UnsupportedOnBrowser()|]; // expected diagnostic - browser unsupported + [|Target.UnsupportedOnWindowsAndBrowser()|]; // expected 2 diagnostics - windows and browser unsupported + [|Target.UnsupportedOnWindows10AndBrowser()|]; // expected diagnostic - browser unsupported + } + + if (OperatingSystemHelper.IsWindows()) + { + [|Target.UnsupportedInWindows()|]; // row 31 expected diagnostic - windows unsupported + [|Target.UnsupportedInWindows10()|]; // expected diagnostic - windows 10 unsupported + Target.UnsupportedOnBrowser(); + [|Target.UnsupportedOnWindowsAndBrowser()|]; // expected diagnostic - windows unsupported + [|Target.UnsupportedOnWindows10AndBrowser()|]; // expected diagnostic - windows 10 unsupported + } + + if (OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(10)) + { + [|Target.UnsupportedInWindows()|]; // row 40 expected diagnostic - windows unsupported + Target.UnsupportedInWindows10(); + Target.UnsupportedOnBrowser(); + [|Target.UnsupportedOnWindowsAndBrowser()|]; // expected diagnostic - windows unsupported + Target.UnsupportedOnWindows10AndBrowser(); + } + + if (OperatingSystemHelper.IsBrowser()) + { + Target.UnsupportedInWindows(); + Target.UnsupportedInWindows10(); + [|Target.UnsupportedOnBrowser()|}; // row 51 expected diagnostic - browser unsupported + [|Target.UnsupportedOnWindowsAndBrowser()|]; // expected diagnostic - browser unsupported + [|Target.UnsupportedOnWindows10AndBrowser()|]; // expected diagnostic - browser unsupported + } + } + } + + class Target + { + [UnsupportedOSPlatform(""windows"")] + public static void UnsupportedInWindows() { } + + [UnsupportedOSPlatform(""windows10.0"")] + public static void UnsupportedInWindows10() { } + + [UnsupportedOSPlatform(""browser"")] + public static void UnsupportedOnBrowser() { } + + [UnsupportedOSPlatform(""windows""), UnsupportedOSPlatform(""browser"")] + public static void UnsupportedOnWindowsAndBrowser() { } + + [UnsupportedOSPlatform(""windows10.0""), UnsupportedOSPlatform(""browser"")] + public static void UnsupportedOnWindows10AndBrowser() { } + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(25, 17).WithMessage("'Target.UnsupportedOnWindowsAndBrowser()' is unsupported on 'browser'")); + } + + [Fact] + public async Task SupportedUnsupportedRange_GuardedWithAnd() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + public void Api_Usage() + { + if (OperatingSystemHelper.IsIOSVersionAtLeast(12,0) && + !OperatingSystemHelper.IsIOSVersionAtLeast(14,0)) + { + Api(); + } + [|Api()|]; // two diagnostics expected + } + + [SupportedOSPlatform(""ios12.0"")] + [UnsupportedOSPlatform(""ios14.0"")] + void Api() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsRule).WithLocation(14, 9) + .WithMessage("'Test.Api()' is unsupported on 'ios' 14.0 and later")); + } + + [Fact] + public async Task Unsupported_GuardedWith_IsOsNameMethods() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + if(!OperatingSystemHelper.IsBrowser()) + { + NotForBrowser(); + [|NotForIos12OrLater()|]; + } + else + { + NotForIos12OrLater(); + [|NotForBrowser()|]; + } + + if(OperatingSystemHelper.IsOSPlatform(""Browser"")) + { + [|NotForBrowser()|]; + } + else + { + NotForBrowser(); + } + + if(OperatingSystemHelper.IsIOS()) + { + [|NotForIos12OrLater()|]; + } + else + { + NotForIos12OrLater(); + } + + if(OperatingSystemHelper.IsIOSVersionAtLeast(12,1)) + { + [|NotForIos12OrLater()|]; + } + else + { + NotForIos12OrLater(); + } + + if(OperatingSystemHelper.IsIOS() && !OperatingSystemHelper.IsIOSVersionAtLeast(12,0)) + { + NotForIos12OrLater(); + } + else + { + [|NotForIos12OrLater()|]; + } + } + + [UnsupportedOSPlatform(""browser"")] + void NotForBrowser() + { + } + + [UnsupportedOSPlatform(""ios12.1"")] + void NotForIos12OrLater() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); + } + + public static IEnumerable OperatingSystem_IsOsNameVersionAtLeast_MethodsTestData() + { + yield return new object[] { "Windows", "IsWindows", "10,1", true }; + yield return new object[] { "windows11.0", "IsWindows", "10,1,2,3", false }; + yield return new object[] { "WINDOWS10.1.2", "IsWindows", "10,1,2", true }; + yield return new object[] { "FreeBSD", "IsFreeBSD", "10", true }; + yield return new object[] { "FreeBSD12.0", "IsFreeBSD", "10,1,2", false }; + yield return new object[] { "freebsd10.1.2", "IsFreeBSD", "10,1,2", true }; + yield return new object[] { "Android", "IsAndroid", "10,1,2", true }; + yield return new object[] { "android11.0", "IsAndroid", "10,1,2", false }; + yield return new object[] { "Android10.1.2", "IsAndroid", "10,1,2", true }; + yield return new object[] { "IOS", "IsIOS", "10,1,2", true }; + yield return new object[] { "ios12.0", "IsIOS", "10,1,2", false }; + yield return new object[] { "iOS10.1.2", "IsIOS", "10,1,2", true }; + yield return new object[] { "MacOS", "IsMacOS", "10,1,2", true }; + yield return new object[] { "macOS14.0", "IsMacOS", "10,1,2", false }; + yield return new object[] { "macos10.1.2", "IsMacOS", "10,1,2", true }; + yield return new object[] { "TvOS", "IsTvOS", "10,1,2", true }; + yield return new object[] { "tvOS13.0", "IsTvOS", "10,1,2", false }; + yield return new object[] { "Tvos10.1", "IsTvOS", "10,1,2", true }; + yield return new object[] { "watchOS", "IsWatchOS", "10,1,2", true }; + yield return new object[] { "WatchOS14.0", "IsWatchOS", "10,1,2", false }; + yield return new object[] { "watchos10.0", "IsWatchOS", "10,1,2", true }; + } + + [Theory] + [MemberData(nameof(OperatingSystem_IsOsNameVersionAtLeast_MethodsTestData))] + public async Task GuardedWith_IsOsNameVersionAtLeast_impleIfElse(string osName, string isOsMethod, string version, bool versionMatch) + { + var match = versionMatch ? "OsSpecificMethod()" : "[|OsSpecificMethod()|]"; + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + if(OperatingSystemHelper." + isOsMethod + @"VersionAtLeast(" + version + @")) + { + " + match + @"; + } + else + { + [|OsSpecificMethod()|]; + } + } + + [SupportedOSPlatform(""" + osName + @""")] + void OsSpecificMethod() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + + public static IEnumerable OperatingSystem_IsOsName_MethodsTestData() + { + yield return new object[] { "Windows", "IsWindows" }; + yield return new object[] { "WINDOWS", "IsWindows" }; + yield return new object[] { "windows", "IsWindows" }; + yield return new object[] { "LinuX", "IsLinux" }; + yield return new object[] { "linux", "IsLinux" }; + yield return new object[] { "Browser", "IsBrowser" }; + yield return new object[] { "browser", "IsBrowser" }; + yield return new object[] { "FreeBSD", "IsFreeBSD" }; + yield return new object[] { "freebsd", "IsFreeBSD" }; + yield return new object[] { "Android", "IsAndroid" }; + yield return new object[] { "android", "IsAndroid" }; + yield return new object[] { "IOS", "IsIOS" }; + yield return new object[] { "Ios", "IsIOS" }; + yield return new object[] { "ios", "IsIOS" }; + yield return new object[] { "MacOS", "IsMacOS" }; + yield return new object[] { "macOS", "IsMacOS" }; + yield return new object[] { "macos", "IsMacOS" }; + yield return new object[] { "TvOS", "IsTvOS" }; + yield return new object[] { "tvOS", "IsTvOS" }; + yield return new object[] { "watchOS", "IsWatchOS" }; + yield return new object[] { "WatchOS", "IsWatchOS" }; + yield return new object[] { "watchos", "IsWatchOS" }; + } + + [Theory] + [MemberData(nameof(OperatingSystem_IsOsName_MethodsTestData))] + public async Task GuardedWith_IsOsNameMethods_SimpleIfElse(string osName, string isOsMethod) + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + if(OperatingSystemHelper." + isOsMethod + @"()) + { + OsSpecificMethod(); + } + else + { + [|OsSpecificMethod()|]; + } + } + + [SupportedOSPlatform(""" + osName + @""")] + void OsSpecificMethod() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedWith_OperatingSystem_IsOSPlatform_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + if(OperatingSystemHelper.IsOSPlatform(""Windows"")) + { + M2(); + } + else + { + [|M2()|]; + } + + if(OperatingSystemHelper.IsOSPlatform(""Windows8.0"")) + { + M2(); + } + else + { + [|M2()|]; + } + } + + [SupportedOSPlatform(""windows"")] + void M2() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedWith_RuntimeInformation_IsOSPlatform_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System.Runtime.InteropServices; + +class Test +{ + void M1() + { + if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + M2(); + } + else + { + [|M2()|]; + } + } + + [SupportedOSPlatform(""Windows"")] + void M2() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedCalled_SimpleIfElse_VersionNotMatch_Warns() + { + var source = @" +using System.Runtime.Versioning; +using System; + +[assembly:SupportedOSPlatform(""windows7.0"")] + +static class Program +{ + public static void Main() + { + if (OperatingSystemHelper.IsWindowsVersionAtLeast(10)) + { + [|WindowsSpecificApis.WindowsOnlyMethod()|]; + } + else + { + [|WindowsSpecificApis.WindowsOnlyMethod()|]; + } + } +} + +public class WindowsSpecificApis +{ + [SupportedOSPlatform(""windows10.1.2.3"")] + public static void WindowsOnlyMethod() { } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task ReintroducingApiSupport_Guarded_NotWarn() + { + var source = @" +using System; +using System.Runtime.Versioning; + +[assembly:SupportedOSPlatform(""windows"")] +static class Program +{ + public static void Main() + { + if (OperatingSystemHelper.IsWindowsVersionAtLeast(10)) + { + Some.WindowsSpecificApi(); + } + else + { + [|Some.WindowsSpecificApi()|]; // should show 2 diagnostic + } + } +} + +static class Some +{ + [UnsupportedOSPlatform(""windows"")] + [SupportedOSPlatform(""windows10.0"")] + public static void WindowsSpecificApi() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsRule).WithLocation(16, 13) + .WithMessage("'Some.WindowsSpecificApi()' is supported on 'windows' 10.0 and later")); + } + + [Fact] + public async Task GuardedCalled_SimpleIf_NotWarns() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + [|M2()|]; + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 1, 2, 3)) + M2(); + } + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + + var vbSource = @" +Imports System.Runtime.Versioning +Imports System + +Public Class Test + Public Sub M1() + [|M2()|] + If OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 1, 2, 3) Then M2() + End Sub + + + Public Sub M2() + End Sub +End Class +" + MockAttributesVbSource + MockRuntimeApiSourceVb; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task GuardedCall_MultipleSimpleIfTests() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + [|M2()|]; + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 1, 2, 3)) + M2(); + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Linux"", 10, 1, 2, 3)) + [|M2()|]; + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + M2(); + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 8, 1, 2, 3)) + [|M2()|]; + } + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedWith_IsOSPlatformVersionAtLeast_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 11)) + { + M2(); + } + else + { + [|M2()|]; + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedWith_AlternativeOf_IsOSPlatformEarlierThan() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + [SupportedOSPlatform(""Windows"")] + void M1() + { + if(OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(10, 0)) + { + [|M2()|]; + M3(); + } + else + { + [|M2()|]; + [|M3()|]; + } + } + + [SupportedOSPlatform(""MacOs12.2.3"")] + void M2() + { + } + + [UnsupportedOSPlatform(""Windows10.0"")] + void M3 () + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedWith_Unsupported_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + [SupportedOSPlatform(""Windows"")] + void M1() + { + if(OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(10, 1, 2, 3)) + { + [|M2()|]; + M3(); + } + else + { + [|M2()|]; + [|M3()|]; + } + + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"",10,1,3)) + { + [|M3()|]; + M2(); + } + else + { + [|M2()|]; + [|M3()|]; + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } + [UnsupportedOSPlatform(""Windows10.1.2.3"")] + void M3 () + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + + var vbSource = @" +Imports System.Runtime.Versioning +Imports System + +Class Test + + Private Sub M1() + If OperatingSystemHelper.IsWindows() AndAlso Not OperatingSystemHelper.IsWindowsVersionAtLeast(10, 1, 2, 3) Then + [|M2()|] + M3() + Else + [|M2()|] + [|M3()|] + End If + + If OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"",10,1,3) Then + [|M3()|] + M2() + Else + [|M2()|] + [|M3()|] + End If + End Sub + + + Private Sub M2() + End Sub + + + Private Sub M3() + End Sub +End Class +" + MockAttributesVbSource + MockRuntimeApiSourceVb; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task OsDependentEnumValue_GuardedCall_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test2 +{ + public void M1() + { + PlatformEnum val = [|PlatformEnum.Windows10|]; + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10)) + { + M2(PlatformEnum.Windows10); + } + else + { + M2([|PlatformEnum.Windows10|]); + } + M2([|PlatformEnum.Linux48|]); + M2(PlatformEnum.NoPlatform); + } + public PlatformEnum M2(PlatformEnum option) + { + return option; + } +} + +public enum PlatformEnum +{ + [SupportedOSPlatform(""Windows10.0"")] + Windows10, + [SupportedOSPlatform(""Linux4.8"")] + Linux48, + NoPlatform +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task OsDependentProperty_GuardedCall_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + [UnsupportedOSPlatform(""Windows8.1"")] + public string RemovedProperty { get; set;} + + public void M1() + { + if(OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(8, 0, 19222)) + { + RemovedProperty = ""Hello""; + string s = RemovedProperty; + M2(RemovedProperty); + } + else + { + [|RemovedProperty|] = ""Hello""; + string s = [|RemovedProperty|]; + M2([|RemovedProperty|]); + } + } + + public string M2(string option) + { + return option; + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); + } + + [Fact] + public async Task OsDependentConstructorOfClass_GuardedCall_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + C instance = new C(); + instance.M2(); + } + else + { + C instance2 = [|new C()|]; + instance2.M2(); + } + } +} + +public class C +{ + [SupportedOSPlatform(""Windows10.1.2.3"")] + public C() + { + } + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task ConstructorAndMethodOfOsDependentClass_GuardedCall_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + OsDependentClass odc = new OsDependentClass(); + odc.Method2(); + } + else + { + OsDependentClass odc2 = [|new OsDependentClass()|]; + [|odc2.Method2()|]; + } + } +} +[SupportedOSPlatform(""Windows10.1.2.3"")] +public class OsDependentClass +{ + public void Method2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + + var vbSource = @" +Imports System.Runtime.Versioning +Imports System + +Public Class Test + Public Sub M1() + If OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2) Then + Dim odc As OsDependentClass = New OsDependentClass() + odc.M2() + Else + Dim odc2 As OsDependentClass = [|New OsDependentClass()|] + [|odc2.M2()|] + End If + End Sub +End Class + + +Public Class OsDependentClass + Public Sub M2() + End Sub +End Class +" + MockRuntimeApiSourceVb + MockAttributesVbSource; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task LocalFunctionCallsOsDependentMember_GuardedCall_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + void Test() + { + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2, 1)) + { + M2(); + } + else + { + [|M2()|]; + } + } + Test(); + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact(Skip = "TODO: Failing because we cannot detect the correct local invocation being called")] + public async Task LocalFunctionCallsPlatformDependentMember_InvokedFromDifferentContext() + { + var source = @" +using System.Runtime.Versioning; +using System; +public class Test +{ + void M() + { + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + LocalM(true); + } + LocalM(false); + return; + + void LocalM(bool suppressed) + { + if (suppressed) + { + WindowsOnlyMethod(); + } + else + { + [|WindowsOnlyMethod()|]; + } + + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + WindowsOnlyMethod(); + } + + if (OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(10,0)) + { + UnsupportedWindows10(); + } + else + { + [|UnsupportedWindows10()|]; + } + } + } + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void WindowsOnlyMethod() + { + } + [UnsupportedOSPlatform(""Windows10.0"")] + public void UnsupportedWindows10() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task LocalFunctionCallsPlatformDependentMember_InvokedFromNotGuardedDifferentContext() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + void M() + { + LocalM(); + + if (!OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Linux"", 10, 2)) + { + LocalM(); + } + + LocalM(); + return; + + void LocalM() + { + [|WindowsOnlyMethod()|]; + + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + WindowsOnlyMethod(); + } + else + { + [|WindowsOnlyMethod()|]; + } + + if (OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(10,0)) + { + UnsupportedWindows10(); + } + else + { + [|UnsupportedWindows10()|]; + } + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void WindowsOnlyMethod() + { + } + + [UnsupportedOSPlatform(""Windows10.0"")] + public void UnsupportedWindows10() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); + } + + [Fact] + public async Task LocalFunctionCallsPlatformDependentMember_InvokedFromGuardedDifferentContext() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + void M() + { + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + LocalM(); + } + + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + LocalM(); + } + + return; + + void LocalM() + { + WindowsOnlyMethod(); + + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + WindowsOnlyMethod(); + } + else + { + WindowsOnlyMethod(); + } + + if (OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(10,0)) + { + UnsupportedWindows10(); + } + else + { + [|UnsupportedWindows10()|]; + } + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void WindowsOnlyMethod() + { + } + + [UnsupportedOSPlatform(""Windows10.0"")] + public void UnsupportedWindows10() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); + } + + [Fact] + public async Task LambdaCallsOsDependentMember_GuardedCall_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2, 1)) + { + void Test() => M2(); + Test(); + } + else + { + void Test() => [|M2()|]; + Test(); + } + + Action action = () => + { + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2, 1)) + { + M2(); + } + else + { + [|M2()|]; + } + }; + action.Invoke(); + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task OsDependentEventAccessed_GuardedCall_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public delegate void Del(); + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public event Del SampleEvent; + + public void M1() + { + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 11)) + { + SampleEvent += M3; + } + else + { + [|SampleEvent|] += M4; + } + } + + public void M2() + { + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 11)) + { + SampleEvent?.Invoke(); + } + else + { + [|SampleEvent|]?.Invoke(); + } + } + + public void M3() + { + } + public void M4() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task OsDependentMethodAssignedToDelegate_GuardedCall_SimpleIfElse() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public delegate void Del(); + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void DelegateMethod() + { + } + public void M1() + { + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 11, 0)) + { + Del handler = DelegateMethod; + handler(); + } + else + { + Del handler = [|DelegateMethod|]; + handler(); + } + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedCall_SimpleIfElseIfElseTest() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + [|M2()|]; + + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 11)) + { + M2(); + } + else if(OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(8, 0, 19222)) + { + [|M2()|]; + } + else if(OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222)) + { + [|M2()|]; + } + else if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 12)) + { + M2(); + } + else + { + [|M2()|]; + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedCall_SimpleIfElseTestWithNegation() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + [|M2()|]; + if(!OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 1, 2, 3)) + [|M2()|]; + else + M2(); + } + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedCall_SimpleIfElseIfElseTestWithNegation() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + [|M2()|]; + if(!OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 1, 2, 3)) + [|M2()|]; + else if(OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222)) + M2(); + else + M2(); + } + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedCall_SimpleIfTestWithNegationAndReturn() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + [|M2()|]; + if(!OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 1, 2, 3)) + return; + M2(); + } + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + + var vbSource = @" +Imports System.Runtime.Versioning +Imports System + +Public Class Test + Public Sub M1() + [|M2()|] + If Not OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 1, 2, 3) Then Return + M2() + End Sub + + + Public Sub M2() + End Sub +End Class +" + MockAttributesVbSource + MockRuntimeApiSourceVb; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task GuardedCall_SimpleIfTestWithLogicalAnd() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2) && + (OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222))) + { + M2(); + } + + if((OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222)) && + OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 12)) + { + M2(); + } + + if((OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222)) && + OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Linux"", 12)) + { + [|M2()|]; + } + + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2) && 1 == 1) + { + M2(); + } + + [|M2()|]; + } + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedCall_SimpleIfElseTestWithLogicalAnd() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + [|M2()|]; + + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2) && + (OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222))) + { + M2(); + } + else + { + [|M2()|]; + } + + if((OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222)) && + OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Linux"", 12)) + { + [|M2()|]; + } + else + { + [|M2()|]; + } + } + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedCall_SimpleIfTestWithLogicalOr() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2) || + (OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222))) + { + [|M2()|]; + } + + if(OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222) || + OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + M2(); + } + + if(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Linux"", 12) || + OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + [|M2()|]; + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedCall_SimpleIfElseTestWithLogicalOr() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2) || + OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 11)) + { + M2(); + } + else + { + [|M2()|]; + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedCall_SimpleIfElseIfElseTestWithLogicalOr() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2) || + OperatingSystemHelper.IsLinux()) + { + [|M2()|]; //12 + } + else if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 11)) + { + M2(); + } + else + [|M2()|]; + + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2) || + (OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222))) + { + [|M2()|]; + } + else + { + [|M2()|]; + } + + if((OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222)) || + OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + { + [|M2()|]; //34 + } + else + { + [|M2()|]; + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedCall_SimpleIfElseTestWithLogicalOrAnd() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + if ((!OperatingSystemHelper.IsWindows() || OperatingSystemHelper.IsWindowsVersionAtLeast(10, 0, 1903)) && + (OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(10, 0, 2004))) + { + M2(); + } + else + { + [|M2()|]; // Two diagnostics expected + } + } + + [UnsupportedOSPlatform(""windows"")] + [SupportedOSPlatform(""windows10.0.1903"")] + [UnsupportedOSPlatform(""windows10.0.2004"")] + void M2() + { + } +}" ++ MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsRule).WithLocation(16, 13).WithMessage("'Test.M2()' is unsupported on 'windows'")); + + var vbSource = @" +Imports System.Runtime.Versioning +Imports System + +Class Test + Private Sub M1() + If (Not OperatingSystemHelper.IsWindows() OrElse OperatingSystemHelper.IsWindowsVersionAtLeast(10, 0, 1903)) AndAlso (OperatingSystemHelper.IsWindows() AndAlso Not OperatingSystemHelper.IsWindowsVersionAtLeast(10, 0, 2004)) Then + M2() + Else + [|M2()|] + End If + End Sub + + + + + Private Sub M2() + End Sub +End Class +" + MockRuntimeApiSourceVb + MockAttributesVbSource; + await VerifyAnalyzerAsyncVb(vbSource, s_msBuildPlatforms, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsRule).WithLocation(10, 13).WithMessage("'Test.M2()' is unsupported on 'Windows'")); + } + + [Fact] + public async Task GuardedWith_ControlFlowAndMultipleChecks() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 8)) + { + [|M2()|]; + + if (OperatingSystemHelper.IsWindows() && !OperatingSystemHelper.IsWindowsVersionAtLeast(12, 0, 19222)) + { + [|M2()|]; + } + else if (!OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2, 1)) + { + [|M2()|]; + } + else + { + M2(); + } + + [|M2()|]; + } + else + { + [|M2()|]; + } + + [|M2()|]; + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedWith_DebugAssertAnalysisTest() + { + var source = @" +using System.Diagnostics; +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + [|M2()|]; + + Debug.Assert(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)); + + M2(); + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + + var vbSource = @" +Imports System.Diagnostics +Imports System.Runtime.Versioning +Imports System + +Class Test + Private Sub M1() + [|M2()|] + Debug.Assert(OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) + M2() + End Sub + + + Private Sub M2() + End Sub +End Class +" + MockAttributesVbSource + MockRuntimeApiSourceVb; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task GuardedWith_ResultSavedInLocal() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + var x1 = OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 11); + var x2 = OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Linux"", 1); + + if (x1) + { + M2(); + } + + if (x1 || x2) + { + [|M2()|]; + } + + if (x2) + [|M2()|]; + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardedWith_VersionSavedInLocal() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + var v11 = 11; + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", v11)) + { + M2(); + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task PlatformSavedInLocal_NotYetSupported() // TODO do we want to support it? + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + var platform = ""Windows""; + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(platform, 11)) + { + [|M2()|]; + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task UnrelatedConditionCheckDoesNotInvalidateState() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1(bool flag1, bool flag2) + { + [|M2()|]; + + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 11)) + { + M2(); + + if (flag1 || flag2) + { + M2(); + } + else + { + M2(); + } + + M2(); + } + + if (flag1 || flag2) + { + [|M2()|]; + } + else + { + [|M2()|]; + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(source); + + var vbSource = @" +Imports System.Runtime.Versioning +Imports System + +Class Test + Private Sub M1(ByVal flag1 As Boolean, ByVal flag2 As Boolean) + [|M2()|] + + If OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 11) Then + M2() + + If flag1 OrElse flag2 Then + M2() + Else + M2() + End If + + M2() + End If + + If flag1 OrElse flag2 Then + [|M2()|] + Else + [|M2()|] + End If + End Sub + + + Private Sub M2() + End Sub +End Class +" + MockRuntimeApiSourceVb + MockAttributesVbSource; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task InterproceduralAnalysisTest() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + [|M2()|]; + + if (IsWindows11OrLater()) + { + M2(); + } + else + { + [|M2()|]; + } + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } + + bool IsWindows11OrLater() + { + return OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"",10,2,3,4); + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source, "dotnet_code_quality.interprocedural_analysis_kind = ContextSensitive"); + } + + [Fact(Skip = "TODO: Analysis value not returned, needs to be fixed")] + public async Task InterproceduralAnalysisTest_LogicalOr() + { + var source = @" +using System.Runtime.Versioning; +using System; + +class Test +{ + void M1() + { + [|M2()|]; + + if (IsWindows11OrLater()) + { + M2(); + } + + [|M2()|]; + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + void M2() + { + } + + bool IsWindows11OrLater() + { + return OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"",10,2,3,4) || + OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"",11); + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source, "dotnet_code_quality.interprocedural_analysis_kind = ContextSensitive"); + } + + private readonly string TargetTypesForTest = @" +namespace PlatformCompatDemo.SupportedUnupported +{ + public class TypeWithoutAttributes + { + [UnsupportedOSPlatform(""windows"")] + public void TypeWithoutAttributes_FunctionUnsupportedOnWindows() { } + + [UnsupportedOSPlatform(""browser"")] + public void TypeWithoutAttributes_FunctionUnsupportedOnBrowser() { } + + [UnsupportedOSPlatform(""windows10.0"")] + public void TypeWithoutAttributes_FunctionUnsupportedOnWindows10() { } + + [UnsupportedOSPlatform(""windows""), UnsupportedOSPlatform(""browser"")] + public void TypeWithoutAttributes_FunctionUnsupportedOnWindowsAndBrowser() { } + + [UnsupportedOSPlatform(""windows10.0""), UnsupportedOSPlatform(""browser"")] + public void TypeWithoutAttributes_FunctionUnsupportedOnWindows10AndBrowser() { } + + [UnsupportedOSPlatform(""windows""), SupportedOSPlatform(""windows11.0"")] + public void TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11() { } + + [UnsupportedOSPlatform(""windows""), SupportedOSPlatform(""windows11.0""), UnsupportedOSPlatform(""windows12.0"")] + public void TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12() { } + + [UnsupportedOSPlatform(""windows""), SupportedOSPlatform(""windows11.0""), UnsupportedOSPlatform(""windows12.0""), SupportedOSPlatform(""windows13.0"")] + public void TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12SupportedOnWindows13() { } + + [SupportedOSPlatform(""windows"")] + public void TypeWithoutAttributes_FunctionSupportedOnWindows() { } + + [SupportedOSPlatform(""windows10.0"")] + public void TypeWithoutAttributes_FunctionSupportedOnWindows10() { } + + [SupportedOSPlatform(""browser"")] + public void TypeWithoutAttributes_FunctionSupportedOnBrowser() { } + + [SupportedOSPlatform(""windows""), SupportedOSPlatform(""browser"")] + public void TypeWithoutAttributes_FunctionSupportedOnWindowsAndBrowser() { } + + [SupportedOSPlatform(""windows10.0""), SupportedOSPlatform(""browser"")] + public void TypeWithoutAttributes_FunctionSupportedOnWindows10AndBrowser() { } + } + + [UnsupportedOSPlatform(""windows"")] + public class TypeUnsupportedOnWindows { + [UnsupportedOSPlatform(""browser"")] // more restrictive should be OK + public void TypeUnsupportedOnWindows_FunctionUnsupportedOnBrowser() { } + + [UnsupportedOSPlatform(""windows11.0"")] + public void TypeUnsupportedOnWindows_FunctionUnsupportedOnWindows11() { } + + [UnsupportedOSPlatform(""windows11.0""), UnsupportedOSPlatform(""browser"")] + public void TypeUnsupportedOnWindows_FunctionUnsupportedOnWindows11AndBrowser() { } + + [SupportedOSPlatform(""windows11.0"")] + public void TypeUnsupportedOnWindows_FunctionSupportedOnWindows11() { } + + [SupportedOSPlatform(""windows11.0""), UnsupportedOSPlatform(""windows12.0"")] + public void TypeUnsupportedOnWindows_FunctionSupportedOnWindows11UnsupportedOnWindows12() { } + + [SupportedOSPlatform(""windows11.0""), UnsupportedOSPlatform(""windows12.0""), SupportedOSPlatform(""windows13.0"")] + public void TypeUnsupportedOnWindows_FunctionSupportedOnWindows11UnsupportedOnWindows12SupportedOnWindows13() { } + } + + [UnsupportedOSPlatform(""browser"")] + public class TypeUnsupportedOnBrowser + { + [UnsupportedOSPlatform(""windows"")] // more restrictive should be OK + public void TypeUnsupportedOnBrowser_FunctionUnsupportedOnWindows() { } + + [UnsupportedOSPlatform(""windows10.0"")] // more restrictive should be OK + public void TypeUnsupportedOnBrowser_FunctionUnsupportedOnWindows10() { } + + [SupportedOSPlatform(""browser"")] + public void TypeUnsupportedOnBrowser_FunctionSupportedOnBrowser() { } + } + + [UnsupportedOSPlatform(""windows10.0"")] + public class TypeUnsupportedOnWindows10 + { + [UnsupportedOSPlatform(""browser"")] // more restrictive should be OK + public void TypeUnsupportedOnWindows10_FunctionUnsupportedOnBrowser() { } + + [UnsupportedOSPlatform(""windows11.0"")] + public void TypeUnsupportedOnWindows10_FunctionUnsupportedOnWindows11() { } + + [UnsupportedOSPlatform(""windows11.0""), UnsupportedOSPlatform(""browser"")] + public void TypeUnsupportedOnWindows10_FunctionUnsupportedOnWindows11AndBrowser() { } + } + + [UnsupportedOSPlatform(""windows""), UnsupportedOSPlatform(""browser"")] + public class TypeUnsupportedOnWindowsAndBrowser + { + [UnsupportedOSPlatform(""windows11.0"")] + public void TypeUnsupportedOnWindowsAndBrowser_FunctionUnsupportedOnWindows11() { } + } + + [UnsupportedOSPlatform(""windows10.0""), UnsupportedOSPlatform(""browser"")] + public class TypeUnsupportedOnWindows10AndBrowser + { + [UnsupportedOSPlatform(""windows11.0"")] + public void TypeUnsupportedOnWindows10AndBrowser_FunctionUnsupportedOnWindows11() { } + } + + [UnsupportedOSPlatform(""windows""), SupportedOSPlatform(""windows11.0"")] + public class TypeUnsupportedOnWindowsSupportedOnWindows11 + { + [UnsupportedOSPlatform(""windows12.0"")] + public void TypeUnsupportedOnWindowsSupportedOnWindows11_FunctionUnsupportedOnWindows12() { } + + [UnsupportedOSPlatform(""windows12.0""), SupportedOSPlatform(""windows13.0"")] + public void TypeUnsupportedOnWindowsSupportedOnWindows11_FunctionUnsupportedOnWindows12SupportedOnWindows13() { } + } + + [UnsupportedOSPlatform(""windows""), SupportedOSPlatform(""windows11.0""), UnsupportedOSPlatform(""windows12.0"")] + public class TypeUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12 + { + [SupportedOSPlatform(""windows13.0"")] + public void TypeUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12_FunctionSupportedOnWindows13() { } + } + [SupportedOSPlatform(""windows"")] + public class TypeSupportedOnWindows { + [SupportedOSPlatform(""browser"")] + public void TypeSupportedOnWindows_FunctionSupportedOnBrowser() { } + + [SupportedOSPlatform(""windows11.0"")] // more restrictive should be OK + public void TypeSupportedOnWindows_FunctionSupportedOnWindows11() { } + + [SupportedOSPlatform(""windows11.0""), SupportedOSPlatform(""browser"")] + public void TypeSupportedOnWindows_FunctionSupportedOnWindows11AndBrowser() { } + } + [SupportedOSPlatform(""browser"")] + public class TypeSupportedOnBrowser + { + [SupportedOSPlatform(""windows"")] + public void TypeSupportedOnBrowser_FunctionSupportedOnWindows() { } + + [SupportedOSPlatform(""windows11.0"")] + public void TypeSupportedOnBrowser_FunctionSupportedOnWindows11() { } + } + + [SupportedOSPlatform(""windows10.0"")] + public class TypeSupportedOnWindows10 + { + [SupportedOSPlatform(""windows"")] // less restrictive should be OK + public void TypeSupportedOnWindows10_FunctionSupportedOnWindows() { } + + [SupportedOSPlatform(""browser"")] + public void TypeSupportedOnWindows10_FunctionSupportedOnBrowser() { } + + [SupportedOSPlatform(""windows11.0"")] // more restrictive should be OK + public void TypeSupportedOnWindows10_FunctionSupportedOnWindows11() { } + + [SupportedOSPlatform(""windows11.0""), SupportedOSPlatform(""browser"")] + public void TypeSupportedOnWindows10_FunctionSupportedOnWindows11AndBrowser() { } + } + + + [SupportedOSPlatform(""windows""), SupportedOSPlatform(""browser"")] + public class TypeSupportedOnWindowsAndBrowser + { + [SupportedOSPlatform(""windows11.0"")] // more restrictive should be OK + public void TypeSupportedOnWindowsAndBrowser_FunctionSupportedOnWindows11() { } + } + + [SupportedOSPlatform(""windows10.0""), SupportedOSPlatform(""browser"")] + public class TypeSupportedOnWindows10AndBrowser + { + [SupportedOSPlatform(""windows11.0"")] // more restrictive should be OK + public void TypeSupportedOnWindows10AndBrowser_FunctionSupportedOnWindows11() { } + } +} +"; + + private readonly string MockOperatingSystemApiSource = @" +namespace System +{ + public sealed class OperatingSystemHelper + { +#pragma warning disable CA1801, IDE0060 // Review unused parameters + public static bool IsOSPlatform(string platform) { return true; } + public static bool IsOSPlatformVersionAtLeast(string platform, int major, int minor = 0, int build = 0, int revision = 0) { return true; } + public static bool IsBrowser() { return true; } + public static bool IsLinux() { return true; } + public static bool IsFreeBSD() { return true; } + public static bool IsFreeBSDVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0) { return true; } + public static bool IsAndroid() { return true; } + public static bool IsAndroidVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0) { return true; } + public static bool IsIOS() { return true; } + public static bool IsIOSVersionAtLeast(int major, int minor = 0, int build = 0) { return true; } + public static bool IsMacOS() { return true; } + public static bool IsMacOSVersionAtLeast(int major, int minor = 0, int build = 0) { return true; } + public static bool IsTvOS() { return true; } + public static bool IsTvOSVersionAtLeast(int major, int minor = 0, int build = 0) { return true; } + public static bool IsWatchOS() { return true; } + public static bool IsWatchOSVersionAtLeast(int major, int minor = 0, int build = 0) { return true; } + public static bool IsWindows() { return true; } + public static bool IsWindowsVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0) { return true; } +#pragma warning restore CA1801, IDE0060 // Review unused parameters + } +}"; + + private readonly string MockRuntimeApiSourceVb = @" +Namespace System + Public NotInheritable Class OperatingSystemHelper + Public Shared Function IsOSPlatform(ByVal platform As String) As Boolean + Return True + End Function + + Public Shared Function IsOSPlatformVersionAtLeast(ByVal platform As String, ByVal major As Integer, ByVal Optional minor As Integer = 0, ByVal Optional build As Integer = 0, ByVal Optional revision As Integer = 0) As Boolean + Return True + End Function + + Public Shared Function IsBrowser() As Boolean + Return True + End Function + + Public Shared Function IsLinux() As Boolean + Return True + End Function + + Public Shared Function IsFreeBSD() As Boolean + Return True + End Function + + Public Shared Function IsFreeBSDVersionAtLeast(ByVal major As Integer, ByVal Optional minor As Integer = 0, ByVal Optional build As Integer = 0, ByVal Optional revision As Integer = 0) As Boolean + Return True + End Function + + Public Shared Function IsAndroid() As Boolean + Return True + End Function + + Public Shared Function IsAndroidVersionAtLeast(ByVal major As Integer, ByVal Optional minor As Integer = 0, ByVal Optional build As Integer = 0, ByVal Optional revision As Integer = 0) As Boolean + Return True + End Function + + Public Shared Function IsIOS() As Boolean + Return True + End Function + + Public Shared Function IsIOSVersionAtLeast(ByVal major As Integer, ByVal Optional minor As Integer = 0, ByVal Optional build As Integer = 0) As Boolean + Return True + End Function + + Public Shared Function IsMacOS() As Boolean + Return True + End Function + + Public Shared Function IsMacOSVersionAtLeast(ByVal major As Integer, ByVal Optional minor As Integer = 0, ByVal Optional build As Integer = 0) As Boolean + Return True + End Function + + Public Shared Function IsTvOS() As Boolean + Return True + End Function + + Public Shared Function IsTvOSVersionAtLeast(ByVal major As Integer, ByVal Optional minor As Integer = 0, ByVal Optional build As Integer = 0) As Boolean + Return True + End Function + + Public Shared Function IsWatchOS() As Boolean + Return True + End Function + + Public Shared Function IsWatchOSVersionAtLeast(ByVal major As Integer, ByVal Optional minor As Integer = 0, ByVal Optional build As Integer = 0) As Boolean + Return True + End Function + + Public Shared Function IsWindows() As Boolean + Return True + End Function + + Public Shared Function IsWindowsVersionAtLeast(ByVal major As Integer, ByVal Optional minor As Integer = 0, ByVal Optional build As Integer = 0, ByVal Optional revision As Integer = 0) As Boolean + Return True + End Function + End Class +End Namespace +"; + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzerTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzerTests.cs new file mode 100644 index 0000000000..755843c515 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatabilityAnalyzerTests.cs @@ -0,0 +1,1708 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.InteropServices.PlatformCompatabilityAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.InteropServices.PlatformCompatabilityAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.InteropServices.UnitTests +{ + public partial class PlatformCompatabilityAnalyzerTests + { + private const string s_msBuildPlatforms = "build_property._SupportedPlatformList=windows,browser, ios"; + + [Fact(Skip = "TODO need to be fixed: Test for for wrong arguments, not sure how to report the Compiler error diagnostic")] + public async Task TestOsPlatformAttributesWithNonStringArgument() + { + var csSource = @" +using System.Runtime.Versioning; +using System.Runtime.InteropServices; + +public class Test +{ + [[|SupportedOSPlatform(""Linux"", ""Windows"")|]] + public void MethodWithTwoArguments() + { + } + [UnsupportedOSPlatform([|new string[]{""Linux"", ""Windows""}|])] + public void MethodWithArrayArgument() + { + } +} +" + MockAttributesCsSource; + + await VerifyAnalyzerAsyncCs(csSource); + } + + [Fact] + public async Task OsDependentMethodsCalledWarns() + { + var csSource = @" +using System.Runtime.Versioning; + +public class Test +{ + [SupportedOSPlatform(""Linux"")] + public void M1() + { + [|WindowsOnly()|]; + [|Unsupported()|]; + } + + [UnsupportedOSPlatform(""Linux4.1"")] + public void Unsupported() + { + } + [SupportedOSPlatform(""Windows10.1.1.1"")] + public void WindowsOnly() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(csSource); + + var vbSource = @" +Imports System.Runtime.Versioning + +Public Class Test + + Public Sub M1() + [|WindowsOnly()|] + [|Unsupported()|] + End Sub + + + Public Sub WindowsOnly() + End Sub + + + Public Sub Unsupported() + End Sub +End Class +" + MockAttributesVbSource; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task WrongPlatformStringsShouldHandledGracefully() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + public void M1() + { + Windows10(); + Windows1_2_3_4_5(); + [|UnsupportedOSPlatformIosDash4_1()|]; + [|UnsupportedOSPlatformIosStar4_1()|]; + [|SupportedLinu4_1()|]; + UnsupportedOSPlatformWithNullString(); + UnsupportedWithEmptyString(); + [|NotForWindows()|]; + } + + [UnsupportedOSPlatform(""Windows"")] + public void NotForWindows() + { + } + [SupportedOSPlatform(""Windows10"")] + public void Windows10() + { + } + [SupportedOSPlatform(""Windows1.2.3.4.5"")] + public void Windows1_2_3_4_5() + { + } + [SupportedOSPlatform(""Ios-4.1"")] + public void UnsupportedOSPlatformIosDash4_1() + { + } + [SupportedOSPlatform(""Ios*4.1"")] + public void UnsupportedOSPlatformIosStar4_1() + { + } + [SupportedOSPlatform(null)] + public void UnsupportedOSPlatformWithNullString() + { + } + [SupportedOSPlatform(""Linu4.1"")] + public void SupportedLinu4_1() + { + } + [UnsupportedOSPlatform("""")] + public void UnsupportedWithEmptyString() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); + } + + [Fact] + public async Task OsDependentPropertyCalledWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + [SupportedOSPlatform(""Windows10.1.1"")] + public string WindowsStringProperty { get; set; } + [UnsupportedOSPlatform(""Linux4.1"")] + public byte UnsupportedProperty { get; } + [SupportedOSPlatform(""Linux"")] + public void M1() + { + [|WindowsStringProperty|] = ""Hello""; + string s = [|WindowsStringProperty|]; + M2([|WindowsStringProperty|]); + M3([|UnsupportedProperty|]); + } + public string M2(string option) + { + return option; + } + public int M3(int option) + { + return option; + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Theory] + [MemberData(nameof(Create_AttributeProperty_WithCondtions))] + public async Task OsDependentPropertyConditionalCheckWarns(string attribute, string property, string condition, string setter, string getter) + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + [" + attribute + @"(""Windows10.1.1"")] + public " + property + @" { get; set; } + + public void M1() + { + [|" + setter + @"; + var s = [|" + getter + @"|]; + bool check = s " + condition + @"; + M2([|" + getter + @"|]); + } + public object M2(object option) + { + return option; + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); + } + + public static IEnumerable Create_AttributeProperty_WithCondtions() + { + yield return new object[] { "SupportedOSPlatform", "string StringProperty", " == [|StringProperty|]", @"StringProperty|] = ""Hello""", "StringProperty" }; + yield return new object[] { "UnsupportedOSPlatform", "int UnsupportedProperty", " <= [|UnsupportedProperty|]", "UnsupportedProperty|] = 3", "UnsupportedProperty" }; + } + + [Fact] + public async Task OsDependentEnumValueCalledWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test2 +{ + public void M1() + { + PlatformEnum val = [|PlatformEnum.Windows10|]; + M2([|PlatformEnum.Windows10|]); + M2([|PlatformEnum.Linux48|]); + M2(PlatformEnum.NoPlatform); + } + public PlatformEnum M2(PlatformEnum option) + { + return option; + } +} + +public enum PlatformEnum +{ + [SupportedOSPlatform(""windows10.0"")] + Windows10, + [SupportedOSPlatform(""linux4.8"")] + Linux48, + NoPlatform +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); + } + + [Fact] + public async Task OsDependentEnumConditionalCheckNotWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test2 +{ + public void M1() + { + PlatformEnum val = [|PlatformEnum.Windows10|]; + if (val == PlatformEnum.Windows10) + return; + M2([|PlatformEnum.Windows10|]); + M2([|PlatformEnum.Linux48|]); + M2(PlatformEnum.NoPlatform); + } + public PlatformEnum M2(PlatformEnum option) + { + return option; + } +} + +public enum PlatformEnum +{ + [SupportedOSPlatform(""Windows10.0"")] + Windows10, + [SupportedOSPlatform(""Linux4.8"")] + Linux48, + NoPlatform +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + + var vbSource = @" +Imports System.Runtime.Versioning + +Public Class Test2 + Public Sub M1() + Dim val As PlatformEnum = [|PlatformEnum.Windows10|] + If val = [|PlatformEnum.Windows10|] Then Return + M2([|PlatformEnum.Windows10|]) + M2([|PlatformEnum.Linux48|]) + M2(PlatformEnum.NoPlatform) + End Sub + + Public Sub M2(ByVal [option] As PlatformEnum) + End Sub +End Class + +Public Enum PlatformEnum + + Windows10 + < SupportedOSPlatform(""Linux4.8"") > + Linux48 + NoPlatform +End Enum +" + MockAttributesVbSource; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task OsDependentFieldCalledWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + [SupportedOSPlatform(""Windows10.1.1.1"")] + string WindowsStringField; + [SupportedOSPlatform(""Windows10.1.1.1"")] + public int WindowsIntField { get; set; } + public void M1() + { + Test test = new Test(); + [|WindowsStringField|] = ""Hello""; + string s = [|WindowsStringField|]; + M2([|test.WindowsStringField|]); + M2([|WindowsStringField|]); + M3([|WindowsIntField|]); + } + public string M2(string option) + { + return option; + } + public int M3(int option) + { + return option; + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact(Skip = "TODO: enable when tests could consume new TargetPlatform attribute, (preview 8)")] + public async Task MethodWithTargetPlatrofrmAttributeDoesNotWarn() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + public void M1() + { + M2(); + } + [TargetPlatform(""Windows10.1.1.1"")] + public void M2() + { + } +} +" + MockAttributesCsSource; + await VerifyCS.VerifyAnalyzerAsync(source); + } + + [Fact] + public async Task OsDependentMethodCalledFromInstanceWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + private B field = new B(); + public void M1() + { + [|field.M2()|]; + } +} +public class B +{ + [SupportedOSPlatform(""Windows10.1.1.1"")] + public void M2() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task OsDependentMethodCalledFromOtherNsInstanceWarns() + { + var source = @" +using System.Runtime.Versioning; +using Ns; + +public class Test +{ + private B field = new B(); + public void M1() + { + [|field.M2()|]; + } +} + +namespace Ns +{ + public class B + { + [SupportedOSPlatform(""Windows10.1.1.1"")] + public void M2() + { + } + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + + var vbSource = @" +Imports System.Runtime.Versioning +Imports Ns + +Public Class Test + Private field As B = New B() + + Public Sub M1() + [|field.M2()|] + End Sub +End Class + +Namespace Ns + Public Class B + + Public Sub M2() + End Sub + End Class +End Namespace +" + MockAttributesVbSource; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task OsDependentConstructorOfClassUsedCalledWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + public void M1() + { + C instance = [|new C()|]; + instance.M2(); + } +} + +public class C +{ + [SupportedOSPlatform(""Windows10.1.2.3"")] + public C() + { + } + public void M2() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task ConstructorAndMethodOfOsDependentClassCalledWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + public void M1() + { + OsDependentClass odc = [|new OsDependentClass()|]; + [|odc.M2()|]; + } +} +[SupportedOSPlatform(""Windows10.1.2.3"")] +public class OsDependentClass +{ + public void M2() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + + var vbSource = @" +Imports System.Runtime.Versioning + +Public Class Test + Public Sub M1() + Dim odc As OsDependentClass = [|New OsDependentClass()|] + [|odc.M2()|] + End Sub +End Class + + +Public Class OsDependentClass + Public Sub M2() + End Sub +End Class +" + MockAttributesVbSource; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task LocalFunctionCallsOsDependentMemberWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + public void M1() + { + void Test() + { + [|M2()|]; + } + Test(); + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task LocalGuardedFunctionCallsOsDependentMemberNotWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + [SupportedOSPlatform(""Windows10.2"")] + public void M1() + { + void Test() + { + M2(); + } + Test(); + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task LambdaCallsOsDependentMemberWarns() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class Test +{ + public void M1() + { + void Test() => [|M2()|]; + Test(); + + Action action = () => + { + [|M2()|]; + }; + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task AttributedLambdaCallsOsDependentMemberNotWarn() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class C +{ + [SupportedOSPlatform(""Windows10.13"")] + public void M1() + { + void Test() => M2(); + Test(); + + Action action = () => + { + M2(); + }; + } + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void M2() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task OsDependentEventAccessedWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + public delegate void Del(); + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public event Del SampleEvent; + + public void M1() + { + [|SampleEvent|] += M3; + M2(); + } + + public void M2() + { + [|SampleEvent|]?.Invoke(); + } + + public void M3() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + + var vbSource = @" +Imports System.Runtime.Versioning + +Public Class Test + Public Delegate Sub Del() + + Public Event SampleEvent As Del + + Public Sub M1() + AddHandler [|SampleEvent|], AddressOf M3 + M2() + End Sub + + Public Sub M2() + RaiseEvent [|SampleEvent|] + End Sub + + Public Sub M3() + End Sub +End Class" ++ MockAttributesVbSource; + await VerifyAnalyzerAsyncVb(vbSource); + } + + [Fact] + public async Task OsDependentMethodAssignedToDelegateWarns() + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + public delegate void Del(); // The attribute not supported on delegates, so no tests for that + + [SupportedOSPlatform(""Windows10.1.2.3"")] + public void DelegateMethod() + { + } + public void M1() + { + Del handler = [|DelegateMethod|]; + handler(); + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task CallerSupportsSubsetOfTarget() + { + var source = @" +using System.Runtime.Versioning; + +namespace CallerSupportsSubsetOfTarget +{ + class Caller + { + [SupportedOSPlatform(""windows"")] + public static void Test() + { + Target.SupportedOnWindows(); + [|Target.SupportedOnBrowser()|]; + Target.SupportedOnWindowsAndBrowser(); + } + } + + class Target + { + [SupportedOSPlatform(""windows"")] + public static void SupportedOnWindows() { } + + [SupportedOSPlatform(""browser"")] + public static void SupportedOnBrowser() { } + [SupportedOSPlatform(""browser""), SupportedOSPlatform(""windows"")] + public static void SupportedOnWindowsAndBrowser() { } + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task CallerUnsupportsNonSubsetOfTargetSupport() + { + var source = @" +using System.Runtime.Versioning; + +namespace CallerUnsupportsNonSubsetOfTarget +{ + class Caller + { + [UnsupportedOSPlatform(""browser"")] + public static void TestWithBrowserUnsupported() + { + [|Target.UnsupportedOnWindowsUntilWindows11()|]; + } + } + class Target + { + [UnsupportedOSPlatform(""windows""), SupportedOSPlatform(""windows11.0"")] + public static void UnsupportedOnWindowsUntilWindows11() { } + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); + } + + [Fact] + public async Task CallerUnsupportsSubsetOfTargetUsupportedFirstThenSupportsNotWarn() + { + var source = @" +using System.Runtime.Versioning; + +namespace CallerUnsupportsSubsetOfTarget +{ + class Caller + { + [UnsupportedOSPlatform(""windows"")] + public void TestUnsupportedOnWindows() + { + // Call site unsupporting Windows, means the call site supports all other platforms + // It is calling into code that was NOT supported only on Windows, but eventually added support, + // as it was only not supported window supporting it later doesn' matter for call site that is + // not supporting windows at all, so it shouldn't raise diagnostic + TargetUnsupportedOnWindows.FunctionSupportedOnWindows1(); // should not warn + TargetUnsupportedOnWindows.FunctionSupportedOnWindowsSameVersion(); + } + } + [UnsupportedOSPlatform(""windows"")] + class TargetUnsupportedOnWindows + { + [SupportedOSPlatform(""windows1.0"")] + public static void FunctionSupportedOnWindows1() { } + + [SupportedOSPlatform(""windows"")] + public static void FunctionSupportedOnWindowsSameVersion() { } + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); + } + + [Fact] + public async Task CallerUnsupportsNonSubsetOfTargetUnsupportedFirstSupportsWarns() + { + var source = @" +using System.Runtime.Versioning; + +namespace CallerUnsupportsNonSubsetOfTarget +{ + class Caller + { + [UnsupportedOSPlatform(""browser"")] + public void TestUnsupportedOnWindows() + { + [|TargetUnsupportedOnWindows.FunctionSupportedOnWindows1()|]; + [|TargetUnsupportedOnWindows.FunctionSupportedOnWindowsSameVersion()|]; + } + } + [UnsupportedOSPlatform(""windows"")] + class TargetUnsupportedOnWindows + { + [SupportedOSPlatform(""windows1.0"")] + public static void FunctionSupportedOnWindows1() { } + + [SupportedOSPlatform(""windows"")] + public static void FunctionSupportedOnWindowsSameVersion() { } + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); + } + + [Fact] + public async Task CallerSupportsSupersetOfTarget_AnotherScenario() + { + var source = @" +using System.Runtime.Versioning; + +namespace CallerSupportsSubsetOfTarget +{ + class Caller + { + [UnsupportedOSPlatform(""browser"")] + public static void TestWithBrowserUnsupported() + { + [|Target.SupportedOnWindows()|]; + [|Target.SupportedOnBrowser()|]; + [|Target.SupportedOnWindowsAndBrowser()|]; // two diagnostics expected + + [|Target.UnsupportedOnWindows()|]; + [|Target.UnsupportedOnWindows11()|]; + Target.UnsupportedOnBrowser(); + [|Target.UnsupportedOnWindowsAndBrowser()|]; + [|Target.UnsupportedOnWindowsUntilWindows11()|]; + } + } + + class Target + { + [SupportedOSPlatform(""windows"")] + public static void SupportedOnWindows() { } + + [SupportedOSPlatform(""browser"")] + public static void SupportedOnBrowser() { } + + [SupportedOSPlatform(""windows""), SupportedOSPlatform(""browser"")] + public static void SupportedOnWindowsAndBrowser() { } + + [UnsupportedOSPlatform(""windows"")] + public static void UnsupportedOnWindows() { } + + [UnsupportedOSPlatform(""windows11.0"")] + public static void UnsupportedOnWindows11() { } + + [UnsupportedOSPlatform(""browser"")] + public static void UnsupportedOnBrowser() { } + + [UnsupportedOSPlatform(""windows""), UnsupportedOSPlatform(""browser"")] + public static void UnsupportedOnWindowsAndBrowser() { } + + [UnsupportedOSPlatform(""windows""), SupportedOSPlatform(""windows11.0"")] + public static void UnsupportedOnWindowsUntilWindows11() { } + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms, VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsRule).WithLocation(13, 13) + .WithMessage("'Target.SupportedOnWindowsAndBrowser()' is supported on 'browser'").WithArguments("Target.SupportedOnWindowsAndBrowser()", "browser")); + } + + [Fact] + public async Task CallerSupportsSupersetOfTarget() + { + var source = @" +using System.Runtime.Versioning; + +namespace CallerSupportsSubsetOfTarget +{ + class Caller + { + [SupportedOSPlatform(""windows""), SupportedOSPlatform(""browser"")] + public static void TestWithWindowsAndBrowserSupported() + { + [|Target.SupportedOnWindows()|]; + [|Target.SupportedOnBrowser()|]; + Target.SupportedOnWindowsAndBrowser(); + + [|Target.UnsupportedOnWindows()|]; + [|Target.UnsupportedOnBrowser()|]; + [|Target.UnsupportedOnWindowsAndBrowser()|]; + } + [UnsupportedOSPlatform(""browser"")] + public static void TestWithBrowserUnsupported() + { + [|Target.SupportedOnWindows()|]; + [|Target.SupportedOnBrowser()|]; + [|Target.SupportedOnWindowsAndBrowser()|]; + + Target.UnsupportedOnWindows(); // if call site has now support of it and MSbuild list not containg the platform name it will not be warned + Target.UnsupportedOnBrowser(); + Target.UnsupportedOnWindowsAndBrowser(); // same here + } + } + + class Target + { + [SupportedOSPlatform(""windows"")] + public static void SupportedOnWindows() { } + + [SupportedOSPlatform(""browser"")] + public static void SupportedOnBrowser() { } + + [SupportedOSPlatform(""windows""), SupportedOSPlatform(""browser"")] + public static void SupportedOnWindowsAndBrowser() { } + + [UnsupportedOSPlatform(""windows"")] + public static void UnsupportedOnWindows() { } + + [UnsupportedOSPlatform(""browser"")] + public static void UnsupportedOnBrowser() { } + + [UnsupportedOSPlatform(""windows""), UnsupportedOSPlatform(""browser"")] + public static void UnsupportedOnWindowsAndBrowser() { } + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsRule).WithLocation(17, 13) + .WithMessage("'Target.UnsupportedOnWindowsAndBrowser()' is unsupported on 'windows'").WithArguments("Target.UnsupportedOnWindowsAndBrowser(", "windows"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsRule).WithLocation(24, 13). + WithMessage("'Target.SupportedOnWindowsAndBrowser()' is supported on 'windows'").WithArguments("Target.SupportedOnWindowsAndBrowser()", "windows")); + } + + [Fact] + public async Task UnsupportedMustSuppressSupported() + { + var source = @" +using System.Runtime.Versioning; + +static class Program +{ + public static void Main() + { + [|Some.Api1()|]; + [|Some.Api2()|]; // TvOs is suppressed + } +} + +[SupportedOSPlatform(""ios10.0"")] +[SupportedOSPlatform(""tvos4.0"")] +class Some +{ + public static void Api1() {} + + [UnsupportedOSPlatform(""tvos"")] + public static void Api2() {} +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsRule).WithLocation(8, 9) + .WithMessage("'Some.Api1()' is supported on 'tvos' 4.0 and later").WithArguments("UnsupportedOnWindowsAndBrowser", "windows")); + } + + [Fact] + public async Task PlatformOverrides() + { + var source = @" +using System.Runtime.Versioning; + +namespace PlatformCompatDemo.Bugs +{ + class Caller + { + [SupportedOSPlatform(""windows"")] + public void TestSupportedOnWindows() + { + [|TargetSupportedOnWindows.FunctionUnsupportedOnWindows()|]; // should warn for unsupported windows + TargetSupportedOnWindows.FunctionUnsupportedOnBrowser(); + + TargetUnsupportedOnWindows.FunctionSupportedOnWindows(); + [|TargetUnsupportedOnWindows.FunctionSupportedOnBrowser()|]; // should warn for unsupported windows + } + + [UnsupportedOSPlatform(""windows"")] + public void TestUnsupportedOnWindows() + { + TargetSupportedOnWindows.FunctionUnsupportedOnWindows(); + [|TargetSupportedOnWindows.FunctionUnsupportedOnBrowser()|]; // should warn supported on windows ignore unsupported on browser as this is allow list + + TargetUnsupportedOnWindows.FunctionSupportedOnBrowser(); // should warn supported on browser, but is this valid scenario? => Invalid scenario, so not warn + TargetUnsupportedOnWindows.FunctionSupportedOnWindows(); // It's unsupporting Windows at the call site, which means the call site supports all other platforms. + } // It's calling into code that was NOT supported only on Windows but eventually added support, so it shouldn't raise diagnostic + } + + [SupportedOSPlatform(""windows"")] + class TargetSupportedOnWindows + { + [UnsupportedOSPlatform(""windows"")] + public static void FunctionUnsupportedOnWindows() { } + + [UnsupportedOSPlatform(""browser"")] + public static void FunctionUnsupportedOnBrowser() { } + } + + [UnsupportedOSPlatform(""windows"")] + class TargetUnsupportedOnWindows + { + [SupportedOSPlatform(""windows"")] + public static void FunctionSupportedOnWindows() { } + + [SupportedOSPlatform(""browser"")] + public static void FunctionSupportedOnBrowser() { } + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task UnsupportedMustSuppressSupportedAssemblyAttribute() + { + var source = @" +using System.Runtime.Versioning; +[assembly:SupportedOSPlatform(""browser"")] + +namespace PlatformCompatDemo +{ + static class Program + { + public static void Main() + { + [|CrossPlatformApis.DoesNotWorkOnBrowser()|]; + var nonBrowser = [|new NonBrowserApis()|]; + } + } + + public class CrossPlatformApis + { + [UnsupportedOSPlatform(""browser"")] + public static void DoesNotWorkOnBrowser() { } + } + + [UnsupportedOSPlatform(""browser"")] + public class NonBrowserApis + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task UsingUnsupportedApiShouldWarn() + { + var source = @" +using System.Runtime.Versioning; +[assembly: SupportedOSPlatform(""windows"")] + +static class Program +{ + public static void Main() + { + [|SomeWindowsSpecific.Api()|]; + } +} + +class SomeWindowsSpecific +{ + [UnsupportedOSPlatform(""windows"")] + public static void Api() { } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task UsingVersionedApiFromUnversionedAssembly() + { + var source = @" +using System.Runtime.Versioning; +[assembly: SupportedOSPlatform(""windows"")] + +static class Program +{ + public static void Main() + { + [|Some.WindowsSpecificApi()|]; + } +} + +[SupportedOSPlatform(""windows10.0"")] +static class Some +{ + public static void WindowsSpecificApi() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task ReintroducingApiSupport_NotWarn() + { + var source = @" +using System.Runtime.Versioning; +[assembly: SupportedOSPlatform(""windows10.0"")] + +static class Program +{ + public static void Main() + { + Some.WindowsSpecificApi(); + } +} + +static class Some +{ + [SupportedOSPlatform(""windows"")] + [UnsupportedOSPlatform(""windows8.1"")] + [SupportedOSPlatform(""windows10.0"")] + public static void WindowsSpecificApi() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source); + } + + [Fact(Skip = "TODO: enable when tests could consume new attribute applied to runtime assemblies")] + public async Task MethodOfOsDependentAssemblyCalledWithoutSuppressionWarns() + { + var source = @" + using System.Runtime.Versioning; + using ns; + public class Test + { + public void M1() + { + OsDependentClass odc = new OsDependentClass(); + odc.M2(); + } + } + [assembly:SupportedOSPlatform(""Windows10.1.2.3"")] + namespace ns + { + public class OsDependentClass + { + public void M2() + { + } + } + } +" + MockAttributesCsSource; + await VerifyCS.VerifyAnalyzerAsync(source, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithSpan(10, 21, 10, 29).WithArguments("M2", "Windows", "10.1.2.3")); + } + + public static IEnumerable SupportedOsAttributeTestData() + { + yield return new object[] { "Windows10.1.2.3", "Windows10.1.2.3", false }; + yield return new object[] { "Windows10.1.2.3", "Windows10.1.3.3", false }; + yield return new object[] { "WINDOWS10.1.2.3", "Windows10.1.3", false }; + yield return new object[] { "Windows10.1.2.3", "Windows11.0", false }; + yield return new object[] { "Windows10.1.2.3", "windows10.2.2.0", false }; + yield return new object[] { "Windows10.1.2.3", "Windows10.1.1.3", true }; + yield return new object[] { "Windows10.1.2.3", "WINDOWS11.1.1.3", false }; + yield return new object[] { "Windows10.1.2.3", "Windows10.1.1.4", true }; + yield return new object[] { "MACOS10.1.2.3", "macos10.2.2.0", false }; + yield return new object[] { "OSX10.1.2.3", "Osx11.1.1.0", false }; + yield return new object[] { "Osx10.1.2.3", "osx10.2", false }; + yield return new object[] { "Windows10.1.2.3", "Osx11.1.1.4", true }; + yield return new object[] { "Windows10.1.2.3", "Windows10.0.1.9", true }; + yield return new object[] { "Windows10.1.2.3", "Windows10.1.1.4", true }; + yield return new object[] { "Windows10.1.2.3", "Windows8.2.3.3", true }; + } + + [Theory] + [MemberData(nameof(SupportedOsAttributeTestData))] + public async Task MethodOfOsDependentClassSuppressedWithSupportedOsAttribute(string dependentVersion, string suppressingVersion, bool warn) + { + var source = @" +using System.Runtime.Versioning; + +public class Test +{ + [SupportedOSPlatform(""" + suppressingVersion + @""")] + public void M1() + { + OsDependentClass odc = new OsDependentClass(); + odc.M2(); + } +} + +[SupportedOSPlatform(""" + dependentVersion + @""")] +public class OsDependentClass +{ + public void M2() + { + } +} +" + MockAttributesCsSource; + + if (warn) + { + await VerifyAnalyzerAsyncCs(source, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithSpan(9, 32, 9, 54).WithArguments("OsDependentClass", "Windows", "10.1.2.3"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithSpan(10, 9, 10, 17).WithArguments("OsDependentClass.M2()", "Windows", "10.1.2.3")); + } + else + { + await VerifyAnalyzerAsyncCs(source); + } + } + + public static IEnumerable UnsupportedAttributeTestData() + { + yield return new object[] { "Windows10.1.2.3", "Windows10.1.2.3", false }; + yield return new object[] { "Windows10.1.2.3", "MacOs10.1.3.3", true }; + yield return new object[] { "Windows10.1.2.3", "Windows10.1.3.1", true }; + yield return new object[] { "Windows10.1.2.3", "Windows11.1", true }; + yield return new object[] { "Windows10.1.2.3", "Windows10.2.2.0", true }; + yield return new object[] { "Windows10.1.2.3", "Windows10.1.1.3", false }; + yield return new object[] { "Windows10.1.2.3", "WINDOWS10.1.1.3", false }; + yield return new object[] { "Windows10.1.2.3", "Windows10.1.1.4", false }; + yield return new object[] { "Windows10.1.2.3", "Osx10.1.1.4", true }; + yield return new object[] { "windows10.1.2.3", "Windows10.1.0.1", false }; + yield return new object[] { "Windows10.1.2.3", "Windows8.2.3.4", false }; + } + + [Theory] + [MemberData(nameof(UnsupportedAttributeTestData))] + public async Task MethodOfOsDependentClassSuppressedWithUnsupportedAttribute(string dependentVersion, string suppressingVersion, bool warn) + { + var source = @" + using System.Runtime.Versioning; + +[SupportedOSPlatform(""Windows"")] +public class Test +{ + [UnsupportedOSPlatform(""" + suppressingVersion + @""")] + public void M1() + { + OsDependentClass odc = new OsDependentClass(); + odc.M2(); + } +} + +[UnsupportedOSPlatform(""" + dependentVersion + @""")] +public class OsDependentClass +{ + public void M2() + { + } +} +" + MockAttributesCsSource; + + if (warn) + { + await VerifyAnalyzerAsyncCs(source, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsVersionRule).WithLocation(10, 32).WithArguments("OsDependentClass", "Windows", "10.1.2.3"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsVersionRule).WithLocation(11, 9).WithArguments("OsDependentClass.M2()", "Windows", "10.1.2.3")); + } + else + { + await VerifyAnalyzerAsyncCs(source); + } + } + + [Fact] + public async Task UnsupportedNotWarnsForUnrelatedSupportedContext() + { + var source = @" + using System.Runtime.Versioning; + +[SupportedOSPlatform(""Linux"")] +public class Test +{ + public void M1() + { + var obj = new C(); + obj.BrowserMethod(); + C.StaticClass.LinuxMethod(); + C.StaticClass.LinuxVersionedMethod(); + } +} + +public class C +{ + [UnsupportedOSPlatform(""browser"")] + public void BrowserMethod() + { + } + + [UnsupportedOSPlatform(""linux4.8"")] + internal static class StaticClass{ + public static void LinuxVersionedMethod() + { + } + [UnsupportedOSPlatform(""linux"")] + public static void LinuxMethod() + { + } + } +} +" + MockAttributesCsSource; + + await VerifyAnalyzerAsyncCs(source, VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsRule).WithLocation(11, 9) + .WithMessage("'C.StaticClass.LinuxMethod()' is unsupported on 'linux'").WithArguments("C.StaticClass.LinuxMethod()", "linux"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsVersionRule).WithLocation(12, 9) + .WithMessage("'C.StaticClass.LinuxVersionedMethod()' is unsupported on 'linux' 4.8 and later").WithArguments("LinuxMethod", "linux")); + } + + [Fact] + + public async Task MultipleAttrbiutesOptionallySupportedListTest() + { + var source = @" + using System.Runtime.Versioning; + +public class Test +{ + C obj = new C(); + [SupportedOSPlatform(""Linux"")] + public void DiffferentOsNotWarn() + { + obj.DoesNotWorkOnWindows(); + } + + [SupportedOSPlatform(""windows"")] + [UnsupportedOSPlatform(""windows10.0.2000"")] + public void SupporteWindows() + { + // Warns for UnsupportedFirst, Supported + obj.DoesNotWorkOnWindows(); + } + + [UnsupportedOSPlatform(""windows"")] + [SupportedOSPlatform(""windows10.1"")] + [UnsupportedOSPlatform(""windows10.0.2003"")] + public void SameSupportForWindowsNotWarn() + { + obj.DoesNotWorkOnWindows(); + } + + public void AllSupportedWarnForAll() + { + obj.DoesNotWorkOnWindows(); + } + + [SupportedOSPlatform(""windows10.0.2000"")] + public void SupportedFromWindows10_0_2000() + { + // Should warn for [UnsupportedOSPlatform] + obj.DoesNotWorkOnWindows(); + } + + [SupportedOSPlatform(""windows10.0.1904"")] + [UnsupportedOSPlatform(""windows10.0.1909"")] + public void SupportedWindowsFrom10_0_1904_To10_0_1909_NotWarn() + { + // Should not warn + obj.DoesNotWorkOnWindows(); + } +} + +public class C +{ + [UnsupportedOSPlatform(""windows"")] + [SupportedOSPlatform(""windows10.0.1903"")] + [UnsupportedOSPlatform(""windows10.0.2004"")] + public void DoesNotWorkOnWindows() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsRule).WithLocation(18, 9).WithMessage("'C.DoesNotWorkOnWindows()' is unsupported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(18, 9).WithMessage("'C.DoesNotWorkOnWindows()' is supported on 'windows' 10.0.1903 and later"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsRule).WithLocation(31, 9).WithMessage("'C.DoesNotWorkOnWindows()' is unsupported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(31, 9).WithMessage("'C.DoesNotWorkOnWindows()' is supported on 'windows' 10.0.1903 and later"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsVersionRule).WithLocation(38, 9).WithMessage("'C.DoesNotWorkOnWindows()' is unsupported on 'windows' 10.0.2004 and later")); + } + + [Fact] + + public async Task MultipleAttrbiutesSupportedOnlyWindowsListTest() + { + var source = @" + using System.Runtime.Versioning; + +public class Test +{ + C obj = new C(); + [SupportedOSPlatform(""Linux"")] + public void DiffferentOsWarnsForAll() + { + obj.WindowsOnlyMethod(); + } + + [SupportedOSPlatform(""windows"")] + [UnsupportedOSPlatform(""windows10.0.2003"")] + public void SameSupportForWindowsNotWarn() + { + obj.WindowsOnlyMethod(); + } + + public void AllSupportedWarnForAll() + { + obj.WindowsOnlyMethod(); + } + + [SupportedOSPlatform(""windows10.0.2000"")] + public void SupportedFromWindows10_0_2000() + { + // Warns for [UnsupportedOSPlatform] + obj.WindowsOnlyMethod(); + } + + [SupportedOSPlatform(""windows10.0.1904"")] + [UnsupportedOSPlatform(""windows10.0.1909"")] + public void SupportedWindowsFrom10_0_1904_To10_0_1909_NotWarn() + { + // Should not warn + obj.WindowsOnlyMethod(); + } +} + +public class C +{ + [SupportedOSPlatform(""windows"")] + [UnsupportedOSPlatform(""windows10.0.2004"")] + public void WindowsOnlyMethod() + { + } +} +" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(source, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsRule).WithLocation(10, 9).WithMessage("'C.WindowsOnlyMethod()' is supported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsVersionRule).WithLocation(10, 9).WithMessage("'C.WindowsOnlyMethod()' is unsupported on 'windows' 10.0.2004 and later"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsRule).WithLocation(22, 9).WithMessage("'C.WindowsOnlyMethod()' is supported on 'windows'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsVersionRule).WithLocation(22, 9).WithMessage("'C.WindowsOnlyMethod()' is unsupported on 'windows' 10.0.2004 and later"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsVersionRule).WithLocation(29, 9).WithMessage("'C.WindowsOnlyMethod()' is unsupported on 'windows' 10.0.2004 and later")); + } + + [Fact] + public async Task CallSiteSupportedUnupportedNoMsBuildOptions() + { + var source = @" + using System.Runtime.Versioning; + +namespace PlatformCompatDemo.SupportedUnupported +{ + public class Test + { + public static void Supported() + { + var supported = new TypeWithoutAttributes(); + [|supported.TypeWithoutAttributes_FunctionSupportedOnWindows()|]; + [|supported.TypeWithoutAttributes_FunctionSupportedOnWindows10()|]; + [|supported.TypeWithoutAttributes_FunctionSupportedOnWindows10AndBrowser()|]; + + var supportedOnWindows = [|new TypeSupportedOnWindows()|]; + [|supportedOnWindows.TypeSupportedOnWindows_FunctionSupportedOnBrowser()|]; + [|supportedOnWindows.TypeSupportedOnWindows_FunctionSupportedOnWindows11AndBrowser()|]; + + var supportedOnBrowser = [|new TypeSupportedOnBrowser()|]; + [|supportedOnBrowser.TypeSupportedOnBrowser_FunctionSupportedOnWindows()|]; + + var supportedOnWindows10 = [|new TypeSupportedOnWindows10()|]; + [|supportedOnWindows10.TypeSupportedOnWindows10_FunctionSupportedOnBrowser()|]; + + var supportedOnWindowsAndBrowser = [|new TypeSupportedOnWindowsAndBrowser()|]; + [|supportedOnWindowsAndBrowser.TypeSupportedOnWindowsAndBrowser_FunctionSupportedOnWindows11()|]; + } + + public static void Unsupported() + { + var unsupported = new TypeWithoutAttributes(); + unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindows(); + unsupported.TypeWithoutAttributes_FunctionUnsupportedOnBrowser(); + unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindows10(); + unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindowsAndBrowser(); + unsupported.TypeWithoutAttributes_FunctionUnsupportedOnWindows10AndBrowser(); + + var unsupportedOnWindows = new TypeUnsupportedOnWindows(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionUnsupportedOnBrowser(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionUnsupportedOnWindows11(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionUnsupportedOnWindows11AndBrowser(); + + var unsupportedOnBrowser = new TypeUnsupportedOnBrowser(); + unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionUnsupportedOnWindows(); + unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionUnsupportedOnWindows10(); + + var unsupportedOnWindows10 = new TypeUnsupportedOnWindows10(); + unsupportedOnWindows10.TypeUnsupportedOnWindows10_FunctionUnsupportedOnBrowser(); + unsupportedOnWindows10.TypeUnsupportedOnWindows10_FunctionUnsupportedOnWindows11(); + unsupportedOnWindows10.TypeUnsupportedOnWindows10_FunctionUnsupportedOnWindows11AndBrowser(); + + var unsupportedOnWindowsAndBrowser = new TypeUnsupportedOnWindowsAndBrowser(); + unsupportedOnWindowsAndBrowser.TypeUnsupportedOnWindowsAndBrowser_FunctionUnsupportedOnWindows11(); + + var unsupportedOnWindows10AndBrowser = new TypeUnsupportedOnWindows10AndBrowser(); + unsupportedOnWindows10AndBrowser.TypeUnsupportedOnWindows10AndBrowser_FunctionUnsupportedOnWindows11(); + } + + public static void UnsupportedCombinations() // no any diagnostics as it is deny list + { + var withoutAttributes = new TypeWithoutAttributes(); + withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11(); + withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12(); + withoutAttributes.TypeWithoutAttributes_FunctionUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12SupportedOnWindows13(); + + var unsupportedOnWindows = new TypeUnsupportedOnWindows(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11UnsupportedOnWindows12(); + unsupportedOnWindows.TypeUnsupportedOnWindows_FunctionSupportedOnWindows11UnsupportedOnWindows12SupportedOnWindows13(); + + var unsupportedOnBrowser = new TypeUnsupportedOnBrowser(); + unsupportedOnBrowser.TypeUnsupportedOnBrowser_FunctionSupportedOnBrowser(); + + var unsupportedOnWindowsSupportedOnWindows11 = new TypeUnsupportedOnWindowsSupportedOnWindows11(); + unsupportedOnWindowsSupportedOnWindows11.TypeUnsupportedOnWindowsSupportedOnWindows11_FunctionUnsupportedOnWindows12(); + unsupportedOnWindowsSupportedOnWindows11.TypeUnsupportedOnWindowsSupportedOnWindows11_FunctionUnsupportedOnWindows12SupportedOnWindows13(); + + var unsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12 = new TypeUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12(); + unsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12.TypeUnsupportedOnWindowsSupportedOnWindows11UnsupportedOnWindows12_FunctionSupportedOnWindows13(); + } + } +} +" + TargetTypesForTest + MockAttributesCsSource; + + await VerifyAnalyzerAsyncCs(source, + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsRule).WithLocation(13, 13).WithMessage("'TypeWithoutAttributes.TypeWithoutAttributes_FunctionSupportedOnWindows10AndBrowser()' is supported on 'browser'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsRule).WithLocation(16, 13).WithMessage("'TypeSupportedOnWindows.TypeSupportedOnWindows_FunctionSupportedOnBrowser()' is supported on 'browser'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.SupportedOsRule).WithLocation(17, 13).WithMessage("'TypeSupportedOnWindows.TypeSupportedOnWindows_FunctionSupportedOnWindows11AndBrowser()' is supported on 'browser'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsVersionRule).WithLocation(20, 13).WithMessage("'TypeSupportedOnBrowser.TypeSupportedOnBrowser_FunctionSupportedOnWindows()' is supported on 'browser'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsVersionRule).WithLocation(23, 13).WithMessage("'TypeSupportedOnWindows10.TypeSupportedOnWindows10_FunctionSupportedOnBrowser()' is supported on 'browser'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsVersionRule).WithLocation(25, 48).WithMessage("'TypeSupportedOnWindowsAndBrowser' is supported on 'browser'"), + VerifyCS.Diagnostic(PlatformCompatabilityAnalyzer.UnsupportedOsVersionRule).WithLocation(26, 13).WithMessage("'TypeSupportedOnWindowsAndBrowser.TypeSupportedOnWindowsAndBrowser_FunctionSupportedOnWindows11()' is supported on 'browser'")); + } + + private static VerifyCS.Test PopulateTestCs(string sourceCode, params DiagnosticResult[] expected) + { + var test = new VerifyCS.Test + { + TestCode = sourceCode, + ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp50, + MarkupOptions = MarkupOptions.UseFirstDescriptor, + TestState = { } + }; + test.ExpectedDiagnostics.AddRange(expected); + return test; + } + + private static async Task VerifyAnalyzerAsyncCs(string sourceCode, params DiagnosticResult[] expectedDiagnostics) + => await PopulateTestCs(sourceCode, expectedDiagnostics).RunAsync(); + + private static async Task VerifyAnalyzerAsyncCs(string sourceCode, string editorconfigText, params DiagnosticResult[] expectedDiagnostics) + { + var test = PopulateTestCs(sourceCode, expectedDiagnostics); + test.TestState.AdditionalFiles.Add((".editorconfig", editorconfigText)); + await test.RunAsync(); + } + + private static async Task VerifyAnalyzerAsyncCs(string sourceCode, string editorconfigText) + { + var test = PopulateTestCs(sourceCode); + test.TestState.AdditionalFiles.Add((".editorconfig", editorconfigText)); + await test.RunAsync(); + } + + private static async Task VerifyAnalyzerAsyncVb(string sourceCode, params DiagnosticResult[] expectedDiagnostics) + => await PopulateTestVb(sourceCode, expectedDiagnostics).RunAsync(); + + private static async Task VerifyAnalyzerAsyncVb(string sourceCode, string editorconfigText, params DiagnosticResult[] expectedDiagnostics) + { + var test = PopulateTestVb(sourceCode, expectedDiagnostics); + test.TestState.AdditionalFiles.Add((".editorconfig", editorconfigText)); + await test.RunAsync(); + } + + private static VerifyVB.Test PopulateTestVb(string sourceCode, params DiagnosticResult[] expected) + { + var test = new VerifyVB.Test + { + TestCode = sourceCode, + ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp50, + MarkupOptions = MarkupOptions.UseFirstDescriptor, + TestState = { }, + }; + test.ExpectedDiagnostics.AddRange(expected); + return test; + } + + private readonly string MockAttributesCsSource = @" +namespace System.Runtime.Versioning +{ + public abstract class OSPlatformAttribute : Attribute + { + private protected OSPlatformAttribute(string platformName) + { + PlatformName = platformName; + } + + public string PlatformName { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, + AllowMultiple = false, Inherited = false)] + public sealed class TargetPlatformAttribute : OSPlatformAttribute + { + public TargetPlatformAttribute(string platformName) : base(platformName) + { } + } + + [AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Class | + AttributeTargets.Constructor | + AttributeTargets.Event | + AttributeTargets.Method | + AttributeTargets.Module | + AttributeTargets.Property | + AttributeTargets.Field | + AttributeTargets.Struct, + AllowMultiple = true, Inherited = false)] + public sealed class SupportedOSPlatformAttribute : OSPlatformAttribute + { + public SupportedOSPlatformAttribute(string platformName) : base(platformName) + { } + } + + [AttributeUsage(AttributeTargets.Assembly | + AttributeTargets.Class | + AttributeTargets.Constructor | + AttributeTargets.Event | + AttributeTargets.Method | + AttributeTargets.Module | + AttributeTargets.Property | + AttributeTargets.Field | + AttributeTargets.Struct, + AllowMultiple = true, Inherited = false)] + public sealed class UnsupportedOSPlatformAttribute : OSPlatformAttribute + { + public UnsupportedOSPlatformAttribute(string platformName) : base(platformName) + { } + } +} +"; + + private readonly string MockAttributesVbSource = @" +Namespace System.Runtime.Versioning + Public MustInherit Class OSPlatformAttribute + Inherits Attribute + + Private Protected Sub New(ByVal platformName As String) + PlatformName = platformName + End Sub + + Public ReadOnly Property PlatformName As String + End Class + + + Public NotInheritable Class TargetPlatformAttribute + Inherits OSPlatformAttribute + + Public Sub New(ByVal platformName As String) + MyBase.New(platformName) + End Sub + End Class + + + Public NotInheritable Class SupportedOSPlatformAttribute + Inherits OSPlatformAttribute + + Public Sub New(ByVal platformName As String) + MyBase.New(platformName) + End Sub + End Class + + + Public NotInheritable Class UnsupportedOSPlatformAttribute + Inherits OSPlatformAttribute + + Public Sub New(ByVal platformName As String) + MyBase.New(platformName) + End Sub + End Class +End Namespace +"; + } +} diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzerTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzerTests.cs deleted file mode 100644 index d8e7174fc1..0000000000 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/RuntimePlatformCheckAnalyzerTests.cs +++ /dev/null @@ -1,820 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow; -using Xunit; -using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< - Microsoft.NetCore.Analyzers.InteropServices.RuntimePlatformCheckAnalyzer, - Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; - -namespace Microsoft.NetCore.Analyzers.InteropServices.UnitTests -{ - public class RuntimePlatformCheckAnalyzerTests - { - private readonly string PlatformCheckApiSource = @" -namespace System.Runtime.InteropServices -{ - public class RuntimeInformationHelper - { - public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major) => true; - public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor) => true; - public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build) => true; - public static bool IsOSPlatformOrLater(OSPlatform osPlatform, int major, int minor, int build, int revision) => true; - - public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major) => true; - public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor) => true; - public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build) => true; - public static bool IsOSPlatformEarlierThan(OSPlatform osPlatform, int major, int minor, int build, int revision) => true; - } -}"; - - [Fact] - public async Task SimpleIfTest() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1)) - { - {|#1:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;1' - } - - if(RuntimeInformationHelper.IsOSPlatformEarlierThan(OSPlatform.Windows, 1, 1)) - { - {|#2:M2()|}; // Platform checks:'IsOSPlatformEarlierThan;Windows;1.1' - } - - {|#3:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("IsOSPlatformEarlierThan;Windows;1.1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("")); - } - - [Fact] - public async Task SimpleIfTest_02() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - if(RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1)) - { - {|#0:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;1' - } - - if(RuntimeInformationHelper.IsOSPlatformEarlierThan(OSPlatform.Windows, 1, 1)) - { - {|#1:M2()|}; // Platform checks:'IsOSPlatformEarlierThan;Windows;1.1' - } - - {|#2:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments("IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("IsOSPlatformEarlierThan;Windows;1.1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("")); - } - - [Fact] - public async Task SimpleIfElseTest() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1)) - { - {|#1:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;1' - } - else - { - {|#2:M2()|}; // Platform checks:'!IsOSPlatformOrLater;Windows;1' - } - - {|#3:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("!IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("")); - } - - [Fact] - public async Task SimpleIfElseIfElseTest() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1)) - { - {|#1:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;1' - } - else if(RuntimeInformationHelper.IsOSPlatformEarlierThan(OSPlatform.Linux, 1, 1)) - { - {|#2:M2()|}; // Platform checks:'!IsOSPlatformOrLater;Windows;1 && IsOSPlatformEarlierThan;Linux;1.1' - } - else - { - {|#3:M2()|}; // Platform checks:'!IsOSPlatformEarlierThan;Linux;1.1 && !IsOSPlatformOrLater;Windows;1' - } - - {|#4:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("!IsOSPlatformOrLater;Windows;1 && IsOSPlatformEarlierThan;Linux;1.1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("!IsOSPlatformEarlierThan;Linux;1.1 && !IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(4).WithArguments("")); - } - - [Fact] - public async Task SimpleIfTestWithNegation() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(!RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1)) - { - {|#1:M2()|}; // Platform checks:'!IsOSPlatformOrLater;Windows;1' - } - - if(!RuntimeInformationHelper.IsOSPlatformEarlierThan(OSPlatform.Windows, 1, 1)) - { - {|#2:M2()|}; // Platform checks:'!IsOSPlatformEarlierThan;Windows;1.1' - } - - {|#3:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("!IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("!IsOSPlatformEarlierThan;Windows;1.1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("")); - } - - [Fact] - public async Task SimpleIfElseTestWithNegation() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(!RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1)) - { - {|#1:M2()|}; // Platform checks:'!IsOSPlatformOrLater;Windows;1' - } - else - { - {|#2:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;1' - } - - {|#3:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("!IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("")); - } - - [Fact] - public async Task SimpleIfElseIfElseTestWithNegation() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(!RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1)) - { - {|#1:M2()|}; // Platform checks:'!IsOSPlatformOrLater;Windows;1' - } - else if(RuntimeInformationHelper.IsOSPlatformEarlierThan(OSPlatform.Linux, 1, 1)) - { - {|#2:M2()|}; // Platform checks:'IsOSPlatformEarlierThan;Linux;1.1 && IsOSPlatformOrLater;Windows;1' - } - else - { - {|#3:M2()|}; // Platform checks:'!IsOSPlatformEarlierThan;Linux;1.1 && IsOSPlatformOrLater;Windows;1' - } - - {|#4:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("!IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("IsOSPlatformEarlierThan;Linux;1.1 && IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("!IsOSPlatformEarlierThan;Linux;1.1 && IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(4).WithArguments("")); - } - - [Fact] - public async Task SimpleIfTestWithLogicalAnd() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1) && - RuntimeInformationHelper.IsOSPlatformEarlierThan(OSPlatform.Windows, 2)) - { - {|#1:M2()|}; // Platform checks:'IsOSPlatformEarlierThan;Windows;2 && IsOSPlatformOrLater;Windows;1' - } - - {|#2:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("IsOSPlatformEarlierThan;Windows;2 && IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("")); - } - - [Fact] - public async Task SimpleIfElseTestWithLogicalAnd() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1) && - RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 1)) - { - {|#1:M2()|}; // Platform checks:'IsOSPlatformOrLater;Linux;1 && IsOSPlatformOrLater;Windows;1' - } - else - { - {|#2:M2()|}; // Platform checks:'' - } - - {|#3:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("IsOSPlatformOrLater;Linux;1 && IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("")); - } - - [Fact] - public async Task SimpleIfTestWithLogicalOr() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1) || - RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 1)) - { - {|#1:M2()|}; // Platform checks:'(!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1)' - } - - {|#2:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("(!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1)"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("")); - } - - [Fact] - public async Task SimpleIfElseTestWithLogicalOr() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1) || - RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 1)) - { - {|#1:M2()|}; // Platform checks:'(!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1)' - } - else - { - {|#2:M2()|}; // Platform checks:'!IsOSPlatformOrLater;Linux;1 && !IsOSPlatformOrLater;Windows;1' - } - - {|#3:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("(!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1)"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("!IsOSPlatformOrLater;Linux;1 && !IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("")); - } - - [Fact] - public async Task SimpleIfElseIfElseTestWithLogicalOr() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if(RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1) || - RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 1)) - { - {|#1:M2()|}; // Platform checks:'(!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1)' - } - else if (RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 3)) - { - {|#2:M2()|}; // Platform checks:'!IsOSPlatformOrLater;Linux;1 && !IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Windows;3' - } - else - { - {|#3:M2()|}; // Platform checks:'!IsOSPlatformOrLater;Linux;1 && !IsOSPlatformOrLater;Windows;1 && !IsOSPlatformOrLater;Windows;3' - } - - {|#4:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("(!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1)"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("!IsOSPlatformOrLater;Linux;1 && !IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Windows;3"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("!IsOSPlatformOrLater;Linux;1 && !IsOSPlatformOrLater;Windows;1 && !IsOSPlatformOrLater;Windows;3"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(4).WithArguments("")); - } - - [Fact] - public async Task SimpleIfElseIfTestWithLogicalOr_02() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if((RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1) || - RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 1)) && - (RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 2) || - RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 2))) - { - {|#1:M2()|}; // Platform checks:'((!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1) && !IsOSPlatformOrLater;Windows;2 && IsOSPlatformOrLater;Linux;2 || (!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1) && IsOSPlatformOrLater;Windows;2)' - } - else if (RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 3) || - RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 3) || - RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 4)) - { - {|#2:M2()|}; // Platform checks:'(!IsOSPlatformOrLater;Linux;3 && !IsOSPlatformOrLater;Windows;3 && IsOSPlatformOrLater;Linux;4 || (!IsOSPlatformOrLater;Windows;3 && IsOSPlatformOrLater;Linux;3 || IsOSPlatformOrLater;Windows;3))' - } - - {|#3:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("((!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1) && !IsOSPlatformOrLater;Windows;2 && IsOSPlatformOrLater;Linux;2 || (!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1) && IsOSPlatformOrLater;Windows;2)"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("(!IsOSPlatformOrLater;Linux;3 && !IsOSPlatformOrLater;Windows;3 && IsOSPlatformOrLater;Linux;4 || (!IsOSPlatformOrLater;Windows;3 && IsOSPlatformOrLater;Linux;3 || IsOSPlatformOrLater;Windows;3))"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("")); - } - - [Fact] - public async Task ControlFlowAndMultipleChecks() - { - var source = @" -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if (RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 1)) - { - {|#1:M2()|}; // Platform checks:'IsOSPlatformOrLater;Linux;1' - - if (RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 2, 0)) - { - {|#2:M2()|}; // Platform checks:'IsOSPlatformOrLater;Linux;1 && IsOSPlatformOrLater;Linux;2' - } - else if (!RuntimeInformationHelper.IsOSPlatformEarlierThan(OSPlatform.Windows, 3, 1, 1)) - { - {|#3:M2()|}; // Platform checks:'!IsOSPlatformEarlierThan;Windows;3.1.1 && !IsOSPlatformOrLater;Linux;2 && IsOSPlatformOrLater;Linux;1' - } - - {|#4:M2()|}; // Platform checks:'IsOSPlatformOrLater;Linux;1' - } - else - { - {|#5:M2()|}; // Platform checks:'!IsOSPlatformOrLater;Linux;1' - } - - {|#6:M2()|}; // Platform checks:'' - - if ({|#7:IsWindows3OrLater()|}) // Platform checks:'' - { - {|#8:M2()|}; // Platform checks:'' - } - - {|#9:M2()|}; // Platform checks:'' - } - - void M2() - { - } - - bool IsWindows3OrLater() - { - return RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 3, 0, 0, 0); - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("IsOSPlatformOrLater;Linux;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("IsOSPlatformOrLater;Linux;1 && IsOSPlatformOrLater;Linux;2"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("!IsOSPlatformEarlierThan;Windows;3.1.1 && !IsOSPlatformOrLater;Linux;2 && IsOSPlatformOrLater;Linux;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(4).WithArguments("IsOSPlatformOrLater;Linux;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(5).WithArguments("!IsOSPlatformOrLater;Linux;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(6).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(7).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(8).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(9).WithArguments("")); - } - - [Fact] - public async Task DebugAssertAnalysisTest() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - {|#1:Debug.Assert(RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 3, 0, 0, 0))|}; // Platform checks:'IsOSPlatformOrLater;Windows;3' - - {|#2:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;3' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("IsOSPlatformOrLater;Windows;3"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("IsOSPlatformOrLater;Windows;3")); - } - - [Fact] - public async Task ResultSavedInLocal() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - var x1 = RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1); - var x2 = RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Linux, 1); - if (x1 || x2) - { - {|#1:M2()|}; // Platform checks:'(!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1)' - } - - {|#2:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("(!IsOSPlatformOrLater;Windows;1 && IsOSPlatformOrLater;Linux;1 || IsOSPlatformOrLater;Windows;1)"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("")); - } - - [Fact] - public async Task VersionSavedInLocal() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - var v1 = 1; - if (RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, v1)) - { - {|#1:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;1' - } - - {|#2:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("")); - } - - [Fact] - public async Task PlatformSavedInLocal_NotYetSupported() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - var platform = OSPlatform.Windows; - if (RuntimeInformationHelper.IsOSPlatformOrLater(platform, 1)) - { - {|#1:M2()|}; // Platform checks:'' - } - - {|#2:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("")); - } - - [Fact] - public async Task UnrelatedConditionCheckDoesNotInvalidateState() - { - var source = @" -using System.Diagnostics; -using System.Runtime.InteropServices; - -class Test -{ - void M1(bool flag1, bool flag2) - { - {|#0:M2()|}; // Platform checks:'' - - if (RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 1)) - { - {|#1:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;1' - - if (flag1 || flag2) - { - {|#2:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;1' - } - else - { - {|#3:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;1' - } - - {|#4:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;1' - } - - {|#5:M2()|}; // Platform checks:'' - } - - void M2() - { - } -}" + PlatformCheckApiSource; - - await VerifyCS.VerifyAnalyzerAsync(source, - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments("IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments("IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(4).WithArguments("IsOSPlatformOrLater;Windows;1"), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(5).WithArguments("")); - } - - [Theory] - [InlineData("")] - [InlineData("dotnet_code_quality.interprocedural_analysis_kind = ContextSensitive")] - public async Task InterproceduralAnalysisTest(string editorconfig) - { - var source = @" -using System.Runtime.InteropServices; - -class Test -{ - void M1() - { - {|#0:M2()|}; // Platform checks:'' - - if ({|#1:IsWindows3OrLater()|}) // Platform checks:'' - { - {|#2:M2()|}; // Platform checks:'IsOSPlatformOrLater;Windows;3' - } - - {|#3:M2()|}; // Platform checks:'' - } - - void M2() - { - } - - bool IsWindows3OrLater() - { - return RuntimeInformationHelper.IsOSPlatformOrLater(OSPlatform.Windows, 3, 0, 0, 0); - } -}" + PlatformCheckApiSource; - - var test = new VerifyCS.Test - { - TestState = - { - Sources = { source }, - AdditionalFiles = { (".editorconfig", editorconfig) } - } - }; - - var argForInterprocDiagnostics = editorconfig.Length == 0 ? "" : "IsOSPlatformOrLater;Windows;3"; - test.ExpectedDiagnostics.AddRange(new[] - { - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(0).WithArguments(""), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(1).WithArguments(argForInterprocDiagnostics), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(2).WithArguments(argForInterprocDiagnostics), - VerifyCS.Diagnostic(RuntimePlatformCheckAnalyzer.Rule).WithLocation(3).WithArguments("") - }); - - await test.RunAsync(); - } - } -} diff --git a/src/Utilities/Compiler/Analyzer.Utilities.projitems b/src/Utilities/Compiler/Analyzer.Utilities.projitems index 5075f8cec9..2623d43a09 100644 --- a/src/Utilities/Compiler/Analyzer.Utilities.projitems +++ b/src/Utilities/Compiler/Analyzer.Utilities.projitems @@ -106,6 +106,7 @@ + diff --git a/src/Utilities/Compiler/Extensions/IOperationExtensions.cs b/src/Utilities/Compiler/Extensions/IOperationExtensions.cs index cb737d3f5c..42ee74f5bb 100644 --- a/src/Utilities/Compiler/Extensions/IOperationExtensions.cs +++ b/src/Utilities/Compiler/Extensions/IOperationExtensions.cs @@ -782,6 +782,25 @@ public static IArgumentOperation GetArgumentForParameterAtIndex( throw new InvalidOperationException(); } + + /// + /// Useful when named arguments used for a method call and you need them in the original parameter order. + /// + /// Arguments of the method + /// Returns the arguments in parameter order + public static ImmutableArray GetArgumentsInParameterOrder( + this ImmutableArray arguments) + { + using var parameterOrderedArguments = ArrayBuilder.GetInstance(arguments.Length, null!); + + foreach (var argument in arguments) + { + Debug.Assert(parameterOrderedArguments[argument.Parameter.Ordinal] == null); + parameterOrderedArguments[argument.Parameter.Ordinal] = argument; + } + + return parameterOrderedArguments.ToImmutableArray(); + } } } diff --git a/src/Utilities/Compiler/Options/MSBuildItemOptionNames.cs b/src/Utilities/Compiler/Options/MSBuildItemOptionNames.cs index 54a0612cb8..91e9faed8a 100644 --- a/src/Utilities/Compiler/Options/MSBuildItemOptionNames.cs +++ b/src/Utilities/Compiler/Options/MSBuildItemOptionNames.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -47,7 +48,15 @@ public static ImmutableArray ParseItemOptionValue(string? itemOptionValu return ImmutableArray.Empty; } - return itemOptionValue.Split(s_itemMetadataValuesSeparators, StringSplitOptions.RemoveEmptyEntries).ToImmutableArray(); + return ProduceTrimmedArray(itemOptionValue).ToImmutableArray(); + } + + private static IEnumerable ProduceTrimmedArray(string itemOptionValue) + { + foreach (var platform in itemOptionValue.Split(s_itemMetadataValuesSeparators, StringSplitOptions.RemoveEmptyEntries)) + { + yield return platform.Trim(); + } } } -} +} \ No newline at end of file diff --git a/src/Utilities/Compiler/SmallDictionary.cs b/src/Utilities/Compiler/SmallDictionary.cs new file mode 100644 index 0000000000..32b0d5aff9 --- /dev/null +++ b/src/Utilities/Compiler/SmallDictionary.cs @@ -0,0 +1,862 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Analyzer.Utilities +{ + /// + /// Copied from https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/Collections/SmallDictionary.cs + /// Dictionary designed to hold small number of items. + /// Compared to the regular Dictionary, average overhead per-item is roughly the same, but + /// unlike regular dictionary, this one is based on an AVL tree and as such does not require + /// rehashing when items are added. + /// It does require rebalancing, but that is allocation-free. + /// + /// Major caveats: + /// 1) There is no Remove method. (can be added, but we do not seem to use Remove that much) + /// 2) foreach [keys|values|pairs] may allocate a small array. + /// 3) Performance is no longer O(1). At a certain count it becomes slower than regular Dictionary. + /// In comparison to regular Dictionary on my machine: + /// On trivial number of elements (5 or so) it is more than 2x faster. + /// The break even count is about 120 elements for read and 55 for write operations (with unknown initial size). + /// At UShort.MaxValue elements, this dictionary is 6x slower to read and 4x slower to write + /// + /// Generally, this dictionary is a win if number of elements is small, not known beforehand or both. + /// + /// If the size of the dictionary is known at creation and it is likely to contain more than 10 elements, + /// then regular Dictionary is a better choice. + /// +#pragma warning disable CA1051, CA1716 // Do not declare visible instance fields + internal sealed class SmallDictionary : IEnumerable> + where K : notnull + { + private AvlNode? _root; + + public readonly IEqualityComparer Comparer; + + // https://github.com/dotnet/roslyn/issues/40344 + public static readonly SmallDictionary Empty = new SmallDictionary(null!); + + public SmallDictionary() : this(EqualityComparer.Default) { } + + public SmallDictionary(IEqualityComparer comparer) + { + Comparer = comparer; + } + + public SmallDictionary(SmallDictionary other, IEqualityComparer comparer) + : this(comparer) + { + // TODO: if comparers are same (often they are), then just need to clone the tree. + foreach (var kv in other) + { + this.Add(kv.Key, kv.Value); + } + } + + private bool CompareKeys(K k1, K k2) + { + return Comparer.Equals(k1, k2); + } + + private int GetHashCode(K k) + { + return Comparer.GetHashCode(k); + } + + public bool TryGetValue(K key, [MaybeNullWhen(returnValue: false)] out V value) + { + if (_root != null) + { + return TryGetValue(GetHashCode(key), key, out value!); + } + + value = default!; + return false; + } + + public void Add(K key, V value) + { + Insert(GetHashCode(key), key, value, add: true); + } + + public V this[K key] + { + get + { + V value; + if (!TryGetValue(key, out value!)) + { + throw new KeyNotFoundException($"Could not find key {key}"); + } + + return value; + } + + set + { + this.Insert(GetHashCode(key), key, value, add: false); + } + } + + public bool ContainsKey(K key) + { + return TryGetValue(key, out V _); + } + +#pragma warning disable CA1822 + [Conditional("DEBUG")] + internal void AssertBalanced() + { +#if DEBUG + AvlNode.AssertBalanced(_root); +#endif + } +#pragma warning restore CA1822 + private abstract class Node + { + public readonly K Key; + public V Value; + + protected Node(K key, V value) + { + this.Key = key; + this.Value = value; + } + + public virtual Node? Next => null; + } + + private sealed class NodeLinked : Node + { + public NodeLinked(K key, V value, Node next) + : base(key, value) + { + this.Next = next; + } + + public override Node Next { get; } + } + + private sealed class AvlNodeHead : AvlNode + { + public Node next; + + public AvlNodeHead(int hashCode, K key, V value, Node next) + : base(hashCode, key, value) + { + this.next = next; + } + + public override Node Next => next; + } + + // separate class to ensure that HashCode field + // is located before other AvlNode fields + // Balance is also here for better packing of AvlNode on 64bit + private abstract class HashedNode : Node + { + public readonly int HashCode; + public sbyte Balance; + + protected HashedNode(int hashCode, K key, V value) + : base(key, value) + { + this.HashCode = hashCode; + } + } + + private class AvlNode : HashedNode + { + public AvlNode? Left; + public AvlNode? Right; + + public AvlNode(int hashCode, K key, V value) + : base(hashCode, key, value) + { } + +#if DEBUG +#pragma warning disable CA1000 // Do not declare static members on generic types + public static int AssertBalanced(AvlNode? V) +#pragma warning restore CA1000 // Do not declare static members on generic types + { + if (V == null) return 0; + + int a = AssertBalanced(V.Left); + int b = AssertBalanced(V.Right); + + if (a - b != V.Balance || + Math.Abs(a - b) >= 2) + { + throw new InvalidOperationException(); + } + + return 1 + Math.Max(a, b); + } +#endif + } + + private bool TryGetValue(int hashCode, K key, [MaybeNullWhen(returnValue: false)] out V value) + { + RoslynDebug.Assert(_root is object); + AvlNode? b = _root; + + do + { + if (b.HashCode > hashCode) + { + b = b.Left; + } + else if (b.HashCode < hashCode) + { + b = b.Right; + } + else + { + goto hasBucket; + } + } while (b != null); + + value = default!; + return false; + + hasBucket: + if (CompareKeys(b.Key, key)) + { + value = b.Value; + return true; + } + + return GetFromList(b.Next, key, out value!); + } + + private bool GetFromList(Node? next, K key, [MaybeNullWhen(returnValue: false)] out V value) + { + while (next != null) + { + if (CompareKeys(key, next.Key)) + { + value = next.Value; + return true; + } + + next = next.Next; + } + + value = default!; + return false; + } + + private void Insert(int hashCode, K key, V value, bool add) + { + AvlNode? currentNode = _root; + + if (currentNode == null) + { + _root = new AvlNode(hashCode, key, value); + return; + } + + AvlNode? currentNodeParent = null; + AvlNode unbalanced = currentNode; + AvlNode? unbalancedParent = null; + + // ====== insert new node + // also make a note of the last unbalanced node and its parent (for rotation if needed) + // nodes on the search path from rotation candidate downwards will change balances because of the node added + // unbalanced node itself will become balanced or will be rotated + // either way nodes above unbalanced do not change their balance + for (; ; ) + { + // schedule hk read + var hc = currentNode.HashCode; + + if (currentNode.Balance != 0) + { + unbalancedParent = currentNodeParent; + unbalanced = currentNode; + } + + if (hc > hashCode) + { + if (currentNode.Left == null) + { + var previousNode = currentNode; + currentNode = new AvlNode(hashCode, key, value); + previousNode.Left = currentNode; + break; + } + + currentNodeParent = currentNode; + currentNode = currentNode.Left; + } + else if (hc < hashCode) + { + if (currentNode.Right == null) + { + var previousNode = currentNode; + currentNode = new AvlNode(hashCode, key, value); + previousNode.Right = currentNode; + break; + } + + currentNodeParent = currentNode; + currentNode = currentNode.Right; + } + else // (p.HashCode == hashCode) + { + this.HandleInsert(currentNode, currentNodeParent, key, value, add); + return; + } + } + + Debug.Assert(unbalanced != currentNode); + + // ====== update balances on the path from unbalanced downwards + var n = unbalanced; + do + { + Debug.Assert(n.HashCode != hashCode); + + if (n.HashCode < hashCode) + { + n.Balance--; + n = n.Right!; + } + else + { + n.Balance++; + n = n.Left!; + } + } + while (n != currentNode); + + // ====== rotate unbalanced node if needed + AvlNode rotated; + var balance = unbalanced.Balance; + if (balance == -2) + { + rotated = unbalanced.Right!.Balance < 0 ? + LeftSimple(unbalanced) : + LeftComplex(unbalanced); + } + else if (balance == 2) + { + rotated = unbalanced.Left!.Balance > 0 ? + RightSimple(unbalanced) : + RightComplex(unbalanced); + } + else + { + return; + } + + // ===== make parent to point to rotated + if (unbalancedParent == null) + { + _root = rotated; + } + else if (unbalanced == unbalancedParent.Left) + { + unbalancedParent.Left = rotated; + } + else + { + unbalancedParent.Right = rotated; + } + } + + private static AvlNode LeftSimple(AvlNode unbalanced) + { + RoslynDebug.Assert(unbalanced.Right is object); + var right = unbalanced.Right; + unbalanced.Right = right.Left; + right.Left = unbalanced; + + unbalanced.Balance = 0; + right.Balance = 0; + return right; + } + + private static AvlNode RightSimple(AvlNode unbalanced) + { + RoslynDebug.Assert(unbalanced.Left is object); + var left = unbalanced.Left; + unbalanced.Left = left.Right; + left.Right = unbalanced; + + unbalanced.Balance = 0; + left.Balance = 0; + return left; + } + + private static AvlNode LeftComplex(AvlNode unbalanced) + { + RoslynDebug.Assert(unbalanced.Right is object); + RoslynDebug.Assert(unbalanced.Right.Left is object); + var right = unbalanced.Right; + var rightLeft = right.Left; + right.Left = rightLeft.Right; + rightLeft.Right = right; + unbalanced.Right = rightLeft.Left; + rightLeft.Left = unbalanced; + + var rightLeftBalance = rightLeft.Balance; + rightLeft.Balance = 0; + + if (rightLeftBalance < 0) + { + right.Balance = 0; + unbalanced.Balance = 1; + } + else + { + right.Balance = (sbyte)-rightLeftBalance; + unbalanced.Balance = 0; + } + + return rightLeft; + } + + private static AvlNode RightComplex(AvlNode unbalanced) + { + RoslynDebug.Assert(unbalanced.Left != null); + RoslynDebug.Assert(unbalanced.Left.Right != null); + var left = unbalanced.Left; + var leftRight = left.Right; + left.Right = leftRight.Left; + leftRight.Left = left; + unbalanced.Left = leftRight.Right; + leftRight.Right = unbalanced; + + var leftRightBalance = leftRight.Balance; + leftRight.Balance = 0; + + if (leftRightBalance < 0) + { + left.Balance = 1; + unbalanced.Balance = 0; + } + else + { + left.Balance = 0; + unbalanced.Balance = (sbyte)-leftRightBalance; + } + + return leftRight; + } + + private void HandleInsert(AvlNode node, AvlNode? parent, K key, V value, bool add) + { + Node? currentNode = node; + do + { + if (CompareKeys(currentNode.Key, key)) + { + if (add) + { + throw new InvalidOperationException(); + } + + currentNode.Value = value; + return; + } + + currentNode = currentNode.Next; + } while (currentNode != null); + + AddNode(node, parent, key, value); + } + + private void AddNode(AvlNode node, AvlNode? parent, K key, V value) + { + if (node is AvlNodeHead head) + { + var newNext = new NodeLinked(key, value, head.next); + head.next = newNext; + return; + } + + var newHead = new AvlNodeHead(node.HashCode, key, value, node); + newHead.Balance = node.Balance; + newHead.Left = node.Left; + newHead.Right = node.Right; + + if (parent == null) + { + _root = newHead; + return; + } + + if (node == parent.Left) + { + parent.Left = newHead; + } + else + { + parent.Right = newHead; + } + } + + public KeyCollection Keys => new KeyCollection(this); + +#pragma warning disable CA1815 // Override equals and operator equals on value types + internal struct KeyCollection : IEnumerable + { + private readonly SmallDictionary _dict; + + public KeyCollection(SmallDictionary dict) + { + _dict = dict; + } + + public struct Enumerator + { + private readonly Stack _stack; + private Node? _next; + private Node _current; + + public Enumerator(SmallDictionary dict) + : this() + { + var root = dict._root; + if (root != null) + { + // left == right only if both are nulls + if (root.Left == root.Right) + { + _next = root; + } + else + { + _stack = new Stack(dict.HeightApprox()); + _stack.Push(root); + } + } + } + + public K Current => _current.Key; + + public bool MoveNext() + { + if (_next != null) + { + _current = _next; + _next = _next.Next; + return true; + } + + if (_stack == null || _stack.Count == 0) + { + return false; + } + + var curr = _stack.Pop(); + _current = curr; + _next = curr.Next; + + PushIfNotNull(curr.Left); + PushIfNotNull(curr.Right); + + return true; + } + + private void PushIfNotNull(AvlNode? child) + { + if (child != null) + { + _stack.Push(child); + } + } + } + + public Enumerator GetEnumerator() + { + return new Enumerator(_dict); + } + +#pragma warning disable CA1063, CA1816 // Implement IDisposable Correctly + public class EnumerableImpl : IEnumerator + { + private Enumerator _e; + + public EnumerableImpl(Enumerator e) + { + _e = e; + } + + K IEnumerator.Current => _e.Current; + + void IDisposable.Dispose() + { + } + + object IEnumerator.Current => _e.Current; + + bool IEnumerator.MoveNext() + { + return _e.MoveNext(); + } + + void IEnumerator.Reset() + { + throw new NotSupportedException(); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new EnumerableImpl(GetEnumerator()); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + public ValueCollection Values => new ValueCollection(this); + + internal struct ValueCollection : IEnumerable + { + private readonly SmallDictionary _dict; + + public ValueCollection(SmallDictionary dict) + { + _dict = dict; + } + + public struct Enumerator + { + private readonly Stack _stack; + private Node? _next; + private Node _current; + + public Enumerator(SmallDictionary dict) + : this() + { + var root = dict._root; + if (root == null) + { + return; + } + + // left == right only if both are nulls + if (root.Left == root.Right) + { + _next = root; + } + else + { + _stack = new Stack(dict.HeightApprox()); + _stack.Push(root); + } + } + + public V Current => _current.Value; + + public bool MoveNext() + { + if (_next != null) + { + _current = _next; + _next = _next.Next; + return true; + } + + if (_stack == null || _stack.Count == 0) + { + return false; + } + + var curr = _stack.Pop(); + _current = curr; + _next = curr.Next; + + PushIfNotNull(curr.Left); + PushIfNotNull(curr.Right); + + return true; + } + + private void PushIfNotNull(AvlNode? child) + { + if (child != null) + { + _stack.Push(child); + } + } + } + + public Enumerator GetEnumerator() + { + return new Enumerator(_dict); + } + + public class EnumerableImpl : IEnumerator + { + private Enumerator _e; + + public EnumerableImpl(Enumerator e) + { + _e = e; + } + + V IEnumerator.Current => _e.Current; + + void IDisposable.Dispose() + { + } + + object? IEnumerator.Current => _e.Current; + + bool IEnumerator.MoveNext() + { + return _e.MoveNext(); + } + + void IEnumerator.Reset() + { + throw new NotImplementedException(); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new EnumerableImpl(GetEnumerator()); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + public struct Enumerator + { + private readonly Stack _stack; + private Node? _next; + private Node _current; + + public Enumerator(SmallDictionary dict) + : this() + { + var root = dict._root; + if (root == null) + { + return; + } + + // left == right only if both are nulls + if (root.Left == root.Right) + { + _next = root; + } + else + { + _stack = new Stack(dict.HeightApprox()); + _stack.Push(root); + } + } + + public KeyValuePair Current => new KeyValuePair(_current.Key, _current.Value); + + public bool MoveNext() + { + if (_next != null) + { + _current = _next; + _next = _next.Next; + return true; + } + + if (_stack == null || _stack.Count == 0) + { + return false; + } + + var curr = _stack.Pop(); + _current = curr; + _next = curr.Next; + + PushIfNotNull(curr.Left); + PushIfNotNull(curr.Right); + + return true; + } + + private void PushIfNotNull(AvlNode? child) + { + if (child != null) + { + _stack.Push(child); + } + } + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public class EnumerableImpl : IEnumerator> + { + private Enumerator _e; + + public EnumerableImpl(Enumerator e) + { + _e = e; + } + + KeyValuePair IEnumerator>.Current => _e.Current; + + void IDisposable.Dispose() + { + } + + object IEnumerator.Current => _e.Current; + + bool IEnumerator.MoveNext() + { + return _e.MoveNext(); + } + + void IEnumerator.Reset() + { + throw new NotImplementedException(); + } + } +#pragma warning restore CA1063, CA1816 // Implement IDisposable Correctly +#pragma warning restore CA1815 // Override equals and operator equals on value types + + IEnumerator> IEnumerable>.GetEnumerator() + { + return new EnumerableImpl(GetEnumerator()); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + private int HeightApprox() + { + // height is less than 1.5 * depth(leftmost node) + var h = 0; + var cur = _root; + while (cur != null) + { + h++; + cur = cur.Left; + } + + h += h / 2; + return h; + } + } +#pragma warning restore CA1051, CA1716 // Do not declare visible instance fields +} diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index b23b394af3..2ea5756fb7 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -216,6 +216,7 @@ internal static class WellKnownTypeNames public const string SystemNullable1 = "System.Nullable`1"; public const string SystemNumber = "System.Number"; public const string SystemObsoleteAttribute = "System.ObsoleteAttribute"; + public const string SystemOperatingSystem = "System.OperatingSystem"; public const string SystemOutOfMemoryException = "System.OutOfMemoryException"; public const string SystemRandom = "System.Random"; public const string SystemRange = "System.Range"; diff --git a/src/Utilities/FlowAnalysis/Extensions/OperationBlocksExtensions.cs b/src/Utilities/FlowAnalysis/Extensions/OperationBlocksExtensions.cs index f75c2b1353..1032006d02 100644 --- a/src/Utilities/FlowAnalysis/Extensions/OperationBlocksExtensions.cs +++ b/src/Utilities/FlowAnalysis/Extensions/OperationBlocksExtensions.cs @@ -9,18 +9,22 @@ namespace Analyzer.Utilities.Extensions { internal static partial class OperationBlocksExtensions { - public static ControlFlowGraph? GetControlFlowGraph(this ImmutableArray operationBlocks) + public static ControlFlowGraph? GetControlFlowGraph(this ImmutableArray operationBlocks, out IBlockOperation? topmostBlock) { foreach (var operationRoot in operationBlocks) { - IBlockOperation? topmostBlock = operationRoot.GetTopmostParentBlock(); + topmostBlock = operationRoot.GetTopmostParentBlock(); if (topmostBlock != null) { return topmostBlock.GetEnclosingControlFlowGraph(); } } + topmostBlock = null; return null; } + + public static ControlFlowGraph? GetControlFlowGraph(this ImmutableArray operationBlocks) + => operationBlocks.GetControlFlowGraph(out _); } } diff --git a/src/Utilities/FlowAnalysis/FlowAnalysis/Analysis/GlobalFlowStateAnalysis/GlobalFlowStateAnalysis.cs b/src/Utilities/FlowAnalysis/FlowAnalysis/Analysis/GlobalFlowStateAnalysis/GlobalFlowStateAnalysis.cs index 9ffb6f5030..f58b98081f 100644 --- a/src/Utilities/FlowAnalysis/FlowAnalysis/Analysis/GlobalFlowStateAnalysis/GlobalFlowStateAnalysis.cs +++ b/src/Utilities/FlowAnalysis/FlowAnalysis/Analysis/GlobalFlowStateAnalysis/GlobalFlowStateAnalysis.cs @@ -42,8 +42,10 @@ private GlobalFlowStateAnalysis(GlobalFlowStateAnalysisDomain analysisDomain, Gl { var interproceduralAnalysisConfig = InterproceduralAnalysisConfiguration.Create( analyzerOptions, rule, owningSymbol, wellKnownTypeProvider.Compilation, interproceduralAnalysisKind, cancellationToken); + var pointsToAnalysisKind = analyzerOptions.GetPointsToAnalysisKindOption(rule, owningSymbol, wellKnownTypeProvider.Compilation, + defaultValue: PointsToAnalysisKind.PartialWithoutTrackingFieldsAndProperties, cancellationToken); return TryGetOrComputeResult(cfg, owningSymbol, createOperationVisitor, wellKnownTypeProvider, analyzerOptions, - interproceduralAnalysisConfig, interproceduralAnalysisPredicate, pessimisticAnalysis, + interproceduralAnalysisConfig, interproceduralAnalysisPredicate, pointsToAnalysisKind, pessimisticAnalysis, performValueContentAnalysis, out valueContentAnalysisResult); } @@ -55,6 +57,7 @@ private GlobalFlowStateAnalysis(GlobalFlowStateAnalysisDomain analysisDomain, Gl AnalyzerOptions analyzerOptions, InterproceduralAnalysisConfiguration interproceduralAnalysisConfig, InterproceduralAnalysisPredicate? interproceduralAnalysisPredicate, + PointsToAnalysisKind pointsToAnalysisKind, bool pessimisticAnalysis, bool performValueContentAnalysis, out ValueContentAnalysisResult? valueContentAnalysisResult) @@ -63,16 +66,18 @@ private GlobalFlowStateAnalysis(GlobalFlowStateAnalysisDomain analysisDomain, Gl RoslynDebug.Assert(owningSymbol != null); PointsToAnalysisResult? pointsToAnalysisResult = null; - valueContentAnalysisResult = performValueContentAnalysis ? ValueContentAnalysis.ValueContentAnalysis.TryGetOrComputeResult( cfg, owningSymbol, analyzerOptions, wellKnownTypeProvider, - PointsToAnalysisKind.PartialWithoutTrackingFieldsAndProperties, - interproceduralAnalysisConfig, out _, + pointsToAnalysisKind, interproceduralAnalysisConfig, out _, out pointsToAnalysisResult, pessimisticAnalysis, performCopyAnalysis: false, interproceduralAnalysisPredicate) : null; + pointsToAnalysisResult ??= PointsToAnalysis.PointsToAnalysis.TryGetOrComputeResult( + cfg, owningSymbol, analyzerOptions, wellKnownTypeProvider, + pointsToAnalysisKind, interproceduralAnalysisConfig, interproceduralAnalysisPredicate); + var analysisContext = GlobalFlowStateAnalysisContext.Create( GlobalFlowStateAnalysisValueSetDomain.Instance, wellKnownTypeProvider, cfg, owningSymbol, analyzerOptions, interproceduralAnalysisConfig, pessimisticAnalysis, pointsToAnalysisResult, diff --git a/src/Utilities/FlowAnalysis/FlowAnalysis/Analysis/GlobalFlowStateAnalysis/GlobalFlowStateDataFlowOperationVisitor.cs b/src/Utilities/FlowAnalysis/FlowAnalysis/Analysis/GlobalFlowStateAnalysis/GlobalFlowStateDataFlowOperationVisitor.cs index 9d5b8c0fd4..3cc4344226 100644 --- a/src/Utilities/FlowAnalysis/FlowAnalysis/Analysis/GlobalFlowStateAnalysis/GlobalFlowStateDataFlowOperationVisitor.cs +++ b/src/Utilities/FlowAnalysis/FlowAnalysis/Analysis/GlobalFlowStateAnalysis/GlobalFlowStateDataFlowOperationVisitor.cs @@ -5,7 +5,9 @@ using System.Diagnostics; using System.Linq; using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.CopyAnalysis; using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis; +using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis; using Microsoft.CodeAnalysis.Operations; using static Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.GlobalFlowStateAnalysis.GlobalFlowStateAnalysis; @@ -99,7 +101,7 @@ protected void MergeAndSetGlobalState(GlobalFlowStateAnalysisValueSet value, boo { Debug.Assert(_hasPredicatedGlobalState || !negate); - if (!value.AnalysisValues.IsEmpty) + if (value.Kind == GlobalFlowStateAnalysisValueSetKind.Known) { var currentGlobalValue = GetAbstractValue(_globalEntity); if (currentGlobalValue.Kind != GlobalFlowStateAnalysisValueSetKind.Unknown) @@ -174,6 +176,17 @@ protected sealed override void ApplyInterproceduralAnalysisResultCore(GlobalFlow => ApplyInterproceduralAnalysisResultHelper(resultData); protected sealed override GlobalFlowStateAnalysisData GetTrimmedCurrentAnalysisData(IEnumerable withEntities) => GetTrimmedCurrentAnalysisDataHelper(withEntities, CurrentAnalysisData, SetAbstractValue); + protected override GlobalFlowStateAnalysisData GetInitialInterproceduralAnalysisData( + IMethodSymbol invokedMethod, + (AnalysisEntity? Instance, PointsToAbstractValue PointsToValue)? invocationInstance, + (AnalysisEntity Instance, PointsToAbstractValue PointsToValue)? thisOrMeInstanceForCaller, + ImmutableDictionary> argumentValuesMap, + IDictionary? pointsToValues, + IDictionary? copyValues, + IDictionary? valueContentValues, + bool isLambdaOrLocalFunction, + bool hasParameterWithDelegateType) + => GetClonedCurrentAnalysisData(); protected GlobalFlowStateAnalysisValueSet GetValueOrDefault(GlobalFlowStateAnalysisValueSet value) => value.Kind == GlobalFlowStateAnalysisValueSetKind.Known ? value : GlobalState; diff --git a/src/Utilities/FlowAnalysis/FlowAnalysis/Framework/DataFlow/DataFlowAnalysisResult.cs b/src/Utilities/FlowAnalysis/FlowAnalysis/Framework/DataFlow/DataFlowAnalysisResult.cs index e17602e8b2..1c32ce0bfb 100644 --- a/src/Utilities/FlowAnalysis/FlowAnalysis/Framework/DataFlow/DataFlowAnalysisResult.cs +++ b/src/Utilities/FlowAnalysis/FlowAnalysis/Framework/DataFlow/DataFlowAnalysisResult.cs @@ -135,6 +135,17 @@ public TAbstractAnalysisValue this[IOperation operation] return null; } + internal IEnumerable>? TryGetInterproceduralResults() + { + foreach (var kvp in _interproceduralResultsMap) + { + if (kvp.Key is IInvocationOperation) + { + yield return (DataFlowAnalysisResult)kvp.Value; + } + } + } + public ControlFlowGraph ControlFlowGraph { get; } public (TAbstractAnalysisValue Value, PredicateValueKind PredicateValueKind)? ReturnValueAndPredicateKind { get; } public TBlockAnalysisResult EntryBlockOutput { get; }