Ghi
Quyền truy cập vào trang này yêu cầu sự cho phép. Bạn có thể thử đăng nhập hoặc thay đổi thư mục.
Quyền truy cập vào trang này yêu cầu sự cho phép. Bạn có thể thử thay đổi thư mục.
| Property | Value |
|---|---|
| Rule ID | CA2020 |
| Title | Prevent behavioral change caused by built-in operators of IntPtr/UIntPtr |
| Category | Reliability |
| Fix is breaking or non-breaking | Non-breaking |
| Enabled by default in .NET 10 | As suggestion |
Cause
This rule fires when it detects a behavioral change between .NET 6 and .NET 7 introduced by the new built-in operators of IntPtr and UIntPtr.
Rule description
With the numeric IntPtr feature, IntPtr and 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
IntPtrorUIntPtrtype is used as a nativeintoruint, change the type tonintornuint. - If the
IntPtrorUIntPtrtype 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
- If the expression could cause a behavioral change, wrap it with a
checkedoruncheckedstatement to preserve the previous behavior.
Example
Violation:
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
IntPtrorUIntPtrtype is used as a nativeintoruint, change the type tonintornuint.
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
checkedoruncheckedstatement to preserve the previous behavior.
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.