gc-safe-coding

bởi facebook

Để biết giải thích đầy đủ và lý do, hãy xem 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.

Thêm skills từ facebook

app-review-prep
facebook
Chuẩn bị ứng dụng Meta cho App Review — kiểm tra trạng thái hiện tại, các yêu cầu còn tồn đọng, quyền đã được cấp và lịch sử gửi duyệt. Sử dụng trước khi gửi ứng dụng…
api-health
facebook
Theo dõi tình trạng API cho ứng dụng Meta — kiểm tra giới hạn tỷ lệ, khối lượng cuộc gọi và các API bị ngừng hỗ trợ. Dùng để chẩn đoán tình trạng hạn chế, lập kế hoạch dung lượng hoặc chuẩn bị cho phiên bản API…
debug-webhooks
facebook
Khắc phục sự cố webhook cho ứng dụng Meta — kiểm tra các đăng ký đang hoạt động, xác định cấu hình sai và gửi payload thử nghiệm để xác minh việc phân phối. Sử dụng khi…
api-integration
facebook
Hướng dẫn nhà phát triển thiết lập tích hợp Meta API từ đầu — khám phá các API phù hợp, tìm nạp hướng dẫn thiết lập, yêu cầu xác thực,…
webhook-setup
facebook
Thiết lập webhooks cho ứng dụng Meta từ đầu đến cuối — khám phá các chủ đề có sẵn, đăng ký nhận các trường và xác minh bằng payload kiểm thử. Sử dụng khi cấu hình webhooks cho…
test-ui
facebook
Kiểm thử hệ thống UI (PanelUI, ScreenSpace) với ví dụ poke bằng iwsdk CLI.
flags
facebook
Kiểm tra và so sánh trạng thái cờ tính năng trên các kênh phát hành React. Xem tất cả cờ trên các kênh (www, www-modern, canary, next, experimental, biến thể rn) hoặc so sánh các kênh cụ thể với --diff. Định dạng đầu ra bao gồm chế độ xem bảng mặc định, xuất CSV và nhóm trạng thái dọn dẹp. Trạng thái cờ được biểu thị bằng ký hiệu: bật (✅), tắt (❌), kiểm thử biến thể (🧪), chỉ lập hồ sơ (📊). Lỗi thường gặp: cờ __VARIANT__ được kiểm thử ở cả hai trạng thái trên www; sử dụng --diff để phát hiện sự khác biệt có ý nghĩa...
compliance-check
facebook
Kiểm tra trạng thái tuân thủ cho ứng dụng Meta — hiển thị các hành động bắt buộc còn mở, vi phạm đang hoạt động và đề xuất kèm hướng dẫn khắc phục. Dùng để kiểm toán…