| /* -*- 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" |
| |
| // Unified leak fix: |
| #include "builtin/ModuleObject.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'; |
| } |