Finalizer No Exception

ID

csharp.finalizer_no_exception

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

CSharp

Tags

finalizer, reliability

Description

Reports throw statements inside a finalizer (~ClassName()). A finalizer is invoked by the garbage collector on a dedicated thread, with no enclosing call site to handle the exception. An unhandled exception escaping a finalizer terminates the process in modern .NET runtimes.

Rationale

The finalizer is the last opportunity to release a native resource that the GC cannot reclaim on its own. Throwing in the middle of that cleanup leaks the resource permanently and abruptly tears down the process, which can also lose pending work elsewhere in the runtime.

Recovery from a finalizer is impossible: there is no caller to catch the exception, and the object that owned the resource is being destroyed. Failures must be logged and swallowed.

public class NativeBuffer
{
    private IntPtr handle;

    ~NativeBuffer()
    {
        if (handle == IntPtr.Zero)
            throw new InvalidOperationException("not allocated"); // FLAW
        Release(handle);
    }

    private static void Release(IntPtr handle) { }
}

public class NativeBufferOk
{
    private IntPtr handle;

    ~NativeBufferOk()                                              // OK
    {
        try
        {
            if (handle != IntPtr.Zero) Release(handle);
        }
        catch (Exception)
        {
        }
    }

    private static void Release(IntPtr handle) { }
}

Remediation

Make the finalizer non-throwing: guard cleanup with try/catch, log unexpected conditions, and never propagate exceptions. Where possible, implement IDisposable and prefer Dispose over relying on the finalizer in the first place.