Bug 1567313: Give Debugger reflection objects a non-static trace method. r=jorendorff
authorJim Blandy <jimb@mozilla.com>
Tue, 13 Aug 2019 23:06:11 +0000
changeset 487822 9de6cb3d8fa1a4792562db739c4dbf68683fc6b8
parent 487821 4914a484157fb09cb4d9034d9e9845376b93a096
child 487823 e3dbaf75ed7c9e1a82bd992fc6a516dc2acffa2f
push id36430
push userdvarga@mozilla.com
push dateWed, 14 Aug 2019 04:09:17 +0000
treeherdermozilla-central@d3deef805f92 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1567313
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1567313: Give Debugger reflection objects a non-static trace method. r=jorendorff Replace the loosely-typed 'trace' static member functions with well-typed non-static trace methods. These can be used by DebuggerWeakMap::traceCrossCompartmentEdges, called from Debugger::traceCrossCompartmentEdges. To produce the plain (JSTracer*, JSObject*) functions still needed for js::ClassOps::trace values, define a CallTraceMethod function template that does the downcast to the most derived type, and then defers to the well-typed method. Differential Revision: https://phabricator.services.mozilla.com/D38573
js/src/debugger/Debugger.cpp
js/src/debugger/Debugger.h
js/src/debugger/Environment.cpp
js/src/debugger/Environment.h
js/src/debugger/Frame.cpp
js/src/debugger/Frame.h
js/src/debugger/Object.cpp
js/src/debugger/Object.h
js/src/debugger/Script.cpp
js/src/debugger/Script.h
js/src/debugger/Source.cpp
js/src/debugger/Source.h
js/src/vm/JSObject.h
--- a/js/src/debugger/Debugger.cpp
+++ b/js/src/debugger/Debugger.cpp
@@ -3471,24 +3471,24 @@ void Debugger::removeAllocationsTracking
   }
 
   allocationsLog.clear();
 }
 
 /*** Debugger JSObjects *****************************************************/
 
 void Debugger::traceCrossCompartmentEdges(JSTracer* trc) {
-  generatorFrames.traceCrossCompartmentEdges<DebuggerFrame::trace>(trc);
-  objects.traceCrossCompartmentEdges<DebuggerObject::trace>(trc);
-  environments.traceCrossCompartmentEdges<DebuggerEnvironment::trace>(trc);
-  scripts.traceCrossCompartmentEdges<DebuggerScript::trace>(trc);
-  lazyScripts.traceCrossCompartmentEdges<DebuggerScript::trace>(trc);
-  sources.traceCrossCompartmentEdges<DebuggerSource::trace>(trc);
-  wasmInstanceScripts.traceCrossCompartmentEdges<DebuggerScript::trace>(trc);
-  wasmInstanceSources.traceCrossCompartmentEdges<DebuggerSource::trace>(trc);
+  generatorFrames.traceCrossCompartmentEdges(trc);
+  objects.traceCrossCompartmentEdges(trc);
+  environments.traceCrossCompartmentEdges(trc);
+  scripts.traceCrossCompartmentEdges(trc);
+  lazyScripts.traceCrossCompartmentEdges(trc);
+  sources.traceCrossCompartmentEdges(trc);
+  wasmInstanceScripts.traceCrossCompartmentEdges(trc);
+  wasmInstanceSources.traceCrossCompartmentEdges(trc);
 }
 
 /*
  * Ordinarily, WeakMap keys and values are marked because at some point it was
  * discovered that the WeakMap was live; that is, some object containing the
  * WeakMap was marked during mark phase.
  *
  * However, during zone GC, we have to do something about cross-compartment
--- a/js/src/debugger/Debugger.h
+++ b/js/src/debugger/Debugger.h
@@ -358,20 +358,19 @@ class DebuggerWeakMap : private WeakMap<
     CheckDebuggeeThing(k, InvisibleKeysOk);
 #endif
     MOZ_ASSERT(!Base::has(k));
     bool ok = Base::relookupOrAdd(p, k, v);
     return ok;
   }
 
  public:
-  template <void(traceValueEdges)(JSTracer*, JSObject*)>
   void traceCrossCompartmentEdges(JSTracer* tracer) {
     for (Enum e(*this); !e.empty(); e.popFront()) {
-      traceValueEdges(tracer, e.front().value());
+      e.front().value()->trace(tracer);
       Key key = e.front().key();
       TraceEdge(tracer, &key, "Debugger WeakMap key");
       if (key != e.front().key()) {
         e.rekeyFront(key);
       }
       key.unsafeSet(nullptr);
     }
   }
--- a/js/src/debugger/Environment.cpp
+++ b/js/src/debugger/Environment.cpp
@@ -46,41 +46,44 @@ class GlobalObject;
 
 using namespace js;
 
 using js::frontend::IsIdentifier;
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::Some;
 
-const ClassOps DebuggerEnvironment::classOps_ = {nullptr, /* addProperty */
-                                                 nullptr, /* delProperty */
-                                                 nullptr, /* enumerate   */
-                                                 nullptr, /* newEnumerate */
-                                                 nullptr, /* resolve     */
-                                                 nullptr, /* mayResolve  */
-                                                 nullptr, /* finalize    */
-                                                 nullptr, /* call        */
-                                                 nullptr, /* hasInstance */
-                                                 nullptr, /* construct   */
-                                                 trace};
+const ClassOps DebuggerEnvironment::classOps_ = {
+    nullptr,                              /* addProperty */
+    nullptr,                              /* delProperty */
+    nullptr,                              /* enumerate   */
+    nullptr,                              /* newEnumerate */
+    nullptr,                              /* resolve     */
+    nullptr,                              /* mayResolve  */
+    nullptr,                              /* finalize    */
+    nullptr,                              /* call        */
+    nullptr,                              /* hasInstance */
+    nullptr,                              /* construct   */
+    CallTraceMethod<DebuggerEnvironment>, /* trace */
+};
 
 const Class DebuggerEnvironment::class_ = {
     "Environment",
     JSCLASS_HAS_PRIVATE |
         JSCLASS_HAS_RESERVED_SLOTS(DebuggerEnvironment::RESERVED_SLOTS),
     &classOps_};
 
