Skip to content

Commit 27a624f

Browse files
buyaa-ngewarren
andauthored
Add doc for CA2020 (#31653)
Co-authored-by: Genevieve Warren <[email protected]>
1 parent 3aa4936 commit 27a624f

File tree

4 files changed

+162
-0
lines changed

4 files changed

+162
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
---
2+
title: "CA2020: Prevent behavioral change caused by built-in operators of IntPtr/UIntPtr"
3+
description: "Learn about code analysis rule CA2020 where some built-in operators behave differently than the previous user-defined operators."
4+
ms.date: 10/07/2022
5+
ms.topic: reference
6+
f1_keywords:
7+
- PreventNumericIntPtrUIntPtrBehavioralChanges
8+
- CA2020
9+
helpviewer_keywords:
10+
- PreventNumericIntPtrUIntPtrBehavioralChanges
11+
- CA2020
12+
author: buyaa-n
13+
---
14+
# CA2020: Prevent behavioral change caused by built-in operators of IntPtr/UIntPtr
15+
16+
| | Value |
17+
|-|-|
18+
| **Rule ID** |CA2020|
19+
| **Category** |[Reliability](reliability-warnings.md)|
20+
| **Fix is breaking or non-breaking** |Non-breaking|
21+
22+
## Cause
23+
24+
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>.
25+
26+
## Rule description
27+
28+
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.
29+
30+
### List of APIs affected
31+
32+
| Operator | Context | In .NET 7 | In .NET 6 and earlier | Example |
33+
|---|---|---|---|---|
34+
| operator +(IntPtr, int) | checked | Throws when overflows | Doesn't throw when overflows | `checked(intPtrVariable + 2);` |
35+
| operator -(IntPtr, int) | checked | Throws when overflows | Doesn't throw when overflows | `checked(intPtrVariable - 2);` |
36+
| explicit operator IntPtr(long) | unchecked | Doesn't throw when overflows | Can throw in 32-bit contexts | `(IntPtr)longVariable;` |
37+
| explicit operator void\*(IntPtr) | checked | throws when overflows | Doesn't throw when overflows | `checked((void*)intPtrVariable);` |
38+
| explicit operator IntPtr(void\*) | checked | throws when overflows | Doesn't throw when overflows | `checked((IntPtr)voidPtrVariable);` |
39+
| explicit operator int(IntPtr) | unchecked | Doesn't throw when overflows | Can throw in 64-bit contexts | `(int)intPtrVariable;` |
40+
| operator +(UIntPtr, int) | checked | Throws when overflows | Doesn't throw when overflows | `checked(uintPtrVariable + 2);` |
41+
| operator -(UIntPtr, int) | checked | Throws when overflows | Doesn't throw when overflows | `checked(uintPtrVariable - 2);` |
42+
| explicit operator UIntPtr(ulong) | unchecked | Doesn't throw when overflows | Can throw in 32-bit contexts | `(UIntPtr)uLongVariable` |
43+
| explicit operator uint(UIntPtr) | unchecked | Doesn't throw when overflows | Can throw in 64-bit contexts | `(uint)uintPtrVariable` |
44+
45+
## How to fix violations
46+
47+
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:
48+
49+
Fix options:
50+
51+
- If the expression would not cause a behavioral change:
52+
- If the `IntPtr` or `UIntPtr` type is used as a native `int` or `uint`, change the type to `nint` or `nuint`.
53+
- If the `IntPtr` or `UIntPtr` type is used as a native pointer, change the type to the corresponding native pointer type.
54+
- If you can't change the type of the variable, suppress the warning.
55+
- If the expression could cause a behavioral change, wrap it with a `checked` or `unchecked` statement to preserve the previous behavior.
56+
57+
### Example
58+
59+
**Violation**:
60+
61+
```csharp
62+
using System;
63+
64+
public unsafe class IntPtrTest
65+
{
66+
IntPtr intPtrVariable;
67+
long longVariable;
68+
69+
void Test ()
70+
{
71+
checked
72+
{
73+
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.
74+
75+
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.
76+
77+
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.
78+
79+
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.
80+
}
81+
82+
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.
83+
84+
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.
85+
}
86+
}
87+
```
88+
89+
**Fix**:
90+
91+
- 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`.
92+
93+
```csharp
94+
using System;
95+
96+
public unsafe class IntPtrTest
97+
{
98+
nint intPtrVariable; // type changed to nint
99+
long longVariable;
100+
101+
void Test ()
102+
{
103+
checked
104+
{
105+
nint result = intPtrVariable + 2; // no warning
106+
107+
result = intPtrVariable - 2;
108+
109+
void* voidPtrVariable = (void*)intPtrVariable;
110+
111+
result = (nint)voidPtrVariable;
112+
}
113+
114+
intPtrVariable = (nint)longVariable;
115+
116+
int a = (int)intPtrVariable;
117+
}
118+
}
119+
```
120+
121+
- If the expression could cause a behavioral change, wrap it with a `checked` or `unchecked` statement to preserve the previous behavior.
122+
123+
```csharp
124+
using System;
125+
126+
public unsafe class IntPtrTest
127+
{
128+
IntPtr intPtrVariable;
129+
long longVariable;
130+
131+
void Test ()
132+
{
133+
checked
134+
{
135+
IntPtr result = unchecked(intPtrVariable + 2); // wrap with unchecked
136+
137+
result = unchecked(intPtrVariable - 2);
138+
139+
void* voidPtrVariable = unchecked((void*)intPtrVariable);
140+
141+
result = unchecked((IntPtr)voidPtrVariable);
142+
}
143+
144+
intPtrVariable = checked((IntPtr)longVariable); // wrap with checked
145+
146+
int a = checked((int)intPtrVariable);
147+
}
148+
}
149+
```
150+
151+
## When to suppress warnings
152+
153+
If the expression would not cause a behavioral change, it's safe to suppress a warning from this rule.
154+
155+
## See also
156+
157+
- [Reliability rules](reliability-warnings.md)
158+
- [numeric IntPtr feature](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/numeric-intptr.md)

docs/fundamentals/code-analysis/quality-rules/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ The following table lists code quality analysis rules.
159159
> | [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. |
160160
> | [CA2017: Parameter count mismatch](ca2017.md) | Number of parameters supplied in the logging message template do not match the number of named placeholders. |
161161
> | [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. |
162+
> | [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. |
162163
> | [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. |
163164
> |[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. |
164165
> | [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. |

docs/fundamentals/code-analysis/quality-rules/reliability-warnings.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ Reliability rules support library and application reliability, such as correct m
3131
|[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. |
3232
|[CA2017: Parameter count mismatch](ca2017.md) | Number of parameters supplied in the logging message template do not match the number of named placeholders. |
3333
|[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. |
34+
|[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. |

docs/fundamentals/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,8 @@ items:
11471147
href: code-analysis/quality-rules/ca2017.md
11481148
- name: CA2018
11491149
href: code-analysis/quality-rules/ca2018.md
1150+
- name: CA2020
1151+
href: code-analysis/quality-rules/ca2020.md
11501152
- name: Security rules
11511153
items:
11521154
- name: Overview

0 commit comments

Comments
 (0)