Fix platform guard analysis with call-site support - #55540
Conversation
Preserve the semantics of supported guards already covered by call-site attributes, and recognize when negated guards make a branch unreachable on every platform supported by the call site. Add regressions for roslyn-analyzers issue 7665 and guarded obsoleted APIs.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 6987672a-aa50-405c-a61a-3656b3e567ca
Exclude individual platforms when a negated guard cannot be reached at the call site's minimum version, while continuing to analyze platforms where the pre-version branch is reachable. Cover the mixed four-platform pattern used by macios.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>\nCopilot-Session: 6987672a-aa50-405c-a61a-3656b3e567ca
|
Azure Pipelines: Successfully started running 1 pipeline(s). 2 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). 2 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
This PR updates PlatformCompatibilityAnalyzer guard-flow analysis to better handle custom [SupportedOSPlatformGuard] / [UnsupportedOSPlatformGuard] scenarios with multiple platform attributes, especially when the call site’s own platform annotations already satisfy some of the guard conditions, and adds regression tests for those cases.
Changes:
- Refines
IsKnownValueGuardedto evaluate mixed-platform guard attributes independently and to account for call-site platform support when suppressing or excluding guard platforms. - Adds new helper predicates (
IsPlatformSupportSuppressedByCallsite,IsPlatformExcludedByCallsite) to control when guarded flow should be preserved vs. re-applied as call-site attributes. - Adds regression tests covering multi-platform custom guards and negated-guard scenarios with call-site suppression.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs | Adjusts guard analysis logic and introduces helpers for call-site suppression/exclusion decisions. |
| src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs | Adds regression test for multi-platform custom guards suppressed by call-site support. |
| src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.ObsoletedOSPlatformTests.cs | Adds regression tests for negated custom guards suppressed (fully/partially) by call-site minimum versions. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd56dce9-69ee-4cd6-9778-1bcee82f66bd
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs:695
IsPlatformSupportSuppressedByCallsitecan throw a NullReferenceException becausecallsiteVersion.SupportedFirstis nullable, but the code callscallsiteVersion.SupportedFirst.IsGreaterThanOrEqualTo(...)without checking for null.
static bool IsPlatformSupportSuppressedByCallsite(
PlatformMethodValue value,
SmallDictionary<string, Versions> attributes,
SmallDictionary<string, Versions> originalAttributes,
SmallDictionary<string, Versions>? callsiteAttributes)
=> !attributes.ContainsKey(value.PlatformName) &&
originalAttributes.TryGetValue(value.PlatformName, out Versions? originalVersion) &&
originalVersion.SupportedFirst != null &&
callsiteAttributes != null &&
callsiteAttributes.TryGetValue(value.PlatformName, out Versions? callsiteVersion) &&
callsiteVersion.SupportedFirst.IsGreaterThanOrEqualTo(originalVersion.SupportedFirst);
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd56dce9-69ee-4cd6-9778-1bcee82f66bd
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs:681
IsPlatformExcludedByCallsitetreats any call site with a non-nullSupportedFirstas having a minimum supported version, but that isn’t true for deny-list call sites (e.g.[UnsupportedOSPlatform("macos12.0")]+[SupportedOSPlatform("macos15.0")]), which are also supported on earlier versions (<12). In those cases the negated guard branch can still be reachable on older OS versions, so excluding the platform here can incorrectly skip analysis and lead to false negatives.
// A call site without a minimum supported version for the platform cannot prove the branch unreachable,
// so 'SupportedFirst' being null must not exclude the platform.
static bool IsPlatformExcludedByCallsite(
PlatformMethodValue value,
SmallDictionary<string, Versions>? callsiteAttributes)
=> callsiteAttributes != null &&
callsiteAttributes.TryGetValue(value.PlatformName, out Versions? attributes) &&
attributes.SupportedFirst != null &&
attributes.SupportedFirst.IsGreaterThanOrEqualTo(value.Version);
Summary
CA1416(Platform compatibility) reported false positives when a custom[SupportedOSPlatformGuard](or[UnsupportedOSPlatformGuard]) declared multiple platform attributes, and the call site's own platform support already satisfied one or more of those guards.Repro
The simplest reproduction (roslyn-analyzers#7665):
Inside the
if,PerformanceRatingis fully guarded, yet the analyzer warned. Tellingly, removing either the macOS or the tvOS attributes (from all three sites) made the warning disappear — the bug only surfaced with mixed-platform guards.Root cause
When processing a guard, the analyzer treated all of its platform attributes as a single unit. If the call site's existing support already covered one platform of a multi-platform guard, the analyzer mishandled the remaining platforms instead of evaluating each one on its own.
Fix
In
PlatformCompatibilityAnalyzer.IsKnownValueGuarded, handle each platform of a mixed guard individually:RemoveOtherSupportsOnDifferentPlatforms) instead of re-applying it as a call-site attribute. Gated by the newIsPlatformSupportSuppressedByCallsitehelper.IsPlatformExcludedByCallsitehelper.Tests
Added regression coverage:
MultipleCustomGuardsSuppressedByCallsite— the exact issue Is there a way to choose the SDK version that dotnet CLI should use? #7665 scenario.NegatedCustomGuardSuppressedByCallsite— negated guard around an obsoleted API.NegatedCustomGuardsPartiallySuppressedByCallsite— mixed four-platform (ios/maccatalyst/macos/tvos) pattern used by macios.All 602
PlatformCompatibilityAnalyzerguard and obsoletion tests pass.Fixes dotnet/roslyn-analyzers#7665