summaryrefslogtreecommitdiffstats
path: root/js/src/vm/UbiNode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/UbiNode.cpp')
-rw-r--r--js/src/vm/UbiNode.cpp519
1 files changed, 519 insertions, 0 deletions
diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp
new file mode 100644
index 000000000..538011bff
--- /dev/null
+++ b/js/src/vm/UbiNode.cpp
@@ -0,0 +1,519 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#include "js/UbiNode.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Range.h"
+#include "mozilla/Scoped.h"
+
+#include <algorithm>
+
+#include "jscntxt.h"
+#include "jsobj.h"
+#include "jsscript.h"
+#include "jsstr.h"
+
+#include "jit/IonCode.h"
+#include "js/Debug.h"
+#include "js/TracingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Utility.h"
+#include "js/Vector.h"
+#include "vm/Debugger.h"
+#include "vm/EnvironmentObject.h"
+#include "vm/GlobalObject.h"
+#include "vm/Scope.h"
+#include "vm/Shape.h"
+#include "vm/String.h"
+#include "vm/Symbol.h"
+
+#include "jsobjinlines.h"
+#include "vm/Debugger-inl.h"
+
+using namespace js;
+
+using mozilla::Some;
+using mozilla::RangedPtr;
+using JS::DispatchTyped;
+using JS::HandleValue;
+using JS::Value;
+using JS::ZoneSet;
+using JS::ubi::AtomOrTwoByteChars;
+using JS::ubi::CoarseType;
+using JS::ubi::Concrete;
+using JS::ubi::Edge;
+using JS::ubi::EdgeRange;
+using JS::ubi::Node;
+using JS::ubi::EdgeVector;
+using JS::ubi::StackFrame;
+using JS::ubi::TracerConcrete;
+using JS::ubi::TracerConcreteWithCompartment;
+
+struct CopyToBufferMatcher
+{
+ RangedPtr<char16_t> destination;
+ size_t maxLength;
+
+ CopyToBufferMatcher(RangedPtr<char16_t> destination, size_t maxLength)
+ : destination(destination)
+ , maxLength(maxLength)
+ { }
+
+ template<typename CharT>
+ static size_t
+ copyToBufferHelper(const CharT* src, RangedPtr<char16_t> dest, size_t length)
+ {
+ size_t i = 0;
+ for ( ; i < length; i++)
+ dest[i] = src[i];
+ return i;
+ }
+
+ size_t
+ match(JSAtom* atom)
+ {
+ if (!atom)
+ return 0;
+
+ size_t length = std::min(atom->length(), maxLength);
+ JS::AutoCheckCannotGC noGC;
+ return atom->hasTwoByteChars()
+ ? copyToBufferHelper(atom->twoByteChars(noGC), destination, length)
+ : copyToBufferHelper(atom->latin1Chars(noGC), destination, length);
+ }
+
+ size_t
+ match(const char16_t* chars)
+ {
+ if (!chars)
+ return 0;
+
+ size_t length = std::min(js_strlen(chars), maxLength);
+ return copyToBufferHelper(chars, destination, length);
+ }
+};
+
+size_t
+JS::ubi::AtomOrTwoByteChars::copyToBuffer(RangedPtr<char16_t> destination, size_t length)
+{
+ CopyToBufferMatcher m(destination, length);
+ return match(m);
+}
+
+struct LengthMatcher
+{
+ size_t
+ match(JSAtom* atom)
+ {
+ return atom ? atom->length() : 0;
+ }
+
+ size_t
+ match(const char16_t* chars)
+ {
+ return chars ? js_strlen(chars) : 0;
+ }
+};
+
+size_t
+JS::ubi::AtomOrTwoByteChars::length()
+{
+ LengthMatcher m;
+ return match(m);
+}
+
+size_t
+StackFrame::source(RangedPtr<char16_t> destination, size_t length) const
+{
+ auto s = source();
+ return s.copyToBuffer(destination, length);
+}
+
+size_t
+StackFrame::functionDisplayName(RangedPtr<char16_t> destination, size_t length) const
+{
+ auto name = functionDisplayName();
+ return name.copyToBuffer(destination, length);
+}
+
+size_t
+StackFrame::sourceLength()
+{
+ return source().length();
+}
+
+size_t
+StackFrame::functionDisplayNameLength()
+{
+ return functionDisplayName().length();
+}
+
+// All operations on null ubi::Nodes crash.
+CoarseType Concrete<void>::coarseType() const { MOZ_CRASH("null ubi::Node"); }
+const char16_t* Concrete<void>::typeName() const { MOZ_CRASH("null ubi::Node"); }
+JS::Zone* Concrete<void>::zone() const { MOZ_CRASH("null ubi::Node"); }
+JSCompartment* Concrete<void>::compartment() const { MOZ_CRASH("null ubi::Node"); }
+
+UniquePtr<EdgeRange>
+Concrete<void>::edges(JSContext*, bool) const {
+ MOZ_CRASH("null ubi::Node");
+}
+
+Node::Size
+Concrete<void>::size(mozilla::MallocSizeOf mallocSizeof) const
+{
+ MOZ_CRASH("null ubi::Node");
+}
+
+struct Node::ConstructFunctor : public js::BoolDefaultAdaptor<Value, false> {
+ template <typename T> bool operator()(T* t, Node* node) { node->construct(t); return true; }
+};
+
+Node::Node(const JS::GCCellPtr &thing)
+{
+ DispatchTyped(ConstructFunctor(), thing, this);
+}
+
+Node::Node(HandleValue value)
+{
+ if (!DispatchTyped(ConstructFunctor(), value, this))
+ construct<void>(nullptr);
+}
+
+Value
+Node::exposeToJS() const
+{
+ Value v;
+
+ if (is<JSObject>()) {
+ JSObject& obj = *as<JSObject>();
+ if (obj.is<js::EnvironmentObject>()) {
+ v.setUndefined();
+ } else if (obj.is<JSFunction>() && js::IsInternalFunctionObject(obj)) {
+ v.setUndefined();
+ } else {
+ v.setObject(obj);
+ }
+ } else if (is<JSString>()) {
+ v.setString(as<JSString>());
+ } else if (is<JS::Symbol>()) {
+ v.setSymbol(as<JS::Symbol>());
+ } else {
+ v.setUndefined();
+ }
+
+ ExposeValueToActiveJS(v);
+
+ return v;
+}
+
+
+// A JS::CallbackTracer subclass that adds a Edge to a Vector for each
+// edge on which it is invoked.
+class EdgeVectorTracer : public JS::CallbackTracer {
+ // The vector to which we add Edges.
+ EdgeVector* vec;
+
+ // True if we should populate the edge's names.
+ bool wantNames;
+
+ void onChild(const JS::GCCellPtr& thing) override {
+ if (!okay)
+ return;
+
+ // Don't trace permanent atoms and well-known symbols that are owned by
+ // a parent JSRuntime.
+ if (thing.is<JSString>() && thing.as<JSString>().isPermanentAtom())
+ return;
+ if (thing.is<JS::Symbol>() && thing.as<JS::Symbol>().isWellKnownSymbol())
+ return;
+
+ char16_t* name16 = nullptr;
+ if (wantNames) {
+ // Ask the tracer to compute an edge name for us.
+ char buffer[1024];
+ getTracingEdgeName(buffer, sizeof(buffer));
+ const char* name = buffer;
+
+ // Convert the name to char16_t characters.
+ name16 = js_pod_malloc<char16_t>(strlen(name) + 1);
+ if (!name16) {
+ okay = false;
+ return;
+ }
+
+ size_t i;
+ for (i = 0; name[i]; i++)
+ name16[i] = name[i];
+ name16[i] = '\0';
+ }
+
+ // The simplest code is correct! The temporary Edge takes
+ // ownership of name; if the append succeeds, the vector element
+ // then takes ownership; if the append fails, then the temporary
+ // retains it, and its destructor will free it.
+ if (!vec->append(mozilla::Move(Edge(name16, Node(thing))))) {
+ okay = false;
+ return;
+ }
+ }
+
+ public:
+ // True if no errors (OOM, say) have yet occurred.
+ bool okay;
+
+ EdgeVectorTracer(JSRuntime* rt, EdgeVector* vec, bool wantNames)
+ : JS::CallbackTracer(rt),
+ vec(vec),
+ wantNames(wantNames),
+ okay(true)
+ { }
+};
+
+
+// An EdgeRange concrete class that simply holds a vector of Edges,
+// populated by the init method.
+class SimpleEdgeRange : public EdgeRange {
+ EdgeVector edges;
+ size_t i;
+
+ void settle() {
+ front_ = i < edges.length() ? &edges[i] : nullptr;
+ }
+
+ public:
+ explicit SimpleEdgeRange() : edges(), i(0) { }
+
+ bool init(JSRuntime* rt, void* thing, JS::TraceKind kind, bool wantNames = true) {
+ EdgeVectorTracer tracer(rt, &edges, wantNames);
+ js::TraceChildren(&tracer, thing, kind);
+ settle();
+ return tracer.okay;
+ }
+
+ void popFront() override { i++; settle(); }
+};
+
+
+template<typename Referent>
+JS::Zone*
+TracerConcrete<Referent>::zone() const
+{
+ return get().zoneFromAnyThread();
+}
+
+template JS::Zone* TracerConcrete<JSScript>::zone() const;
+template JS::Zone* TracerConcrete<js::LazyScript>::zone() const;
+template JS::Zone* TracerConcrete<js::Shape>::zone() const;
+template JS::Zone* TracerConcrete<js::BaseShape>::zone() const;
+template JS::Zone* TracerConcrete<js::ObjectGroup>::zone() const;
+template JS::Zone* TracerConcrete<js::Scope>::zone() const;
+template JS::Zone* TracerConcrete<JS::Symbol>::zone() const;
+template JS::Zone* TracerConcrete<JSString>::zone() const;
+
+template<typename Referent>
+UniquePtr<EdgeRange>
+TracerConcrete<Referent>::edges(JSContext* cx, bool wantNames) const {
+ UniquePtr<SimpleEdgeRange, JS::DeletePolicy<SimpleEdgeRange>> range(js_new<SimpleEdgeRange>());
+ if (!range)
+ return nullptr;
+
+ if (!range->init(cx, ptr, JS::MapTypeToTraceKind<Referent>::kind, wantNames))
+ return nullptr;
+
+ return UniquePtr<EdgeRange>(range.release());
+}
+
+template UniquePtr<EdgeRange> TracerConcrete<JSScript>::edges(JSContext* cx, bool wantNames) const;
+template UniquePtr<EdgeRange> TracerConcrete<js::LazyScript>::edges(JSContext* cx, bool wantNames) const;
+template UniquePtr<EdgeRange> TracerConcrete<js::Shape>::edges(JSContext* cx, bool wantNames) const;
+template UniquePtr<EdgeRange> TracerConcrete<js::BaseShape>::edges(JSContext* cx, bool wantNames) const;
+template UniquePtr<EdgeRange> TracerConcrete<js::ObjectGroup>::edges(JSContext* cx, bool wantNames) const;
+template UniquePtr<EdgeRange> TracerConcrete<js::Scope>::edges(JSContext* cx, bool wantNames) const;
+template UniquePtr<EdgeRange> TracerConcrete<JS::Symbol>::edges(JSContext* cx, bool wantNames) const;
+template UniquePtr<EdgeRange> TracerConcrete<JSString>::edges(JSContext* cx, bool wantNames) const;
+
+template<typename Referent>
+JSCompartment*
+TracerConcreteWithCompartment<Referent>::compartment() const
+{
+ return TracerBase::get().compartment();
+}
+
+template JSCompartment* TracerConcreteWithCompartment<JSScript>::compartment() const;
+
+bool
+Concrete<JSObject>::hasAllocationStack() const
+{
+ return !!js::Debugger::getObjectAllocationSite(get());
+}
+
+StackFrame
+Concrete<JSObject>::allocationStack() const
+{
+ MOZ_ASSERT(hasAllocationStack());
+ return StackFrame(js::Debugger::getObjectAllocationSite(get()));
+}
+
+const char*
+Concrete<JSObject>::jsObjectClassName() const
+{
+ return Concrete::get().getClass()->name;
+}
+
+bool
+Concrete<JSObject>::jsObjectConstructorName(JSContext* cx, UniqueTwoByteChars& outName) const
+{
+ JSAtom* name = Concrete::get().maybeConstructorDisplayAtom();
+ if (!name) {
+ outName.reset(nullptr);
+ return true;
+ }
+
+ auto len = JS_GetStringLength(name);
+ auto size = len + 1;
+
+ outName.reset(cx->pod_malloc<char16_t>(size * sizeof(char16_t)));
+ if (!outName)
+ return false;
+
+ mozilla::Range<char16_t> chars(outName.get(), size);
+ if (!JS_CopyStringChars(cx, chars, name))
+ return false;
+
+ outName[len] = '\0';
+ return true;
+}
+
+const char16_t Concrete<JS::Symbol>::concreteTypeName[] = u"JS::Symbol";
+const char16_t Concrete<JSScript>::concreteTypeName[] = u"JSScript";
+const char16_t Concrete<js::LazyScript>::concreteTypeName[] = u"js::LazyScript";
+const char16_t Concrete<js::jit::JitCode>::concreteTypeName[] = u"js::jit::JitCode";
+const char16_t Concrete<js::Shape>::concreteTypeName[] = u"js::Shape";
+const char16_t Concrete<js::BaseShape>::concreteTypeName[] = u"js::BaseShape";
+const char16_t Concrete<js::ObjectGroup>::concreteTypeName[] = u"js::ObjectGroup";
+const char16_t Concrete<js::Scope>::concreteTypeName[] = u"js::Scope";
+
+namespace JS {
+namespace ubi {
+
+RootList::RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames /* = false */)
+ : noGC(noGC),
+ cx(cx),
+ edges(),
+ wantNames(wantNames)
+{ }
+
+
+bool
+RootList::init()
+{
+ EdgeVectorTracer tracer(cx, &edges, wantNames);
+ js::TraceRuntime(&tracer);
+ if (!tracer.okay)
+ return false;
+ noGC.emplace(cx);
+ return true;
+}
+
+bool
+RootList::init(CompartmentSet& debuggees)
+{
+ EdgeVector allRootEdges;
+ EdgeVectorTracer tracer(cx, &allRootEdges, wantNames);
+
+ ZoneSet debuggeeZones;
+ if (!debuggeeZones.init())
+ return false;
+ for (auto range = debuggees.all(); !range.empty(); range.popFront()) {
+ if (!debuggeeZones.put(range.front()->zone()))
+ return false;
+ }
+
+ js::TraceRuntime(&tracer);
+ if (!tracer.okay)
+ return false;
+ TraceIncomingCCWs(&tracer, debuggees);
+ if (!tracer.okay)
+ return false;
+
+ for (EdgeVector::Range r = allRootEdges.all(); !r.empty(); r.popFront()) {
+ Edge& edge = r.front();
+
+ JSCompartment* compartment = edge.referent.compartment();
+ if (compartment && !debuggees.has(compartment))
+ continue;
+
+ Zone* zone = edge.referent.zone();
+ if (zone && !debuggeeZones.has(zone))
+ continue;
+
+ if (!edges.append(mozilla::Move(edge)))
+ return false;
+ }
+
+ noGC.emplace(cx);
+ return true;
+}
+
+bool
+RootList::init(HandleObject debuggees)
+{
+ MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(*debuggees));
+ js::Debugger* dbg = js::Debugger::fromJSObject(debuggees.get());
+
+ CompartmentSet debuggeeCompartments;
+ if (!debuggeeCompartments.init())
+ return false;
+
+ for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) {
+ if (!debuggeeCompartments.put(r.front()->compartment()))
+ return false;
+ }
+
+ if (!init(debuggeeCompartments))
+ return false;
+
+ // Ensure that each of our debuggee globals are in the root list.
+ for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) {
+ if (!addRoot(JS::ubi::Node(static_cast<JSObject*>(r.front())),
+ u"debuggee global"))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+RootList::addRoot(Node node, const char16_t* edgeName)
+{
+ MOZ_ASSERT(noGC.isSome());
+ MOZ_ASSERT_IF(wantNames, edgeName);
+
+ UniqueTwoByteChars name;
+ if (edgeName) {
+ name = js::DuplicateString(edgeName);
+ if (!name)
+ return false;
+ }
+
+ return edges.append(mozilla::Move(Edge(name.release(), node)));
+}
+
+const char16_t Concrete<RootList>::concreteTypeName[] = u"JS::ubi::RootList";
+
+UniquePtr<EdgeRange>
+Concrete<RootList>::edges(JSContext* cx, bool wantNames) const {
+ MOZ_ASSERT_IF(wantNames, get().wantNames);
+ return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
+}
+
+} // namespace ubi
+} // namespace JS