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