A raw native handle field is exposed outside its type

ID

vbnet.maintainability.no_visible_unmanaged_pointer

Severity

low

Remediation Complexity

hard

Remediation Risk

medium

Remediation Effort

medium

Resource

Interop

Language

VB.NET

Description

Reports a field of type IntPtr, UIntPtr, System.IntPtr or System.UIntPtr declared Public, Protected or Protected Friend, shared or per-instance, read-only or writable. Private and Friend fields are not reported, nor are properties, nor fields typed as SafeHandle or one of its derivatives.

Rationale

An IntPtr field is an integer that happens to be an address or an operating-system handle. The type carries no ownership, no lifetime and no validity: nothing links the value to the code that allocated it or to the code that must release it. Publishing that field hands the raw value to every caller that can see the type while the owning type remains responsible for freeing it, and the two cannot be reconciled. A caller that copies the handle and uses it after Dispose is operating on a closed handle - or, once the operating system has recycled the number, on a completely unrelated object, which is how a read of one file ends up returning the contents of another. A writable field is worse, because a caller can store a value the type never allocated and the eventual release call then applies to memory or a handle the process does not own; the failure surfaces as a corrupted heap or an access violation at some later, unrelated call. None of this is diagnosable from the field itself: IntPtr has no debugger representation beyond a number, the finaliser that closes the handle runs on another thread at an unpredictable time, and handle recycling makes the bad case look like a working case most of the time.

The following code illustrates the pattern detected by this rule:

Public Class DeviceSession

    ' FLAGGED: A raw native handle field is exposed outside its type
    Public Handle As IntPtr

    ' FLAGGED: A raw native handle field is exposed outside its type
    Public Shared SharedContext As System.IntPtr = IntPtr.Zero

    ' FLAGGED: A raw native handle field is exposed outside its type
    Protected Buffer As UIntPtr

    ' FLAGGED: A raw native handle field is exposed outside its type
    Protected Friend ReadOnly Callback As IntPtr

Remediation

Do not publish the handle. Keep the IntPtr Private and let the type own it end to end: acquire it in the constructor or a factory, use it only inside the type’s own methods, and release it in Dispose. Where the handle genuinely has to cross the type boundary - a P/Invoke signature a caller supplies, or a handle passed to another component - wrap it in a SafeHandle subclass (SafeFileHandle and friends already exist for the common operating-system objects). A SafeHandle gives the value reference-counted ownership, a critical finaliser that runs even during abort, and protection against the handle being released while a call is in flight, which is precisely what a bare IntPtr lacks. Expose operations rather than the handle: a method named for what the caller wants to do keeps the lifetime inside the type that manages it.

' Before: callers hold a raw handle whose lifetime this type controls
Public Class DeviceSession
    Public Handle As IntPtr
End Class

' After: ownership and lifetime travel with the value
Public Class DeviceSession
    Implements IDisposable

    Private ReadOnly _handle As SafeFileHandle

    Public Sub Dispose() Implements IDisposable.Dispose
        _handle.Dispose()
    End Sub
End Class

Configuration

This detector does not need any configuration.