diff options
Diffstat (limited to 'dom/heapsnapshot/DeserializedNode.h')
-rw-r--r-- | dom/heapsnapshot/DeserializedNode.h | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/dom/heapsnapshot/DeserializedNode.h b/dom/heapsnapshot/DeserializedNode.h new file mode 100644 index 0000000000..60d1fb408a --- /dev/null +++ b/dom/heapsnapshot/DeserializedNode.h @@ -0,0 +1,317 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_devtools_DeserializedNode__ +#define mozilla_devtools_DeserializedNode__ + +#include "js/UbiNode.h" +#include "js/UniquePtr.h" +#include "mozilla/devtools/CoreDump.pb.h" +#include "mozilla/Maybe.h" +#include "mozilla/Move.h" +#include "mozilla/Vector.h" + +// `Deserialized{Node,Edge}` translate protobuf messages from our core dump +// format into structures we can rely upon for implementing `JS::ubi::Node` +// specializations on top of. All of the properties of the protobuf messages are +// optional for future compatibility, and this is the layer where we validate +// that the properties that do actually exist in any given message fulfill our +// semantic requirements. +// +// Both `DeserializedNode` and `DeserializedEdge` are always owned by a +// `HeapSnapshot` instance, and their lifetimes must not extend after that of +// their owning `HeapSnapshot`. + +namespace mozilla { +namespace devtools { + +class HeapSnapshot; + +using NodeId = uint64_t; +using StackFrameId = uint64_t; + +// A `DeserializedEdge` represents an edge in the heap graph pointing to the +// node with id equal to `DeserializedEdge::referent` that we deserialized from +// a core dump. +struct DeserializedEdge { + NodeId referent; + // A borrowed reference to a string owned by this node's owning HeapSnapshot. + const char16_t* name; + + explicit DeserializedEdge(NodeId referent, const char16_t* edgeName = nullptr) + : referent(referent) + , name(edgeName) + { } + DeserializedEdge(DeserializedEdge&& rhs); + DeserializedEdge& operator=(DeserializedEdge&& rhs); + +private: + DeserializedEdge(const DeserializedEdge&) = delete; + DeserializedEdge& operator=(const DeserializedEdge&) = delete; +}; + +// A `DeserializedNode` is a node in the heap graph that we deserialized from a +// core dump. +struct DeserializedNode { + using EdgeVector = Vector<DeserializedEdge>; + using UniqueStringPtr = UniquePtr<char16_t[]>; + + NodeId id; + JS::ubi::CoarseType coarseType; + // A borrowed reference to a string owned by this node's owning HeapSnapshot. + const char16_t* typeName; + uint64_t size; + EdgeVector edges; + Maybe<StackFrameId> allocationStack; + // A borrowed reference to a string owned by this node's owning HeapSnapshot. + const char* jsObjectClassName; + // A borrowed reference to a string owned by this node's owning HeapSnapshot. + const char* scriptFilename; + // A weak pointer to this node's owning `HeapSnapshot`. Safe without + // AddRef'ing because this node's lifetime is equal to that of its owner. + HeapSnapshot* owner; + + DeserializedNode(NodeId id, + JS::ubi::CoarseType coarseType, + const char16_t* typeName, + uint64_t size, + EdgeVector&& edges, + Maybe<StackFrameId> allocationStack, + const char* className, + const char* filename, + HeapSnapshot& owner) + : id(id) + , coarseType(coarseType) + , typeName(typeName) + , size(size) + , edges(Move(edges)) + , allocationStack(allocationStack) + , jsObjectClassName(className) + , scriptFilename(filename) + , owner(&owner) + { } + virtual ~DeserializedNode() { } + + DeserializedNode(DeserializedNode&& rhs) + : id(rhs.id) + , coarseType(rhs.coarseType) + , typeName(rhs.typeName) + , size(rhs.size) + , edges(Move(rhs.edges)) + , allocationStack(rhs.allocationStack) + , jsObjectClassName(rhs.jsObjectClassName) + , scriptFilename(rhs.scriptFilename) + , owner(rhs.owner) + { } + + DeserializedNode& operator=(DeserializedNode&& rhs) + { + MOZ_ASSERT(&rhs != this); + this->~DeserializedNode(); + new(this) DeserializedNode(Move(rhs)); + return *this; + } + + // Get a borrowed reference to the given edge's referent. This method is + // virtual to provide a hook for gmock and gtest. + virtual JS::ubi::Node getEdgeReferent(const DeserializedEdge& edge); + + struct HashPolicy; + +protected: + // This is only for use with `MockDeserializedNode` in testing. + DeserializedNode(NodeId id, const char16_t* typeName, uint64_t size) + : id(id) + , coarseType(JS::ubi::CoarseType::Other) + , typeName(typeName) + , size(size) + , edges() + , allocationStack(Nothing()) + , jsObjectClassName(nullptr) + , scriptFilename(nullptr) + , owner(nullptr) + { } + +private: + DeserializedNode(const DeserializedNode&) = delete; + DeserializedNode& operator=(const DeserializedNode&) = delete; +}; + +static inline js::HashNumber +hashIdDerivedFromPtr(uint64_t id) +{ + // NodeIds and StackFrameIds are always 64 bits, but they are derived from + // the original referents' addresses, which could have been either 32 or 64 + // bits long. As such, NodeId and StackFrameId have little entropy in their + // bottom three bits, and may or may not have entropy in their upper 32 + // bits. This hash should manage both cases well. + id >>= 3; + return js::HashNumber((id >> 32) ^ id); +} + +struct DeserializedNode::HashPolicy +{ + using Lookup = NodeId; + + static js::HashNumber hash(const Lookup& lookup) { + return hashIdDerivedFromPtr(lookup); + } + + static bool match(const DeserializedNode& existing, const Lookup& lookup) { + return existing.id == lookup; + } +}; + +// A `DeserializedStackFrame` is a stack frame referred to by a thing in the +// heap graph that we deserialized from a core dump. +struct DeserializedStackFrame { + StackFrameId id; + Maybe<StackFrameId> parent; + uint32_t line; + uint32_t column; + // Borrowed references to strings owned by this DeserializedStackFrame's + // owning HeapSnapshot. + const char16_t* source; + const char16_t* functionDisplayName; + bool isSystem; + bool isSelfHosted; + // A weak pointer to this frame's owning `HeapSnapshot`. Safe without + // AddRef'ing because this frame's lifetime is equal to that of its owner. + HeapSnapshot* owner; + + explicit DeserializedStackFrame(StackFrameId id, + const Maybe<StackFrameId>& parent, + uint32_t line, + uint32_t column, + const char16_t* source, + const char16_t* functionDisplayName, + bool isSystem, + bool isSelfHosted, + HeapSnapshot& owner) + : id(id) + , parent(parent) + , line(line) + , column(column) + , source(source) + , functionDisplayName(functionDisplayName) + , isSystem(isSystem) + , isSelfHosted(isSelfHosted) + , owner(&owner) + { + MOZ_ASSERT(source); + } + + JS::ubi::StackFrame getParentStackFrame() const; + + struct HashPolicy; + +protected: + // This is exposed only for MockDeserializedStackFrame in the gtests. + explicit DeserializedStackFrame() + : id(0) + , parent(Nothing()) + , line(0) + , column(0) + , source(nullptr) + , functionDisplayName(nullptr) + , isSystem(false) + , isSelfHosted(false) + , owner(nullptr) + { }; +}; + +struct DeserializedStackFrame::HashPolicy { + using Lookup = StackFrameId; + + static js::HashNumber hash(const Lookup& lookup) { + return hashIdDerivedFromPtr(lookup); + } + + static bool match(const DeserializedStackFrame& existing, const Lookup& lookup) { + return existing.id == lookup; + } +}; + +} // namespace devtools +} // namespace mozilla + +namespace JS { +namespace ubi { + +using mozilla::devtools::DeserializedNode; +using mozilla::devtools::DeserializedStackFrame; + +template<> +class Concrete<DeserializedNode> : public Base +{ +protected: + explicit Concrete(DeserializedNode* ptr) : Base(ptr) { } + DeserializedNode& get() const { + return *static_cast<DeserializedNode*>(ptr); + } + +public: + static void construct(void* storage, DeserializedNode* ptr) { + new (storage) Concrete(ptr); + } + + CoarseType coarseType() const final { return get().coarseType; } + Id identifier() const override { return get().id; } + bool isLive() const override { return false; } + const char16_t* typeName() const override; + Node::Size size(mozilla::MallocSizeOf mallocSizeof) const override; + const char* jsObjectClassName() const override { return get().jsObjectClassName; } + const char* scriptFilename() const final { return get().scriptFilename; } + + bool hasAllocationStack() const override { return get().allocationStack.isSome(); } + StackFrame allocationStack() const override; + + // We ignore the `bool wantNames` parameter because we can't control whether + // the core dump was serialized with edge names or not. + js::UniquePtr<EdgeRange> edges(JSContext* cx, bool) const override; + + static const char16_t concreteTypeName[]; +}; + +template<> +class ConcreteStackFrame<DeserializedStackFrame> : public BaseStackFrame +{ +protected: + explicit ConcreteStackFrame(DeserializedStackFrame* ptr) + : BaseStackFrame(ptr) + { } + + DeserializedStackFrame& get() const { + return *static_cast<DeserializedStackFrame*>(ptr); + } + +public: + static void construct(void* storage, DeserializedStackFrame* ptr) { + new (storage) ConcreteStackFrame(ptr); + } + + uint64_t identifier() const override { return get().id; } + uint32_t line() const override { return get().line; } + uint32_t column() const override { return get().column; } + bool isSystem() const override { return get().isSystem; } + bool isSelfHosted(JSContext* cx) const override { return get().isSelfHosted; } + void trace(JSTracer* trc) override { } + AtomOrTwoByteChars source() const override { + return AtomOrTwoByteChars(get().source); + } + AtomOrTwoByteChars functionDisplayName() const override { + return AtomOrTwoByteChars(get().functionDisplayName); + } + + StackFrame parent() const override; + bool constructSavedFrameStack(JSContext* cx, + MutableHandleObject outSavedFrameStack) + const override; +}; + +} // namespace ubi +} // namespace JS + +#endif // mozilla_devtools_DeserializedNode__ |