diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.Value.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.Value.cs index 2dd346d6b6..d3ef5da64e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.Value.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.Value.cs @@ -215,15 +215,11 @@ static Version CreateVersion(ArrayBuilder versionBuilder) { 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]); + + return new Version(versionBuilder[0], versionBuilder[1], versionBuilder[2]); } + + return new Version(versionBuilder[0], versionBuilder[1], versionBuilder[2], versionBuilder[3]); } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs index 375454a4b4..3683de026d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs @@ -421,7 +421,8 @@ static bool IsKnownValueGuarded( { if (info.Negated) { - if (attribute.UnsupportedFirst != null) + if (attribute.UnsupportedFirst != null && + attribute.UnsupportedFirst.IsGreaterThanOrEqualTo(info.Version)) { if (attribute.UnsupportedFirst >= info.Version) { @@ -435,7 +436,8 @@ static bool IsKnownValueGuarded( } } - if (attribute.UnsupportedSecond != null) + if (attribute.UnsupportedSecond != null && + info.Version.IsGreaterThanOrEqualTo(attribute.UnsupportedSecond)) { if (attribute.UnsupportedSecond <= info.Version) { @@ -454,21 +456,21 @@ static bool IsKnownValueGuarded( { if (attribute.UnsupportedFirst != null && capturedVersions.TryGetValue(info.PlatformName, out var version) && - attribute.UnsupportedFirst >= version) + attribute.UnsupportedFirst.IsGreaterThanOrEqualTo(version)) { attribute.UnsupportedFirst = null; } if (attribute.UnsupportedSecond != null && capturedVersions.TryGetValue(info.PlatformName, out version) && - attribute.UnsupportedSecond <= version) + version.IsGreaterThanOrEqualTo(attribute.UnsupportedSecond)) { attribute.UnsupportedSecond = null; } } if (attribute.SupportedFirst != null && - attribute.SupportedFirst <= info.Version) + info.Version.IsGreaterThanOrEqualTo(attribute.SupportedFirst)) { attribute.SupportedFirst = null; RemoveUnsupportedWithLessVersion(info.Version, attribute); @@ -476,7 +478,7 @@ static bool IsKnownValueGuarded( } if (attribute.SupportedSecond != null && - attribute.SupportedSecond <= info.Version) + info.Version.IsGreaterThanOrEqualTo(attribute.SupportedSecond)) { attribute.SupportedSecond = null; RemoveUnsupportedWithLessVersion(info.Version, attribute); @@ -572,8 +574,7 @@ static void RemoveUnsupportsOnDifferentPlatforms(SmallDictionary= version ? supportedVersion : version; + version = supportedVersion.IsGreaterThanOrEqualTo(version) ? supportedVersion : version; } else { - version = supportedVersion; + var unsupportedVersion = attribute.UnsupportedSecond ?? attribute.UnsupportedFirst; + if (unsupportedVersion != null) + { + version = unsupportedVersion.IsGreaterThanOrEqualTo(version) ? unsupportedVersion : version; + } + else + { + version = supportedVersion; + } } } if (version != null && !IsEmptyVersion(version)) @@ -1263,25 +1272,25 @@ static bool UnsupportedSecondSuppressed(PlatformAttributes attribute, PlatformAt SuppressedByCallSiteUnsupported(callSiteAttribute, attribute.UnsupportedSecond!); static bool SuppressedByCallSiteUnsupported(PlatformAttributes callSiteAttribute, Version unsupporteAttribute) => - callSiteAttribute.UnsupportedFirst != null && unsupporteAttribute >= callSiteAttribute.UnsupportedFirst || - callSiteAttribute.UnsupportedSecond != null && unsupporteAttribute >= callSiteAttribute.UnsupportedSecond; + callSiteAttribute.UnsupportedFirst != null && unsupporteAttribute.IsGreaterThanOrEqualTo(callSiteAttribute.UnsupportedFirst) || + callSiteAttribute.UnsupportedSecond != null && unsupporteAttribute.IsGreaterThanOrEqualTo(callSiteAttribute.UnsupportedSecond); static bool SuppressedByCallSiteSupported(PlatformAttributes attribute, Version? callSiteSupportedFirst) => - callSiteSupportedFirst != null && callSiteSupportedFirst >= attribute.SupportedFirst! && - attribute.SupportedSecond != null && callSiteSupportedFirst >= attribute.SupportedSecond; + callSiteSupportedFirst != null && callSiteSupportedFirst.IsGreaterThanOrEqualTo(attribute.SupportedFirst) && + attribute.SupportedSecond != null && callSiteSupportedFirst.IsGreaterThanOrEqualTo(attribute.SupportedSecond); static bool UnsupportedFirstSuppressed(PlatformAttributes attribute, PlatformAttributes callSiteAttribute) => - callSiteAttribute.SupportedFirst != null && callSiteAttribute.SupportedFirst >= attribute.SupportedFirst || + callSiteAttribute.SupportedFirst != null && callSiteAttribute.SupportedFirst.IsGreaterThanOrEqualTo(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); + (callSiteAttribute.SupportedFirst == null || callSiteAttribute.SupportedFirst.IsGreaterThanOrEqualTo(attribute.SupportedFirst)) && + (callSiteAttribute.SupportedSecond == null || callSiteAttribute.SupportedSecond.IsGreaterThanOrEqualTo(attribute.SupportedFirst)); static bool MandatoryOsVersionsSuppressed(PlatformAttributes callSitePlatforms, Version checkingVersion) => - callSitePlatforms.SupportedFirst != null && checkingVersion <= callSitePlatforms.SupportedFirst || - callSitePlatforms.SupportedSecond != null && checkingVersion <= callSitePlatforms.SupportedSecond; + callSitePlatforms.SupportedFirst != null && callSitePlatforms.SupportedFirst.IsGreaterThanOrEqualTo(checkingVersion) || + callSitePlatforms.SupportedSecond != null && callSitePlatforms.SupportedSecond.IsGreaterThanOrEqualTo(checkingVersion); } private static PlatformAttributes CopyAllAttributes(PlatformAttributes copyTo, PlatformAttributes copyFrom) @@ -1387,14 +1396,14 @@ static void MergePlatformAttributes(ImmutableArray immediateAttri if (childAttributes.TryGetValue(platform, out var childAttribute)) { // only later versions could narrow, other versions ignored - if (childAttribute.SupportedFirst > attributes.SupportedFirst) + if (childAttribute.SupportedFirst.IsGreaterThanOrEqualTo(attributes.SupportedFirst)) { attributes.SupportedSecond = childAttribute.SupportedFirst; } if (childAttribute.UnsupportedFirst != null) { - if (childAttribute.UnsupportedFirst <= attributes.SupportedFirst) + if (attributes.SupportedFirst.IsGreaterThanOrEqualTo(childAttribute.UnsupportedFirst)) { attributes.SupportedFirst = null; attributes.SupportedSecond = null; @@ -1547,7 +1556,7 @@ static void AddOrUpdateSupportedAttribute(PlatformAttributes attributes, Version /// true if it is allow list private static bool AllowList(PlatformAttributes attributes) => attributes.SupportedFirst != null && - (attributes.UnsupportedFirst == null || attributes.SupportedFirst <= attributes.UnsupportedFirst); + (attributes.UnsupportedFirst == null || attributes.UnsupportedFirst.IsGreaterThanOrEqualTo(attributes.SupportedFirst)); /// /// Determines if the attributes unsupported only for the platform (deny list) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs index 3e744ba3eb..80fce583a5 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs @@ -90,6 +90,103 @@ void Api() await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); } + [Fact, WorkItem(4932, "https://github.com/dotnet/roslyn-analyzers/issues/4932")] + public async Task GuardMethodWith3VersionPartsEquavalentTo4PartsWithLeading0() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class MSAL +{ + [SupportedOSPlatform(""windows10.0.17763.0"")] + public static void UseWAMWithLeading0() { } + + [SupportedOSPlatform(""windows10.0.17763"")] + public static void UseWAMNoLeading0() { } + + static void Test() + { + if (OperatingSystemHelper.IsWindowsVersionAtLeast(10, 0, 17763, 0)) + { + UseWAMWithLeading0(); + UseWAMNoLeading0(); + } + + if (OperatingSystemHelper.IsWindowsVersionAtLeast(10, 0, 17763)) + { + UseWAMWithLeading0(); + UseWAMNoLeading0(); + } + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardMethodWith1VersionPartsEquavalentTo2PartsWithLeading0() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class MSAL +{ + [SupportedOSPlatform(""windows10.0"")] + public static void UseWAMWithLeading0() { } + + static void Test() + { + if (OperatingSystemHelper.IsWindowsVersionAtLeast(10)) + { + UseWAMWithLeading0(); + } + + if (OperatingSystemHelper.IsWindowsVersionAtLeast(10, 0)) + { + UseWAMWithLeading0(); + } + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + + [Fact] + public async Task GuardMethodWith2VersionPartsEquavalentTo3PartsWithLeading0() + { + var source = @" +using System.Runtime.Versioning; +using System; + +public class MSAL +{ + [SupportedOSPlatform(""windows10.1.0"")] + public static void UseWAMWithLeading0() { } + + [SupportedOSPlatform(""windows10.1"")] + public static void UseWAMNoLeading0() { } + + static void Test() + { + if (OperatingSystemHelper.IsWindowsVersionAtLeast(10, 1)) + { + UseWAMWithLeading0(); + UseWAMNoLeading0(); + } + + if (OperatingSystemHelper.IsWindowsVersionAtLeast(10, 1, 0)) + { + UseWAMWithLeading0(); + UseWAMNoLeading0(); + } + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + + await VerifyAnalyzerAsyncCs(source); + } + [Fact] public async Task GuardsAroundSupported_SimpleIfElse() { diff --git a/src/Utilities/Compiler/Analyzer.Utilities.projitems b/src/Utilities/Compiler/Analyzer.Utilities.projitems index 9e3e75dbd2..1a55c0a361 100644 --- a/src/Utilities/Compiler/Analyzer.Utilities.projitems +++ b/src/Utilities/Compiler/Analyzer.Utilities.projitems @@ -53,6 +53,7 @@ + diff --git a/src/Utilities/Compiler/Extensions/VersionExtensions.cs b/src/Utilities/Compiler/Extensions/VersionExtensions.cs new file mode 100644 index 0000000000..c74b3df227 --- /dev/null +++ b/src/Utilities/Compiler/Extensions/VersionExtensions.cs @@ -0,0 +1,45 @@ +// 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 Analyzer.Utilities.Extensions +{ + internal static class VersionExtension + { + public static bool IsGreaterThanOrEqualTo(this Version? current, Version? compare) + { + if (current == null) + { + return compare == null; + } + + if (compare == null) + { + return true; + } + + if (current.Major != compare.Major) + { + return current.Major > compare.Major; + } + + if (current.Minor != compare.Minor) + { + return current.Minor > compare.Minor; + } + + // For build or revision value of 0 equals to -1 + if (current.Build != compare.Build && (current.Build > 0 || compare.Build > 0)) + { + return current.Build > compare.Build; + } + + if (current.Revision != compare.Revision && (current.Revision > 0 || compare.Revision > 0)) + { + return current.Revision > compare.Revision; + } + + return true; + } + } +}