diff options
Diffstat (limited to 'js/src/vm/UbiNode.cpp')
-rw-r--r-- | js/src/vm/UbiNode.cpp | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp new file mode 100644 index 0000000000..c541e933de --- /dev/null +++ b/js/src/vm/UbiNode.cpp @@ -0,0 +1,527 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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 <algorithm> + +#include "debugger/Debugger.h" +#include "gc/GC.h" +#include "jit/JitCode.h" +#include "js/Debug.h" +#include "js/TracingAPI.h" +#include "js/TypeDecls.h" +#include "js/UbiNodeUtils.h" +#include "js/Utility.h" +#include "util/Text.h" +#include "vm/BigIntType.h" +#include "vm/Compartment.h" +#include "vm/EnvironmentObject.h" +#include "vm/GetterSetter.h" +#include "vm/GlobalObject.h" +#include "vm/JSContext.h" +#include "vm/JSObject.h" +#include "vm/JSScript.h" +#include "vm/PropMap.h" +#include "vm/Scope.h" +#include "vm/Shape.h" +#include "vm/StringType.h" +#include "vm/SymbolType.h" + +#include "debugger/Debugger-inl.h" +#include "gc/StableCellHasher-inl.h" +#include "vm/JSObject-inl.h" + +using namespace js; + +using JS::ApplyGCThingTyped; +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::EdgeVector; +using JS::ubi::Node; +using JS::ubi::StackFrame; +using JS::ubi::TracerConcrete; +using JS::ubi::TracerConcreteWithRealm; +using mozilla::RangedPtr; + +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 operator()(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 operator()(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 operator()(JSAtom* atom) { return atom ? atom->length() : 0; } + + size_t operator()(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"); } +JS::Compartment* Concrete<void>::compartment() const { + MOZ_CRASH("null ubi::Node"); +} +JS::Realm* Concrete<void>::realm() 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"); +} + +Node::Node(JS::GCCellPtr thing) { + ApplyGCThingTyped(thing, [this](auto t) { this->construct(t); }); +} + +Node::Node(HandleValue value) { + if (!ApplyGCThingTyped(value, [this](auto t) { this->construct(t); })) { + construct<void>(nullptr); + } +} + +static bool IsSafeToExposeToJS(JSObject* obj) { + if (obj->is<js::EnvironmentObject>() || obj->is<js::ScriptSourceObject>() || + obj->is<js::DebugEnvironmentProxy>()) { + return false; + } + if (obj->is<JSFunction>() && js::IsInternalFunctionObject(*obj)) { + return false; + } + return true; +} + +Value Node::exposeToJS() const { + Value v; + + if (is<JSObject>()) { + JSObject* obj = as<JSObject>(); + if (IsSafeToExposeToJS(obj)) { + v.setObject(*obj); + } else { + v.setUndefined(); + } + } else if (is<JSString>()) { + v.setString(as<JSString>()); + } else if (is<JS::Symbol>()) { + v.setSymbol(as<JS::Symbol>()); + } else if (is<BigInt>()) { + v.setBigInt(as<BigInt>()); + } 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 final : 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(JS::GCCellPtr thing, const char* name) 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]; + context().getEdgeName(name, buffer, sizeof(buffer)); + 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(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) {} +}; + +template <typename Referent> +JS::Zone* TracerConcrete<Referent>::zone() const { + return get().zoneFromAnyThread(); +} + +template JS::Zone* TracerConcrete<js::BaseScript>::zone() const; +template JS::Zone* TracerConcrete<js::Shape>::zone() const; +template JS::Zone* TracerConcrete<js::BaseShape>::zone() const; +template JS::Zone* TracerConcrete<js::GetterSetter>::zone() const; +template JS::Zone* TracerConcrete<js::PropMap>::zone() const; +template JS::Zone* TracerConcrete<js::RegExpShared>::zone() const; +template JS::Zone* TracerConcrete<js::Scope>::zone() const; +template JS::Zone* TracerConcrete<JS::Symbol>::zone() const; +template JS::Zone* TracerConcrete<BigInt>::zone() const; +template JS::Zone* TracerConcrete<JSString>::zone() const; + +template <typename Referent> +UniquePtr<EdgeRange> TracerConcrete<Referent>::edges(JSContext* cx, + bool wantNames) const { + auto range = js::MakeUnique<SimpleEdgeRange>(); + if (!range) { + return nullptr; + } + + if (!range->addTracerEdges(cx->runtime(), ptr, + JS::MapTypeToTraceKind<Referent>::kind, + wantNames)) { + return nullptr; + } + + // Note: Clang 3.8 (or older) require an explicit construction of the + // target UniquePtr type. When we no longer require to support these Clang + // versions the return statement can be simplified to |return range;|. + return UniquePtr<EdgeRange>(range.release()); +} + +template UniquePtr<EdgeRange> TracerConcrete<js::BaseScript>::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::GetterSetter>::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr<EdgeRange> TracerConcrete<js::PropMap>::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr<EdgeRange> TracerConcrete<js::RegExpShared>::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<BigInt>::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr<EdgeRange> TracerConcrete<JSString>::edges( + JSContext* cx, bool wantNames) const; + +template <typename Referent> +JS::Compartment* TracerConcreteWithRealm<Referent>::compartment() const { + return TracerBase::get().compartment(); +} + +template <typename Referent> +Realm* TracerConcreteWithRealm<Referent>::realm() const { + return TracerBase::get().realm(); +} + +template Realm* TracerConcreteWithRealm<js::BaseScript>::realm() const; +template JS::Compartment* TracerConcreteWithRealm<js::BaseScript>::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; +} + +JS::Compartment* Concrete<JSObject>::compartment() const { + return Concrete::get().compartment(); +} + +Realm* Concrete<JSObject>::realm() const { + // Cross-compartment wrappers are shared by all realms in the compartment, + // so we return nullptr in that case. + return JS::GetObjectRealmOrNull(&Concrete::get()); +} + +const char16_t Concrete<JS::Symbol>::concreteTypeName[] = u"JS::Symbol"; +const char16_t Concrete<BigInt>::concreteTypeName[] = u"JS::BigInt"; +const char16_t Concrete<js::BaseScript>::concreteTypeName[] = u"js::BaseScript"; +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::GetterSetter>::concreteTypeName[] = + u"js::GetterSetter"; +const char16_t Concrete<js::PropMap>::concreteTypeName[] = u"js::PropMap"; +const char16_t Concrete<js::Scope>::concreteTypeName[] = u"js::Scope"; +const char16_t Concrete<js::RegExpShared>::concreteTypeName[] = + u"js::RegExpShared"; + +namespace JS { +namespace ubi { + +RootList::RootList(JSContext* cx, bool wantNames /* = false */) + : cx(cx), edges(), wantNames(wantNames), inited(false) {} + +std::pair<bool, JS::AutoCheckCannotGC> RootList::init() { + EdgeVectorTracer tracer(cx->runtime(), &edges, wantNames); + js::TraceRuntime(&tracer); + inited = tracer.okay; + return {tracer.okay, JS::AutoCheckCannotGC(cx)}; +} + +std::pair<bool, JS::AutoCheckCannotGC> RootList::init( + CompartmentSet& debuggees) { + EdgeVector allRootEdges; + EdgeVectorTracer tracer(cx->runtime(), &allRootEdges, wantNames); + + ZoneSet debuggeeZones; + for (auto range = debuggees.all(); !range.empty(); range.popFront()) { + if (!debuggeeZones.put(range.front()->zone())) { + return {false, JS::AutoCheckCannotGC(cx)}; + } + } + + js::TraceRuntime(&tracer); + if (!tracer.okay) { + return {false, JS::AutoCheckCannotGC(cx)}; + } + js::gc::TraceIncomingCCWs(&tracer, debuggees); + if (!tracer.okay) { + return {false, JS::AutoCheckCannotGC(cx)}; + } + + for (EdgeVector::Range r = allRootEdges.all(); !r.empty(); r.popFront()) { + Edge& edge = r.front(); + + JS::Compartment* compartment = edge.referent.compartment(); + if (compartment && !debuggees.has(compartment)) { + continue; + } + + Zone* zone = edge.referent.zone(); + if (zone && !debuggeeZones.has(zone)) { + continue; + } + + if (!edges.append(std::move(edge))) { + return {false, JS::AutoCheckCannotGC(cx)}; + } + } + + inited = true; + return {true, JS::AutoCheckCannotGC(cx)}; +} + +std::pair<bool, JS::AutoCheckCannotGC> RootList::init(HandleObject debuggees) { + MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(*debuggees)); + js::Debugger* dbg = js::Debugger::fromJSObject(debuggees.get()); + + CompartmentSet debuggeeCompartments; + + for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); + r.popFront()) { + if (!debuggeeCompartments.put(r.front()->compartment())) { + return {false, JS::AutoCheckCannotGC(cx)}; + } + } + + auto [ok, nogc] = init(debuggeeCompartments); + if (!ok) { + return {false, nogc}; + } + + // 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, nogc}; + } + } + + inited = true; + return {true, nogc}; +} + +bool RootList::addRoot(Node node, const char16_t* edgeName) { + MOZ_ASSERT_IF(wantNames, edgeName); + + UniqueTwoByteChars name; + if (edgeName) { + name = js::DuplicateString(edgeName); + if (!name) { + return false; + } + } + + return edges.append(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 js::MakeUnique<PreComputedEdgeRange>(get().edges); +} + +bool SimpleEdgeRange::addTracerEdges(JSRuntime* rt, void* thing, + JS::TraceKind kind, bool wantNames) { + EdgeVectorTracer tracer(rt, &edges, wantNames); + JS::TraceChildren(&tracer, JS::GCCellPtr(thing, kind)); + settle(); + return tracer.okay; +} + +void Concrete<JSObject>::construct(void* storage, JSObject* ptr) { + if (ptr) { + auto clasp = ptr->getClass(); + auto callback = ptr->compartment() + ->runtimeFromMainThread() + ->constructUbiNodeForDOMObjectCallback; + if (clasp->isDOMClass() && callback) { + AutoSuppressGCAnalysis suppress; + callback(storage, ptr); + return; + } + } + new (storage) Concrete(ptr); +} + +void SetConstructUbiNodeForDOMObjectCallback(JSContext* cx, + void (*callback)(void*, + JSObject*)) { + cx->runtime()->constructUbiNodeForDOMObjectCallback = callback; +} + +JS_PUBLIC_API const char* CoarseTypeToString(CoarseType type) { + switch (type) { + case CoarseType::Other: + return "Other"; + case CoarseType::Object: + return "Object"; + case CoarseType::Script: + return "Script"; + case CoarseType::String: + return "String"; + case CoarseType::DOMNode: + return "DOMNode"; + default: + return "Unknown"; + } +}; + +} // namespace ubi +} // namespace JS |