--- name: gc-safe-coding description: > Rules for writing and reviewing GC-safe C++ code in the Hermes VM runtime. Use when writing, modifying, or reviewing C++ runtime VM code that uses internal Hermes VM APIs (as opposed to code using JSI). This includes working with GC-managed types (HermesValue, Handle, PinnedValue, JSObject, StringPrimitive, etc.), Locals, GCScope, PseudoHandle, CallResult, or any function with _RJS suffix. Typically in lib/VM/, include/hermes/VM/, API/hermes/, or API/napi/. --- For the full explanation and rationale, see [doc/GCSafeCoding.md](doc/GCSafeCoding.md). ## GC safepoints A GC safepoint is either a GC heap allocation or a function call that might transitively reach one (regular C heap allocations like `malloc` are not safepoints). Any function that takes `Runtime &` or `PointerBase &` may trigger GC, unless documented otherwise or named with `_noalloc`/`_nogc`. Functions with `_RJS` suffix invoke JavaScript recursively and always trigger GC. **All raw pointers and PseudoHandles to GC objects must be rooted before any GC safepoint.** `PseudoHandle` is *not* a root — it is just as dangerous as a raw pointer across a safepoint. The same applies to bare `SymbolID` values extracted from a non-uniqued source (e.g., the `SymbolID` pulled out of the `Handle` returned by `getSymbolHandleFromPrimitive` for a freshly-allocated `StringPrimitive`): once nothing roots it, the lookup-table slot is reclaimed by `freeUnmarkedSymbols` during sweep. Pin via `PinnedValue`. ## Rooting local values: use Locals + PinnedValue (required for new code) All new code must use `Locals` + `PinnedValue`. Do not introduce new `GCScope` instances or `makeHandle()` calls. ```cpp struct : public Locals { PinnedValue obj; PinnedValue str; PinnedValue sym; PinnedValue<> genericValue; } lv; LocalsRAII lraii(runtime, &lv); ``` ### Assignment patterns - **From PseudoHandle:** `lv.obj = std::move(*callResult);` - **From HermesValue with known type:** `lv.obj.castAndSetHermesValue(hv);` - **From raw pointer:** `lv.obj = somePtr;` - **Clear:** `lv.obj = nullptr;` - **In template context:** `lv.obj.template castAndSetHermesValue(hv);` ### Passing to functions `PinnedValue` implicitly converts to `Handle`. Pass directly to functions that accept `Handle`. ## Error handling with CallResult Always check for exceptions before using the value: ```cpp auto result = someOperation_RJS(runtime, args); if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) return ExecutionStatus::EXCEPTION; lv.obj = std::move(*result); ``` ## When Handle usage is fine (do not flag) Not every use of `Handle<>` needs to be converted to `PinnedValue`. The rule "use Locals, not GCScope" applies to **creating new rooted values** — allocating new `PinnedHermesValue` slots via `makeHandle()` or `Handle<>` constructors. The following are **not** allocating new handles and do not need conversion: - **`vmcast<>(handle)`** — casts an existing handle to a different type. It does not take `Runtime &` and does not allocate a GCScope slot. The result points to the same `PinnedHermesValue` as the input. - **`args.getArgHandle(n)`** — returns a handle pointing into the register stack, which is already a root. No new allocation. - **Passing or receiving a `Handle<>` parameter** — the handle was allocated by the caller; the callee is just using it. Only flag handle usage when a **new** `PinnedHermesValue` slot is being allocated (via `makeHandle()`, `makeMutableHandle()`, or `Handle<>`/ `MutableHandle<>` constructors that take `Runtime &`). ## Checklist for writing / reviewing GC-safe code 1. **No raw pointers or PseudoHandles across GC safepoints.** Every pointer to a GC object — including values held in `PseudoHandle` — must be stored in a `PinnedValue` before any call that takes `Runtime &` or is `_RJS`. Watch for multi-step creation patterns: if `Foo::create()` returns a `PseudoHandle` and the next line calls `Bar::create(runtime)`, the first `PseudoHandle` is stale after the second allocation. Equally watch for capture-via-deref: `auto *x = vmcast(*pinned)` extracts a raw pointer from a pinned location (e.g., a `PinnedHermesValue *` such as a `napi_value`). The pinned slot stays GC-safe, but the local raw pointer does not. Re-deref `*pinned` at each use site, or pin via `PinnedValue`. 2. **Use Locals, not GCScope.** New code must not introduce `GCScope` or `makeHandle()`. Declare a `struct : public Locals` with `PinnedValue` fields and a `LocalsRAII`. 3. **Check every CallResult.** Never dereference a `CallResult` without first checking `== ExecutionStatus::EXCEPTION`. 4. **Never return Handle from local roots.** Do not return `Handle` pointing into a `PinnedValue` or `GCScope` that is about to be destroyed. Return `CallResult>` or `CallResult` instead. 5. **Null prototype checks.** When traversing prototype chains, check for null before calling `castAndSetHermesValue`. 6. **Loops are safe with Locals.** `PinnedValue` fields are reused each iteration — no unbounded growth. If a `GCScope` is still needed for legacy APIs that return `Handle`, use `GCScopeMarkerRAII` or `flushToMarker`. 7. **Handles allocate in the topmost GCScope.** `makeHandle()`, `makeMutableHandle()`, `Handle<>` and `MutableHandle<>` constructors, and calls to functions that take `Runtime &`/`PointerBase &` and return `Handle<>`, all allocate a slot in the topmost `GCScope`. Functions that create or receive handles without returning them need their own `GCScope` or `GCScopeMarkerRAII` (preferred for one or two handles). Functions like `vmcast<>` that do not take `Runtime &` just cast existing handles without allocating. 8. **`flushToMarker` invalidates handles allocated after the marker.** Any value extracted from such a Handle (raw pointer, bare `SymbolID`) is unrooted after the flush. Pin into a `PinnedValue` *before* the flush if the value is needed later. ## Debugging tips - If `IdentifierTable::materializeLazyIdentifier` asserts `(entry.isLazyASCII() || entry.isLazyUTF16()) && "identifier is not lazy"`, the entry is most often a free-list slot — look up the call stack for an unrooted `SymbolID` held across an allocation.