Skip to content

Commit 581a85f

Browse files
committed
Add doc for CA2020
1 parent ca9670a commit 581a85f

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 - Some built in operators added in .NET 7 behave differently than the user defined operators in .NET 6 and below. Some operators that used to throw in unchecked context while overflowing will not throw anymore unless wrapped within checked context, and some operators that not used to throw in checked context now would throw unless wrapped within unchecked context"
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.0 and .NET 7.0 introduced with new built-in operators of numeric IntPtr/UIntPtr feature.
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), System.IntPtr and System.UIntPtr gained some built-in operators (conversions, unary and binary). Those now might throw when overflowing within checked context or may not throw in unchecked context compared to the previous `user-defined` operators in .NET 6.0 and below, which could cause behavioral change when upgrading from .NET 6.0 and below into .NET 7.
29+
30+
### List of APIs affected
31+
32+
| Operator | context | in .NET 7.0 | in NET 6.0 and below | Example |
33+
|---|---|---|---|---|
34+
| operator +(IntPtr, int) | checked | throws when overflows | do not throw when overflow | `checked(intPtrVariable + 2);` |
35+
| operator -(IntPtr, int) | checked | throws when overflows | do not throw when overflow | `checked(intPtrVariable - 2);` |
36+
| explicit operator IntPtr(long) | unchecked | will not throw when overflow | could throw in 32-bit contexts | `(IntPtr)longVariable;` |
37+
| explicit operator void\*(IntPtr) | checked | throws when overflows | do not throw when overflow | `checked((void*)intPtrVariable);` |
38+
| explicit operator IntPtr(void\*) | checked | throws when overflows | do not throw when overflow | `checked((IntPtr)voidPtrVariable);` |
39+
| explicit operator int(IntPtr) | unchecked | will not throw when overflows | could throw in 64-bit contexts | `(int)intPtrVariable;` |
40+
| operator +(UIntPtr, int) | checked | throws when overflows | do not throw when overflows | `checked(uintPtrVariable + 2);` |
41+
| operator -(UIntPtr, int) | checked | throws when overflows | do not throw when overflows | `checked(uintPtrVariable - 2);` |
42+
| explicit operator UIntPtr(ulong) | unchecked | will not throw when overflows | could throw in 32-bit contexts | `(UIntPtr)uLongVariable` |
43+
| explicit operator uint(UIntPtr) | unchecked | will not throw when overflows | could throw in 64-bit contexts | `(uint)uintPtrVariable` |
44+
45+
## How to fix violations
46+
47+
Investigate the code to determine if the expression flagged could cause a behavioral change, and choose appropriate way to fix the diagnostic
48+
49+
Fix options:
50+
- If the expression would not cause a behavioral change:
51+
- If the `IntPtr/UIntPtr` type used as native int/uint purpose change the type to `nint/nuint`.
52+
- If the `IntPtr/UIntPtr` type used as native pointer change the type to corresponding native pointer type
53+
- If you cannot change the type of the variable last resort will be suppress the warning
54+
- If the expression could cause a behavioral change wrap it with corresponding `checked`/`unchecked` statement to preserve the .NET 6.0 behavior.
55+
56+
### Example
57+
58+
**Violation**:
59+
60+
```csharp
61+
using System;
62+
63+
public unsafe class IntPtrTest
64+
{
65+
IntPtr intPtrVariable;
66+
long longVariable;
67+
68+
void Test ()
69+
{
70+
checked
71+
{
72+
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.
73+
74+
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.
75+
76+
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.
77+
78+
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.
79+
}
80+
81+
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.
82+
83+
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.
84+
}
85+
}
86+
```
87+
88+
**Fix**:
89+
90+
- If the expression would not cause a behavioral change:
91+
- If the IntPtr/UIntPtr type used as native int/uint purpose change the type to nint/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 corresponding `checked`/`unchecked` statement to preserve the old .NET 6.0 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 is 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 below. Some operators that used to throw in unchecked context while overflowing will not throw anymore unless wrapped within checked context, and some operators that not used to throw in checked context now would 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 below. Some operators that used to throw in unchecked context while overflowing will not throw anymore unless wrapped within checked context, and some operators that not used to throw in checked context now would 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)