Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add doc for CA2020 #31653

Merged
merged 6 commits into from
Oct 10, 2022
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
158 changes: 158 additions & 0 deletions docs/fundamentals/code-analysis/quality-rules/ca2020.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
title: "CA2020: Prevent behavioral change caused by built-in operators of IntPtr/UIntPtr"
description: "Learn about code analysis rule CA2020 where some built-in operators behave differently than the previous user-defined operators."
ms.date: 10/07/2022
ms.topic: reference
f1_keywords:
- PreventNumericIntPtrUIntPtrBehavioralChanges
- CA2020
helpviewer_keywords:
- PreventNumericIntPtrUIntPtrBehavioralChanges
- CA2020
author: buyaa-n
---
# CA2020: Prevent behavioral change caused by built-in operators of IntPtr/UIntPtr

| | Value |
|-|-|
| **Rule ID** |CA2020|
| **Category** |[Reliability](reliability-warnings.md)|
| **Fix is breaking or non-breaking** |Non-breaking|

## Cause

This rule fires when it detects a behavioral change between .NET 6 and .NET 7 introduced by the new built-in operators of <xref:System.IntPtr> and <xref:System.UIntPtr>.

## Rule description

With the [numeric IntPtr feature](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/numeric-intptr.md), <xref:System.IntPtr> and <xref:System.UIntPtr> gained built-in operators for conversions, unary operations, and binary operations. These operators might throw when overflowing within checked context or may not throw in unchecked context compared to the previous user-defined operators in .NET 6 and earlier versions. You might encounter this behavioral change when upgrading to .NET 7.

### List of APIs affected

| Operator | Context | In .NET 7 | In .NET 6 and earlier | Example |
|---|---|---|---|---|
| operator +(IntPtr, int) | checked | Throws when overflows | Doesn't throw when overflows | `checked(intPtrVariable + 2);` |
| operator -(IntPtr, int) | checked | Throws when overflows | Doesn't throw when overflows | `checked(intPtrVariable - 2);` |
| explicit operator IntPtr(long) | unchecked | Doesn't throw when overflows | Can throw in 32-bit contexts | `(IntPtr)longVariable;` |
| explicit operator void\*(IntPtr) | checked | throws when overflows | Doesn't throw when overflows | `checked((void*)intPtrVariable);` |
| explicit operator IntPtr(void\*) | checked | throws when overflows | Doesn't throw when overflows | `checked((IntPtr)voidPtrVariable);` |
| explicit operator int(IntPtr) | unchecked | Doesn't throw when overflows | Can throw in 64-bit contexts | `(int)intPtrVariable;` |
| operator +(UIntPtr, int) | checked | Throws when overflows | Doesn't throw when overflows | `checked(uintPtrVariable + 2);` |
| operator -(UIntPtr, int) | checked | Throws when overflows | Doesn't throw when overflows | `checked(uintPtrVariable - 2);` |
| explicit operator UIntPtr(ulong) | unchecked | Doesn't throw when overflows | Can throw in 32-bit contexts | `(UIntPtr)uLongVariable` |
| explicit operator uint(UIntPtr) | unchecked | Doesn't throw when overflows | Can throw in 64-bit contexts | `(uint)uintPtrVariable` |

## How to fix violations

Examine your code to determine if the flagged expression could cause a behavioral change, and choose an appropriate way to fix the diagnostic from the following options:

Fix options:

- If the expression would not cause a behavioral change:
- If the `IntPtr` or `UIntPtr` type is used as a native `int` or `uint`, change the type to `nint` or `nuint`.
- If the `IntPtr` or `UIntPtr` type is used as a native pointer, change the type to the corresponding native pointer type.
- If you can't change the type of the variable, suppress the warning.
- If the expression could cause a behavioral change, wrap it with a `checked` or `unchecked` statement to preserve the previous behavior.

### Example

**Violation**:

```csharp
using System;

public unsafe class IntPtrTest
{
IntPtr intPtrVariable;
long longVariable;

void Test ()
{
checked
{
IntPtr result = intPtrVariable + 2; // Warns: Starting with .NET 7 the operator '+' will throw when overflowing in a checked context. Wrap the expression with an 'unchecked' statement to restore the .NET 6 behavior.

result = intPtrVariable - 2; // Starting with .NET 7 the operator '-' will throw when overflowing in a checked context. Wrap the expression with an 'unchecked' statement to restore the .NET 6 behavior.

void* voidPtrVariable = (void*)intPtrVariable; // Starting with .NET 7 the explicit conversion '(void*)IntPtr' will throw when overflowing in a checked context. Wrap the expression with an 'unchecked' statement to restore the .NET 6 behavior.

result = (IntPtr)voidPtrVariable; // Starting with .NET 7 the explicit conversion '(IntPtr)void*' will throw when overflowing in a checked context. Wrap the expression with an 'unchecked' statement to restore the .NET 6 behavior.
}

intPtrVariable = (IntPtr)longVariable; // Starting with .NET 7 the explicit conversion '(IntPtr)Int64' will not throw when overflowing in an unchecked context. Wrap the expression with a 'checked' statement to restore the .NET 6 behavior.

int a = (int)intPtrVariable; // Starting with .NET 7 the explicit conversion '(Int32)IntPtr' will not throw when overflowing in an unchecked context. Wrap the expression with a 'checked' statement to restore the .NET 6 behavior.
}
}
```

