mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-06-16 14:29:46 +00:00
540 lines
16 KiB
C++
540 lines
16 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 "gc/Tracer.h"
|
||
|
||
#include "mozilla/DebugOnly.h"
|
||
#include "mozilla/SizePrintfMacros.h"
|
||
|
||
#include "jsapi.h"
|
||
#include "jsfun.h"
|
||
#include "jsgc.h"
|
||
#include "jsprf.h"
|
||
#include "jsscript.h"
|
||
#include "jsutil.h"
|
||
#include "NamespaceImports.h"
|
||
|
||
#include "gc/GCInternals.h"
|
||
#include "gc/Marking.h"
|
||
#include "gc/Zone.h"
|
||
|
||
#include "vm/Shape.h"
|
||
#include "vm/Symbol.h"
|
||
|
||
#include "jscompartmentinlines.h"
|
||
#include "jsgcinlines.h"
|
||
|
||
#include "vm/ObjectGroup-inl.h"
|
||
|
||
using namespace js;
|
||
using namespace js::gc;
|
||
using mozilla::DebugOnly;
|
||
|
||
namespace js {
|
||
template<typename T>
|
||
void
|
||
CheckTracedThing(JSTracer* trc, T thing);
|
||
} // namespace js
|
||
|
||
|
||
/*** Callback Tracer Dispatch ********************************************************************/
|
||
|
||
template <typename T>
|
||
T
|
||
DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name)
|
||
{
|
||
CheckTracedThing(trc, *thingp);
|
||
JS::AutoTracingName ctx(trc, name);
|
||
trc->dispatchToOnEdge(thingp);
|
||
return *thingp;
|
||
}
|
||
#define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(name, type, _) \
|
||
template type* DoCallback<type*>(JS::CallbackTracer*, type**, const char*);
|
||
JS_FOR_EACH_TRACEKIND(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS);
|
||
#undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS
|
||
|
||
template <typename S>
|
||
struct DoCallbackFunctor : public IdentityDefaultAdaptor<S> {
|
||
template <typename T> S operator()(T* t, JS::CallbackTracer* trc, const char* name) {
|
||
return js::gc::RewrapTaggedPointer<S, T*>::wrap(DoCallback(trc, &t, name));
|
||
}
|
||
};
|
||
|
||
template <>
|
||
Value
|
||
DoCallback<Value>(JS::CallbackTracer* trc, Value* vp, const char* name)
|
||
{
|
||
*vp = DispatchTyped(DoCallbackFunctor<Value>(), *vp, trc, name);
|
||
return *vp;
|
||
}
|
||
|
||
template <>
|
||
jsid
|
||
DoCallback<jsid>(JS::CallbackTracer* trc, jsid* idp, const char* name)
|
||
{
|
||
*idp = DispatchTyped(DoCallbackFunctor<jsid>(), *idp, trc, name);
|
||
return *idp;
|
||
}
|
||
|
||
template <>
|
||
TaggedProto
|
||
DoCallback<TaggedProto>(JS::CallbackTracer* trc, TaggedProto* protop, const char* name)
|
||
{
|
||
*protop = DispatchTyped(DoCallbackFunctor<TaggedProto>(), *protop, trc, name);
|
||
return *protop;
|
||
}
|
||
|
||
void
|
||
JS::CallbackTracer::getTracingEdgeName(char* buffer, size_t bufferSize)
|
||
{
|
||
MOZ_ASSERT(bufferSize > 0);
|
||
if (contextFunctor_) {
|
||
(*contextFunctor_)(this, buffer, bufferSize);
|
||
return;
|
||
}
|
||
if (contextIndex_ != InvalidIndex) {
|
||
JS_snprintf(buffer, bufferSize, "%s[%lu]", contextName_, contextIndex_);
|
||
return;
|
||
}
|
||
JS_snprintf(buffer, bufferSize, "%s", contextName_);
|
||
}
|
||
|
||
|
||
/*** Public Tracing API **************************************************************************/
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallUnbarrieredValueTracer(JSTracer* trc, Value* valuep, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, valuep, name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallUnbarrieredIdTracer(JSTracer* trc, jsid* idp, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, idp, name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallUnbarrieredObjectTracer(JSTracer* trc, JSObject** objp, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, objp, name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallUnbarrieredStringTracer(JSTracer* trc, JSString** strp, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, strp, name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallUnbarrieredScriptTracer(JSTracer* trc, JSScript** scriptp, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, scriptp, name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallValueTracer(JSTracer* trc, JS::Heap<JS::Value>* valuep, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, valuep->unsafeGet(), name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallIdTracer(JSTracer* trc, JS::Heap<jsid>* idp, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, idp->unsafeGet(), name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallObjectTracer(JSTracer* trc, JS::Heap<JSObject*>* objp, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, objp->unsafeGet(), name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallStringTracer(JSTracer* trc, JS::Heap<JSString*>* strp, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, strp->unsafeGet(), name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallScriptTracer(JSTracer* trc, JS::Heap<JSScript*>* scriptp, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, scriptp->unsafeGet(), name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallFunctionTracer(JSTracer* trc, JS::Heap<JSFunction*>* funp, const char* name)
|
||
{
|
||
TraceManuallyBarrieredEdge(trc, funp->unsafeGet(), name);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_CallTenuredObjectTracer(JSTracer* trc, JS::TenuredHeap<JSObject*>* objp, const char* name)
|
||
{
|
||
JSObject* obj = objp->getPtr();
|
||
if (!obj)
|
||
return;
|
||
|
||
TraceManuallyBarrieredEdge(trc, &obj, name);
|
||
|
||
objp->setPtr(obj);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS::TraceChildren(JSTracer* trc, GCCellPtr thing)
|
||
{
|
||
js::TraceChildren(trc, thing.asCell(), thing.kind());
|
||
}
|
||
|
||
struct TraceChildrenFunctor {
|
||
template <typename T>
|
||
void operator()(JSTracer* trc, void* thing) {
|
||
static_cast<T*>(thing)->traceChildren(trc);
|
||
}
|
||
};
|
||
|
||
void
|
||
js::TraceChildren(JSTracer* trc, void* thing, JS::TraceKind kind)
|
||
{
|
||
MOZ_ASSERT(thing);
|
||
TraceChildrenFunctor f;
|
||
DispatchTraceKindTyped(f, kind, trc, thing);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_TraceRuntime(JSTracer* trc)
|
||
{
|
||
AssertHeapIsIdle(trc->runtime());
|
||
TraceRuntime(trc);
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_TraceIncomingCCWs(JSTracer* trc, const JS::ZoneSet& zones)
|
||
{
|
||
for (js::ZonesIter z(trc->runtime(), SkipAtoms); !z.done(); z.next()) {
|
||
Zone* zone = z.get();
|
||
if (!zone || zones.has(zone))
|
||
continue;
|
||
|
||
for (js::CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
|
||
JSCompartment* comp = c.get();
|
||
if (!comp)
|
||
continue;
|
||
|
||
for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) {
|
||
const CrossCompartmentKey& key = e.front().key();
|
||
JSObject* obj;
|
||
JSScript* script;
|
||
|
||
switch (key.kind) {
|
||
case CrossCompartmentKey::StringWrapper:
|
||
// StringWrappers are just used to avoid copying strings
|
||
// across zones multiple times, and don't hold a strong
|
||
// reference.
|
||
continue;
|
||
|
||
case CrossCompartmentKey::ObjectWrapper:
|
||
case CrossCompartmentKey::DebuggerObject:
|
||
case CrossCompartmentKey::DebuggerSource:
|
||
case CrossCompartmentKey::DebuggerEnvironment:
|
||
obj = static_cast<JSObject*>(key.wrapped);
|
||
// Ignore CCWs whose wrapped value doesn't live in our given
|
||
// set of zones.
|
||
if (!zones.has(obj->zone()))
|
||
continue;
|
||
|
||
TraceManuallyBarrieredEdge(trc, &obj, "cross-compartment wrapper");
|
||
MOZ_ASSERT(obj == key.wrapped);
|
||
break;
|
||
|
||
case CrossCompartmentKey::DebuggerScript:
|
||
script = static_cast<JSScript*>(key.wrapped);
|
||
// Ignore CCWs whose wrapped value doesn't live in our given
|
||
// set of zones.
|
||
if (!zones.has(script->zone()))
|
||
continue;
|
||
TraceManuallyBarrieredEdge(trc, &script, "cross-compartment wrapper");
|
||
MOZ_ASSERT(script == key.wrapped);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/*** Cycle Collector Helpers **********************************************************************/
|
||
|
||
// This function is used by the Cycle Collector (CC) to trace through -- or in
|
||
// CC parlance, traverse -- a Shape tree. The CC does not care about Shapes or
|
||
// BaseShapes, only the JSObjects held live by them. Thus, we walk the Shape
|
||
// lineage, but only report non-Shape things. This effectively makes the entire
|
||
// shape lineage into a single node in the CC, saving tremendous amounts of
|
||
// space and time in its algorithms.
|
||
//
|
||
// The algorithm implemented here uses only bounded stack space. This would be
|
||
// possible to implement outside the engine, but would require much extra
|
||
// infrastructure and many, many more slow GOT lookups. We have implemented it
|
||
// inside SpiderMonkey, despite the lack of general applicability, for the
|
||
// simplicity and performance of FireFox's embedding of this engine.
|
||
void
|
||
gc::TraceCycleCollectorChildren(JS::CallbackTracer* trc, Shape* shape)
|
||
{
|
||
// We need to mark the global, but it's OK to only do this once instead of
|
||
// doing it for every Shape in our lineage, since it's always the same
|
||
// global.
|
||
JSObject* global = shape->compartment()->unsafeUnbarrieredMaybeGlobal();
|
||
MOZ_ASSERT(global);
|
||
DoCallback(trc, &global, "global");
|
||
|
||
do {
|
||
MOZ_ASSERT(global == shape->compartment()->unsafeUnbarrieredMaybeGlobal());
|
||
|
||
MOZ_ASSERT(shape->base());
|
||
shape->base()->assertConsistency();
|
||
|
||
TraceEdge(trc, &shape->propidRef(), "propid");
|
||
|
||
if (shape->hasGetterObject()) {
|
||
JSObject* tmp = shape->getterObject();
|
||
DoCallback(trc, &tmp, "getter");
|
||
MOZ_ASSERT(tmp == shape->getterObject());
|
||
}
|
||
|
||
if (shape->hasSetterObject()) {
|
||
JSObject* tmp = shape->setterObject();
|
||
DoCallback(trc, &tmp, "setter");
|
||
MOZ_ASSERT(tmp == shape->setterObject());
|
||
}
|
||
|
||
shape = shape->previous();
|
||
} while (shape);
|
||
}
|
||
|
||
void
|
||
TraceObjectGroupCycleCollectorChildrenCallback(JS::CallbackTracer* trc,
|
||
void** thingp, JS::TraceKind kind);
|
||
|
||
// Object groups can point to other object groups via an UnboxedLayout or the
|
||
// the original unboxed group link. There can potentially be deep or cyclic
|
||
// chains of such groups to trace through without going through a thing that
|
||
// participates in cycle collection. These need to be handled iteratively to
|
||
// avoid blowing the stack when running the cycle collector's callback tracer.
|
||
struct ObjectGroupCycleCollectorTracer : public JS::CallbackTracer
|
||
{
|
||
explicit ObjectGroupCycleCollectorTracer(JS::CallbackTracer* innerTracer)
|
||
: JS::CallbackTracer(innerTracer->runtime(), DoNotTraceWeakMaps),
|
||
innerTracer(innerTracer)
|
||
{}
|
||
|
||
void onChild(const JS::GCCellPtr& thing) override;
|
||
|
||
JS::CallbackTracer* innerTracer;
|
||
Vector<ObjectGroup*, 4, SystemAllocPolicy> seen, worklist;
|
||
};
|
||
|
||
void
|
||
ObjectGroupCycleCollectorTracer::onChild(const JS::GCCellPtr& thing)
|
||
{
|
||
if (thing.is<JSObject>() || thing.is<JSScript>()) {
|
||
// Invoke the inner cycle collector callback on this child. It will not
|
||
// recurse back into TraceChildren.
|
||
innerTracer->onChild(thing);
|
||
return;
|
||
}
|
||
|
||
if (thing.is<ObjectGroup>()) {
|
||
// If this group is required to be in an ObjectGroup chain, trace it
|
||
// via the provided worklist rather than continuing to recurse.
|
||
ObjectGroup& group = thing.as<ObjectGroup>();
|
||
if (group.maybeUnboxedLayout()) {
|
||
for (size_t i = 0; i < seen.length(); i++) {
|
||
if (seen[i] == &group)
|
||
return;
|
||
}
|
||
if (seen.append(&group) && worklist.append(&group)) {
|
||
return;
|
||
} else {
|
||
// If append fails, keep tracing normally. The worst that will
|
||
// happen is we end up overrecursing.
|
||
}
|
||
}
|
||
}
|
||
|
||
TraceChildren(this, thing.asCell(), thing.kind());
|
||
}
|
||
|
||
void
|
||
gc::TraceCycleCollectorChildren(JS::CallbackTracer* trc, ObjectGroup* group)
|
||
{
|
||
MOZ_ASSERT(trc->isCallbackTracer());
|
||
|
||
// Early return if this group is not required to be in an ObjectGroup chain.
|
||
if (!group->maybeUnboxedLayout())
|
||
return group->traceChildren(trc);
|
||
|
||
ObjectGroupCycleCollectorTracer groupTracer(trc->asCallbackTracer());
|
||
group->traceChildren(&groupTracer);
|
||
|
||
while (!groupTracer.worklist.empty()) {
|
||
ObjectGroup* innerGroup = groupTracer.worklist.popCopy();
|
||
innerGroup->traceChildren(&groupTracer);
|
||
}
|
||
}
|
||
|
||
|
||
/*** Traced Edge Printer *************************************************************************/
|
||
|
||
static size_t
|
||
CountDecimalDigits(size_t num)
|
||
{
|
||
size_t numDigits = 0;
|
||
do {
|
||
num /= 10;
|
||
numDigits++;
|
||
} while (num > 0);
|
||
|
||
return numDigits;
|
||
}
|
||
|
||
JS_PUBLIC_API(void)
|
||
JS_GetTraceThingInfo(char* buf, size_t bufsize, JSTracer* trc, void* thing,
|
||
JS::TraceKind kind, bool details)
|
||
{
|
||
const char* name = nullptr; /* silence uninitialized warning */
|
||
size_t n;
|
||
|
||
if (bufsize == 0)
|
||
return;
|
||
|
||
switch (kind) {
|
||
case JS::TraceKind::Object:
|
||
{
|
||
name = static_cast<JSObject*>(thing)->getClass()->name;
|
||
break;
|
||
}
|
||
|
||
case JS::TraceKind::Script:
|
||
name = "script";
|
||
break;
|
||
|
||
case JS::TraceKind::String:
|
||
name = ((JSString*)thing)->isDependent()
|
||
? "substring"
|
||
: "string";
|
||
break;
|
||
|
||
case JS::TraceKind::Symbol:
|
||
name = "symbol";
|
||
break;
|
||
|
||
case JS::TraceKind::BaseShape:
|
||
name = "base_shape";
|
||
break;
|
||
|
||
case JS::TraceKind::JitCode:
|
||
name = "jitcode";
|
||
break;
|
||
|
||
case JS::TraceKind::LazyScript:
|
||
name = "lazyscript";
|
||
break;
|
||
|
||
case JS::TraceKind::Shape:
|
||
name = "shape";
|
||
break;
|
||
|
||
case JS::TraceKind::ObjectGroup:
|
||
name = "object_group";
|
||
break;
|
||
|
||
default:
|
||
name = "INVALID";
|
||
break;
|
||
}
|
||
|
||
n = strlen(name);
|
||
if (n > bufsize - 1)
|
||
n = bufsize - 1;
|
||
js_memcpy(buf, name, n + 1);
|
||
buf += n;
|
||
bufsize -= n;
|
||
*buf = '\0';
|
||
|
||
if (details && bufsize > 2) {
|
||
switch (kind) {
|
||
case JS::TraceKind::Object:
|
||
{
|
||
JSObject* obj = (JSObject*)thing;
|
||
if (obj->is<JSFunction>()) {
|
||
JSFunction* fun = &obj->as<JSFunction>();
|
||
if (fun->displayAtom()) {
|
||
*buf++ = ' ';
|
||
bufsize--;
|
||
PutEscapedString(buf, bufsize, fun->displayAtom(), 0);
|
||
}
|
||
} else if (obj->getClass()->flags & JSCLASS_HAS_PRIVATE) {
|
||
JS_snprintf(buf, bufsize, " %p", obj->as<NativeObject>().getPrivate());
|
||
} else {
|
||
JS_snprintf(buf, bufsize, " <no private>");
|
||
}
|
||
break;
|
||
}
|
||
|
||
case JS::TraceKind::Script:
|
||
{
|
||
JSScript* script = static_cast<JSScript*>(thing);
|
||
JS_snprintf(buf, bufsize, " %s:%" PRIuSIZE, script->filename(), script->lineno());
|
||
break;
|
||
}
|
||
|
||
case JS::TraceKind::String:
|
||
{
|
||
*buf++ = ' ';
|
||
bufsize--;
|
||
JSString* str = (JSString*)thing;
|
||
|
||
if (str->isLinear()) {
|
||
bool willFit = str->length() + strlen("<length > ") +
|
||
CountDecimalDigits(str->length()) < bufsize;
|
||
|
||
n = JS_snprintf(buf, bufsize, "<length %d%s> ",
|
||
(int)str->length(),
|
||
willFit ? "" : " (truncated)");
|
||
buf += n;
|
||
bufsize -= n;
|
||
|
||
PutEscapedString(buf, bufsize, &str->asLinear(), 0);
|
||
} else {
|
||
JS_snprintf(buf, bufsize, "<rope: length %d>", (int)str->length());
|
||
}
|
||
break;
|
||
}
|
||
|
||
case JS::TraceKind::Symbol:
|
||
{
|
||
JS::Symbol* sym = static_cast<JS::Symbol*>(thing);
|
||
if (JSString* desc = sym->description()) {
|
||
if (desc->isLinear()) {
|
||
*buf++ = ' ';
|
||
bufsize--;
|
||
PutEscapedString(buf, bufsize, &desc->asLinear(), 0);
|
||
} else {
|
||
JS_snprintf(buf, bufsize, "<nonlinear desc>");
|
||
}
|
||
} else {
|
||
JS_snprintf(buf, bufsize, "<null>");
|
||
}
|
||
break;
|
||
}
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
buf[bufsize - 1] = '\0';
|
||
}
|