gc-safe-coding

작성자: facebook

전체 설명과 근거는 doc/GCSafeCoding.md를 참조하십시오.

npx skills add https://github.com/facebook/hermes --skill gc-safe-coding

For the full explanation and rationale, see 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. The same applies to bare SymbolID values extracted from a non-uniqued source (e.g., the SymbolID pulled out of the Handle<SymbolID> 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<SymbolID>.

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.

struct : public Locals {
  PinnedValue<JSObject> obj;
  PinnedValue<StringPrimitive> str;
  PinnedValue<SymbolID> 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<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:

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. Equally watch for capture-via-deref: auto *x = vmcast<T>(*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<T>.
  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.
  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.

facebook의 다른 스킬

app-review-prep
facebook
Meta 앱을 App Review용으로 준비합니다 — 현재 상태, 미해결 요구 사항, 부여된 권한, 제출 내역을 확인합니다. 앱을 제출하기 전에 사용하세요…
api-health
facebook
Meta 앱의 API 상태를 모니터링합니다 — 비율 제한, 호출량, API 지원 중단 여부를 확인합니다. 트래픽 제한을 진단하거나, 용량을 계획하거나, API 버전 변경에 대비하는 데 사용합니다…
debug-webhooks
facebook
Meta 앱의 웹훅 문제를 해결합니다 — 활성 구독을 검사하고, 잘못된 구성을 식별하며, 테스트 페이로드를 전송하여 전달을 확인합니다. 다음과 같은 경우에 사용하세요…
api-integration
facebook
개발자가 Meta API 통합을 처음부터 설정하도록 안내합니다 — 적절한 API를 찾아내고, 설정 가이드, 인증 요구 사항 등을 가져옵니다…
webhook-setup
facebook
Meta 앱용 웹훅을 처음부터 끝까지 설정하세요 — 사용 가능한 주제를 탐색하고, 필드를 구독하고, 테스트 페이로드로 검증합니다. 웹훅을 구성할 때 사용하세요…
test-ui
facebook
iwsdk CLI를 사용하여 포크 예제에 대해 Test UI 시스템(PanelUI, ScreenSpace)을 테스트합니다.
flags
facebook
React 릴리스 채널 간 기능 플래그 상태를 검사하고 비교합니다. 모든 채널(www, www-modern, canary, next, experimental, rn 변형)의 플래그를 보거나 --diff로 특정 채널을 비교합니다. 출력 형식은 기본 테이블 보기, CSV 내보내기, 정리 상태 그룹화를 포함합니다. 플래그 상태는 기호로 표시됩니다: 활성화(✅), 비활성화(❌), 변형 테스트(🧪), 프로파일링 전용(📊). 일반적인 실수: __VARIANT__ 플래그는 www에서 두 상태 모두 테스트되며, --diff를 사용하여 의미 있는 차이를 찾습니다...
compliance-check
facebook
Meta 앱의 컴플라이언스 상태를 확인합니다 — 미해결 필수 조치, 활성 위반 사항, 권장 사항 및 수정 지침을 표시합니다. 감사에 사용하세요…