Call to a framework method whose behaviour is unreliable or unsafe
ID |
vbnet.correctness.avoid_calling_problematic_methods |
Severity |
critical |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Api Design |
Language |
VB.NET |
Description
Reports calls to a small set of framework methods that are documented as problematic and have a
safer replacement: GC.Collect, Thread.Suspend and Thread.Resume, AppDomain.Unload,
Assembly.LoadFrom, and SafeHandle.DangerousGetHandle. Each is recognised by the method name
together with the receiver it is called on - GC, Assembly and AppDomain qualifiers, and a
variable whose name identifies a thread for Suspend and Resume - so a same-named method on
an unrelated type is not reported. Related methods that are not problematic, such as
Assembly.Load, GC.WaitForPendingFinalizers and AppDomain.CreateDomain, are left alone.
Test sources are excluded, since forcing a collection is a legitimate step in a memory test.
Rationale
These calls fail in ways that are hard to attribute back to them. GC.Collect forces a blocking
collection of every generation, discarding the collector’s own tuning: the long-lived objects it
was deliberately leaving in generation 0 get promoted, so the next natural collection has more to
do, and the pattern typically makes throughput and pause times worse than the behaviour it was
added to fix. Thread.Suspend stops a thread at an arbitrary instruction, which may be inside a
lock, a finally block or the allocator; the suspended thread keeps whatever it holds, and any
thread that later needs it deadlocks - which is why the methods are obsolete and unavailable on
.NET Core. AppDomain.Unload cannot force a thread out of the domain being torn down: a thread
in an unbreakable region or a finalizer that does not return leaves the unload hanging or raises
CannotUnloadAppDomainException, and the caller has no way to tell which. Assembly.LoadFrom
resolves through a separate load context with its own binding rules, so the same file can end up
loaded twice under different identities; the types then compare unequal, casts between them fail
with messages naming the same type on both sides, and a dependency next to the loaded file wins
over the one the application resolved. SafeHandle.DangerousGetHandle returns the raw handle
with no reference held, so the SafeHandle may be finalized while the returned value is still in
use - the handle is then closed, or worse recycled by the operating system for something else,
and the call that uses it operates on an unrelated object.
The following code illustrates the pattern detected by this rule:
Public Sub TrimMemory()
' FLAGGED: Call to a framework method whose behaviour is unreliable or unsafe
GC.Collect()
' FLAGGED: Call to a framework method whose behaviour is unreliable or unsafe
System.GC.Collect(2, GCCollectionMode.Forced)
Remediation
Replace each call with the supported alternative. Let the garbage collector schedule its own
collections and, where a specific object must be released promptly, implement IDisposable and
use Using instead of forcing a collection. Coordinate threads with a ManualResetEventSlim,
SemaphoreSlim or a CancellationToken that the thread itself observes, rather than suspending
it from outside. Isolate untrusted or reloadable code in a separate process, or on .NET Core in a
collectible AssemblyLoadContext, instead of relying on domain unload. Load assemblies by
identity with Assembly.Load so binding stays in one context. Pass the SafeHandle itself to
the P/Invoke declaration and let the marshaller keep it alive; if the raw value is unavoidable,
bracket its use with DangerousAddRef and DangerousRelease.
' Before
Public Sub Pause()
workerThread.Suspend()
GC.Collect()
End Sub
' After
Public Sub Pause()
pauseRequested.Reset() ' the worker waits on this at a safe point
End Sub
References
-
https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/induced
-
https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.suspend
-
https://learn.microsoft.com/en-us/dotnet/api/system.appdomain.unload
-
https://learn.microsoft.com/en-us/dotnet/framework/deployment/best-practices-for-assembly-loading