SuperAPI AI 工具集

gc-safe-coding

编程与开发

查看项目

更新于 10-16 09:54

技能介绍

用途:面向在 Hermes VM 运行时中编写、修改或审查 C++ 代码的场景,确保 GC 安全,避免对象在垃圾回收后被错误复用。

主要能力与规则:

  1. GC 安全点:GC 堆分配,或可能间接触达分配的函数调用。凡接受 Runtime & 或 PointerBase & 的函数都可能触发 GC,除非另有说明或命名带 _noalloc/_nogc;带 _RJS 后缀的函数递归调用 JavaScript,总会触发 GC。malloc 等普通 C 堆分配不是安全点。
  2. 根化要求:指向 GC 对象的裸指针和 PseudoHandle 在任何安全点前必须根化。PseudoHandle<T> 不是根,跨安全点与裸指针同样危险。从非唯一来源取得的裸 SymbolID(例如从 getSymbolHandleFromPrimitive 返回的 Handle<SymbolID> 中取出)也需用 PinnedValue<SymbolID> 固定。
  3. 新代码必须使用 Locals + PinnedValue<T>:声明 struct : public Locals 并包含 PinnedValue 字段,配合 LocalsRAII。不得引入新的 GCScope 或 makeHandle()。赋值可用 std::move(*callResult)、castAndSetHermesValue<JSObject>(hv)、裸指针赋值或置 nullptr;模板中写 lv.obj.template castAndSetHermesValue<T>(hv)。PinnedValue<T> 可隐式转换为 Handle<T>,直接传给接受 Handle<T> 的函数。
  4. 错误处理:使用 CallResult 时先检查 result == ExecutionStatus::EXCEPTION 再取值。
  5. 无需转换句柄的情况:vmcast<>(handle) 只是类型转换且不接受 Runtime &,不分配槽位;args.getArgHandle(n) 指向寄存器栈,已是根;传入或接收 Handle<> 参数由调用方分配。只有通过 makeHandle()、makeMutableHandle() 或接受 Runtime & 的 Handle 构造函数新分配 PinnedHermesValue 槽位时才需标记。

审查清单:跨安全点不得持有裸指针或 PseudoHandle;注意多步创建模式中前一个 PseudoHandle 在后续分配后失效;注意 auto *x = vmcast<T>(*pinned) 这类解引用捕获出的裸指针在每次使用处重新解引用或用 PinnedValue 固定;新代码使用 Locals 而非 GCScope;检查每个 CallResult;不要返回指向即将销毁的 PinnedValue 或 GCScope 的 Handle,应返回 CallResult<PseudoHandle<T>> 或 CallResult<HermesValue>;遍历原型链时先判空再 castAndSetHermesValue;Locals 在循环中安全,字段每次迭代复用。句柄分配在最顶层 GCScope,需要时用 GCScopeMarkerRAII 或 flushToMarker;flush 后从该 Handle 取出的裸指针或裸 SymbolID 失去根化,需提前用 PinnedValue 固定。

调试提示:若 IdentifierTable::materializeLazyIdentifier 断言 identifier is not lazy,该条目常是 free-list 槽位,可在调用栈中查找跨分配持有的未根化 SymbolID。

限制:更完整说明与理由见 doc/GCSafeCoding.md。其他细节暂未提供。