/* -*- 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 void CheckTracedThing(JSTracer* trc, T thing); } // namespace js /*** Callback Tracer Dispatch ********************************************************************/ template 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(JS::CallbackTracer*, type**, const char*); JS_FOR_EACH_TRACEKIND(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS); #undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS template struct DoCallbackFunctor : public IdentityDefaultAdaptor { template S operator()(T* t, JS::CallbackTracer* trc, const char* name) { return js::gc::RewrapTaggedPointer::wrap(DoCallback(trc, &t, name)); } }; template <> Value DoCallback(JS::CallbackTracer* trc, Value* vp, const char* name) { *vp = DispatchTyped(DoCallbackFunctor(), *vp, trc, name); return *vp; } template <> jsid DoCallback(JS::CallbackTracer* trc, jsid* idp, const char* name) { *idp = DispatchTyped(DoCallbackFunctor(), *idp, trc, name); return *idp; } template <> TaggedProto DoCallback(JS::CallbackTracer* trc, TaggedProto* protop, const char* name) { *protop = DispatchTyped(DoCallbackFunctor(), *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* valuep, const char* name) { TraceManuallyBarrieredEdge(trc, valuep->unsafeGet(), name); } JS_PUBLIC_API(void) JS_CallIdTracer(JSTracer* trc, JS::Heap* idp, const char* name) { TraceManuallyBarrieredEdge(trc, idp->unsafeGet(), name); } JS_PUBLIC_API(void) JS_CallObjectTracer(JSTracer* trc, JS::Heap* objp, const char* name) { TraceManuallyBarrieredEdge(trc, objp->unsafeGet(), name); } JS_PUBLIC_API(void) JS_CallStringTracer(JSTracer* trc, JS::Heap* strp, const char* name) { TraceManuallyBarrieredEdge(trc, strp->unsafeGet(), name); } JS_PUBLIC_API(void) JS_CallScriptTracer(JSTracer* trc, JS::Heap* scriptp, const char* name) { TraceManuallyBarrieredEdge(trc, scriptp->unsafeGet(), name); } JS_PUBLIC_API(void) JS_CallFunctionTracer(JSTracer* trc, JS::Heap* funp, const char* name) { TraceManuallyBarrieredEdge(trc, funp->unsafeGet(), name); } JS_PUBLIC_API(void) JS_CallTenuredObjectTracer(JSTracer* trc, JS::TenuredHeap* 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 void operator()(JSTracer* trc, void* thing) { static_cast(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(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(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 seen, worklist; }; void ObjectGroupCycleCollectorTracer::onChild(const JS::GCCellPtr& thing) { if (thing.is() || thing.is()) { // Invoke the inner cycle collector callback on this child. It will not // recurse back into TraceChildren. innerTracer->onChild(thing); return; } if (thing.is()) { // 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(); 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(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* fun = &obj->as(); 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().getPrivate()); } else { JS_snprintf(buf, bufsize, " "); } break; } case JS::TraceKind::Script: { JSScript* script = static_cast(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(" ") + CountDecimalDigits(str->length()) < bufsize; n = JS_snprintf(buf, bufsize, " ", (int)str->length(), willFit ? "" : " (truncated)"); buf += n; bufsize -= n; PutEscapedString(buf, bufsize, &str->asLinear(), 0); } else { JS_snprintf(buf, bufsize, "", (int)str->length()); } break; } case JS::TraceKind::Symbol: { JS::Symbol* sym = static_cast(thing); if (JSString* desc = sym->description()) { if (desc->isLinear()) { *buf++ = ' '; bufsize--; PutEscapedString(buf, bufsize, &desc->asLinear(), 0); } else { JS_snprintf(buf, bufsize, ""); } } else { JS_snprintf(buf, bufsize, ""); } break; } default: break; } } buf[bufsize - 1] = '\0'; }