All rules
CA2020Reliability Enabled by default: As suggestion

Prevent behavioral change caused by built-in operators of IntPtr/UIntPtr

Prevent behavioral change caused by built-in operators of IntPtr/UIntPtr

Microsoft docs

Description

With the numeric IntPtr feature, System.IntPtr and 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.

### 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 |

Cause

This rule fires when it detects a behavioral change between .NET 6 and .NET 7 introduced by the new built-in operators of System.IntPtr and System.UIntPtr.

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 or UIntPtr type is used as a native int or uint, change the type to nint or nuint.
  • If the IntPtr or UIntPtr 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 expression could cause a behavioral change, wrap it with a checked or unchecked statement to preserve the previous behavior.

### Example

Violation:

Fix:

  • If the expression would not cause a behavioral change and the IntPtr or UIntPtr type is used as a native int or uint, change the type to nint or nuint.
  • If the expression could cause a behavioral change, wrap it with a checked or unchecked statement to preserve the previous behavior.

Example

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.
    }
}

When to suppress

If the expression would not cause a behavioral change, it's safe to suppress a warning from this rule.

Group results
0 yes 0 no
ConsensusNone (disabled)
Severity preference (yes voters)
Suggestion0
Warning0
Error0