mirror of
https://github.com/ManchildProductions/UXP-Fixed.git
synced 2026-06-21 01:58:29 +00:00
520 lines
14 KiB
C++
520 lines
14 KiB
C++
/* -*- 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
|