From f121b81571f435b3bc1a4ca78542f70eb74660d9 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Sun, 9 Feb 2025 21:55:20 -0800 Subject: [PATCH 1/2] Handle guarded maccatalyst attribute issue that suppressed by call site --- .../PlatformCompatibilityAnalyzer.cs | 50 ++++++++++------ ...tibilityAnalyzerTests.GuardedCallsTests.cs | 58 +++++++++++++++++++ 2 files changed, 91 insertions(+), 17 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs index 874bf18ddb..14a403c754 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs @@ -57,6 +57,8 @@ public sealed partial class PlatformCompatibilityAnalyzer : DiagnosticAnalyzer private const string macOS = nameof(macOS); private const string OSX = nameof(OSX); private const string MacSlashOSX = "macOS/OSX"; + private const string ios = nameof(ios); + private const string maccatalyst = nameof(maccatalyst); private static readonly Version EmptyVersion = new(0, 0); internal static readonly DiagnosticDescriptor OnlySupportedCsReachable = DiagnosticDescriptorHelper.Create( @@ -365,15 +367,18 @@ private void AnalyzeOperationBlock( { var value = analysisResult[platformSpecificOperation.Key.Kind, platformSpecificOperation.Key.Syntax]; var csAttributes = pair.csAttributes != null ? CopyAttributes(pair.csAttributes) : null; + var symbol = platformSpecificOperation.Value is IMethodSymbol method && method.IsConstructor() ? + platformSpecificOperation.Value.ContainingType : platformSpecificOperation.Value; + var originalAttributes = platformSpecificMembers[symbol].Platforms ?? pair.attributes; - if ((value.Kind == GlobalFlowStateAnalysisValueSetKind.Known && IsKnownValueGuarded(pair.attributes, ref csAttributes, value, pair.csAttributes)) || + if ((value.Kind == GlobalFlowStateAnalysisValueSetKind.Known && IsKnownValueGuarded(pair.attributes, ref csAttributes, value, pair.csAttributes, originalAttributes)) || (value.Kind == GlobalFlowStateAnalysisValueSetKind.Unknown && HasGuardedLambdaOrLocalFunctionResult(platformSpecificOperation.Key, - pair.attributes, ref csAttributes, analysisResult, pair.csAttributes))) + pair.attributes, ref csAttributes, analysisResult, pair.csAttributes, originalAttributes))) { continue; } - ReportDiagnostics(platformSpecificOperation, pair.attributes, csAttributes, context, platformSpecificMembers); + ReportDiagnostics(platformSpecificOperation.Key, pair.attributes, csAttributes, context, symbol, originalAttributes); } } finally @@ -406,7 +411,7 @@ argument.ConstantValue.Value is string platformName && private static bool HasGuardedLambdaOrLocalFunctionResult(IOperation platformSpecificOperation, SmallDictionary attributes, ref SmallDictionary? csAttributes, DataFlowAnalysisResult analysisResult, SmallDictionary? originalCsAttributes) + GlobalFlowStateAnalysisValueSet> analysisResult, SmallDictionary? originalCsAttributes, SmallDictionary originalAttributes) { if (!platformSpecificOperation.IsWithinLambdaOrLocalFunction(out var containingLambdaOrLocalFunctionOperation)) { @@ -426,7 +431,7 @@ private static bool HasGuardedLambdaOrLocalFunctionResult(IOperation platformSpe // NOTE: IsKnownValueGuarded mutates the input values, so we pass in cloned values // to ensure that evaluation of each result is independent of evaluation of other parts. if (localValue.Kind != GlobalFlowStateAnalysisValueSetKind.Known || - !IsKnownValueGuarded(CopyAttributes(attributes), ref csAttributes, localValue, originalCsAttributes)) + !IsKnownValueGuarded(CopyAttributes(attributes), ref csAttributes, localValue, originalCsAttributes, originalAttributes)) { return false; } @@ -474,17 +479,19 @@ invocation.Arguments[0].Value is IPropertyReferenceOperation propertyReference & } private static bool IsKnownValueGuarded(SmallDictionary attributes, - ref SmallDictionary? csAttributes, GlobalFlowStateAnalysisValueSet value, SmallDictionary? originalCsAttributes) + ref SmallDictionary? csAttributes, GlobalFlowStateAnalysisValueSet value, + SmallDictionary? originalCsAttributes, SmallDictionary originalAttributes) { using var capturedVersions = PooledDictionary.GetInstance(StringComparer.OrdinalIgnoreCase); - return IsKnownValueGuarded(attributes, ref csAttributes, value, capturedVersions, originalCsAttributes); + return IsKnownValueGuarded(attributes, ref csAttributes, value, capturedVersions, originalCsAttributes, originalAttributes); static bool IsKnownValueGuarded( SmallDictionary attributes, ref SmallDictionary? csAttributes, GlobalFlowStateAnalysisValueSet value, PooledDictionary capturedVersions, - SmallDictionary? originalCsAttributes) + SmallDictionary? originalCsAttributes, + SmallDictionary originalAttributes) { // 'GlobalFlowStateAnalysisValueSet.AnalysisValues' represent the && of values. foreach (var analysisValue in value.AnalysisValues) @@ -630,9 +637,21 @@ static bool IsKnownValueGuarded( { continue; } + + // Skip maccatalyst check in case it was suppressed by callsite attribute + if (parent.AnalysisValues.Count == 1 && attributes.ContainsKey(ios) && !attributes.ContainsKey(maccatalyst)) + { + PlatformMethodValue parentValue = (PlatformMethodValue)parent.AnalysisValues.First(); + if (!parentValue.Negated && parentValue.PlatformName.Equals(maccatalyst, StringComparison.OrdinalIgnoreCase) && + originalAttributes.TryGetValue(maccatalyst, out Versions? macVersion) && + parentValue.Version.IsGreaterThanOrEqualTo(macVersion.SupportedFirst)) + { + continue; + } + } } - if (!IsKnownValueGuarded(parentAttributes, ref parentCsAttributes, parent, parentCapturedVersions, originalCsAttributes)) + if (!IsKnownValueGuarded(parentAttributes, ref parentCsAttributes, parent, parentCapturedVersions, originalCsAttributes, originalAttributes)) { csAttributes = parentCsAttributes; return false; @@ -791,24 +810,21 @@ static void RemoveOtherSupportsOnDifferentPlatforms(SmallDictionary version.Major == 0 && version.Minor == 0; - private static void ReportDiagnostics(KeyValuePair operationToSymbol, SmallDictionary attributes, + private static void ReportDiagnostics(IOperation operation, SmallDictionary attributes, SmallDictionary? csAttributes, OperationBlockAnalysisContext context, - ConcurrentDictionary platformSpecificMembers) + ISymbol symbol, SmallDictionary originalAttributes) { - var symbol = operationToSymbol.Value is IMethodSymbol method && method.IsConstructor() ? operationToSymbol.Value.ContainingType : operationToSymbol.Value; - var operationName = symbol.ToDisplayString(GetLanguageSpecificFormat(operationToSymbol.Key)); - - var originalAttributes = platformSpecificMembers[symbol].Platforms ?? attributes; + var operationName = symbol.ToDisplayString(GetLanguageSpecificFormat(operation)); foreach (var attribute in originalAttributes.Values) { if (AllowList(attribute)) { - ReportSupportedDiagnostic(operationToSymbol.Key, context, operationName, attributes, csAttributes); + ReportSupportedDiagnostic(operation, context, operationName, attributes, csAttributes); } else { - ReportUnsupportedDiagnostic(operationToSymbol.Key, context, operationName, attributes, csAttributes); + ReportUnsupportedDiagnostic(operation, context, operationName, attributes, csAttributes); } break; 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 6396dfb9b5..cf3d6f9ed4 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs @@ -5340,6 +5340,64 @@ class MyType { } await VerifyAnalyzerCSAsync(source, s_msBuildPlatforms); } + [Fact, WorkItem(7239, "https://github.com/dotnet/roslyn-analyzers/issues/7239")] + public async Task MacCatalystSuppressedByCallsiteSupportWithinGuard() + { + var source = @" +using System; +using System.Runtime.Versioning; +class TestType +{ + [SupportedOSPlatform(""ios13.0"")] + [SupportedOSPlatform(""maccatalyst13.0"")] + private void Tapped() + { + if (OperatingSystem.IsIOSVersionAtLeast(15,0)) + DoSomething(); + + [|DoSomething()|]; // This call site is reachable on: 'ios' 13.0 and later, 'maccatalyst' 13.0 and later. 'TestType.DoSomething()' is only supported on: 'ios' 14.0 and later. + } + + [SupportedOSPlatform(""ios14.0"")] + [SupportedOSPlatform(""maccatalyst"")] + public void DoSomething() {} +}"; + + string msBuildPlatforms = "build_property.TargetFramework=net8.0-maccatalyst13.0;\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v8.0"; + await VerifyAnalyzerCSAsync(source, msBuildPlatforms); + } + + [Fact, WorkItem(6955, "https://github.com/dotnet/roslyn-analyzers/issues/6955")] + public async Task MacCatalystSuppressedByCallSiteSupportCalledWithinCustomGuard() + { + var source = @" +using System; +using System.Runtime.Versioning; +class TestType +{ + [SupportedOSPlatform(""ios12.0"")] + private void Tapped() + { + if (CheckSystemVersion(13,0)) + DoSomething(); + + [|DoSomething()|]; // This call site is reachable on: 'ios' 12.0 and later, 'maccatalyst' 12.0 and later. 'TestType.DoSomething()' is only supported on: 'ios' 13.0 and later. + } + + [SupportedOSPlatform(""ios13.0"")] + [SupportedOSPlatform(""maccatalyst"")] + public void DoSomething() {} + + [SupportedOSPlatformGuard (""ios"")] + [SupportedOSPlatformGuard (""tvos"")] + [SupportedOSPlatformGuard (""maccatalyst"")] + public bool CheckSystemVersion (int major, int minor) => false; +}"; + + string msBuildPlatforms = "build_property.TargetFramework=net8.0-maccatalyst12.0;\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v8.0"; + await VerifyAnalyzerCSAsync(source, msBuildPlatforms); + } + private readonly string TargetTypesForTest = @" namespace PlatformCompatDemo.SupportedUnupported { From 1b5fece6e077866e12863cb3fae95deb2d4d0e7c Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Mon, 10 Feb 2025 12:24:39 -0800 Subject: [PATCH 2/2] Skip any guarded platform that was suppressed by call site --- .../PlatformCompatibilityAnalyzer.cs | 20 ++++++------ ...tibilityAnalyzerTests.GuardedCallsTests.cs | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs index 14a403c754..2b71051189 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs @@ -57,8 +57,6 @@ public sealed partial class PlatformCompatibilityAnalyzer : DiagnosticAnalyzer private const string macOS = nameof(macOS); private const string OSX = nameof(OSX); private const string MacSlashOSX = "macOS/OSX"; - private const string ios = nameof(ios); - private const string maccatalyst = nameof(maccatalyst); private static readonly Version EmptyVersion = new(0, 0); internal static readonly DiagnosticDescriptor OnlySupportedCsReachable = DiagnosticDescriptorHelper.Create( @@ -638,16 +636,11 @@ static bool IsKnownValueGuarded( continue; } - // Skip maccatalyst check in case it was suppressed by callsite attribute - if (parent.AnalysisValues.Count == 1 && attributes.ContainsKey(ios) && !attributes.ContainsKey(maccatalyst)) + // Skip the platform check that was originally in the list and suppressed by callsite attributes + if (parent.AnalysisValues.Count == 1 && + IsPlatformSupportWasSuppresed((PlatformMethodValue)parent.AnalysisValues.First(), attributes, originalAttributes)) { - PlatformMethodValue parentValue = (PlatformMethodValue)parent.AnalysisValues.First(); - if (!parentValue.Negated && parentValue.PlatformName.Equals(maccatalyst, StringComparison.OrdinalIgnoreCase) && - originalAttributes.TryGetValue(maccatalyst, out Versions? macVersion) && - parentValue.Version.IsGreaterThanOrEqualTo(macVersion.SupportedFirst)) - { - continue; - } + continue; } } @@ -662,6 +655,11 @@ static bool IsKnownValueGuarded( return true; } + static bool IsPlatformSupportWasSuppresed(PlatformMethodValue parentValue, SmallDictionary attributes, SmallDictionary originalAttributes) + => !parentValue.Negated && !attributes.ContainsKey(parentValue.PlatformName) && + originalAttributes.TryGetValue(parentValue.PlatformName, out Versions? version) && + parentValue.Version.IsGreaterThanOrEqualTo(version.SupportedFirst); + static bool IsOnlySupportNeedsGuard(string platformName, SmallDictionary attributes, SmallDictionary csAttributes) => csAttributes.TryGetValue(platformName, out var versions) && AllowList(versions) && 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 cf3d6f9ed4..27f1d5ac9a 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs @@ -5398,6 +5398,37 @@ public void DoSomething() {} await VerifyAnalyzerCSAsync(source, msBuildPlatforms); } + [Fact, WorkItem(7530, "https://github.com/dotnet/roslyn-analyzers/issues/7530")] + public async Task OneOfCustomGuardsSuppressedByCallsite() + { + var source = @" +using System; +using System.Runtime.Versioning; +class TestType +{ + [SupportedOSPlatform(""macos15.0"")] + [SupportedOSPlatform(""tvos12.2"")] + private void GetFilter () + { + if (IsAtLeast) + DoSomething(); + + [|DoSomething()|]; // This call site is reachable on: 'macOS/OSX' 15.0 and later, 'tvos' 12.2 and later. 'TestType.DoSomething()' is only supported on: 'tvos' 15.0 and later. + } + + [SupportedOSPlatform(""tvos15.0"")] + [SupportedOSPlatform(""macos15.0"")] + public void DoSomething() {} + + [SupportedOSPlatformGuard (""macos15.0"")] + [SupportedOSPlatformGuard (""tvos15.0"")] + public bool IsAtLeast => false; +}"; + + string msBuildPlatforms = "build_property.TargetFramework=net8.0-maccatalyst12.0;\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v8.0"; + await VerifyAnalyzerCSAsync(source, msBuildPlatforms); + } + private readonly string TargetTypesForTest = @" namespace PlatformCompatDemo.SupportedUnupported {