← Back to AILP Home

Handle diagnostics

The handle subsystem owns one dedicated diagnostic code, ARIA-032, plus the type-mismatch error you get from the general type checker when you mix incompatible handle types.

For the broader set of memory-region codes (ARIA-014, ARIA-015, ARIA-028, ARIA-029, ARIA-031), see memory/diagnostics.md.


ARIA-032 — handle outlives its arena

The borrow checker has seen HandleArena.destroy(a) earlier in the same function, and now sees a HandleArena.deref(h) or HandleArena.free(h) where h is a handle bound to a.

use "handle.npk".*;

func:main = int32() {
    int64:a            = raw HandleArena.create();
    Handle<int32>:h    = raw HandleArena.alloc(a, 4i64);
    raw HandleArena.destroy(a);
    int64:p = raw HandleArena.deref(h);   // [ARIA-032]
    exit 0;
};
error[ARIA-032]: Handle 'h' outlives its arena 'a'. The arena was
destroyed earlier in this function; the handle is no longer valid.
See ARIA-032 and guide/memory/handles.md.

Fix: either drop the deref/free, move it before HandleArena.destroy(a), or restructure so the arena outlives all its handles:

int64:a            = raw HandleArena.create();
Handle<int32>:h    = raw HandleArena.alloc(a, 4i64);
int64:p = raw HandleArena.deref(h);       // use first
raw HandleArena.free(h);                   // then free
raw HandleArena.destroy(a);                // then destroy

Scope

The rule is intra-function only. Handles that cross function boundaries (parameters, returns) or go through struct storage / FFI are tracked at runtime instead — deref returns 0i64. The static rule is a safety net for the common straight-line case; it never accepts code the runtime would reject.

Regression tests


Type-mismatch on Handle<T>

Assigning between two Handle<T> types with different T is a type error from the general type checker:

Handle<int32>:hi = raw HandleArena.alloc(a, 4i64);
Handle<int64>:hl = hi;          // error: cannot assign Handle<int32> to Handle<int64>

Handle<T>int64 is bidirectional and silent, so the way out is to round-trip through int64:

int64:tok        = hi;
Handle<int64>:hl = tok;          // OK — explicit

This is the same pattern you would use for FFI; bug257_handle_type_mismatch_rejected.npk is the regression test.


Runtime NULL deref (not a diagnostic)

HandleArena.deref(h) returning 0i64 is not a compile error — it is the runtime contract for stale or freed handles. Always check:

int64:p = raw HandleArena.deref(h);
if (p == 0i64) {
    exit 1;        // handle was stale; recover or bail
};

bug258_handle_uaf_returns_null.npk and bug259_arena_destroy_invalidates.npk (now compile-failing under ARIA-032 after v0.27.9, but the runtime behaviour is unchanged) cover this.

See also