# Facebook Hermes Gc Safe Coding

> For the full explanation and rationale, see [doc/GCSafeCoding.md](doc/GCSafeCoding.md).

- Skill: `tomevault-io/facebook-hermes-gc-safe-coding` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/facebook-hermes-gc-safe-coding`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/facebook-hermes-gc-safe-coding/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/facebook-hermes-gc-safe-coding

---


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<T>` is *not* a root — it is just as dangerous as
a raw pointer across a safepoint.

## Rooting local values: use Locals + PinnedValue (required for new code)

All new code must use `Locals` + `PinnedValue<T>`. Do not introduce new
`GCScope` instances or `makeHandle()` calls.

```cpp
struct : public Locals {
  PinnedValue<JSObject> obj;
  PinnedValue<StringPrimitive> str;
  PinnedValue<> genericValue;
} lv;
LocalsRAII lraii(runtime, &lv);
```

### Assignment patterns

- **From PseudoHandle:** `lv.obj = std::move(*callResult);`
- **From HermesValue with known type:** `lv.obj.castAndSetHermesValue<JSObject>(hv);`
- **From raw pointer:** `lv.obj = somePtr;`
- **Clear:** `lv.obj = nullptr;`
- **In template context:** `lv.obj.template castAndSetHermesValue<T>(hv);`

### Passing to functions

`PinnedValue<T>` implicitly converts to `Handle<T>`. Pass directly to functions
that accept `Handle<T>`.

## 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<T>` — 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.
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<T>` pointing
   into a `PinnedValue` or `GCScope` that is about to be destroyed. Return
   `CallResult<PseudoHandle<T>>` or `CallResult<HermesValue>` 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.

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/facebook) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-11 -->