-void DebuggerEnvironment::trace(JSTracer* trc, JSObject* obj) {
+void DebuggerEnvironment::trace(JSTracer* trc) {
   // There is a barrier on private pointers, so the Unbarriered marking
   // is okay.
-  if (Env* referent = (JSObject*)obj->as<NativeObject>().getPrivate()) {
-    TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &referent,
-                                               "Debugger.Environment referent");
-    obj->as<NativeObject>().setPrivateUnbarriered(referent);
+  if (Env* referent = (JSObject*)getPrivate()) {
+    TraceManuallyBarrieredCrossCompartmentEdge(
+        trc, static_cast<JSObject*>(this), &referent,
+        "Debugger.Environment referent");
+    setPrivateUnbarriered(referent);
   }
 }
 
 static DebuggerEnvironment* DebuggerEnvironment_checkThis(
     JSContext* cx, const CallArgs& args, const char* fnname,
     bool requireDebuggee) {
   JSObject* thisobj = RequireObject(cx, args.thisv());
   if (!thisobj) {
--- a/js/src/debugger/Environment.h
+++ b/js/src/debugger/Environment.h
@@ -40,17 +40,17 @@ class DebuggerEnvironment : public Nativ
   static const Class class_;
 
   static NativeObject* initClass(JSContext* cx, Handle<GlobalObject*> global,
                                  HandleObject dbgCtor);
   static DebuggerEnvironment* create(JSContext* cx, HandleObject proto,
                                      HandleObject referent,
                                      HandleNativeObject debugger);
 
-  static void trace(JSTracer* trc, JSObject* obj);
+  void trace(JSTracer* trc);
 
   DebuggerEnvironmentType type() const;
   mozilla::Maybe<ScopeKind> scopeKind() const;
   MOZ_MUST_USE bool getParent(JSContext* cx,
                               MutableHandleDebuggerEnvironment result) const;
   MOZ_MUST_USE bool getObject(JSContext* cx,
                               MutableHandleDebuggerObject result) const;
   MOZ_MUST_USE bool getCallee(JSContext* cx,
--- a/js/src/debugger/Frame.cpp
+++ b/js/src/debugger/Frame.cpp
@@ -168,27 +168,27 @@ bool ScriptedOnPopHandler::onPop(JSConte
 size_t ScriptedOnPopHandler::allocSize() const { return sizeof(*this); }
 
 inline js::Debugger* js::DebuggerFrame::owner() const {
   JSObject* dbgobj = &getReservedSlot(OWNER_SLOT).toObject();
   return Debugger::fromJSObject(dbgobj);
 }
 
 const ClassOps DebuggerFrame::classOps_ = {
-    nullptr,  /* addProperty */
-    nullptr,  /* delProperty */
-    nullptr,  /* enumerate   */
-    nullptr,  /* newEnumerate */
-    nullptr,  /* resolve     */
-    nullptr,  /* mayResolve  */
-    finalize, /* finalize */
-    nullptr,  /* call        */
-    nullptr,  /* hasInstance */
-    nullptr,  /* construct   */
-    trace,    /* trace */
+    nullptr,                        /* addProperty */
+    nullptr,                        /* delProperty */
+    nullptr,                        /* enumerate   */
+    nullptr,                        /* newEnumerate */
+    nullptr,                        /* resolve     */
+    nullptr,                        /* mayResolve  */
+    finalize,                       /* finalize */
+    nullptr,                        /* call        */
+    nullptr,                        /* hasInstance */
+    nullptr,                        /* construct   */
+    CallTraceMethod<DebuggerFrame>, /* trace */
 };
 
 const Class DebuggerFrame::class_ = {
     "Frame",
     JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
         // We require foreground finalization so we can destruct GeneratorInfo's
         // HeapPtrs.
         JSCLASS_FOREGROUND_FINALIZE,
@@ -1070,30 +1070,28 @@ void DebuggerFrame::finalize(JSFreeOp* f
     onStepHandler->drop(fop, &frameobj);
   }
   OnPopHandler* onPopHandler = frameobj.onPopHandler();
   if (onPopHandler) {
     onPopHandler->drop(fop, &frameobj);
   }
 }
 
-/* static */
-void DebuggerFrame::trace(JSTracer* trc, JSObject* obj) {
-  OnStepHandler* onStepHandler = obj->as<DebuggerFrame>().onStepHandler();
+void DebuggerFrame::trace(JSTracer* trc) {
+  OnStepHandler* onStepHandler = this->onStepHandler();
   if (onStepHandler) {
     onStepHandler->trace(trc);
   }
-  OnPopHandler* onPopHandler = obj->as<DebuggerFrame>().onPopHandler();
+  OnPopHandler* onPopHandler = this->onPopHandler();
   if (onPopHandler) {
     onPopHandler->trace(trc);
   }
 
-  DebuggerFrame& frameObj = obj->as<DebuggerFrame>();
-  if (frameObj.hasGenerator()) {
-    frameObj.generatorInfo()->trace(trc, frameObj);
+  if (hasGenerator()) {
+    generatorInfo()->trace(trc, *this);
   }
 }
 
 /* static */
 DebuggerFrame* DebuggerFrame::checkThis(JSContext* cx, const CallArgs& args,
                                         const char* fnname, bool checkLive) {
   JSObject* thisobj = RequireObject(cx, args.thisv());
   if (!thisobj) {
--- a/js/src/debugger/Frame.h
+++ b/js/src/debugger/Frame.h
@@ -136,17 +136,17 @@ class DebuggerFrame : public NativeObjec
     // Debugger.Frame, this link represents the reverse relation, from a
     // Debugger.Frame to its generator object. This slot is set if and only if
     // there is a corresponding entry in generatorFrames.
     GENERATOR_INFO_SLOT,
 
     RESERVED_SLOTS,
   };
 
-  static void trace(JSTracer* trc, JSObject* obj);
+  void trace(JSTracer* trc);
 
   static NativeObject* initClass(JSContext* cx, Handle<GlobalObject*> global,
                                  HandleObject dbgCtor);
   static DebuggerFrame* create(JSContext* cx, HandleObject proto,
                                const FrameIter& iter,
                                HandleNativeObject debugger);
 
   static MOZ_MUST_USE bool getScript(JSContext* cx, unsigned argc, Value* vp);
--- a/js/src/debugger/Object.cpp
+++ b/js/src/debugger/Object.cpp
@@ -66,39 +66,42 @@
 
 using namespace js;
 
 using JS::AutoStableStringChars;
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::Some;
 
-const ClassOps DebuggerObject::classOps_ = {nullptr, /* addProperty */
-                                            nullptr, /* delProperty */
-                                            nullptr, /* enumerate   */
-                                            nullptr, /* newEnumerate */
-                                            nullptr, /* resolve     */
-                                            nullptr, /* mayResolve  */
-                                            nullptr, /* finalize    */
-                                            nullptr, /* call        */
-                                            nullptr, /* hasInstance */
-                                            nullptr, /* construct   */
-                                            trace};
+const ClassOps DebuggerObject::classOps_ = {
+    nullptr,                         /* addProperty */
+    nullptr,                         /* delProperty */
+    nullptr,                         /* enumerate   */
+    nullptr,                         /* newEnumerate */
+    nullptr,                         /* resolve     */
+    nullptr,                         /* mayResolve  */
+    nullptr,                         /* finalize    */
+    nullptr,                         /* call        */
+    nullptr,                         /* hasInstance */
+    nullptr,                         /* construct   */
+    CallTraceMethod<DebuggerObject>, /* trace */
+};
 
 const Class DebuggerObject::class_ = {
     "Object", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS),
     &classOps_};
 
-void DebuggerObject::trace(JSTracer* trc, JSObject* obj) {
+void DebuggerObject::trace(JSTracer* trc) {
   // There is a barrier on private pointers, so the Unbarriered marking
   // is okay.
-  if (JSObject* referent = (JSObject*)obj->as<NativeObject>().getPrivate()) {
-    TraceManuallyBarrieredCrossCompartmentEdge(trc, obj, &referent,
-                                               "Debugger.Object referent");
-    obj->as<NativeObject>().setPrivateUnbarriered(referent);
+  if (JSObject* referent = (JSObject*)getPrivate()) {
+    TraceManuallyBarrieredCrossCompartmentEdge(
+        trc, static_cast<JSObject*>(this), &referent,
+        "Debugger.Object referent");
+    setPrivateUnbarriered(referent);
   }
 }
 
 static DebuggerObject* DebuggerObject_checkThis(JSContext* cx,
                                                 const CallArgs& args,
                                                 const char* fnname) {
   JSObject* thisobj = RequireObject(cx, args.thisv());
   if (!thisobj) {
--- a/js/src/debugger/Object.h
+++ b/js/src/debugger/Object.h
@@ -39,17 +39,17 @@ class DebuggerObject : public NativeObje
   static const Class class_;
 
   static NativeObject* initClass(JSContext* cx, Handle<GlobalObject*> global,
                                  HandleObject debugCtor);
   static DebuggerObject* create(JSContext* cx, HandleObject proto,
                                 HandleObject referent,
                                 HandleNativeObject debugger);
 
-  static void trace(JSTracer* trc, JSObject* obj);
+  void trace(JSTracer* trc);
 
   // Properties
   static MOZ_MUST_USE bool getClassName(JSContext* cx,
                                         HandleDebuggerObject object,
                                         MutableHandleString result);
   static MOZ_MUST_USE bool getParameterNames(
       JSContext* cx, HandleDebuggerObject object,
       MutableHandle<StringVector> result);
--- a/js/src/debugger/Script.cpp
+++ b/js/src/debugger/Script.cpp
@@ -52,54 +52,55 @@
 #include "vm/ObjectOperations-inl.h"  // for GetProperty
 #include "vm/Realm-inl.h"             // for AutoRealm::AutoRealm
 
 using namespace js;
 
 using mozilla::Maybe;
 using mozilla::Some;
 
-const ClassOps DebuggerScript::classOps_ = {nullptr, /* addProperty */
-                                            nullptr, /* delProperty */
-                                            nullptr, /* enumerate   */
-                                            nullptr, /* newEnumerate */
-                                            nullptr, /* resolve     */
-                                            nullptr, /* mayResolve  */
-                                            nullptr, /* finalize    */
-                                            nullptr, /* call        */
-                                            nullptr, /* hasInstance */
-                                            nullptr, /* construct   */
-                                            trace};
+const ClassOps DebuggerScript::classOps_ = {
+    nullptr,                         /* addProperty */
+    nullptr,                         /* delProperty */
+    nullptr,                         /* enumerate   */
+    nullptr,                         /* newEnumerate */
+    nullptr,                         /* resolve     */
+    nullptr,                         /* mayResolve  */
+    nullptr,                         /* finalize    */
+    nullptr,                         /* call        */
+    nullptr,                         /* hasInstance */
+    nullptr,                         /* construct   */
+    CallTraceMethod<DebuggerScript>, /* trace */
+};
 
 const Class DebuggerScript::class_ = {
     "Script", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS),
     &classOps_};
 
-/* static */
-void DebuggerScript::trace(JSTracer* trc, JSObject* obj) {
-  DebuggerScript* self = &obj->as<DebuggerScript>();
+void DebuggerScript::trace(JSTracer* trc) {
+  JSObject* upcast = this;
   // This comes from a private pointer, so no barrier needed.
-  gc::Cell* cell = self->getReferentCell();
+  gc::Cell* cell = getReferentCell();
   if (cell) {
     if (cell->is<JSScript>()) {
       JSScript* script = cell->as<JSScript>();
       TraceManuallyBarrieredCrossCompartmentEdge(
-          trc, self, &script, "Debugger.Script script referent");
-      self->setPrivateUnbarriered(script);
+          trc, upcast, &script, "Debugger.Script script referent");
+      setPrivateUnbarriered(script);
     } else if (cell->is<LazyScript>()) {
       LazyScript* lazyScript = cell->as<LazyScript>();
       TraceManuallyBarrieredCrossCompartmentEdge(
-          trc, self, &lazyScript, "Debugger.Script lazy script referent");
-      self->setPrivateUnbarriered(lazyScript);
+          trc, upcast, &lazyScript, "Debugger.Script lazy script referent");
+      setPrivateUnbarriered(lazyScript);
     } else {
       JSObject* wasm = cell->as<JSObject>();
       TraceManuallyBarrieredCrossCompartmentEdge(
-          trc, self, &wasm, "Debugger.Script wasm referent");
+          trc, upcast, &wasm, "Debugger.Script wasm referent");
       MOZ_ASSERT(wasm->is<WasmInstanceObject>());
-      self->setPrivateUnbarriered(wasm);
+      setPrivateUnbarriered(wasm);
     }
   }
 }
 
 /* static */
 NativeObject* DebuggerScript::initClass(JSContext* cx,
                                         Handle<GlobalObject*> global,
                                         HandleObject debugCtor) {
--- a/js/src/debugger/Script.h
+++ b/js/src/debugger/Script.h
@@ -39,17 +39,17 @@ class DebuggerScript : public NativeObje
   };
 
   static NativeObject* initClass(JSContext* cx, Handle<GlobalObject*> global,
                                  HandleObject debugCtor);
   static DebuggerScript* create(JSContext* cx, HandleObject proto,
                                 Handle<DebuggerScriptReferent> referent,
                                 HandleNativeObject debugger);
 
-  static void trace(JSTracer* trc, JSObject* obj);
+  void trace(JSTracer* trc);
 
   using ReferentVariant = DebuggerScriptReferent;
 
   inline gc::Cell* getReferentCell() const;
   inline js::BaseScript* getReferentScript() const;
   inline DebuggerScriptReferent getReferent() const;
 
   static DebuggerScript* check(JSContext* cx, HandleValue v,
--- a/js/src/debugger/Source.cpp
+++ b/js/src/debugger/Source.cpp
@@ -44,27 +44,29 @@ class GlobalObject;
 using namespace js;
 
 using JS::AutoStableStringChars;
 using mozilla::AsVariant;
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::Some;
 
-const ClassOps DebuggerSource::classOps_ = {nullptr, /* addProperty */
-                                            nullptr, /* delProperty */
-                                            nullptr, /* enumerate   */
-                                            nullptr, /* newEnumerate */
-                                            nullptr, /* resolve     */
-                                            nullptr, /* mayResolve  */
-                                            nullptr, /* finalize    */
-                                            nullptr, /* call        */
-                                            nullptr, /* hasInstance */
-                                            nullptr, /* construct   */
-                                            trace};
+const ClassOps DebuggerSource::classOps_ = {
+    nullptr,                         /* addProperty */
+    nullptr,                         /* delProperty */
+    nullptr,                         /* enumerate   */
+    nullptr,                         /* newEnumerate */
+    nullptr,                         /* resolve     */
+    nullptr,                         /* mayResolve  */
+    nullptr,                         /* finalize    */
+    nullptr,                         /* call        */
+    nullptr,                         /* hasInstance */
+    nullptr,                         /* construct   */
+    CallTraceMethod<DebuggerSource>, /* trace */
+};
 
 const Class DebuggerSource::class_ = {
     "Source", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS),
     &classOps_};
 
 /* static */
 NativeObject* DebuggerSource::initClass(JSContext* cx,
                                         Handle<GlobalObject*> global,
@@ -100,25 +102,24 @@ DebuggerSourceReferent DebuggerSource::g
     if (referent->is<ScriptSourceObject>()) {
       return AsVariant(&referent->as<ScriptSourceObject>());
     }
     return AsVariant(&referent->as<WasmInstanceObject>());
   }
   return AsVariant(static_cast<ScriptSourceObject*>(nullptr));
 }
 
-/* static */
-void DebuggerSource::trace(JSTracer* trc, JSObject* obj) {
-  DebuggerSource* sourceObj = &obj->as<DebuggerSource>();
+void DebuggerSource::trace(JSTracer* trc) {
   // There is a barrier on private pointers, so the Unbarriered marking
   // is okay.
-  if (JSObject* referent = sourceObj->getReferentRawObject()) {
-    TraceManuallyBarrieredCrossCompartmentEdge(trc, sourceObj, &referent,
-                                               "Debugger.Source referent");
-    sourceObj->setPrivateUnbarriered(referent);
+  if (JSObject* referent = getReferentRawObject()) {
+    TraceManuallyBarrieredCrossCompartmentEdge(
+        trc, static_cast<JSObject*>(this), &referent,
+        "Debugger.Source referent");
+    setPrivateUnbarriered(referent);
   }
 }
 
 /* static */
 bool DebuggerSource::construct(JSContext* cx, unsigned argc, Value* vp) {
   JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR,
                             "Debugger.Source");
   return false;
--- a/js/src/debugger/Source.h
+++ b/js/src/debugger/Source.h
@@ -31,17 +31,17 @@ class DebuggerSource : public NativeObje
   };
 
   static NativeObject* initClass(JSContext* cx, Handle<GlobalObject*> global,
                                  HandleObject debugCtor);
   static DebuggerSource* create(JSContext* cx, HandleObject proto,
                                 Handle<DebuggerSourceReferent> referent,
                                 HandleNativeObject debugger);
 
-  static void trace(JSTracer* trc, JSObject* obj);
+  void trace(JSTracer* trc);
 
   using ReferentVariant = DebuggerSourceReferent;
 
   NativeObject* getReferentRawObject() const;
   DebuggerSourceReferent getReferent() const;
 
   static DebuggerSource* check(JSContext* cx, HandleValue v,
                                const char* fnname);
--- a/js/src/vm/JSObject.h
+++ b/js/src/vm/JSObject.h
@@ -1113,11 +1113,24 @@ extern bool GetObjectFromIncumbentGlobal
 inline bool IsObjectValueInCompartment(const Value& v, JS::Compartment* comp) {
   if (!v.isObject()) {
     return true;
   }
   return v.toObject().compartment() == comp;
 }
 #endif
 
+/*
+ * A generic trace hook that calls the object's 'trace' method.
+ *
+ * If you are introducing a new JSObject subclass, MyObject, that needs a custom
+ * js::ClassOps::trace function, it's often helpful to write `trace` as a
+ * non-static member function, since `this` will the correct type. In this case,
+ * you can use `CallTraceMethod<MyObject>` as your js::ClassOps::trace value.
+ */
+template <typename ObjectSubclass>
+void CallTraceMethod(JSTracer* trc, JSObject* obj) {
+  obj->as<ObjectSubclass>().trace(trc);
+}
+
 } /* namespace js */
 
 #endif /* vm_JSObject_h */