summaryrefslogtreecommitdiff
path: root/dom/heapsnapshot/DeserializedNode.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/heapsnapshot/DeserializedNode.h')
-rw-r--r--dom/heapsnapshot/DeserializedNode.h317
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__