CA2020: Prevent behavioral change caused by built-in operators of IntPtr/UIntPtr
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 8 | 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
IntPtr
orUIntPtr
type is used as a nativeint
oruint
, change the type tonint
ornuint
. - If the
IntPtr
orUIntPtr
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
- If the expression could cause a behavioral change, wrap it with a
checked
orunchecked
statement 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
IntPtr
orUIntPtr
type is used as a nativeint
oruint
, change the type tonint
ornuint
.
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
orunchecked
statement 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.
See also
Feedback
https://aka.ms/ContentUserFeedback.
În curând: Pe parcursul anului 2024, vom elimina treptat Probleme legate de GitHub ca mecanism de feedback pentru conținut și îl vom înlocui cu un nou sistem de feedback. Pentru mai multe informații, consultați:Trimiteți și vizualizați feedback pentru