Skip to content

Commit

Permalink
Handle guarded maccatalyst attribute issue that suppressed by call site
Browse files Browse the repository at this point in the history
  • Loading branch information
buyaa-n committed Feb 10, 2025
1 parent 8fe7aeb commit 3265f07
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -406,7 +411,7 @@ argument.ConstantValue.Value is string platformName &&

private static bool HasGuardedLambdaOrLocalFunctionResult(IOperation platformSpecificOperation, SmallDictionary<string, Versions> attributes,
ref SmallDictionary<string, Versions>? csAttributes, DataFlowAnalysisResult<GlobalFlowStateBlockAnalysisResult,
GlobalFlowStateAnalysisValueSet> analysisResult, SmallDictionary<string, Versions>? originalCsAttributes)
GlobalFlowStateAnalysisValueSet> analysisResult, SmallDictionary<string, Versions>? originalCsAttributes, SmallDictionary<string, Versions> originalAttributes)
{
if (!platformSpecificOperation.IsWithinLambdaOrLocalFunction(out var containingLambdaOrLocalFunctionOperation))
{
Expand All @@ -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;
}
Expand Down Expand Up @@ -474,17 +479,19 @@ invocation.Arguments[0].Value is IPropertyReferenceOperation propertyReference &
}

private static bool IsKnownValueGuarded(SmallDictionary<string, Versions> attributes,
ref SmallDictionary<string, Versions>? csAttributes, GlobalFlowStateAnalysisValueSet value, SmallDictionary<string, Versions>? originalCsAttributes)
ref SmallDictionary<string, Versions>? csAttributes, GlobalFlowStateAnalysisValueSet value,
SmallDictionary<string, Versions>? originalCsAttributes, SmallDictionary<string, Versions> originalAttributes)
{
using var capturedVersions = PooledDictionary<string, Version>.GetInstance(StringComparer.OrdinalIgnoreCase);
return IsKnownValueGuarded(attributes, ref csAttributes, value, capturedVersions, originalCsAttributes);
return IsKnownValueGuarded(attributes, ref csAttributes, value, capturedVersions, originalCsAttributes, originalAttributes);

static bool IsKnownValueGuarded(
SmallDictionary<string, Versions> attributes,
ref SmallDictionary<string, Versions>? csAttributes,
GlobalFlowStateAnalysisValueSet value,
PooledDictionary<string, Version> capturedVersions,
SmallDictionary<string, Versions>? originalCsAttributes)
SmallDictionary<string, Versions>? originalCsAttributes,
SmallDictionary<string, Versions> originalAttributes)
{
// 'GlobalFlowStateAnalysisValueSet.AnalysisValues' represent the && of values.
foreach (var analysisValue in value.AnalysisValues)
Expand Down Expand Up @@ -630,9 +637,19 @@ 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.PlatformName.Equals(maccatalyst, StringComparison.OrdinalIgnoreCase) && originalAttributes.ContainsKey(maccatalyst))
{
continue;
}
}
}

if (!IsKnownValueGuarded(parentAttributes, ref parentCsAttributes, parent, parentCapturedVersions, originalCsAttributes))
if (!IsKnownValueGuarded(parentAttributes, ref parentCsAttributes, parent, parentCapturedVersions, originalCsAttributes, originalAttributes))
{
csAttributes = parentCsAttributes;
return false;
Expand Down Expand Up @@ -791,24 +808,21 @@ static void RemoveOtherSupportsOnDifferentPlatforms(SmallDictionary<string, Vers

private static bool IsEmptyVersion(Version version) => version.Major == 0 && version.Minor == 0;

private static void ReportDiagnostics(KeyValuePair<IOperation, ISymbol> operationToSymbol, SmallDictionary<string, Versions> attributes,
private static void ReportDiagnostics(IOperation operation, SmallDictionary<string, Versions> attributes,
SmallDictionary<string, Versions>? csAttributes, OperationBlockAnalysisContext context,
ConcurrentDictionary<ISymbol, PlatformAttributes> platformSpecificMembers)
ISymbol symbol, SmallDictionary<string, Versions> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;";
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;";
await VerifyAnalyzerCSAsync(source, msBuildPlatforms);
}

private readonly string TargetTypesForTest = @"
namespace PlatformCompatDemo.SupportedUnupported
{
Expand Down

0 comments on commit 3265f07

Please sign in to comment.