Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 24 additions & 23 deletions docs/fundamentals/code-analysis/quality-rules/ca1416.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "CA1416: Validate platform compatibility (code analysis)"
description: "Learn about code analysis rule ca1416: Validate platform compatibility"
ms.date: 09/01/2020
ms.date: 09/20/2021
ms.topic: reference
f1_keywords:
- "PlatformCompatibilityAnalyzer"
Expand All @@ -24,22 +24,23 @@ ms.author: bunamnan

Violations are reported if a platform-specific API is used in the context of a different platform or if the platform isn't verified (platform-neutral). Violations are also reported if an API that's not supported for the target platform of the project is used.

This rule is enabled by default only for projects targeting .NET 5.0 or later. However, you can [enable](#configure-code-to-analyze) it for projects that target other frameworks.
This rule is enabled by default only for projects that target .NET 5 or later. However, you can [enable](#configure-code-to-analyze) it for projects that target other frameworks.

## Rule description

.NET 5.0 added new attributes to annotate platform-specific APIs. This works as follows:
.NET 5.0 added new attributes, <xref:System.Runtime.Versioning.SupportedOSPlatformAttribute> and <xref:System.Runtime.Versioning.UnsupportedOSPlatformAttribute>, to annotate platform-specific APIs. Both attributes can be instantiated with or without version numbers as part of the platform name. They can also be applied multiple times with different platforms.

- An unmarked API is considered to work on all OS platforms.
- An API marked with `[SupportedOSPlatform("platformName")]` is considered only portable to the specified OS platforms (the attribute can be applied multiple times with different platforms).
- An API marked with `[UnsupportedOSPlatform("platformName")]` is considered unsupported only to the specified OS platforms (the attribute can be applied multiple times with different platforms).
- Both attributes can be instantiated with or without version numbers as part of the platform name.
- If a combination of `[SupportedOSPlatform] and [UnsupportedOSPlatform]` attributes are present, we group all attributes by OS platform identifier:
- **Allow list**. If the lowest version for each OS platform is a `[SupportedOSPlatform]` attribute, the API is considered to only be supported by the listed platforms and unsupported by all other platforms. The list could have `[UnsupportedOSPlatform]` attribute with same platform but only with higher version which denotes that the API is removed from that version.
- **Deny list**. If the lowest version for each OS platform is an `[UnsupportedOSPlatform]` attribute, then the API is considered to only be unsupported by the listed platforms and supported by all other platforms. The list could have `[SupportedOSPlatform]` attribute with same platform but only with higher version which denotes that the API is added support from that version.
- **Inconsistent list**. If the lowest version for some platforms is `[SupportedOSPlatform]` while it is `[UnsupportedOSPlatform]` for other platforms, is considered inconsistent, and the some annotations on the API are ignored. We plan to introduce another analyzer producing a warning in case of inconsistency in the future.
- An unannotated API is considered to work on all operating system (OS) platforms.
- An API marked with `[SupportedOSPlatform("platformName")]` is considered to be portable to the specified OS platforms only. If the platform is a [subset of another platform](../../../standard/analyzers/platform-compat-analyzer.md#platform-inclusion), the attribute implies that that platform is also supported.
- An API marked with `[UnsupportedOSPlatform("platformName")]` is considered to be unsupported on the specified OS platforms. If the platform is a [subset of another platform](../../../standard/analyzers/platform-compat-analyzer.md#platform-inclusion), the attribute implies that that platform is also unsupported.

Accessing an APIs annotated with the above attributes from the context of a different platform could cause violations.
You can combine `[SupportedOSPlatform]` and `[UnsupportedOSPlatform]` attributes on a single API. In this case, the following rules apply:

- **Allow list**. If the lowest version for each OS platform is a `[SupportedOSPlatform]` attribute, the API is considered to only be supported by the listed platforms and unsupported by all other platforms. The list can have an `[UnsupportedOSPlatform]` attribute with the same platform, but only with a higher version, which denotes that the API is removed from that version.
- **Deny list**. If the lowest version for each OS platform is an `[UnsupportedOSPlatform]` attribute, then the API is considered to only be unsupported by the listed platforms and supported by all other platforms. The list can have a `[SupportedOSPlatform]` attribute with the same platform, but only with a higher version, which denotes that the API is supported since that version.
- **Inconsistent list**. If the lowest version for some platforms is `[SupportedOSPlatform]` but `[UnsupportedOSPlatform]` for other platforms, this combination is considered inconsistent. Some annotations on the API are ignored. In the future, we may introduce an analyzer that produces a warning in case of inconsistency.

If you access an API annotated with these attributes from the context of a different platform, you can see CA1416 violations.

### TFM target platforms

Expand All @@ -57,17 +58,17 @@ The analyzer does not check target framework moniker (TFM) target platforms from
[SupportedOSPlatform("linux")]
public void LinuxOnlyApi() { }

// An API supported on Windows and iOS from version 14.0.
// API is supported on Windows, iOS from version 14.0, and MacCatalyst from version 14.0.
[SupportedOSPlatform("windows")]
[SupportedOSPlatform("ios14.0")]
public void SupportedOnWindowsAndIos14() { }
[SupportedOSPlatform("ios14.0")] // MacCatalyst is a superset of iOS, therefore it's also supported.
public void SupportedOnWindowsIos14AndMacCatalyst14() { }

public void Caller()
{
LinuxOnlyApi(); // This call site is reachable on all platforms. 'LinuxOnlyApi()' is only supported on: 'linux'

SupportedOnWindowsAndIos14(); // This call site is reachable on all platforms. 'SupportedOnWindowsAndIos14()'
// is only supported on: 'windows', 'ios' 14.0 and later
SupportedOnWindowsIos14AndMacCatalyst14(); // This call site is reachable on all platforms. 'SupportedOnWindowsIos14AndMacCatalyst14()'
// is only supported on: 'windows', 'ios' 14.0 and later, 'MacCatalyst' 14.0 and later.
}
```

Expand Down Expand Up @@ -119,22 +120,22 @@ The recommended way to deal with violations is to make sure you only call platfo
[SupportedOSPlatform("linux")]
public void LinuxOnlyApi() { }

// An API supported on Windows and iOS from version 14.0.
// API is supported on Windows, iOS from version 14.0, and MacCatalyst from version 14.0.
[SupportedOSPlatform("windows")]
[SupportedOSPlatform("ios14.0")]
public void SupportedOnWindowsAndIos14() { }
[SupportedOSPlatform("ios14.0")] // MacCatalyst is a superset of iOS, therefore it's also supported.
public void SupportedOnWindowsIos14AndMacCatalyst14() { }

public void Caller()
{
LinuxOnlyApi(); // This call site is reachable on all platforms. 'LinuxOnlyApi()' is only supported on: 'linux'.

SupportedOnWindowsAndIos14(); // This call site is reachable on all platforms. 'SupportedOnWindowsAndIos14()'
// is only supported on: 'windows', 'ios' 14.0 and later
SupportedOnWindowsIos14AndMacCatalyst14(); // This call site is reachable on all platforms. 'SupportedOnWindowsIos14AndMacCatalyst14()'
// is only supported on: 'windows', 'ios' 14.0 and later, 'MacCatalyst' 14.0 and later.
}

[SupportedOSPlatformGuard("windows")] // The platform guard attributes used
[SupportedOSPlatformGuard("ios14.0")]
private readonly bool _isWindowOrMacOS14 = OperatingSystem.IsWindows() || OperatingSystem.IsMacOSVersionAtLeast(14);
private readonly bool _isWindowOrIOS14 = OperatingSystem.IsWindows() || OperatingSystem.IsIOSVersionAtLeast(14);

// The warnings are avoided using platform guard methods.
public void Caller()
Expand Down
65 changes: 57 additions & 8 deletions docs/standard/analyzers/platform-compat-analyzer.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Platform compatibility analyzer
description: A Roslyn analyzer that can help detect platform compatibility issues in cross-platform apps and libraries.
author: buyaa-n
ms.date: 05/04/2021
ms.date: 09/20/2021
---

# Platform compatibility analyzer
Expand All @@ -23,16 +23,18 @@ The platform compatibility analyzer is one of the Roslyn code quality analyzers.
## How the analyzer determines platform dependency

- An **unattributed API** is considered to work on **all OS platforms**.
- An API marked with `[SupportedOSPlatform("platform")]` is considered only portable to the specified OS `platform`.
- The attribute can be applied multiple times to indicate **multiple platform support** (`[SupportedOSPlatform("windows"), SupportedOSPlatform("Android29.0")]`).
- An API marked with `[SupportedOSPlatform("platform")]` is considered only portable to the specified platform and any platforms it's a subset of.
- The attribute can be applied multiple times to indicate **multiple platform support**, for example `[SupportedOSPlatform("windows"), SupportedOSPlatform("Android29.0")]`.
- If the platform is a [subset of another platform](#platform-inclusion), the attribute implies that the superset platform is also supported. For example, `[SupportedOSPlatform("iOS")]` implies that the API is supported on `iOS` and also on its superset platform, `MacCatalyst`.
- The analyzer will produce a **warning** if platform-specific APIs are referenced without a proper **platform context**:
- **Warns** if the project doesn't target the supported platform (for example, a Windows-specific API called from a project targeting iOS `<TargetFramework>net5.0-ios14.0</TargetFramework>`).
- **Warns** if the project is cross-platform and calls platform-specific APIs (for example, a Windows-specific API called from cross platform TFM `<TargetFramework>net5.0</TargetFramework>`).
- **Doesn't warn** if the platform-specific API is referenced within a project that targets any of the **specified platforms** (for example, for a Windows-specific API called from a project targeting windows `<TargetFramework>net5.0-windows</TargetFramework>`).
- **Doesn't warn** if the platform-specific API call is guarded by corresponding platform-check methods (for example, a Windows-specific API call guarded by `OperatingSystem.IsWindows()`).
- **Doesn't warn** if the platform-specific API is referenced from the same platform-specific context (**call site also attributed** with `[SupportedOSPlatform("platform")`).
- An API marked with `[UnsupportedOSPlatform("platform")]` is considered unsupported only on the specified OS `platform` but supported for all other platforms.
- An API marked with `[UnsupportedOSPlatform("platform")]` is considered to be unsupported on the specified platform and any platforms it's a subset of, but supported for all other platforms.
- The attribute can be applied multiple times with different platforms, for example, `[UnsupportedOSPlatform("iOS"), UnsupportedOSPlatform("Android29.0")]`.
- If the platform is a [subset of another platform](#platform-inclusion), the attribute implies that the superset platform is also unsupported. For example, `[UnsupportedOSPlatform("iOS")]` implies that the API is unsupported on `iOS` and also on its superset platform, `MacCatalyst`.
- The analyzer produces a **warning** only if the `platform` is effective for the call site:
- **Warns** if the project targets the platform that's attributed as unsupported (for example, if the API is attributed with `[UnsupportedOSPlatform("windows")]` and the call site targets `<TargetFramework>net5.0-windows</TargetFramework>`).
- **Warns** if the project is multi-targeted and the `platform` is included in the default [MSBuild `<SupportedPlatform>`](https://github.com/dotnet/sdk/blob/main/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.SupportedPlatforms.props) items group, or the `platform` is manually included within the `MSBuild` \<SupportedPlatform> items group:
Expand All @@ -44,8 +46,7 @@ The platform compatibility analyzer is one of the Roslyn code quality analyzers.
```

- **Doesn't warn** if you're building an app that doesn't target the unsupported platform or is multi-targeted and the platform is not included in the default [MSBuild `<SupportedPlatform>`](https://github.com/dotnet/sdk/blob/main/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.SupportedPlatforms.props) items group.
- Both attributes can be instantiated with or without version numbers as part of the platform name.
- Version numbers are in the format of `major.minor[.build[.revision]]`; `major.minor` is required and the `build` and `revision` parts are optional. For example, "Windows7.0" indicates Windows version 7.0, but "Windows" is interpreted as Windows 0.0.
- Both attributes can be instantiated with or without version numbers as part of the platform name. Version numbers are in the format of `major.minor[.build[.revision]]`; `major.minor` is required and the `build` and `revision` parts are optional. For example, "Windows7.0" indicates Windows version 7.0, but "Windows" is interpreted as Windows 0.0.

For more information, see [examples of how the attributes work and what diagnostics they cause](#examples-of-how-the-attributes-work-and-what-diagnostics-they-produce).

Expand All @@ -56,7 +57,55 @@ The analyzer does not check target framework moniker (TFM) target platforms from
> [!NOTE]
> If the *AssemblyInfo.cs* file generation is disabled for the project (that is, the `<GenerateAssemblyInfo>` property is set to `false`), the required assembly level `SupportedOSPlatform` attribute can't be added by MSBuild. In this case, you could see warnings for a platform-specific APIs usage even if you're targeting that platform. To resolve the warnings, enable the *AssemblyInfo.cs* file generation or add the attribute manually in your project.

### Advanced scenarios for combining attributes
### Platform inclusion

.NET 6 introduces the concept of *platform inclusion*, where one platform can be a subset of another platform. An annotation for the subset platform implies the same support (or lack thereof) for the superset platform. If a platform check method in the <xref:System.OperatingSystem> type has a `SupportedOSPlatformGuard("supersetPlatform")]` attribute, then `supersetPlatform` is considered a superset of the OS platform that the method checks for.

For example, the <xref:System.OperatingSystem.IsIOS?displayProperty=nameWithType> method is attributed `[SupportedOSPlatformGuard("MacCatalyst")]`. Therefore, the following statements apply:

- The <xref:System.OperatingSystem.IsIOS?displayProperty=nameWithType> and <xref:System.OperatingSystem.IsIOSVersionAtLeast%2A?displayProperty=nameWithType> methods check not only the `iOS` platform, but also the `MacCatalyst` platform.
- `[SupportedOSPlatform("iOS")]` implies that the API is supported on `iOS` and also on its superset platform, `MacCatalyst`. You can use the `[UnsupportedOSPlatform("MacCatalyst")]` attribute to exclude this implied support.
- `[UnsupportedOSPlatform("iOS")` implies that the API is not supported on `iOS` and `MacCatalyst`. You can use the `[SupportedOSPlatform("MacCatalyst")]` attribute to exclude this implied lack of support.

Consider the following coverage matrix, where ✔️ indicates that the platform is supported, and ❌ indicates that the platform is *not* supported.

| Platform | `SupportedOSPlatform(subset)` | `SupportedOSPlatform(superset)` | `UnsupportedOSPlatform(subset)` | `UnsupportedOSPlatform(superset)` |
| - | - | - | - | - |
| **Subset** | ✔️ | ❌ | ✔️ | ❌ |
| **Superset** | ✔️ | ✔️ | ✔️ | ✔️ |

> [!TIP]
> The same rules apply for the `SupportedOSPlatformGuard` and `UnsupportedOSPlatformGuard` attributes.

The following code snippet shows how you can combine attributes to set the right level of support.

```csharp
// MacCatalyst is a superset of iOS therefore supported on iOS and MacCatalyst
[SupportedOSPlatform("iOS")]
public void ApiOnlySupportedOnIOSAndMacCatalyst() { }

// Does not imply iOS, only supported on MacCatalyst
[SupportedOSPlatform("MacCatalyst")]
public void ApiOnlySupportedOnMacCatalyst() { }

[SupportedOSPlatform("iOS")] // Supported on iOS and MacCatalyst
[UnsupportedOSPlatform("MacCatalyst")] // Removes implied MacCatalyst support
public void ApiOnlySupportedOnIos() { }

// Unsupported on iOS and MacCatalyst
[UnsupportedOSPlatform("iOS")]
public void ApiUnsupportedOnIOSAndMacCatalyst();

// Does not imply iOS, only unsupported on MacCatalyst
[UnsupportedOSPlatform("MacCatalyst")]
public void ApiUnsupportedOnMacCatalyst() { }

[UnsupportedOSPlatform("iOS")] // Unsupported on iOS and MacCatalyst
[SupportedOSPlatform("MacCatalyst")] // Removes implied MacCatalyst unsupportedness
public void ApiUnsupportedOnIos() { }
```

### Advanced scenarios for attribute combinations

- If a combination of `[SupportedOSPlatform]` and `[UnsupportedOSPlatform]` attributes are present, all attributes are grouped by OS platform identifier:
- **Supported only list**. If the lowest version for each OS platform is a `[SupportedOSPlatform]` attribute, the API is considered to only be supported by the listed platforms and unsupported by all other platforms. The optional `[UnsupportedOSPlatform]` attributes for each platform can only have higher version of the minimum supported version, which denotes that the API is removed starting from the specified version.
Expand All @@ -82,7 +131,7 @@ The analyzer does not check target framework moniker (TFM) target platforms from
```

- **Inconsistent list**. If the lowest version for some platforms is `[SupportedOSPlatform]` while it is `[UnsupportedOSPlatform]` for other platforms, it's considered to be inconsistent, which is not supported for the analyzer. If inconsistency occurs, the analyzer ignores the `[UnsupportedOSPlatform]` platforms.
- If the lowest versions of `[SupportedOSPlatform]` and `[UnsupportedOSPlatform]` attributes are equal, the analyzer considers the platform as part of the **Supported only list**.
- If the lowest versions of `[SupportedOSPlatform]` and `[UnsupportedOSPlatform]` attributes are equal, the analyzer considers the platform as part of the **Supported only list**.
- Platform attributes can be applied to types, members (methods, fields, properties, and events) and assemblies with different platform names or versions.
- Attributes applied at the top level `target` affect all of its members and types.
- Child-level attributes only apply if they adhere to the rule "child annotations can narrow the platforms support, but they cannot widen it".
Expand Down