|
| 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) |
0 commit comments