Skip to content

Commit b88dcdb

Browse files
authored
Handle guarded maccatalyst attribute issue that suppressed by call site (#7569)
* Handle guarded maccatalyst attribute issue that suppressed by call site * Skip any guarded platform that was suppressed by call site
1 parent 483fca0 commit b88dcdb

File tree

2 files changed

+120
-17
lines changed

2 files changed

+120
-17
lines changed

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -365,15 +365,18 @@ private void AnalyzeOperationBlock(
365365
{
366366
var value = analysisResult[platformSpecificOperation.Key.Kind, platformSpecificOperation.Key.Syntax];
367367
var csAttributes = pair.csAttributes != null ? CopyAttributes(pair.csAttributes) : null;
368+
var symbol = platformSpecificOperation.Value is IMethodSymbol method && method.IsConstructor() ?
369+
platformSpecificOperation.Value.ContainingType : platformSpecificOperation.Value;
370+
var originalAttributes = platformSpecificMembers[symbol].Platforms ?? pair.attributes;
368371

369-
if ((value.Kind == GlobalFlowStateAnalysisValueSetKind.Known && IsKnownValueGuarded(pair.attributes, ref csAttributes, value, pair.csAttributes)) ||
372+
if ((value.Kind == GlobalFlowStateAnalysisValueSetKind.Known && IsKnownValueGuarded(pair.attributes, ref csAttributes, value, pair.csAttributes, originalAttributes)) ||
370373
(value.Kind == GlobalFlowStateAnalysisValueSetKind.Unknown && HasGuardedLambdaOrLocalFunctionResult(platformSpecificOperation.Key,
371-
pair.attributes, ref csAttributes, analysisResult, pair.csAttributes)))
374+
pair.attributes, ref csAttributes, analysisResult, pair.csAttributes, originalAttributes)))
372375
{
373376
continue;
374377
}
375378

376-
ReportDiagnostics(platformSpecificOperation, pair.attributes, csAttributes, context, platformSpecificMembers);
379+
ReportDiagnostics(platformSpecificOperation.Key, pair.attributes, csAttributes, context, symbol, originalAttributes);
377380
}
378381
}
379382
finally
@@ -406,7 +409,7 @@ argument.ConstantValue.Value is string platformName &&
406409

