Differential D3094 Diff 14018 ps/trunk/libraries/source/spidermonkey/include-win32-debug/js/UbiNode.h
Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/libraries/source/spidermonkey/include-win32-debug/js/UbiNode.h
Show All 9 Lines | |||||
#include "mozilla/Alignment.h" | #include "mozilla/Alignment.h" | ||||
#include "mozilla/Assertions.h" | #include "mozilla/Assertions.h" | ||||
#include "mozilla/Attributes.h" | #include "mozilla/Attributes.h" | ||||
#include "mozilla/Maybe.h" | #include "mozilla/Maybe.h" | ||||
#include "mozilla/MemoryReporting.h" | #include "mozilla/MemoryReporting.h" | ||||
#include "mozilla/Move.h" | #include "mozilla/Move.h" | ||||
#include "mozilla/RangedPtr.h" | #include "mozilla/RangedPtr.h" | ||||
#include "mozilla/TypeTraits.h" | #include "mozilla/TypeTraits.h" | ||||
#include "mozilla/UniquePtr.h" | |||||
#include "mozilla/Variant.h" | #include "mozilla/Variant.h" | ||||
#include "jspubtd.h" | #include "jspubtd.h" | ||||
#include "js/GCAPI.h" | #include "js/GCAPI.h" | ||||
#include "js/HashTable.h" | #include "js/HashTable.h" | ||||
#include "js/RootingAPI.h" | #include "js/RootingAPI.h" | ||||
#include "js/TracingAPI.h" | #include "js/TracingAPI.h" | ||||
#include "js/TypeDecls.h" | #include "js/TypeDecls.h" | ||||
#include "js/UniquePtr.h" | |||||
#include "js/Value.h" | #include "js/Value.h" | ||||
#include "js/Vector.h" | #include "js/Vector.h" | ||||
// JS::ubi::Node | // JS::ubi::Node | ||||
// | // | ||||
// JS::ubi::Node is a pointer-like type designed for internal use by heap | // JS::ubi::Node is a pointer-like type designed for internal use by heap | ||||
// analysis tools. A ubi::Node can refer to: | // analysis tools. A ubi::Node can refer to: | ||||
// | // | ||||
Show All 30 Lines | |||||
// ubi::Node values require no supporting data structures, making them | // ubi::Node values require no supporting data structures, making them | ||||
// feasible for use in memory-constrained devices --- ideally, the memory | // feasible for use in memory-constrained devices --- ideally, the memory | ||||
// requirements of the algorithm which uses them will be the limiting factor, | // requirements of the algorithm which uses them will be the limiting factor, | ||||
// not the demands of ubi::Node itself. | // not the demands of ubi::Node itself. | ||||
// | // | ||||
// One can construct a ubi::Node value given a pointer to a type that ubi::Node | // One can construct a ubi::Node value given a pointer to a type that ubi::Node | ||||
// supports. In the other direction, one can convert a ubi::Node back to a | // supports. In the other direction, one can convert a ubi::Node back to a | ||||
// pointer; these downcasts are checked dynamically. In particular, one can | // pointer; these downcasts are checked dynamically. In particular, one can | ||||
// convert a 'JSRuntime*' to a ubi::Node, yielding a node with an outgoing edge | // convert a 'JSContext*' to a ubi::Node, yielding a node with an outgoing edge | ||||
// for every root registered with the runtime; starting from this, one can walk | // for every root registered with the runtime; starting from this, one can walk | ||||
// the entire heap. (Of course, one could also start traversal at any other kind | // the entire heap. (Of course, one could also start traversal at any other kind | ||||
// of type to which one has a pointer.) | // of type to which one has a pointer.) | ||||
// | // | ||||
// | // | ||||
// Extending ubi::Node To Handle Your Embedding's Types | // Extending ubi::Node To Handle Your Embedding's Types | ||||
// | // | ||||
// To add support for a new ubi::Node referent type R, you must define a | // To add support for a new ubi::Node referent type R, you must define a | ||||
▲ Show 20 Lines • Show All 86 Lines • ▼ Show 20 Lines | |||||
class Edge; | class Edge; | ||||
class EdgeRange; | class EdgeRange; | ||||
class StackFrame; | class StackFrame; | ||||
} // namespace ubi | } // namespace ubi | ||||
} // namespace JS | } // namespace JS | ||||
namespace mozilla { | |||||
template<> | |||||
class DefaultDelete<JS::ubi::EdgeRange> : public JS::DeletePolicy<JS::ubi::EdgeRange> { }; | |||||
template<> | |||||
class DefaultDelete<JS::ubi::StackFrame> : public JS::DeletePolicy<JS::ubi::StackFrame> { }; | |||||
} // namespace mozilla | |||||
namespace JS { | namespace JS { | ||||
namespace ubi { | namespace ubi { | ||||
using mozilla::Forward; | using mozilla::Forward; | ||||
using mozilla::Maybe; | using mozilla::Maybe; | ||||
using mozilla::Move; | using mozilla::Move; | ||||
using mozilla::RangedPtr; | using mozilla::RangedPtr; | ||||
using mozilla::UniquePtr; | |||||
using mozilla::Variant; | using mozilla::Variant; | ||||
template <typename T> | |||||
using Vector = mozilla::Vector<T, 0, js::SystemAllocPolicy>; | |||||
/*** ubi::StackFrame ******************************************************************************/ | /*** ubi::StackFrame ******************************************************************************/ | ||||
// Concrete JS::ubi::StackFrame instances backed by a live SavedFrame object | // Concrete JS::ubi::StackFrame instances backed by a live SavedFrame object | ||||
// store their strings as JSAtom*, while deserialized stack frames from offline | // store their strings as JSAtom*, while deserialized stack frames from offline | ||||
// heap snapshots store their strings as const char16_t*. In order to provide | // heap snapshots store their strings as const char16_t*. In order to provide | ||||
// zero-cost accessors to these strings in a single interface that works with | // zero-cost accessors to these strings in a single interface that works with | ||||
// both cases, we use this variant type. | // both cases, we use this variant type. | ||||
class AtomOrTwoByteChars : public Variant<JSAtom*, const char16_t*> { | class JS_PUBLIC_API(AtomOrTwoByteChars) : public Variant<JSAtom*, const char16_t*> { | ||||
using Base = Variant<JSAtom*, const char16_t*>; | using Base = Variant<JSAtom*, const char16_t*>; | ||||
public: | public: | ||||
template<typename T> | template<typename T> | ||||
MOZ_IMPLICIT AtomOrTwoByteChars(T&& rhs) : Base(Forward<T>(rhs)) { } | MOZ_IMPLICIT AtomOrTwoByteChars(T&& rhs) : Base(Forward<T>(rhs)) { } | ||||
template<typename T> | template<typename T> | ||||
AtomOrTwoByteChars& operator=(T&& rhs) { | AtomOrTwoByteChars& operator=(T&& rhs) { | ||||
▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | public: | ||||
virtual AtomOrTwoByteChars functionDisplayName() const = 0; | virtual AtomOrTwoByteChars functionDisplayName() const = 0; | ||||
// Returns true if this frame's function is system JavaScript running with | // Returns true if this frame's function is system JavaScript running with | ||||
// trusted principals, false otherwise. | // trusted principals, false otherwise. | ||||
virtual bool isSystem() const = 0; | virtual bool isSystem() const = 0; | ||||
// Return true if this frame's function is a self-hosted JavaScript builtin, | // Return true if this frame's function is a self-hosted JavaScript builtin, | ||||
// false otherwise. | // false otherwise. | ||||
virtual bool isSelfHosted() const = 0; | virtual bool isSelfHosted(JSContext* cx) const = 0; | ||||
// Construct a SavedFrame stack for the stack starting with this frame and | // Construct a SavedFrame stack for the stack starting with this frame and | ||||
// containing all of its parents. The SavedFrame objects will be placed into | // containing all of its parents. The SavedFrame objects will be placed into | ||||
// cx's current compartment. | // cx's current compartment. | ||||
// | // | ||||
// Note that the process of | // Note that the process of | ||||
// | // | ||||
// SavedFrame | // SavedFrame | ||||
Show All 10 Lines | public: | ||||
// V | // V | ||||
// SavedFrame | // SavedFrame | ||||
// | // | ||||
// is lossy because we cannot serialize and deserialize the SavedFrame's | // is lossy because we cannot serialize and deserialize the SavedFrame's | ||||
// principals in the offline heap snapshot, so JS::ubi::StackFrame | // principals in the offline heap snapshot, so JS::ubi::StackFrame | ||||
// simplifies the principals check into the boolean isSystem() state. This | // simplifies the principals check into the boolean isSystem() state. This | ||||
// is fine because we only expose JS::ubi::Stack to devtools and chrome | // is fine because we only expose JS::ubi::Stack to devtools and chrome | ||||
// code, and not to the web platform. | // code, and not to the web platform. | ||||
virtual bool constructSavedFrameStack(JSContext* cx, | virtual MOZ_MUST_USE bool constructSavedFrameStack(JSContext* cx, | ||||
MutableHandleObject outSavedFrameStack) const = 0; | MutableHandleObject outSavedFrameStack) | ||||
const = 0; | |||||
// Trace the concrete implementation of JS::ubi::StackFrame. | // Trace the concrete implementation of JS::ubi::StackFrame. | ||||
virtual void trace(JSTracer* trc) = 0; | virtual void trace(JSTracer* trc) = 0; | ||||
}; | }; | ||||
// A traits template with a specialization for each backing type that implements | // A traits template with a specialization for each backing type that implements | ||||
// the ubi::BaseStackFrame interface. Each specialization must be the a subclass | // the ubi::BaseStackFrame interface. Each specialization must be the a subclass | ||||
// of ubi::BaseStackFrame. | // of ubi::BaseStackFrame. | ||||
template<typename T> class ConcreteStackFrame; | template<typename T> class ConcreteStackFrame; | ||||
// A JS::ubi::StackFrame represents a frame in a recorded stack. It can be | // A JS::ubi::StackFrame represents a frame in a recorded stack. It can be | ||||
// backed either by a live SavedFrame object or by a structure deserialized from | // backed either by a live SavedFrame object or by a structure deserialized from | ||||
// an offline heap snapshot. | // an offline heap snapshot. | ||||
// | // | ||||
// It is a value type that may be memcpy'd hither and thither without worrying | // It is a value type that may be memcpy'd hither and thither without worrying | ||||
// about constructors or destructors, similar to POD types. | // about constructors or destructors, similar to POD types. | ||||
// | // | ||||
// Its lifetime is the same as the lifetime of the graph that is being analyzed | // Its lifetime is the same as the lifetime of the graph that is being analyzed | ||||
// by the JS::ubi::Node that the JS::ubi::StackFrame came from. That is, if the | // by the JS::ubi::Node that the JS::ubi::StackFrame came from. That is, if the | ||||
// graph being analyzed is the live heap graph, the JS::ubi::StackFrame is only | // graph being analyzed is the live heap graph, the JS::ubi::StackFrame is only | ||||
// valid within the scope of an AutoCheckCannotGC; if the graph being analyzed | // valid within the scope of an AutoCheckCannotGC; if the graph being analyzed | ||||
// is an offline heap snapshot, the JS::ubi::StackFrame is valid as long as the | // is an offline heap snapshot, the JS::ubi::StackFrame is valid as long as the | ||||
// offline heap snapshot is alive. | // offline heap snapshot is alive. | ||||
class StackFrame : public JS::Traceable { | class StackFrame { | ||||
// Storage in which we allocate BaseStackFrame subclasses. | // Storage in which we allocate BaseStackFrame subclasses. | ||||
mozilla::AlignedStorage2<BaseStackFrame> storage; | mozilla::AlignedStorage2<BaseStackFrame> storage; | ||||
BaseStackFrame* base() { return storage.addr(); } | BaseStackFrame* base() { return storage.addr(); } | ||||
const BaseStackFrame* base() const { return storage.addr(); } | const BaseStackFrame* base() const { return storage.addr(); } | ||||
template<typename T> | template<typename T> | ||||
void construct(T* ptr) { | void construct(T* ptr) { | ||||
▲ Show 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | public: | ||||
// buffer. Copy no more than |length| characters. The result is *not* null | // buffer. Copy no more than |length| characters. The result is *not* null | ||||
// terminated. Returns how many characters were written into the buffer. | // terminated. Returns how many characters were written into the buffer. | ||||
size_t functionDisplayName(RangedPtr<char16_t> destination, size_t length) const; | size_t functionDisplayName(RangedPtr<char16_t> destination, size_t length) const; | ||||
// Get the size of the respective strings. 0 is returned for null strings. | // Get the size of the respective strings. 0 is returned for null strings. | ||||
size_t sourceLength(); | size_t sourceLength(); | ||||
size_t functionDisplayNameLength(); | size_t functionDisplayNameLength(); | ||||
// JS::Traceable implementation just forwards to our virtual trace method. | |||||
static void trace(StackFrame* frame, JSTracer* trc) { | |||||
if (frame) | |||||
frame->trace(trc); | |||||
} | |||||
// Methods that forward to virtual calls through BaseStackFrame. | // Methods that forward to virtual calls through BaseStackFrame. | ||||
void trace(JSTracer* trc) { base()->trace(trc); } | void trace(JSTracer* trc) { base()->trace(trc); } | ||||
uint64_t identifier() const { | uint64_t identifier() const { | ||||
auto id = base()->identifier(); | auto id = base()->identifier(); | ||||
MOZ_ASSERT(JS::Value::isNumberRepresentable(id)); | MOZ_ASSERT(JS::Value::isNumberRepresentable(id)); | ||||
return id; | return id; | ||||
} | } | ||||
uint32_t line() const { return base()->line(); } | uint32_t line() const { return base()->line(); } | ||||
uint32_t column() const { return base()->column(); } | uint32_t column() const { return base()->column(); } | ||||
AtomOrTwoByteChars source() const { return base()->source(); } | AtomOrTwoByteChars source() const { return base()->source(); } | ||||
AtomOrTwoByteChars functionDisplayName() const { return base()->functionDisplayName(); } | AtomOrTwoByteChars functionDisplayName() const { return base()->functionDisplayName(); } | ||||
StackFrame parent() const { return base()->parent(); } | StackFrame parent() const { return base()->parent(); } | ||||
bool isSystem() const { return base()->isSystem(); } | bool isSystem() const { return base()->isSystem(); } | ||||
bool isSelfHosted() const { return base()->isSelfHosted(); } | bool isSelfHosted(JSContext* cx) const { return base()->isSelfHosted(cx); } | ||||
bool constructSavedFrameStack(JSContext* cx, | MOZ_MUST_USE bool constructSavedFrameStack(JSContext* cx, | ||||
MutableHandleObject outSavedFrameStack) const { | MutableHandleObject outSavedFrameStack) const { | ||||
return base()->constructSavedFrameStack(cx, outSavedFrameStack); | return base()->constructSavedFrameStack(cx, outSavedFrameStack); | ||||
} | } | ||||
struct HashPolicy { | struct HashPolicy { | ||||
using Lookup = JS::ubi::StackFrame; | using Lookup = JS::ubi::StackFrame; | ||||
static js::HashNumber hash(const Lookup& lookup) { | static js::HashNumber hash(const Lookup& lookup) { | ||||
return lookup.identifier(); | return lookup.identifier(); | ||||
Show All 15 Lines | |||||
class ConcreteStackFrame<void> : public BaseStackFrame { | class ConcreteStackFrame<void> : public BaseStackFrame { | ||||
explicit ConcreteStackFrame(void* ptr) : BaseStackFrame(ptr) { } | explicit ConcreteStackFrame(void* ptr) : BaseStackFrame(ptr) { } | ||||
public: | public: | ||||
static void construct(void* storage, void*) { new (storage) ConcreteStackFrame(nullptr); } | static void construct(void* storage, void*) { new (storage) ConcreteStackFrame(nullptr); } | ||||
uint64_t identifier() const override { return 0; } | uint64_t identifier() const override { return 0; } | ||||
void trace(JSTracer* trc) override { } | void trace(JSTracer* trc) override { } | ||||
bool constructSavedFrameStack(JSContext* cx, MutableHandleObject out) const override { | MOZ_MUST_USE bool constructSavedFrameStack(JSContext* cx, MutableHandleObject out) | ||||
const override | |||||
{ | |||||
out.set(nullptr); | out.set(nullptr); | ||||
return true; | return true; | ||||
} | } | ||||
uint32_t line() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | uint32_t line() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | ||||
uint32_t column() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | uint32_t column() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | ||||
AtomOrTwoByteChars source() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | AtomOrTwoByteChars source() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | ||||
AtomOrTwoByteChars functionDisplayName() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | AtomOrTwoByteChars functionDisplayName() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | ||||
StackFrame parent() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | StackFrame parent() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | ||||
bool isSystem() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | bool isSystem() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | ||||
bool isSelfHosted() const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | bool isSelfHosted(JSContext* cx) const override { MOZ_CRASH("null JS::ubi::StackFrame"); } | ||||
}; | }; | ||||
bool ConstructSavedFrameStackSlow(JSContext* cx, JS::ubi::StackFrame& frame, | MOZ_MUST_USE JS_PUBLIC_API(bool) | ||||
ConstructSavedFrameStackSlow(JSContext* cx, | |||||
JS::ubi::StackFrame& frame, | |||||
MutableHandleObject outSavedFrameStack); | MutableHandleObject outSavedFrameStack); | ||||
/*** ubi::Node ************************************************************************************/ | /*** ubi::Node ************************************************************************************/ | ||||
// A concrete node specialization can claim its referent is a member of a | // A concrete node specialization can claim its referent is a member of a | ||||
// particular "coarse type" which is less specific than the actual | // particular "coarse type" which is less specific than the actual | ||||
// implementation type but generally more palatable for web developers. For | // implementation type but generally more palatable for web developers. For | ||||
// example, JitCode can be considered to have a coarse type of "Script". This is | // example, JitCode can be considered to have a coarse type of "Script". This is | ||||
Show All 34 Lines | |||||
Uint32ToCoarseType(uint32_t n) | Uint32ToCoarseType(uint32_t n) | ||||
{ | { | ||||
MOZ_ASSERT(Uint32IsValidCoarseType(n)); | MOZ_ASSERT(Uint32IsValidCoarseType(n)); | ||||
return static_cast<CoarseType>(n); | return static_cast<CoarseType>(n); | ||||
} | } | ||||
// The base class implemented by each ubi::Node referent type. Subclasses must | // The base class implemented by each ubi::Node referent type. Subclasses must | ||||
// not add data members to this class. | // not add data members to this class. | ||||
class Base { | class JS_PUBLIC_API(Base) { | ||||
friend class Node; | friend class Node; | ||||
// For performance's sake, we'd prefer to avoid a virtual destructor; and | // For performance's sake, we'd prefer to avoid a virtual destructor; and | ||||
// an empty constructor seems consistent with the 'lightweight value type' | // an empty constructor seems consistent with the 'lightweight value type' | ||||
// visible behavior we're trying to achieve. But if the destructor isn't | // visible behavior we're trying to achieve. But if the destructor isn't | ||||
// virtual, and a subclass overrides it, the subclass's destructor will be | // virtual, and a subclass overrides it, the subclass's destructor will be | ||||
// ignored. Is there a way to make the compiler catch that error? | // ignored. Is there a way to make the compiler catch that error? | ||||
Show All 33 Lines | public: | ||||
// opposed to something from a deserialized core dump. Returns false, | // opposed to something from a deserialized core dump. Returns false, | ||||
// otherwise. | // otherwise. | ||||
virtual bool isLive() const { return true; }; | virtual bool isLive() const { return true; }; | ||||
// Return the coarse-grained type-of-thing that this node represents. | // Return the coarse-grained type-of-thing that this node represents. | ||||
virtual CoarseType coarseType() const { return CoarseType::Other; } | virtual CoarseType coarseType() const { return CoarseType::Other; } | ||||
// Return a human-readable name for the referent's type. The result should | // Return a human-readable name for the referent's type. The result should | ||||
// be statically allocated. (You can use MOZ_UTF16("strings") for this.) | // be statically allocated. (You can use u"strings" for this.) | ||||
// | // | ||||
// This must always return Concrete<T>::concreteTypeName; we use that | // This must always return Concrete<T>::concreteTypeName; we use that | ||||
// pointer as a tag for this particular referent type. | // pointer as a tag for this particular referent type. | ||||
virtual const char16_t* typeName() const = 0; | virtual const char16_t* typeName() const = 0; | ||||
// Return the size of this node, in bytes. Include any structures that this | // Return the size of this node, in bytes. Include any structures that this | ||||
// node owns exclusively that are not exposed as their own ubi::Nodes. | // node owns exclusively that are not exposed as their own ubi::Nodes. | ||||
// |mallocSizeOf| should be a malloc block sizing function; see | // |mallocSizeOf| should be a malloc block sizing function; see | ||||
// |mfbt/MemoryReporting.h|. | // |mfbt/MemoryReporting.h|. | ||||
// | |||||
// Because we can use |JS::ubi::Node|s backed by a snapshot that was taken | |||||
// on a 64-bit platform when we are currently on a 32-bit platform, we | |||||
// cannot rely on |size_t| for node sizes. Instead, |Size| is uint64_t on | |||||
// all platforms. | |||||
using Size = uint64_t; | using Size = uint64_t; | ||||
virtual Size size(mozilla::MallocSizeOf mallocSizeof) const { return 1; } | virtual Size size(mozilla::MallocSizeOf mallocSizeof) const { return 1; } | ||||
// Return an EdgeRange that initially contains all the referent's outgoing | // Return an EdgeRange that initially contains all the referent's outgoing | ||||
// edges. The caller takes ownership of the EdgeRange. | // edges. The caller takes ownership of the EdgeRange. | ||||
// | // | ||||
// If wantNames is true, compute names for edges. Doing so can be expensive | // If wantNames is true, compute names for edges. Doing so can be expensive | ||||
// in time and memory. | // in time and memory. | ||||
virtual UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const = 0; | virtual js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const = 0; | ||||
// Return the Zone to which this node's referent belongs, or nullptr if the | // Return the Zone to which this node's referent belongs, or nullptr if the | ||||
// referent is not of a type allocated in SpiderMonkey Zones. | // referent is not of a type allocated in SpiderMonkey Zones. | ||||
virtual JS::Zone* zone() const { return nullptr; } | virtual JS::Zone* zone() const { return nullptr; } | ||||
// Return the compartment for this node. Some ubi::Node referents are not | // Return the compartment for this node. Some ubi::Node referents are not | ||||
// associated with JSCompartments, such as JSStrings (which are associated | // associated with JSCompartments, such as JSStrings (which are associated | ||||
// with Zones). When the referent is not associated with a compartment, | // with Zones). When the referent is not associated with a compartment, | ||||
Show All 19 Lines | public: | ||||
// Return the object's [[Class]]'s name. | // Return the object's [[Class]]'s name. | ||||
virtual const char* jsObjectClassName() const { return nullptr; } | virtual const char* jsObjectClassName() const { return nullptr; } | ||||
// If this object was constructed with `new` and we have the data available, | // If this object was constructed with `new` and we have the data available, | ||||
// place the contructor function's display name in the out parameter. | // place the contructor function's display name in the out parameter. | ||||
// Otherwise, place nullptr in the out parameter. Caller maintains ownership | // Otherwise, place nullptr in the out parameter. Caller maintains ownership | ||||
// of the out parameter. True is returned on success, false is returned on | // of the out parameter. True is returned on success, false is returned on | ||||
// OOM. | // OOM. | ||||
virtual bool jsObjectConstructorName(JSContext* cx, | virtual MOZ_MUST_USE bool jsObjectConstructorName(JSContext* cx, UniqueTwoByteChars& outName) | ||||
UniquePtr<char16_t[], JS::FreePolicy>& outName) const { | const | ||||
{ | |||||
outName.reset(nullptr); | outName.reset(nullptr); | ||||
return true; | return true; | ||||
} | } | ||||
// Methods for CoarseType::Script referents | // Methods for CoarseType::Script referents | ||||
// Return the script's source's filename if available. If unavailable, | // Return the script's source's filename if available. If unavailable, | ||||
// return nullptr. | // return nullptr. | ||||
virtual const char* scriptFilename() const { return nullptr; } | virtual const char* scriptFilename() const { return nullptr; } | ||||
private: | private: | ||||
Base(const Base& rhs) = delete; | Base(const Base& rhs) = delete; | ||||
Base& operator=(const Base& rhs) = delete; | Base& operator=(const Base& rhs) = delete; | ||||
}; | }; | ||||
// A traits template with a specialization for each referent type that | // A traits template with a specialization for each referent type that | ||||
// ubi::Node supports. The specialization must be the concrete subclass of | // ubi::Node supports. The specialization must be the concrete subclass of Base | ||||
// Base that represents a pointer to the referent type. It must also | // that represents a pointer to the referent type. It must include these | ||||
// include the members described here. | // members: | ||||
// | |||||
// // The specific char16_t array returned by Concrete<T>::typeName(). | |||||
// static const char16_t concreteTypeName[]; | |||||
// | |||||
// // Construct an instance of this concrete class in |storage| referring | |||||
// // to |referent|. Implementations typically use a placement 'new'. | |||||
// // | |||||
// // In some cases, |referent| will contain dynamic type information that | |||||
// // identifies it a some more specific subclass of |Referent|. For | |||||
// // example, when |Referent| is |JSObject|, then |referent->getClass()| | |||||
// // could tell us that it's actually a JSFunction. Similarly, if | |||||
// // |Referent| is |nsISupports|, we would like a ubi::Node that knows its | |||||
// // final implementation type. | |||||
// // | |||||
// // So we delegate the actual construction to this specialization, which | |||||
// // knows Referent's details. | |||||
// static void construct(void* storage, Referent* referent); | |||||
template<typename Referent> | template<typename Referent> | ||||
struct Concrete { | class Concrete; | ||||
// The specific char16_t array returned by Concrete<T>::typeName. | |||||
static const char16_t concreteTypeName[]; | |||||
// Construct an instance of this concrete class in |storage| referring | |||||
// to |referent|. Implementations typically use a placement 'new'. | |||||
// | |||||
// In some cases, |referent| will contain dynamic type information that | |||||
// identifies it a some more specific subclass of |Referent|. For example, | |||||
// when |Referent| is |JSObject|, then |referent->getClass()| could tell us | |||||
// that it's actually a JSFunction. Similarly, if |Referent| is | |||||
// |nsISupports|, we would like a ubi::Node that knows its final | |||||
// implementation type. | |||||
// | |||||
// So, we delegate the actual construction to this specialization, which | |||||
// knows Referent's details. | |||||
static void construct(void* storage, Referent* referent); | |||||
}; | |||||
// A container for a Base instance; all members simply forward to the contained | // A container for a Base instance; all members simply forward to the contained | ||||
// instance. This container allows us to pass ubi::Node instances by value. | // instance. This container allows us to pass ubi::Node instances by value. | ||||
class Node { | class Node { | ||||
// Storage in which we allocate Base subclasses. | // Storage in which we allocate Base subclasses. | ||||
mozilla::AlignedStorage2<Base> storage; | mozilla::AlignedStorage2<Base> storage; | ||||
Base* base() { return storage.addr(); } | Base* base() { return storage.addr(); } | ||||
const Base* base() const { return storage.addr(); } | const Base* base() const { return storage.addr(); } | ||||
template<typename T> | template<typename T> | ||||
void construct(T* ptr) { | void construct(T* ptr) { | ||||
static_assert(sizeof(Concrete<T>) == sizeof(*base()), | static_assert(sizeof(Concrete<T>) == sizeof(*base()), | ||||
"ubi::Base specializations must be the same size as ubi::Base"); | "ubi::Base specializations must be the same size as ubi::Base"); | ||||
static_assert(mozilla::IsBaseOf<Base, Concrete<T>>::value, | |||||
"ubi::Concrete<T> must inherit from ubi::Base"); | |||||
Concrete<T>::construct(base(), ptr); | Concrete<T>::construct(base(), ptr); | ||||
} | } | ||||
struct ConstructFunctor; | struct ConstructFunctor; | ||||
public: | public: | ||||
Node() { construct<void>(nullptr); } | Node() { construct<void>(nullptr); } | ||||
template<typename T> | template<typename T> | ||||
▲ Show 20 Lines • Show All 78 Lines • ▼ Show 20 Lines | public: | ||||
// not all!) JSObjects can be exposed. | // not all!) JSObjects can be exposed. | ||||
JS::Value exposeToJS() const; | JS::Value exposeToJS() const; | ||||
CoarseType coarseType() const { return base()->coarseType(); } | CoarseType coarseType() const { return base()->coarseType(); } | ||||
const char16_t* typeName() const { return base()->typeName(); } | const char16_t* typeName() const { return base()->typeName(); } | ||||
JS::Zone* zone() const { return base()->zone(); } | JS::Zone* zone() const { return base()->zone(); } | ||||
JSCompartment* compartment() const { return base()->compartment(); } | JSCompartment* compartment() const { return base()->compartment(); } | ||||
const char* jsObjectClassName() const { return base()->jsObjectClassName(); } | const char* jsObjectClassName() const { return base()->jsObjectClassName(); } | ||||
bool jsObjectConstructorName(JSContext* cx, | MOZ_MUST_USE bool jsObjectConstructorName(JSContext* cx, UniqueTwoByteChars& outName) const { | ||||
UniquePtr<char16_t[], JS::FreePolicy>& outName) const { | |||||
return base()->jsObjectConstructorName(cx, outName); | return base()->jsObjectConstructorName(cx, outName); | ||||
} | } | ||||
const char* scriptFilename() const { return base()->scriptFilename(); } | const char* scriptFilename() const { return base()->scriptFilename(); } | ||||
using Size = Base::Size; | using Size = Base::Size; | ||||
Size size(mozilla::MallocSizeOf mallocSizeof) const { | Size size(mozilla::MallocSizeOf mallocSizeof) const { | ||||
auto size = base()->size(mallocSizeof); | auto size = base()->size(mallocSizeof); | ||||
MOZ_ASSERT(size > 0, | MOZ_ASSERT(size > 0, | ||||
"C++ does not have zero-sized types! Choose 1 if you just need a " | "C++ does not have zero-sized types! Choose 1 if you just need a " | ||||
"conservative default."); | "conservative default."); | ||||
return size; | return size; | ||||
} | } | ||||
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames = true) const { | js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames = true) const { | ||||
return base()->edges(rt, wantNames); | return base()->edges(cx, wantNames); | ||||
} | } | ||||
bool hasAllocationStack() const { return base()->hasAllocationStack(); } | bool hasAllocationStack() const { return base()->hasAllocationStack(); } | ||||
StackFrame allocationStack() const { | StackFrame allocationStack() const { | ||||
return base()->allocationStack(); | return base()->allocationStack(); | ||||
} | } | ||||
using Id = Base::Id; | using Id = Base::Id; | ||||
Show All 13 Lines | class HashPolicy { | ||||
typedef Node Lookup; | typedef Node Lookup; | ||||
static js::HashNumber hash(const Lookup& l) { return PtrHash::hash(l.base()->ptr); } | static js::HashNumber hash(const Lookup& l) { return PtrHash::hash(l.base()->ptr); } | ||||
static bool match(const Node& k, const Lookup& l) { return k == l; } | static bool match(const Node& k, const Lookup& l) { return k == l; } | ||||
static void rekey(Node& k, const Node& newKey) { k = newKey; } | static void rekey(Node& k, const Node& newKey) { k = newKey; } | ||||
}; | }; | ||||
}; | }; | ||||
using NodeSet = js::HashSet<Node, js::DefaultHasher<Node>, js::SystemAllocPolicy>; | |||||
using NodeSetPtr = mozilla::UniquePtr<NodeSet, JS::DeletePolicy<NodeSet>>; | |||||
/*** Edge and EdgeRange ***************************************************************************/ | /*** Edge and EdgeRange ***************************************************************************/ | ||||
using EdgeName = UniquePtr<const char16_t[], JS::FreePolicy>; | using EdgeName = UniqueTwoByteChars; | ||||
// An outgoing edge to a referent node. | // An outgoing edge to a referent node. | ||||
class Edge { | class Edge { | ||||
public: | public: | ||||
Edge() : name(nullptr), referent() { } | Edge() : name(nullptr), referent() { } | ||||
// Construct an initialized Edge, taking ownership of |name|. | // Construct an initialized Edge, taking ownership of |name|. | ||||
Edge(char16_t* name, const Node& referent) | Edge(char16_t* name, const Node& referent) | ||||
Show All 16 Lines | public: | ||||
Edge(const Edge&) = delete; | Edge(const Edge&) = delete; | ||||
Edge& operator=(const Edge&) = delete; | Edge& operator=(const Edge&) = delete; | ||||
// This edge's name. This may be nullptr, if Node::edges was called with | // This edge's name. This may be nullptr, if Node::edges was called with | ||||
// false as the wantNames parameter. | // false as the wantNames parameter. | ||||
// | // | ||||
// The storage is owned by this Edge, and will be freed when this Edge is | // The storage is owned by this Edge, and will be freed when this Edge is | ||||
// destructed. | // destructed. You may take ownership of the name by `mozilla::Move`ing it | ||||
// out of the edge; it is just a UniquePtr. | |||||
// | // | ||||
// (In real life we'll want a better representation for names, to avoid | // (In real life we'll want a better representation for names, to avoid | ||||
// creating tons of strings when the names follow a pattern; and we'll need | // creating tons of strings when the names follow a pattern; and we'll need | ||||
// to think about lifetimes carefully to ensure traversal stays cheap.) | // to think about lifetimes carefully to ensure traversal stays cheap.) | ||||
EdgeName name; | EdgeName name; | ||||
// This edge's referent. | // This edge's referent. | ||||
Node referent; | Node referent; | ||||
▲ Show 20 Lines • Show All 79 Lines • ▼ Show 20 Lines | |||||
// has been created, GC must not occur, as the referent ubi::Nodes are not | // has been created, GC must not occur, as the referent ubi::Nodes are not | ||||
// stable across GC. The init calls emplace on |noGC|'s AutoCheckCannotGC, whose | // stable across GC. The init calls emplace on |noGC|'s AutoCheckCannotGC, whose | ||||
// lifetime must extend at least as long as the RootList itself. | // lifetime must extend at least as long as the RootList itself. | ||||
// | // | ||||
// Example usage: | // Example usage: | ||||
// | // | ||||
// { | // { | ||||
// mozilla::Maybe<JS::AutoCheckCannotGC> maybeNoGC; | // mozilla::Maybe<JS::AutoCheckCannotGC> maybeNoGC; | ||||
// JS::ubi::RootList rootList(rt, maybeNoGC); | // JS::ubi::RootList rootList(cx, maybeNoGC); | ||||
// if (!rootList.init()) | // if (!rootList.init()) | ||||
// return false; | // return false; | ||||
// | // | ||||
// // The AutoCheckCannotGC is guaranteed to exist if init returned true. | // // The AutoCheckCannotGC is guaranteed to exist if init returned true. | ||||
// MOZ_ASSERT(maybeNoGC.isSome()); | // MOZ_ASSERT(maybeNoGC.isSome()); | ||||
// | // | ||||
// JS::ubi::Node root(&rootList); | // JS::ubi::Node root(&rootList); | ||||
// | // | ||||
// ... | // ... | ||||
// } | // } | ||||
class MOZ_STACK_CLASS RootList { | class MOZ_STACK_CLASS JS_PUBLIC_API(RootList) { | ||||
Maybe<AutoCheckCannotGC>& noGC; | Maybe<AutoCheckCannotGC>& noGC; | ||||
public: | public: | ||||
JSRuntime* rt; | JSContext* cx; | ||||
EdgeVector edges; | EdgeVector edges; | ||||
bool wantNames; | bool wantNames; | ||||
RootList(JSRuntime* rt, Maybe<AutoCheckCannotGC>& noGC, bool wantNames = false); | RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames = false); | ||||
// Find all GC roots. | // Find all GC roots. | ||||
bool init(); | MOZ_MUST_USE bool init(); | ||||
// Find only GC roots in the provided set of |Zone|s. | // Find only GC roots in the provided set of |JSCompartment|s. | ||||
bool init(ZoneSet& debuggees); | MOZ_MUST_USE bool init(CompartmentSet& debuggees); | ||||
// Find only GC roots in the given Debugger object's set of debuggee zones. | // Find only GC roots in the given Debugger object's set of debuggee | ||||
bool init(HandleObject debuggees); | // compartments. | ||||
MOZ_MUST_USE bool init(HandleObject debuggees); | |||||
// Returns true if the RootList has been initialized successfully, false | // Returns true if the RootList has been initialized successfully, false | ||||
// otherwise. | // otherwise. | ||||
bool initialized() { return noGC.isSome(); } | bool initialized() { return noGC.isSome(); } | ||||
// Explicitly add the given Node as a root in this RootList. If wantNames is | // Explicitly add the given Node as a root in this RootList. If wantNames is | ||||
// true, you must pass an edgeName. The RootList does not take ownership of | // true, you must pass an edgeName. The RootList does not take ownership of | ||||
// edgeName. | // edgeName. | ||||
bool addRoot(Node node, const char16_t* edgeName = nullptr); | MOZ_MUST_USE bool addRoot(Node node, const char16_t* edgeName = nullptr); | ||||
}; | }; | ||||
/*** Concrete classes for ubi::Node referent types ************************************************/ | /*** Concrete classes for ubi::Node referent types ************************************************/ | ||||
template<> | template<> | ||||
struct Concrete<RootList> : public Base { | class JS_PUBLIC_API(Concrete<RootList>) : public Base { | ||||
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override; | |||||
const char16_t* typeName() const override { return concreteTypeName; } | |||||
protected: | protected: | ||||
explicit Concrete(RootList* ptr) : Base(ptr) { } | explicit Concrete(RootList* ptr) : Base(ptr) { } | ||||
RootList& get() const { return *static_cast<RootList*>(ptr); } | RootList& get() const { return *static_cast<RootList*>(ptr); } | ||||
public: | public: | ||||
static const char16_t concreteTypeName[]; | |||||
static void construct(void* storage, RootList* ptr) { new (storage) Concrete(ptr); } | static void construct(void* storage, RootList* ptr) { new (storage) Concrete(ptr); } | ||||
js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override; | |||||
const char16_t* typeName() const override { return concreteTypeName; } | |||||
static const char16_t concreteTypeName[]; | |||||
}; | }; | ||||
// A reusable ubi::Concrete specialization base class for types supported by | // A reusable ubi::Concrete specialization base class for types supported by | ||||
// JS::TraceChildren. | // JS::TraceChildren. | ||||
template<typename Referent> | template<typename Referent> | ||||
class TracerConcrete : public Base { | class JS_PUBLIC_API(TracerConcrete) : public Base { | ||||
const char16_t* typeName() const override { return concreteTypeName; } | js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override; | ||||
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override; | |||||
JS::Zone* zone() const override; | JS::Zone* zone() const override; | ||||
protected: | protected: | ||||
explicit TracerConcrete(Referent* ptr) : Base(ptr) { } | explicit TracerConcrete(Referent* ptr) : Base(ptr) { } | ||||
Referent& get() const { return *static_cast<Referent*>(ptr); } | Referent& get() const { return *static_cast<Referent*>(ptr); } | ||||
public: | |||||
static const char16_t concreteTypeName[]; | |||||
static void construct(void* storage, Referent* ptr) { new (storage) TracerConcrete(ptr); } | |||||
}; | }; | ||||
// For JS::TraceChildren-based types that have a 'compartment' method. | // For JS::TraceChildren-based types that have a 'compartment' method. | ||||
template<typename Referent> | template<typename Referent> | ||||
class TracerConcreteWithCompartment : public TracerConcrete<Referent> { | class JS_PUBLIC_API(TracerConcreteWithCompartment) : public TracerConcrete<Referent> { | ||||
typedef TracerConcrete<Referent> TracerBase; | typedef TracerConcrete<Referent> TracerBase; | ||||
JSCompartment* compartment() const override; | JSCompartment* compartment() const override; | ||||
protected: | protected: | ||||
explicit TracerConcreteWithCompartment(Referent* ptr) : TracerBase(ptr) { } | explicit TracerConcreteWithCompartment(Referent* ptr) : TracerBase(ptr) { } | ||||
public: | |||||
static void construct(void* storage, Referent* ptr) { | |||||
new (storage) TracerConcreteWithCompartment(ptr); | |||||
} | |||||
}; | }; | ||||
// Define specializations for some commonly-used public JSAPI types. | // Define specializations for some commonly-used public JSAPI types. | ||||
// These can use the generic templates above. | // These can use the generic templates above. | ||||
template<> | template<> | ||||
struct Concrete<JS::Symbol> : TracerConcrete<JS::Symbol> { | class JS_PUBLIC_API(Concrete<JS::Symbol>) : TracerConcrete<JS::Symbol> { | ||||
Size size(mozilla::MallocSizeOf mallocSizeOf) const override; | |||||
protected: | protected: | ||||
explicit Concrete(JS::Symbol* ptr) : TracerConcrete(ptr) { } | explicit Concrete(JS::Symbol* ptr) : TracerConcrete(ptr) { } | ||||
public: | public: | ||||
static void construct(void* storage, JS::Symbol* ptr) { | static void construct(void* storage, JS::Symbol* ptr) { | ||||
new (storage) Concrete(ptr); | new (storage) Concrete(ptr); | ||||
} | } | ||||
}; | |||||
template<> struct Concrete<JSScript> : TracerConcreteWithCompartment<JSScript> { | |||||
CoarseType coarseType() const final { return CoarseType::Script; } | |||||
Size size(mozilla::MallocSizeOf mallocSizeOf) const override; | Size size(mozilla::MallocSizeOf mallocSizeOf) const override; | ||||
const char* scriptFilename() const final; | |||||
const char16_t* typeName() const override { return concreteTypeName; } | |||||
static const char16_t concreteTypeName[]; | |||||
}; | |||||
template<> | |||||
class JS_PUBLIC_API(Concrete<JSScript>) : TracerConcreteWithCompartment<JSScript> { | |||||
protected: | protected: | ||||
explicit Concrete(JSScript *ptr) : TracerConcreteWithCompartment<JSScript>(ptr) { } | explicit Concrete(JSScript *ptr) : TracerConcreteWithCompartment<JSScript>(ptr) { } | ||||
public: | public: | ||||
static void construct(void *storage, JSScript *ptr) { new (storage) Concrete(ptr); } | static void construct(void *storage, JSScript *ptr) { new (storage) Concrete(ptr); } | ||||
}; | |||||
// The JSObject specialization. | CoarseType coarseType() const final { return CoarseType::Script; } | ||||
template<> | |||||
class Concrete<JSObject> : public TracerConcreteWithCompartment<JSObject> { | |||||
const char* jsObjectClassName() const override; | |||||
bool jsObjectConstructorName(JSContext* cx, | |||||
UniquePtr<char16_t[], JS::FreePolicy>& outName) const override; | |||||
Size size(mozilla::MallocSizeOf mallocSizeOf) const override; | Size size(mozilla::MallocSizeOf mallocSizeOf) const override; | ||||
const char* scriptFilename() const final; | |||||
bool hasAllocationStack() const override; | const char16_t* typeName() const override { return concreteTypeName; } | ||||
StackFrame allocationStack() const override; | static const char16_t concreteTypeName[]; | ||||
}; | |||||
CoarseType coarseType() const final { return CoarseType::Object; } | |||||
// The JSObject specialization. | |||||
template<> | |||||
class JS_PUBLIC_API(Concrete<JSObject>) : public TracerConcreteWithCompartment<JSObject> { | |||||
protected: | protected: | ||||
explicit Concrete(JSObject* ptr) : TracerConcreteWithCompartment(ptr) { } | explicit Concrete(JSObject* ptr) : TracerConcreteWithCompartment(ptr) { } | ||||
public: | public: | ||||
static void construct(void* storage, JSObject* ptr) { | static void construct(void* storage, JSObject* ptr) { | ||||
new (storage) Concrete(ptr); | new (storage) Concrete(ptr); | ||||
} | } | ||||
}; | |||||
// For JSString, we extend the generic template with a 'size' implementation. | const char* jsObjectClassName() const override; | ||||
template<> struct Concrete<JSString> : TracerConcrete<JSString> { | MOZ_MUST_USE bool jsObjectConstructorName(JSContext* cx, UniqueTwoByteChars& outName) | ||||
const override; | |||||
Size size(mozilla::MallocSizeOf mallocSizeOf) const override; | Size size(mozilla::MallocSizeOf mallocSizeOf) const override; | ||||
CoarseType coarseType() const final { return CoarseType::String; } | bool hasAllocationStack() const override; | ||||
StackFrame allocationStack() const override; | |||||
CoarseType coarseType() const final { return CoarseType::Object; } | |||||
const char16_t* typeName() const override { return concreteTypeName; } | |||||
static const char16_t concreteTypeName[]; | |||||
}; | |||||
// For JSString, we extend the generic template with a 'size' implementation. | |||||
template<> | |||||
class JS_PUBLIC_API(Concrete<JSString>) : TracerConcrete<JSString> { | |||||
protected: | protected: | ||||
explicit Concrete(JSString *ptr) : TracerConcrete<JSString>(ptr) { } | explicit Concrete(JSString *ptr) : TracerConcrete<JSString>(ptr) { } | ||||
public: | public: | ||||
static void construct(void *storage, JSString *ptr) { new (storage) Concrete(ptr); } | static void construct(void *storage, JSString *ptr) { new (storage) Concrete(ptr); } | ||||
Size size(mozilla::MallocSizeOf mallocSizeOf) const override; | |||||
CoarseType coarseType() const final { return CoarseType::String; } | |||||
const char16_t* typeName() const override { return concreteTypeName; } | |||||
static const char16_t concreteTypeName[]; | |||||
}; | }; | ||||
// The ubi::Node null pointer. Any attempt to operate on a null ubi::Node asserts. | // The ubi::Node null pointer. Any attempt to operate on a null ubi::Node asserts. | ||||
template<> | template<> | ||||
class Concrete<void> : public Base { | class JS_PUBLIC_API(Concrete<void>) : public Base { | ||||
const char16_t* typeName() const override; | const char16_t* typeName() const override; | ||||
Size size(mozilla::MallocSizeOf mallocSizeOf) const override; | Size size(mozilla::MallocSizeOf mallocSizeOf) const override; | ||||
UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override; | js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override; | ||||
JS::Zone* zone() const override; | JS::Zone* zone() const override; | ||||
JSCompartment* compartment() const override; | JSCompartment* compartment() const override; | ||||
CoarseType coarseType() const final; | CoarseType coarseType() const final; | ||||
explicit Concrete(void* ptr) : Base(ptr) { } | explicit Concrete(void* ptr) : Base(ptr) { } | ||||
public: | public: | ||||
static void construct(void* storage, void* ptr) { new (storage) Concrete(ptr); } | static void construct(void* storage, void* ptr) { new (storage) Concrete(ptr); } | ||||
static const char16_t concreteTypeName[]; | |||||
}; | }; | ||||
} // namespace ubi | } // namespace ubi | ||||
} // namespace JS | } // namespace JS | ||||
namespace js { | namespace js { | ||||
// Make ubi::Node::HashPolicy the default hash policy for ubi::Node. | // Make ubi::Node::HashPolicy the default hash policy for ubi::Node. | ||||
template<> struct DefaultHasher<JS::ubi::Node> : JS::ubi::Node::HashPolicy { }; | template<> struct DefaultHasher<JS::ubi::Node> : JS::ubi::Node::HashPolicy { }; | ||||
template<> struct DefaultHasher<JS::ubi::StackFrame> : JS::ubi::StackFrame::HashPolicy { }; | template<> struct DefaultHasher<JS::ubi::StackFrame> : JS::ubi::StackFrame::HashPolicy { }; | ||||
} // namespace js | } // namespace js | ||||
#endif // js_UbiNode_h | #endif // js_UbiNode_h |
Wildfire Games · Phabricator