From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/vm/UbiNode.cpp | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 527 insertions(+) create mode 100644 js/src/vm/UbiNode.cpp (limited to 'js/src/vm/UbiNode.cpp') diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp new file mode 100644 index 0000000000..424c5a9d78 --- /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 + +#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 destination; + size_t maxLength; + + CopyToBufferMatcher(RangedPtr destination, size_t maxLength) + : destination(destination), maxLength(maxLength) {} + + template + static size_t copyToBufferHelper(const CharT* src, RangedPtr 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 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 destination, + size_t length) const { + auto s = source(); + return s.copyToBuffer(destination, length); +} + +size_t StackFrame::functionDisplayName(RangedPtr 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::coarseType() const { MOZ_CRASH("null ubi::Node"); } +const char16_t* Concrete::typeName() const { + MOZ_CRASH("null ubi::Node"); +} +JS::Zone* Concrete::zone() const { MOZ_CRASH("null ubi::Node"); } +JS::Compartment* Concrete::compartment() const { + MOZ_CRASH("null ubi::Node"); +} +JS::Realm* Concrete::realm() const { MOZ_CRASH("null ubi::Node"); } + +UniquePtr Concrete::edges(JSContext*, bool) const { + MOZ_CRASH("null ubi::Node"); +} + +Node::Size Concrete::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(nullptr); + } +} + +static bool IsSafeToExposeToJS(JSObject* obj) { + if (obj->is() || obj->is() || + obj->is()) { + return false; + } + if (obj->is() && js::IsInternalFunctionObject(*obj)) { + return false; + } + return true; +} + +Value Node::exposeToJS() const { + Value v; + + if (is()) { + JSObject* obj = as(); + if (IsSafeToExposeToJS(obj)) { + v.setObject(*obj); + } else { + v.setUndefined(); + } + } else if (is()) { + v.setString(as()); + } else if (is()) { + v.setSymbol(as()); + } else if (is()) { + v.setBigInt(as()); + } 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() && thing.as().isPermanentAtom()) { + return; + } + if (thing.is() && thing.as().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(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 +JS::Zone* TracerConcrete::zone() const { + return get().zoneFromAnyThread(); +} + +template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; +template JS::Zone* TracerConcrete::zone() const; + +template +UniquePtr TracerConcrete::edges(JSContext* cx, + bool wantNames) const { + auto range = js::MakeUnique(); + if (!range) { + return nullptr; + } + + if (!range->addTracerEdges(cx->runtime(), ptr, + JS::MapTypeToTraceKind::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(range.release()); +} + +template UniquePtr TracerConcrete::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges( + JSContext* cx, bool wantNames) const; +template UniquePtr TracerConcrete::edges( + JSContext* cx, bool wantNames) const; + +template +JS::Compartment* TracerConcreteWithRealm::compartment() const { + return TracerBase::get().compartment(); +} + +template +Realm* TracerConcreteWithRealm::realm() const { + return TracerBase::get().realm(); +} + +template Realm* TracerConcreteWithRealm::realm() const; +template JS::Compartment* TracerConcreteWithRealm::compartment() + const; + +bool Concrete::hasAllocationStack() const { + return !!js::Debugger::getObjectAllocationSite(get()); +} + +StackFrame Concrete::allocationStack() const { + MOZ_ASSERT(hasAllocationStack()); + return StackFrame(js::Debugger::getObjectAllocationSite(get())); +} + +const char* Concrete::jsObjectClassName() const { + return Concrete::get().getClass()->name; +} + +JS::Compartment* Concrete::compartment() const { + return Concrete::get().compartment(); +} + +Realm* Concrete::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::concreteTypeName[] = u"JS::Symbol"; +const char16_t Concrete::concreteTypeName[] = u"JS::BigInt"; +const char16_t Concrete::concreteTypeName[] = u"js::BaseScript"; +const char16_t Concrete::concreteTypeName[] = + u"js::jit::JitCode"; +const char16_t Concrete::concreteTypeName[] = u"js::Shape"; +const char16_t Concrete::concreteTypeName[] = u"js::BaseShape"; +const char16_t Concrete::concreteTypeName[] = + u"js::GetterSetter"; +const char16_t Concrete::concreteTypeName[] = u"js::PropMap"; +const char16_t Concrete::concreteTypeName[] = u"js::Scope"; +const char16_t Concrete::concreteTypeName[] = + u"js::RegExpShared"; + +namespace JS { +namespace ubi { + +RootList::RootList(JSContext* cx, bool wantNames /* = false */) + : cx(cx), wantNames(wantNames), inited(false) {} + +std::pair RootList::init() { + EdgeVectorTracer tracer(cx->runtime(), &edges, wantNames); + js::TraceRuntime(&tracer); + inited = tracer.okay; + return {tracer.okay, JS::AutoCheckCannotGC(cx)}; +} + +std::pair 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 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(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::concreteTypeName[] = u"JS::ubi::RootList"; + +UniquePtr Concrete::edges(JSContext* cx, + bool wantNames) const { + MOZ_ASSERT_IF(wantNames, get().wantNames); + return js::MakeUnique(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::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 -- cgit v1.2.3