407410
private static bool HasGuardedLambdaOrLocalFunctionResult(IOperation platformSpecificOperation, SmallDictionary<string, Versions> attributes,
408411
ref SmallDictionary<string, Versions>? csAttributes, DataFlowAnalysisResult<GlobalFlowStateBlockAnalysisResult,
409-
GlobalFlowStateAnalysisValueSet> analysisResult, SmallDictionary<string, Versions>? originalCsAttributes)
412+
GlobalFlowStateAnalysisValueSet> analysisResult, SmallDictionary<string, Versions>? originalCsAttributes, SmallDictionary<string, Versions> originalAttributes)
410413
{
411414
if (!platformSpecificOperation.IsWithinLambdaOrLocalFunction(out var containingLambdaOrLocalFunctionOperation))
412415
{
@@ -426,7 +429,7 @@ private static bool HasGuardedLambdaOrLocalFunctionResult(IOperation platformSpe
426429
// NOTE: IsKnownValueGuarded mutates the input values, so we pass in cloned values
427430
// to ensure that evaluation of each result is independent of evaluation of other parts.
428431
if (localValue.Kind != GlobalFlowStateAnalysisValueSetKind.Known ||
429-
!IsKnownValueGuarded(CopyAttributes(attributes), ref csAttributes, localValue, originalCsAttributes))
432+
!IsKnownValueGuarded(CopyAttributes(attributes), ref csAttributes, localValue, originalCsAttributes, originalAttributes))
430433
{
431434
return false;
432435
}
@@ -474,17 +477,19 @@ invocation.Arguments[0].Value is IPropertyReferenceOperation propertyReference &
474477
}
475478

476479
private static bool IsKnownValueGuarded(SmallDictionary<string, Versions> attributes,
477-
ref SmallDictionary<string, Versions>? csAttributes, GlobalFlowStateAnalysisValueSet value, SmallDictionary<string, Versions>? originalCsAttributes)
480+
ref SmallDictionary<string, Versions>? csAttributes, GlobalFlowStateAnalysisValueSet value,
481+
SmallDictionary<string, Versions>? originalCsAttributes, SmallDictionary<string, Versions> originalAttributes)
478482
{
479483
using var capturedVersions = PooledDictionary<string, Version>.GetInstance(StringComparer.OrdinalIgnoreCase);
480-
return IsKnownValueGuarded(attributes, ref csAttributes, value, capturedVersions, originalCsAttributes);
484+
return IsKnownValueGuarded(attributes, ref csAttributes, value, capturedVersions, originalCsAttributes, originalAttributes);
481485

482486
static bool IsKnownValueGuarded(
483487
SmallDictionary<string, Versions> attributes,
484488
ref SmallDictionary<string, Versions>? csAttributes,
485489
GlobalFlowStateAnalysisValueSet value,
486490
PooledDictionary<string, Version> capturedVersions,
487-
SmallDictionary<string, Versions>? originalCsAttributes)
491+
SmallDictionary<string, Versions>? originalCsAttributes,
492+
SmallDictionary<string, Versions> originalAttributes)
488493
{
489494
// 'GlobalFlowStateAnalysisValueSet.AnalysisValues' represent the && of values.
490495
foreach (var analysisValue in value.AnalysisValues)
@@ -630,9 +635,16 @@ static bool IsKnownValueGuarded(
630635
{
631636
continue;
632637
}
638+
639+
// Skip the platform check that was originally in the list and suppressed by callsite attributes
640+
if (parent.AnalysisValues.Count == 1 &&
641+
IsPlatformSupportWasSuppresed((PlatformMethodValue)parent.AnalysisValues.First(), attributes, originalAttributes))
642+
{
643+
continue;
644+
}
633645
}
634646

635-
if (!IsKnownValueGuarded(parentAttributes, ref parentCsAttributes, parent, parentCapturedVersions, originalCsAttributes))
647+
if (!IsKnownValueGuarded(parentAttributes, ref parentCsAttributes, parent, parentCapturedVersions, originalCsAttributes, originalAttributes))
636648
{
637649
csAttributes = parentCsAttributes;
638650
return false;
@@ -643,6 +655,11 @@ static bool IsKnownValueGuarded(
643655
return true;
644656
}
645657

658+
static bool IsPlatformSupportWasSuppresed(PlatformMethodValue parentValue, SmallDictionary<string, Versions> attributes, SmallDictionary<string, Versions> originalAttributes)
659+
=> !parentValue.Negated && !attributes.ContainsKey(parentValue.PlatformName) &&
660+
originalAttributes.TryGetValue(parentValue.PlatformName, out Versions? version) &&
661+
parentValue.Version.IsGreaterThanOrEqualTo(version.SupportedFirst);
662+
646663
static bool IsOnlySupportNeedsGuard(string platformName, SmallDictionary<string, Versions> attributes, SmallDictionary<string, Versions> csAttributes)
647664
=> csAttributes.TryGetValue(platformName, out var versions) &&
648665
AllowList(versions) &&
@@ -791,24 +808,21 @@ static void RemoveOtherSupportsOnDifferentPlatforms(SmallDictionary<string, Vers
791808

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

794-
private static void ReportDiagnostics(KeyValuePair<IOperation, ISymbol> operationToSymbol, SmallDictionary<string, Versions> attributes,
811+
private static void ReportDiagnostics(IOperation operation, SmallDictionary<string, Versions> attributes,
795812
SmallDictionary<string, Versions>? csAttributes, OperationBlockAnalysisContext context,
796-
ConcurrentDictionary<ISymbol, PlatformAttributes> platformSpecificMembers)
813+
ISymbol symbol, SmallDictionary<string, Versions> originalAttributes)
797814
{
798-
var symbol = operationToSymbol.Value is IMethodSymbol method && method.IsConstructor() ? operationToSymbol.Value.ContainingType : operationToSymbol.Value;
799-
var operationName = symbol.ToDisplayString(GetLanguageSpecificFormat(operationToSymbol.Key));
800-
801-
var originalAttributes = platformSpecificMembers[symbol].Platforms ?? attributes;
815+
var operationName = symbol.ToDisplayString(GetLanguageSpecificFormat(operation));
802816

803817
foreach (var attribute in originalAttributes.Values)
804818
{
805819
if (AllowList(attribute))
806820
{
807-
ReportSupportedDiagnostic(operationToSymbol.Key, context, operationName, attributes, csAttributes);
821+
ReportSupportedDiagnostic(operation, context, operationName, attributes, csAttributes);
808822
}
809823
else
810824
{
811-
ReportUnsupportedDiagnostic(operationToSymbol.Key, context, operationName, attributes, csAttributes);
825+
ReportUnsupportedDiagnostic(operation, context, operationName, attributes, csAttributes);
812826
}
813827

814828
break;

src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5340,6 +5340,95 @@ class MyType { }
53405340
await VerifyAnalyzerCSAsync(source, s_msBuildPlatforms);
53415341
}
53425342

5343+
[Fact, WorkItem(7239, "https://github.com/dotnet/roslyn-analyzers/issues/7239")]
5344+
public async Task MacCatalystSuppressedByCallsiteSupportWithinGuard()
5345+
{
5346+
var source = @"
5347+
using System;
5348+
using System.Runtime.Versioning;
5349+
class TestType
5350+
{
5351+
[SupportedOSPlatform(""ios13.0"")]
5352+
[SupportedOSPlatform(""maccatalyst13.0"")]
5353+
private void Tapped()
5354+
{
5355+
if (OperatingSystem.IsIOSVersionAtLeast(15,0))
5356+
DoSomething();
5357+
5358+
[|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.
5359+
}
5360+
5361+
[SupportedOSPlatform(""ios14.0"")]
5362+
[SupportedOSPlatform(""maccatalyst"")]
5363+
public void DoSomething() {}
5364+
}";
5365+
5366+
string msBuildPlatforms = "build_property.TargetFramework=net8.0-maccatalyst13.0;\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v8.0";
5367+
await VerifyAnalyzerCSAsync(source, msBuildPlatforms);
5368+
}
5369+
5370+
[Fact, WorkItem(6955, "https://github.com/dotnet/roslyn-analyzers/issues/6955")]
5371+
public async Task MacCatalystSuppressedByCallSiteSupportCalledWithinCustomGuard()
5372+
{
5373+
var source = @"
5374+
using System;
5375+
using System.Runtime.Versioning;
5376+
class TestType
5377+
{
5378+
[SupportedOSPlatform(""ios12.0"")]
5379+
private void Tapped()
5380+
{
5381+
if (CheckSystemVersion(13,0))
5382+
DoSomething();
5383+
5384+
[|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.
5385+
}
5386+
5387+
[SupportedOSPlatform(""ios13.0"")]
5388+
[SupportedOSPlatform(""maccatalyst"")]
5389+
public void DoSomething() {}
5390+
5391+
[SupportedOSPlatformGuard (""ios"")]
5392+
[SupportedOSPlatformGuard (""tvos"")]
5393+
[SupportedOSPlatformGuard (""maccatalyst"")]
5394+
public bool CheckSystemVersion (int major, int minor) => false;
5395+
}";
5396+
5397+
string msBuildPlatforms = "build_property.TargetFramework=net8.0-maccatalyst12.0;\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v8.0";
5398+
await VerifyAnalyzerCSAsync(source, msBuildPlatforms);
5399+
}
5400+
5401+
[Fact, WorkItem(7530, "https://github.com/dotnet/roslyn-analyzers/issues/7530")]
5402+
public async Task OneOfCustomGuardsSuppressedByCallsite()
5403+
{
5404+
var source = @"
5405+
using System;
5406+
using System.Runtime.Versioning;
5407+
class TestType
5408+
{
5409+
[SupportedOSPlatform(""macos15.0"")]
5410+
[SupportedOSPlatform(""tvos12.2"")]
5411+
private void GetFilter ()
5412+
{
5413+
if (IsAtLeast)
5414+
DoSomething();
5415+
5416+
[|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.
5417+
}
5418+
5419+
[SupportedOSPlatform(""tvos15.0"")]
5420+
[SupportedOSPlatform(""macos15.0"")]
5421+
public void DoSomething() {}
5422+
5423+
[SupportedOSPlatformGuard (""macos15.0"")]
5424+
[SupportedOSPlatformGuard (""tvos15.0"")]
5425+
public bool IsAtLeast => false;
5426+
}";
5427+
5428+
string msBuildPlatforms = "build_property.TargetFramework=net8.0-maccatalyst12.0;\nbuild_property.TargetFrameworkIdentifier=.NETCoreApp\nbuild_property.TargetFrameworkVersion=v8.0";
5429+
await VerifyAnalyzerCSAsync(source, msBuildPlatforms);
5430+
}
5431+
53435432
private readonly string TargetTypesForTest = @"
53445433
namespace PlatformCompatDemo.SupportedUnupported
53455434
{

0 commit comments

Comments
 (0)