diff options
Diffstat (limited to 'js/src/vm/UbiNode.cpp')
-rw-r--r-- | js/src/vm/UbiNode.cpp | 519 |
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 |