**Fix**:

- If the expression would not cause a behavioral change and the the `IntPtr` or `UIntPtr` type is used as a native `int` or `uint`, change the type to `nint` or `nuint`.

```csharp
using System;

public unsafe class IntPtrTest
{
nint intPtrVariable; // type changed to nint
long longVariable;

void Test ()
{
checked
{
nint result = intPtrVariable + 2; // no warning

result = intPtrVariable - 2;

void* voidPtrVariable = (void*)intPtrVariable;

result = (nint)voidPtrVariable;
}

intPtrVariable = (nint)longVariable;

int a = (int)intPtrVariable;
}
}
```

- If the expression could cause a behavioral change, wrap it with a `checked` or `unchecked` statement to preserve the previous behavior.

```csharp
using System;

public unsafe class IntPtrTest
{
IntPtr intPtrVariable;
long longVariable;

void Test ()
{
checked
{
IntPtr result = unchecked(intPtrVariable + 2); // wrap with unchecked

result = unchecked(intPtrVariable - 2);

void* voidPtrVariable = unchecked((void*)intPtrVariable);

result = unchecked((IntPtr)voidPtrVariable);
}

intPtrVariable = checked((IntPtr)longVariable); // wrap with checked

int a = checked((int)intPtrVariable);
}
}
```

## When to suppress warnings

If the expression would not cause a behavioral change, it's safe to suppress a warning from this rule.

## See also

- [Reliability rules](reliability-warnings.md)
- [numeric IntPtr feature](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/numeric-intptr.md)
1 change: 1 addition & 0 deletions docs/fundamentals/code-analysis/quality-rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ The following table lists code quality analysis rules.
> | [CA2016: Forward the CancellationToken parameter to methods that take one](ca2016.md) | Forward the `CancellationToken` parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in `CancellationToken.None` explicitly to indicate intentionally not propagating the token. |
> | [CA2017: Parameter count mismatch](ca2017.md) | Number of parameters supplied in the logging message template do not match the number of named placeholders. |
> | [CA2018: The `count` argument to `Buffer.BlockCopy` should specify the number of bytes to copy](ca2018.md) | When using `Buffer.BlockCopy`, the `count` argument specifies the number of bytes to copy. You should only use `Array.Length` for the `count` argument on arrays whose elements are exactly one byte in size. `byte`, `sbyte`, and `bool` arrays have elements that are one byte in size. |
> | [CA2020: Prevent behavioral change caused by built-in operators of IntPtr/UIntPtr](ca2020.md) | Some built-in operators added in .NET 7 behave differently than the user-defined operators in .NET 6 and earlier versions. Some operators that used to throw in unchecked context while overflowing don't throw anymore unless wrapped within checked context. Some operators that previously didn't throw in checked context now throw unless wrapped within unchecked context. |
> | [CA2100: Review SQL queries for security vulnerabilities](ca2100.md) | A method sets the System.Data.IDbCommand.CommandText property by using a string that is built from a string argument to the method. This rule assumes that the string argument contains user input. A SQL command string that is built from user input is vulnerable to SQL injection attacks. |
> |[CA2101: Specify marshalling for P/Invoke string arguments](ca2101.md) | A platform invoke member allows partially trusted callers, has a string parameter, and does not explicitly marshal the string. This can cause a potential security vulnerability. |
> | [CA2109: Review visible event handlers](ca2109.md) | A public or protected event-handling method was detected. Event-handling methods should not be exposed unless absolutely necessary. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ Reliability rules support library and application reliability, such as correct m
|[CA2016: Forward the CancellationToken parameter to methods that take one](ca2016.md) | Forward the `CancellationToken` parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in `CancellationToken.None` explicitly to indicate intentionally not propagating the token. |
|[CA2017: Parameter count mismatch](ca2017.md) | Number of parameters supplied in the logging message template do not match the number of named placeholders. |
|[CA2018: The `count` argument to `Buffer.BlockCopy` should specify the number of bytes to copy](ca2018.md) | When using `Buffer.BlockCopy`, the `count` argument specifies the number of bytes to copy. You should only use `Array.Length` for the `count` argument on arrays whose elements are exactly one byte in size. `byte`, `sbyte`, and `bool` arrays have elements that are one byte in size. |
|[CA2020: Prevent behavioral change caused by built-in operators of IntPtr/UIntPtr](ca2020.md) | Some built-in operators added in .NET 7 behave differently than the user-defined operators in .NET 6 and earlier versions. Some operators that used to throw in unchecked context while overflowing don't throw anymore unless wrapped within checked context. Some operators that previously didn't throw in checked context now throw unless wrapped within unchecked context. |
2 changes: 2 additions & 0 deletions docs/fundamentals/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,8 @@ items:
href: code-analysis/quality-rules/ca2017.md
- name: CA2018
href: code-analysis/quality-rules/ca2018.md
- name: CA2020
href: code-analysis/quality-rules/ca2020.md
- name: Security rules
items:
- name: Overview
Expand Down