| /* -*- 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 "vm/SavedStacks.h" |
| |
| #include "mozilla/ArrayUtils.h" |
| #include "mozilla/Attributes.h" |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/Move.h" |
| |
| #include <algorithm> |
| #include <math.h> |
| |
| #include "jsapi.h" |
| #include "jscompartment.h" |
| #include "jsfriendapi.h" |
| #include "jshashutil.h" |
| #include "jsmath.h" |
| #include "jsnum.h" |
| #include "jsscript.h" |
| |
| #include "gc/Marking.h" |
| #include "gc/Rooting.h" |
| #include "js/Vector.h" |
| #include "vm/Debugger.h" |
| #include "vm/SavedFrame.h" |
| #include "vm/StringBuffer.h" |
| #include "vm/Time.h" |
| #include "vm/WrapperObject.h" |
| |
| #include "jscntxtinlines.h" |
| |
| #include "vm/NativeObject-inl.h" |
| #include "vm/Stack-inl.h" |
| |
| using mozilla::AddToHash; |
| using mozilla::DebugOnly; |
| using mozilla::HashString; |
| using mozilla::Maybe; |
| using mozilla::Move; |
| using mozilla::Nothing; |
| using mozilla::Some; |
| |
| namespace js { |
| |
| /** |
| * Maximum number of saved frames returned for an async stack. |
| */ |
| const unsigned ASYNC_STACK_MAX_FRAME_COUNT = 60; |
| |
| /* static */ Maybe<LiveSavedFrameCache::FramePtr> |
| LiveSavedFrameCache::getFramePtr(FrameIter& iter) |
| { |
| if (iter.hasUsableAbstractFramePtr()) |
| return Some(FramePtr(iter.abstractFramePtr())); |
| |
| if (iter.isPhysicalIonFrame()) |
| return Some(FramePtr(iter.physicalIonFrame())); |
| |
| return Nothing(); |
| } |
| |
| /* static */ void |
| LiveSavedFrameCache::trace(LiveSavedFrameCache* cache, JSTracer* trc) |
| { |
| if (!cache->initialized()) |
| return; |
| |
| for (auto* entry = cache->frames->begin(); entry < cache->frames->end(); entry++) { |
| TraceEdge(trc, |
| &entry->savedFrame, |
| "LiveSavedFrameCache::frames SavedFrame"); |
| } |
| } |
| |
| bool |
| LiveSavedFrameCache::insert(JSContext* cx, FramePtr& framePtr, jsbytecode* pc, |
| HandleSavedFrame savedFrame) |
| { |
| MOZ_ASSERT(initialized()); |
| |
| if (!frames->emplaceBack(framePtr, pc, savedFrame)) { |
| ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| // Safe to dereference the cache key because the stack frames are still |
| // live. After this point, they should never be dereferenced again. |
| if (framePtr.is<AbstractFramePtr>()) |
| framePtr.as<AbstractFramePtr>().setHasCachedSavedFrame(); |
| else |
| framePtr.as<jit::CommonFrameLayout*>()->setHasCachedSavedFrame(); |
| |
| return true; |
| } |
| |
| void |
| LiveSavedFrameCache::find(JSContext* cx, FrameIter& frameIter, MutableHandleSavedFrame frame) const |
| { |
| MOZ_ASSERT(initialized()); |
| MOZ_ASSERT(!frameIter.done()); |
| MOZ_ASSERT(frameIter.hasCachedSavedFrame()); |
| |
| Maybe<FramePtr> maybeFramePtr = getFramePtr(frameIter); |
| MOZ_ASSERT(maybeFramePtr.isSome()); |
| |
| FramePtr framePtr(*maybeFramePtr); |
| jsbytecode* pc = frameIter.pc(); |
| size_t numberStillValid = 0; |
| |
| frame.set(nullptr); |
| for (auto* p = frames->begin(); p < frames->end(); p++) { |
| numberStillValid++; |
| if (framePtr == p->framePtr && pc == p->pc) { |
| frame.set(p->savedFrame); |
| break; |
| } |
| } |
| |
| if (!frame) { |
| frames->clear(); |
| return; |
| } |
| |
| MOZ_ASSERT(0 < numberStillValid && numberStillValid <= frames->length()); |
| |
| if (frame->compartment() != cx->compartment()) { |
| frame.set(nullptr); |
| numberStillValid--; |
| } |
| |
| // Everything after the cached SavedFrame are stale younger frames we have |
| // since popped. |
| frames->shrinkBy(frames->length() - numberStillValid); |
| } |
| |
| struct SavedFrame::Lookup { |
| Lookup(JSAtom* source, uint32_t line, uint32_t column, |
| JSAtom* functionDisplayName, JSAtom* asyncCause, SavedFrame* parent, |
| JSPrincipals* principals, Maybe<LiveSavedFrameCache::FramePtr> framePtr = Nothing(), |
| jsbytecode* pc = nullptr, Activation* activation = nullptr) |
| : source(source), |
| line(line), |
| column(column), |
| functionDisplayName(functionDisplayName), |
| asyncCause(asyncCause), |
| parent(parent), |
| principals(principals), |
| framePtr(framePtr), |
| pc(pc), |
| activation(activation) |
| { |
| MOZ_ASSERT(source); |
| MOZ_ASSERT_IF(framePtr.isSome(), pc); |
| MOZ_ASSERT_IF(framePtr.isSome(), activation); |
| |
| #ifdef JS_MORE_DETERMINISTIC |
| column = 0; |
| #endif |
| } |
| |
| explicit Lookup(SavedFrame& savedFrame) |
| : source(savedFrame.getSource()), |
| line(savedFrame.getLine()), |
| column(savedFrame.getColumn()), |
| functionDisplayName(savedFrame.getFunctionDisplayName()), |
| asyncCause(savedFrame.getAsyncCause()), |
| parent(savedFrame.getParent()), |
| principals(savedFrame.getPrincipals()), |
| framePtr(Nothing()), |
| pc(nullptr), |
| activation(nullptr) |
| { |
| MOZ_ASSERT(source); |
| } |
| |
| JSAtom* source; |
| uint32_t line; |
| uint32_t column; |
| JSAtom* functionDisplayName; |
| JSAtom* asyncCause; |
| SavedFrame* parent; |
| JSPrincipals* principals; |
| |
| // These are used only by the LiveSavedFrameCache and not used for identity or |
| // hashing. |
| Maybe<LiveSavedFrameCache::FramePtr> framePtr; |
| jsbytecode* pc; |
| Activation* activation; |
| |
| void trace(JSTracer* trc) { |
| TraceManuallyBarrieredEdge(trc, &source, "SavedFrame::Lookup::source"); |
| if (functionDisplayName) { |
| TraceManuallyBarrieredEdge(trc, &functionDisplayName, |
| "SavedFrame::Lookup::functionDisplayName"); |
| } |
| if (asyncCause) |
| TraceManuallyBarrieredEdge(trc, &asyncCause, "SavedFrame::Lookup::asyncCause"); |
| if (parent) |
| TraceManuallyBarrieredEdge(trc, &parent, "SavedFrame::Lookup::parent"); |
| } |
| }; |
| |
| class MOZ_STACK_CLASS SavedFrame::AutoLookupVector : public JS::CustomAutoRooter { |
| public: |
| explicit AutoLookupVector(JSContext* cx) |
| : JS::CustomAutoRooter(cx), |
| lookups(cx) |
| { } |
| |
| typedef Vector<Lookup, ASYNC_STACK_MAX_FRAME_COUNT> LookupVector; |
| inline LookupVector* operator->() { return &lookups; } |
| inline HandleLookup operator[](size_t i) { return HandleLookup(lookups[i]); } |
| |
| private: |
| LookupVector lookups; |
| |
| virtual void trace(JSTracer* trc) { |
| for (size_t i = 0; i < lookups.length(); i++) |
| lookups[i].trace(trc); |
| } |
| }; |
| |
| /* static */ HashNumber |
| SavedFrame::HashPolicy::hash(const Lookup& lookup) |
| { |
| JS::AutoCheckCannotGC nogc; |
| // Assume that we can take line mod 2^32 without losing anything of |
| // interest. If that assumption changes, we'll just need to start with 0 |
| // and add another overload of AddToHash with more arguments. |
| return AddToHash(lookup.line, |
| lookup.column, |
| lookup.source, |
| lookup.functionDisplayName, |
| lookup.asyncCause, |
| SavedFramePtrHasher::hash(lookup.parent), |
| JSPrincipalsPtrHasher::hash(lookup.principals)); |
| } |
| |
| /* static */ bool |
| SavedFrame::HashPolicy::match(SavedFrame* existing, const Lookup& lookup) |
| { |
| if (existing->getLine() != lookup.line) |
| return false; |
| |
| if (existing->getColumn() != lookup.column) |
| return false; |
| |
| if (existing->getParent() != lookup.parent) |
| return false; |
| |
| if (existing->getPrincipals() != lookup.principals) |
| return false; |
| |
| JSAtom* source = existing->getSource(); |
| if (source != lookup.source) |
| return false; |
| |
| JSAtom* functionDisplayName = existing->getFunctionDisplayName(); |
| if (functionDisplayName != lookup.functionDisplayName) |
| return false; |
| |
| JSAtom* asyncCause = existing->getAsyncCause(); |
| if (asyncCause != lookup.asyncCause) |
| return false; |
| |
| return true; |
| } |
| |
| /* static */ void |
| SavedFrame::HashPolicy::rekey(Key& key, const Key& newKey) |
| { |
| key = newKey; |
| } |
| |
| /* static */ bool |
| SavedFrame::finishSavedFrameInit(JSContext* cx, HandleObject ctor, HandleObject proto) |
| { |
| // The only object with the SavedFrame::class_ that doesn't have a source |
| // should be the prototype. |
| proto->as<NativeObject>().setReservedSlot(SavedFrame::JSSLOT_SOURCE, NullValue()); |
| |
| return FreezeObject(cx, proto); |
| } |
| |
| /* static */ const Class SavedFrame::class_ = { |
| "SavedFrame", |
| JSCLASS_HAS_PRIVATE | |
| JSCLASS_HAS_RESERVED_SLOTS(SavedFrame::JSSLOT_COUNT) | |
| JSCLASS_HAS_CACHED_PROTO(JSProto_SavedFrame) | |
| JSCLASS_IS_ANONYMOUS, |
| nullptr, // addProperty |
| nullptr, // delProperty |
| nullptr, // getProperty |
| nullptr, // setProperty |
| nullptr, // enumerate |
| nullptr, // resolve |
| nullptr, // mayResolve |
| SavedFrame::finalize, // finalize |
| nullptr, // call |
| nullptr, // hasInstance |
| nullptr, // construct |
| nullptr, // trace |
| |
| // ClassSpec |
| { |
| GenericCreateConstructor<SavedFrame::construct, 0, gc::AllocKind::FUNCTION>, |
| GenericCreatePrototype, |
| SavedFrame::staticFunctions, |
| nullptr, |
| SavedFrame::protoFunctions, |
| SavedFrame::protoAccessors, |
| SavedFrame::finishSavedFrameInit, |
| ClassSpec::DontDefineConstructor |
| } |
| }; |
| |
| /* static */ const JSFunctionSpec |
| SavedFrame::staticFunctions[] = { |
| JS_FS_END |
| }; |
| |
| /* static */ const JSFunctionSpec |
| SavedFrame::protoFunctions[] = { |
| JS_FN("constructor", SavedFrame::construct, 0, 0), |
| JS_FN("toString", SavedFrame::toStringMethod, 0, 0), |
| JS_FS_END |
| }; |
| |
| /* static */ const JSPropertySpec |
| SavedFrame::protoAccessors[] = { |
| JS_PSG("source", SavedFrame::sourceProperty, 0), |
| JS_PSG("line", SavedFrame::lineProperty, 0), |
| JS_PSG("column", SavedFrame::columnProperty, 0), |
| JS_PSG("functionDisplayName", SavedFrame::functionDisplayNameProperty, 0), |
| JS_PSG("asyncCause", SavedFrame::asyncCauseProperty, 0), |
| JS_PSG("asyncParent", SavedFrame::asyncParentProperty, 0), |
| JS_PSG("parent", SavedFrame::parentProperty, 0), |
| JS_PS_END |
| }; |
| |
| /* static */ void |
| SavedFrame::finalize(FreeOp* fop, JSObject* obj) |
| { |
| JSPrincipals* p = obj->as<SavedFrame>().getPrincipals(); |
| if (p) { |
| JSRuntime* rt = obj->runtimeFromMainThread(); |
| JS_DropPrincipals(rt, p); |
| } |
| } |
| |
| JSAtom* |
| SavedFrame::getSource() |
| { |
| const Value& v = getReservedSlot(JSSLOT_SOURCE); |
| JSString* s = v.toString(); |
| return &s->asAtom(); |
| } |
| |
| uint32_t |
| SavedFrame::getLine() |
| { |
| const Value& v = getReservedSlot(JSSLOT_LINE); |
| return v.toPrivateUint32(); |
| } |
| |
| uint32_t |
| SavedFrame::getColumn() |
| { |
| const Value& v = getReservedSlot(JSSLOT_COLUMN); |
| return v.toPrivateUint32(); |
| } |
| |
| JSAtom* |
| SavedFrame::getFunctionDisplayName() |
| { |
| const Value& v = getReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME); |
| if (v.isNull()) |
| return nullptr; |
| JSString* s = v.toString(); |
| return &s->asAtom(); |
| } |
| |
| JSAtom* |
| SavedFrame::getAsyncCause() |
| { |
| const Value& v = getReservedSlot(JSSLOT_ASYNCCAUSE); |
| if (v.isNull()) |
| return nullptr; |
| JSString* s = v.toString(); |
| return &s->asAtom(); |
| } |
| |
| SavedFrame* |
| SavedFrame::getParent() const |
| { |
| const Value& v = getReservedSlot(JSSLOT_PARENT); |
| return v.isObject() ? &v.toObject().as<SavedFrame>() : nullptr; |
| } |
| |
| JSPrincipals* |
| SavedFrame::getPrincipals() |
| { |
| const Value& v = getReservedSlot(JSSLOT_PRINCIPALS); |
| if (v.isUndefined()) |
| return nullptr; |
| return static_cast<JSPrincipals*>(v.toPrivate()); |
| } |
| |
| void |
| SavedFrame::initSource(JSAtom* source) |
| { |
| MOZ_ASSERT(source); |
| initReservedSlot(JSSLOT_SOURCE, StringValue(source)); |
| } |
| |
| void |
| SavedFrame::initLine(uint32_t line) |
| { |
| initReservedSlot(JSSLOT_LINE, PrivateUint32Value(line)); |
| } |
| |
| void |
| SavedFrame::initColumn(uint32_t column) |
| { |
| #ifdef JS_MORE_DETERMINISTIC |
| column = 0; |
| #endif |
| initReservedSlot(JSSLOT_COLUMN, PrivateUint32Value(column)); |
| } |
| |
| void |
| SavedFrame::initPrincipals(JSPrincipals* principals) |
| { |
| if (principals) |
| JS_HoldPrincipals(principals); |
| initPrincipalsAlreadyHeld(principals); |
| } |
| |
| void |
| SavedFrame::initPrincipalsAlreadyHeld(JSPrincipals* principals) |
| { |
| MOZ_ASSERT_IF(principals, principals->refcount > 0); |
| initReservedSlot(JSSLOT_PRINCIPALS, PrivateValue(principals)); |
| } |
| |
| void |
| SavedFrame::initFunctionDisplayName(JSAtom* maybeName) |
| { |
| initReservedSlot(JSSLOT_FUNCTIONDISPLAYNAME, maybeName ? StringValue(maybeName) : NullValue()); |
| } |
| |
| void |
| SavedFrame::initAsyncCause(JSAtom* maybeCause) |
| { |
| initReservedSlot(JSSLOT_ASYNCCAUSE, maybeCause ? StringValue(maybeCause) : NullValue()); |
| } |
| |
| void |
| SavedFrame::initParent(SavedFrame* maybeParent) |
| { |
| initReservedSlot(JSSLOT_PARENT, ObjectOrNullValue(maybeParent)); |
| } |
| |
| void |
| SavedFrame::initFromLookup(SavedFrame::HandleLookup lookup) |
| { |
| initSource(lookup->source); |
| initLine(lookup->line); |
| initColumn(lookup->column); |
| initFunctionDisplayName(lookup->functionDisplayName); |
| initAsyncCause(lookup->asyncCause); |
| initParent(lookup->parent); |
| initPrincipals(lookup->principals); |
| } |
| |
| /* static */ SavedFrame* |
| SavedFrame::create(JSContext* cx) |
| { |
| RootedGlobalObject global(cx, cx->global()); |
| assertSameCompartment(cx, global); |
| |
| // Ensure that we don't try to capture the stack again in the |
| // `SavedStacksMetadataCallback` for this new SavedFrame object, and |
| // accidentally cause O(n^2) behavior. |
| SavedStacks::AutoReentrancyGuard guard(cx->compartment()->savedStacks()); |
| |
| RootedNativeObject proto(cx, GlobalObject::getOrCreateSavedFramePrototype(cx, global)); |
| if (!proto) |
| return nullptr; |
| assertSameCompartment(cx, proto); |
| |
| RootedObject frameObj(cx, NewObjectWithGivenProto(cx, &SavedFrame::class_, proto)); |
| if (!frameObj) |
| return nullptr; |
| |
| return &frameObj->as<SavedFrame>(); |
| } |
| |
| bool |
| SavedFrame::isSelfHosted() |
| { |
| JSAtom* source = getSource(); |
| return StringEqualsAscii(source, "self-hosted"); |
| } |
| |
| /* static */ bool |
| SavedFrame::construct(JSContext* cx, unsigned argc, Value* vp) |
| { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NO_CONSTRUCTOR, |
| "SavedFrame"); |
| return false; |
| } |
| |
| static bool |
| SavedFrameSubsumedByCaller(JSContext* cx, HandleSavedFrame frame) |
| { |
| auto subsumes = cx->runtime()->securityCallbacks->subsumes; |
| if (!subsumes) |
| return true; |
| |
| auto currentCompartmentPrincipals = cx->compartment()->principals(); |
| MOZ_ASSERT(!ReconstructedSavedFramePrincipals::is(currentCompartmentPrincipals)); |
| |
| auto framePrincipals = frame->getPrincipals(); |
| |
| // Handle SavedFrames that have been reconstructed from stacks in a heap |
| // snapshot. |
| if (framePrincipals == &ReconstructedSavedFramePrincipals::IsSystem) |
| return cx->runningWithTrustedPrincipals(); |
| if (framePrincipals == &ReconstructedSavedFramePrincipals::IsNotSystem) |
| return true; |
| |
| return subsumes(currentCompartmentPrincipals, framePrincipals); |
| } |
| |
| // Return the first SavedFrame in the chain that starts with |frame| whose |
| // principals are subsumed by |principals|, according to |subsumes|. If there is |
| // no such frame, return nullptr. |skippedAsync| is set to true if any of the |
| // skipped frames had the |asyncCause| property set, otherwise it is explicitly |
| // set to false. |
| static SavedFrame* |
| GetFirstSubsumedFrame(JSContext* cx, HandleSavedFrame frame, JS::SavedFrameSelfHosted selfHosted, |
| bool& skippedAsync) |
| { |
| skippedAsync = false; |
| |
| RootedSavedFrame rootedFrame(cx, frame); |
| while (rootedFrame) { |
| if ((selfHosted == JS::SavedFrameSelfHosted::Include || !rootedFrame->isSelfHosted()) && |
| SavedFrameSubsumedByCaller(cx, rootedFrame)) |
| { |
| return rootedFrame; |
| } |
| |
| if (rootedFrame->getAsyncCause()) |
| skippedAsync = true; |
| |
| rootedFrame = rootedFrame->getParent(); |
| } |
| |
| return nullptr; |
| } |
| |
| JS_FRIEND_API(JSObject*) |
| GetFirstSubsumedSavedFrame(JSContext* cx, HandleObject savedFrame, |
| JS::SavedFrameSelfHosted selfHosted) |
| { |
| if (!savedFrame) |
| return nullptr; |
| bool skippedAsync; |
| RootedSavedFrame frame(cx, &savedFrame->as<SavedFrame>()); |
| return GetFirstSubsumedFrame(cx, frame, selfHosted, skippedAsync); |
| } |
| |
| /* static */ bool |
| SavedFrame::checkThis(JSContext* cx, CallArgs& args, const char* fnName, |
| MutableHandleObject frame) |
| { |
| const Value& thisValue = args.thisv(); |
| |
| if (!thisValue.isObject()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, InformalValueTypeName(thisValue)); |
| return false; |
| } |
| |
| JSObject* thisObject = CheckedUnwrap(&thisValue.toObject()); |
| if (!thisObject || !thisObject->is<SavedFrame>()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
| SavedFrame::class_.name, fnName, |
| thisObject ? thisObject->getClass()->name : "object"); |
| return false; |
| } |
| |
| // Check for SavedFrame.prototype, which has the same class as SavedFrame |
| // instances, however doesn't actually represent a captured stack frame. It |
| // is the only object that is<SavedFrame>() but doesn't have a source. |
| if (thisObject->as<SavedFrame>().getReservedSlot(JSSLOT_SOURCE).isNull()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, |
| SavedFrame::class_.name, fnName, "prototype object"); |
| return false; |
| } |
| |
| // Now set "frame" to the actual object we were invoked in (which may be a |
| // wrapper), not the unwrapped version. Consumers will need to know what |
| // that original object was, and will do principal checks as needed. |
| frame.set(&thisValue.toObject()); |
| return true; |
| } |
| |
| // Get the SavedFrame * from the current this value and handle any errors that |
| // might occur therein. |
| // |
| // These parameters must already exist when calling this macro: |
| // - JSContext* cx |
| // - unsigned argc |
| // - Value* vp |
| // - const char* fnName |
| // These parameters will be defined after calling this macro: |
| // - CallArgs args |
| // - Rooted<SavedFrame*> frame (will be non-null) |
| #define THIS_SAVEDFRAME(cx, argc, vp, fnName, args, frame) \ |
| CallArgs args = CallArgsFromVp(argc, vp); \ |
| RootedObject frame(cx); \ |
| if (!checkThis(cx, args, fnName, &frame)) \ |
| return false; |
| |
| } /* namespace js */ |
| |
| namespace JS { |
| |
| namespace { |
| |
| // It's possible that our caller is privileged (and hence would see the entire |
| // stack) but we're working with an SavedFrame object that was captured in |
| // unprivileged code. If so, drop privileges down to its level. The idea is |
| // that this way devtools code that's asking an exception object for a stack to |
| // display will end up with the stack the web developer would see via doing |
| // .stack in a web page, with Firefox implementation details excluded. |
| // |
| // We want callers to pass us the object they were actually passed, not an |
| // unwrapped form of it. That way Xray access to SavedFrame objects should not |
| // be affected by AutoMaybeEnterFrameCompartment and the only things that will |
| // be affected will be cases in which privileged code works with some C++ object |
| // that then pokes at an unprivileged StackFrame it has on hand. |
| class MOZ_STACK_CLASS AutoMaybeEnterFrameCompartment |
| { |
| public: |
| AutoMaybeEnterFrameCompartment(JSContext* cx, |
| HandleObject obj |
| MOZ_GUARD_OBJECT_NOTIFIER_PARAM) |
| { |
| MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
| // Note that obj might be null here, since we're doing this |
| // before UnwrapSavedFrame. |
| if (obj && cx->compartment() != obj->compartment()) |
| { |
| JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes; |
| if (subsumes && subsumes(cx->compartment()->principals(), |
| obj->compartment()->principals())) |
| { |
| ac_.emplace(cx, obj); |
| } |
| } |
| } |
| |
| private: |
| Maybe<JSAutoCompartment> ac_; |
| MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
| }; |
| |
| } // namespace |
| |
| static inline js::SavedFrame* |
| UnwrapSavedFrame(JSContext* cx, HandleObject obj, SavedFrameSelfHosted selfHosted, |
| bool& skippedAsync) |
| { |
| if (!obj) |
| return nullptr; |
| |
| RootedObject savedFrameObj(cx, CheckedUnwrap(obj)); |
| if (!savedFrameObj) |
| return nullptr; |
| |
| MOZ_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*savedFrameObj)); |
| js::RootedSavedFrame frame(cx, &savedFrameObj->as<js::SavedFrame>()); |
| return GetFirstSubsumedFrame(cx, frame, selfHosted, skippedAsync); |
| } |
| |
| JS_PUBLIC_API(SavedFrameResult) |
| GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep, |
| SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) |
| { |
| AutoMaybeEnterFrameCompartment ac(cx, savedFrame); |
| bool skippedAsync; |
| js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync)); |
| if (!frame) { |
| sourcep.set(cx->runtime()->emptyString); |
| return SavedFrameResult::AccessDenied; |
| } |
| sourcep.set(frame->getSource()); |
| return SavedFrameResult::Ok; |
| } |
| |
| JS_PUBLIC_API(SavedFrameResult) |
| GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep, |
| SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) |
| { |
| MOZ_ASSERT(linep); |
| AutoMaybeEnterFrameCompartment ac(cx, savedFrame); |
| bool skippedAsync; |
| js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync)); |
| if (!frame) { |
| *linep = 0; |
| return SavedFrameResult::AccessDenied; |
| } |
| *linep = frame->getLine(); |
| return SavedFrameResult::Ok; |
| } |
| |
| JS_PUBLIC_API(SavedFrameResult) |
| GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp, |
| SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) |
| { |
| MOZ_ASSERT(columnp); |
| AutoMaybeEnterFrameCompartment ac(cx, savedFrame); |
| bool skippedAsync; |
| js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync)); |
| if (!frame) { |
| *columnp = 0; |
| return SavedFrameResult::AccessDenied; |
| } |
| *columnp = frame->getColumn(); |
| return SavedFrameResult::Ok; |
| } |
| |
| JS_PUBLIC_API(SavedFrameResult) |
| GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep, |
| SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) |
| { |
| AutoMaybeEnterFrameCompartment ac(cx, savedFrame); |
| bool skippedAsync; |
| js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync)); |
| if (!frame) { |
| namep.set(nullptr); |
| return SavedFrameResult::AccessDenied; |
| } |
| namep.set(frame->getFunctionDisplayName()); |
| return SavedFrameResult::Ok; |
| } |
| |
| JS_PUBLIC_API(SavedFrameResult) |
| GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep, |
| SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) |
| { |
| AutoMaybeEnterFrameCompartment ac(cx, savedFrame); |
| bool skippedAsync; |
| js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync)); |
| if (!frame) { |
| asyncCausep.set(nullptr); |
| return SavedFrameResult::AccessDenied; |
| } |
| asyncCausep.set(frame->getAsyncCause()); |
| if (!asyncCausep && skippedAsync) |
| asyncCausep.set(cx->names().Async); |
| return SavedFrameResult::Ok; |
| } |
| |
| JS_PUBLIC_API(SavedFrameResult) |
| GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp, |
| SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) |
| { |
| AutoMaybeEnterFrameCompartment ac(cx, savedFrame); |
| bool skippedAsync; |
| js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync)); |
| if (!frame) { |
| asyncParentp.set(nullptr); |
| return SavedFrameResult::AccessDenied; |
| } |
| js::RootedSavedFrame parent(cx, frame->getParent()); |
| |
| // The current value of |skippedAsync| is not interesting, because we are |
| // interested in whether we would cross any async parents to get from here |
| // to the first subsumed parent frame instead. |
| js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, selfHosted, |
| skippedAsync)); |
| |
| // Even if |parent| is not subsumed, we still want to return a pointer to it |
| // rather than |subsumedParent| so it can pick up any |asyncCause| from the |
| // inaccessible part of the chain. |
| if (subsumedParent && (subsumedParent->getAsyncCause() || skippedAsync)) |
| asyncParentp.set(parent); |
| else |
| asyncParentp.set(nullptr); |
| return SavedFrameResult::Ok; |
| } |
| |
| JS_PUBLIC_API(SavedFrameResult) |
| GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp, |
| SavedFrameSelfHosted selfHosted /* = SavedFrameSelfHosted::Include */) |
| { |
| AutoMaybeEnterFrameCompartment ac(cx, savedFrame); |
| bool skippedAsync; |
| js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, selfHosted, skippedAsync)); |
| if (!frame) { |
| parentp.set(nullptr); |
| return SavedFrameResult::AccessDenied; |
| } |
| js::RootedSavedFrame parent(cx, frame->getParent()); |
| |
| // The current value of |skippedAsync| is not interesting, because we are |
| // interested in whether we would cross any async parents to get from here |
| // to the first subsumed parent frame instead. |
| js::RootedSavedFrame subsumedParent(cx, GetFirstSubsumedFrame(cx, parent, selfHosted, |
| skippedAsync)); |
| |
| // Even if |parent| is not subsumed, we still want to return a pointer to it |
| // rather than |subsumedParent| so it can pick up any |asyncCause| from the |
| // inaccessible part of the chain. |
| if (subsumedParent && !(subsumedParent->getAsyncCause() || skippedAsync)) |
| parentp.set(parent); |
| else |
| parentp.set(nullptr); |
| return SavedFrameResult::Ok; |
| } |
| |
| JS_PUBLIC_API(bool) |
| BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp, size_t indent) |
| { |
| js::StringBuffer sb(cx); |
| |
| // Enter a new block to constrain the scope of possibly entering the stack's |
| // compartment. This ensures that when we finish the StringBuffer, we are |
| // back in the cx's original compartment, and fulfill our contract with |
| // callers to place the output string in the cx's current compartment. |
| { |
| AutoMaybeEnterFrameCompartment ac(cx, stack); |
| bool skippedAsync; |
| js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, stack, SavedFrameSelfHosted::Exclude, |
| skippedAsync)); |
| if (!frame) { |
| stringp.set(cx->runtime()->emptyString); |
| return true; |
| } |
| |
| js::RootedSavedFrame parent(cx); |
| do { |
| MOZ_ASSERT(SavedFrameSubsumedByCaller(cx, frame)); |
| MOZ_ASSERT(!frame->isSelfHosted()); |
| |
| RootedString asyncCause(cx, frame->getAsyncCause()); |
| if (!asyncCause && skippedAsync) |
| asyncCause.set(cx->names().Async); |
| |
| js::RootedAtom name(cx, frame->getFunctionDisplayName()); |
| if ((indent && !sb.appendN(' ', indent)) |
| || (asyncCause && (!sb.append(asyncCause) || !sb.append('*'))) |
| || (name && !sb.append(name)) |
| || !sb.append('@') |
| || !sb.append(frame->getSource()) |
| || !sb.append(':') |
| || !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb) |
| || !sb.append(':') |
| || !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb) |
| || !sb.append('\n')) |
| { |
| return false; |
| } |
| |
| parent = frame->getParent(); |
| frame = js::GetFirstSubsumedFrame(cx, parent, SavedFrameSelfHosted::Exclude, skippedAsync); |
| } while (frame); |
| } |
| |
| JSString* str = sb.finishString(); |
| if (!str) |
| return false; |
| assertSameCompartment(cx, str); |
| stringp.set(str); |
| return true; |
| } |
| |
| } /* namespace JS */ |
| |
| namespace js { |
| |
| /* static */ bool |
| SavedFrame::sourceProperty(JSContext* cx, unsigned argc, Value* vp) |
| { |
| THIS_SAVEDFRAME(cx, argc, vp, "(get source)", args, frame); |
| RootedString source(cx); |
| if (JS::GetSavedFrameSource(cx, frame, &source) == JS::SavedFrameResult::Ok) { |
| if (!cx->compartment()->wrap(cx, &source)) |
| return false; |
| args.rval().setString(source); |
| } else { |
| args.rval().setNull(); |
| } |
| return true; |
| } |
| |
| /* static */ bool |
| SavedFrame::lineProperty(JSContext* cx, unsigned argc, Value* vp) |
| { |
| THIS_SAVEDFRAME(cx, argc, vp, "(get line)", args, frame); |
| uint32_t line; |
| if (JS::GetSavedFrameLine(cx, frame, &line) == JS::SavedFrameResult::Ok) |
| args.rval().setNumber(line); |
| else |
| args.rval().setNull(); |
| return true; |
| } |
| |
| /* static */ bool |
| SavedFrame::columnProperty(JSContext* cx, unsigned argc, Value* vp) |
| { |
| THIS_SAVEDFRAME(cx, argc, vp, "(get column)", args, frame); |
| uint32_t column; |
| if (JS::GetSavedFrameColumn(cx, frame, &column) == JS::SavedFrameResult::Ok) |
| args.rval().setNumber(column); |
| else |
| args.rval().setNull(); |
| return true; |
| } |
| |
| /* static */ bool |
| SavedFrame::functionDisplayNameProperty(JSContext* cx, unsigned argc, Value* vp) |
| { |
| THIS_SAVEDFRAME(cx, argc, vp, "(get functionDisplayName)", args, frame); |
| RootedString name(cx); |
| JS::SavedFrameResult result = JS::GetSavedFrameFunctionDisplayName(cx, frame, &name); |
| if (result == JS::SavedFrameResult::Ok && name) { |
| if (!cx->compartment()->wrap(cx, &name)) |
| return false; |
| args.rval().setString(name); |
| } else { |
| args.rval().setNull(); |
| } |
| return true; |
| } |
| |
| /* static */ bool |
| SavedFrame::asyncCauseProperty(JSContext* cx, unsigned argc, Value* vp) |
| { |
| THIS_SAVEDFRAME(cx, argc, vp, "(get asyncCause)", args, frame); |
| RootedString asyncCause(cx); |
| JS::SavedFrameResult result = JS::GetSavedFrameAsyncCause(cx, frame, &asyncCause); |
| if (result == JS::SavedFrameResult::Ok && asyncCause) { |
| if (!cx->compartment()->wrap(cx, &asyncCause)) |
| return false; |
| args.rval().setString(asyncCause); |
| } else { |
| args.rval().setNull(); |
| } |
| return true; |
| } |
| |
| /* static */ bool |
| SavedFrame::asyncParentProperty(JSContext* cx, unsigned argc, Value* vp) |
| { |
| THIS_SAVEDFRAME(cx, argc, vp, "(get asyncParent)", args, frame); |
| RootedObject asyncParent(cx); |
| (void) JS::GetSavedFrameAsyncParent(cx, frame, &asyncParent); |
| if (!cx->compartment()->wrap(cx, &asyncParent)) |
| return false; |
| args.rval().setObjectOrNull(asyncParent); |
| return true; |
| } |
| |
| /* static */ bool |
| SavedFrame::parentProperty(JSContext* cx, unsigned argc, Value* vp) |
| { |
| THIS_SAVEDFRAME(cx, argc, vp, "(get parent)", args, frame); |
| RootedObject parent(cx); |
| (void) JS::GetSavedFrameParent(cx, frame, &parent); |
| if (!cx->compartment()->wrap(cx, &parent)) |
| return false; |
| args.rval().setObjectOrNull(parent); |
| return true; |
| } |
| |
| /* static */ bool |
| SavedFrame::toStringMethod(JSContext* cx, unsigned argc, Value* vp) |
| { |
| THIS_SAVEDFRAME(cx, argc, vp, "toString", args, frame); |
| RootedString string(cx); |
| if (!JS::BuildStackString(cx, frame, &string)) |
| return false; |
| args.rval().setString(string); |
| return true; |
| } |
| |
| bool |
| SavedStacks::init() |
| { |
| mozilla::Array<uint64_t, 2> seed; |
| GenerateXorShift128PlusSeed(seed); |
| bernoulli.setRandomState(seed[0], seed[1]); |
| |
| if (!pcLocationMap.init()) |
| return false; |
| |
| return frames.init(); |
| } |
| |
| bool |
| SavedStacks::saveCurrentStack(JSContext* cx, MutableHandleSavedFrame frame, unsigned maxFrameCount) |
| { |
| MOZ_ASSERT(initialized()); |
| assertSameCompartment(cx, this); |
| |
| if (creatingSavedFrame || |
| cx->isExceptionPending() || |
| !cx->global()->isStandardClassResolved(JSProto_Object)) |
| { |
| frame.set(nullptr); |
| return true; |
| } |
| |
| FrameIter iter(cx, FrameIter::ALL_CONTEXTS, FrameIter::GO_THROUGH_SAVED); |
| return insertFrames(cx, iter, frame, maxFrameCount); |
| } |
| |
| bool |
| SavedStacks::copyAsyncStack(JSContext* cx, HandleObject asyncStack, HandleString asyncCause, |
| MutableHandleSavedFrame adoptedStack, unsigned maxFrameCount) |
| { |
| MOZ_ASSERT(initialized()); |
| assertSameCompartment(cx, this); |
| |
| RootedObject asyncStackObj(cx, CheckedUnwrap(asyncStack)); |
| MOZ_ASSERT(asyncStackObj); |
| MOZ_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*asyncStackObj)); |
| RootedSavedFrame frame(cx, &asyncStackObj->as<js::SavedFrame>()); |
| |
| return adoptAsyncStack(cx, frame, asyncCause, adoptedStack, maxFrameCount); |
| } |
| |
| void |
| SavedStacks::sweep(JSRuntime* rt) |
| { |
| frames.sweep(); |
| sweepPCLocationMap(); |
| } |
| |
| void |
| SavedStacks::trace(JSTracer* trc) |
| { |
| if (pcLocationMap.initialized()) { |
| // Mark each of the source strings in our pc to location cache. |
| for (PCLocationMap::Enum e(pcLocationMap); !e.empty(); e.popFront()) { |
| LocationValue& loc = e.front().value(); |
| TraceEdge(trc, &loc.source, "SavedStacks::PCLocationMap's memoized script source name"); |
| } |
| } |
| } |
| |
| uint32_t |
| SavedStacks::count() |
| { |
| MOZ_ASSERT(initialized()); |
| return frames.count(); |
| } |
| |
| void |
| SavedStacks::clear() |
| { |
| frames.clear(); |
| } |
| |
| size_t |
| SavedStacks::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) |
| { |
| return frames.sizeOfExcludingThis(mallocSizeOf); |
| } |
| |
| bool |
| SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFrame frame, |
| unsigned maxFrameCount) |
| { |
| // In order to lookup a cached SavedFrame object, we need to have its parent |
| // SavedFrame, which means we need to walk the stack from oldest frame to |
| // youngest. However, FrameIter walks the stack from youngest frame to |
| // oldest. The solution is to append stack frames to a vector as we walk the |
| // stack with FrameIter, and then do a second pass through that vector in |
| // reverse order after the traversal has completed and get or create the |
| // SavedFrame objects at that time. |
| // |
| // To avoid making many copies of FrameIter (whose copy constructor is |
| // relatively slow), we use a vector of `SavedFrame::Lookup` objects, which |
| // only contain the FrameIter data we need. The `SavedFrame::Lookup` |
| // objects are partially initialized with everything except their parent |
| // pointers on the first pass, and then we fill in the parent pointers as we |
| // return in the second pass. |
| |
| Activation* asyncActivation = nullptr; |
| RootedSavedFrame asyncStack(cx, nullptr); |
| RootedString asyncCause(cx, nullptr); |
| bool parentIsInCache = false; |
| RootedSavedFrame cachedFrame(cx, nullptr); |
| |
| // Accumulate the vector of Lookup objects in |stackChain|. |
| SavedFrame::AutoLookupVector stackChain(cx); |
| while (!iter.done()) { |
| Activation& activation = *iter.activation(); |
| |
| if (asyncActivation && asyncActivation != &activation) { |
| // We found an async stack in the previous activation, and we |
| // walked past the oldest frame of that activation, we're done. |
| // However, we only want to use the async parent if it was |
| // explicitly requested; if we got here otherwise, we have |
| // a direct parent, which we prefer. |
| if (asyncActivation->asyncCallIsExplicit()) |
| break; |
| asyncActivation = nullptr; |
| } |
| |
| if (!asyncActivation) { |
| asyncStack = activation.asyncStack(); |
| if (asyncStack) { |
| // While walking from the youngest to the oldest frame, we found |
| // an activation that has an async stack set. We will use the |
| // youngest frame of the async stack as the parent of the oldest |
| // frame of this activation. We still need to iterate over other |
| // frames in this activation before reaching the oldest frame. |
| asyncCause = activation.asyncCause(); |
| asyncActivation = &activation; |
| } |
| } |
| |
| AutoLocationValueRooter location(cx); |
| { |
| AutoCompartment ac(cx, iter.compartment()); |
| if (!cx->compartment()->savedStacks().getLocation(cx, iter, &location)) |
| return false; |
| } |
| |
| // The bit set means that the next older parent (frame, pc) pair *must* |
| // be in the cache. |
| if (maxFrameCount == 0) |
| parentIsInCache = iter.hasCachedSavedFrame(); |
| |
| auto displayAtom = iter.isNonEvalFunctionFrame() ? iter.functionDisplayAtom() : nullptr; |
| if (!stackChain->emplaceBack(location->source, |
| location->line, |
| location->column, |
| displayAtom, |
| nullptr, |
| nullptr, |
| iter.compartment()->principals(), |
| LiveSavedFrameCache::getFramePtr(iter), |
| iter.pc(), |
| &activation)) |
| { |
| ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| ++iter; |
| |
| if (maxFrameCount == 1) { |
| // The frame we just saved was the last one we were asked to save. |
| // If we had an async stack, ensure we don't use any of its frames. |
| asyncStack.set(nullptr); |
| break; |
| } |
| |
| if (parentIsInCache && |
| !iter.done() && |
| iter.hasCachedSavedFrame()) |
| { |
| auto* cache = activation.getLiveSavedFrameCache(cx); |
| if (!cache) |
| return false; |
| cache->find(cx, iter, &cachedFrame); |
| if (cachedFrame) |
| break; |
| } |
| |
| // If maxFrameCount is zero there's no limit on the number of frames. |
| if (maxFrameCount == 0) |
| continue; |
| |
| maxFrameCount--; |
| } |
| |
| // Limit the depth of the async stack, if any, and ensure that the |
| // SavedFrame instances we use are stored in the same compartment as the |
| // rest of the synchronous stack chain. |
| RootedSavedFrame parentFrame(cx, cachedFrame); |
| if (asyncStack && !adoptAsyncStack(cx, asyncStack, asyncCause, &parentFrame, maxFrameCount)) |
| return false; |
| |
| // Iterate through |stackChain| in reverse order and get or create the |
| // actual SavedFrame instances. |
| for (size_t i = stackChain->length(); i != 0; i--) { |
| SavedFrame::HandleLookup lookup = stackChain[i-1]; |
| lookup->parent = parentFrame; |
| parentFrame.set(getOrCreateSavedFrame(cx, lookup)); |
| if (!parentFrame) |
| return false; |
| |
| if (maxFrameCount == 0 && lookup->framePtr && parentFrame != cachedFrame) { |
| auto* cache = lookup->activation->getLiveSavedFrameCache(cx); |
| if (!cache || !cache->insert(cx, *lookup->framePtr, lookup->pc, parentFrame)) |
| return false; |
| } |
| } |
| |
| frame.set(parentFrame); |
| return true; |
| } |
| |
| bool |
| SavedStacks::adoptAsyncStack(JSContext* cx, HandleSavedFrame asyncStack, |
| HandleString asyncCause, |
| MutableHandleSavedFrame adoptedStack, |
| unsigned maxFrameCount) |
| { |
| RootedAtom asyncCauseAtom(cx, AtomizeString(cx, asyncCause)); |
| if (!asyncCauseAtom) |
| return false; |
| |
| // If maxFrameCount is zero, the caller asked for an unlimited number of |
| // stack frames, but async stacks are not limited by the available stack |
| // memory, so we need to set an arbitrary limit when collecting them. We |
| // still don't enforce an upper limit if the caller requested more frames. |
| unsigned maxFrames = maxFrameCount > 0 ? maxFrameCount : ASYNC_STACK_MAX_FRAME_COUNT; |
| |
| // Accumulate the vector of Lookup objects in |stackChain|. |
| SavedFrame::AutoLookupVector stackChain(cx); |
| SavedFrame* currentSavedFrame = asyncStack; |
| SavedFrame* firstSavedFrameParent = nullptr; |
| for (unsigned i = 0; i < maxFrames && currentSavedFrame; i++) { |
| if (!stackChain->emplaceBack(*currentSavedFrame)) { |
| ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| currentSavedFrame = currentSavedFrame->getParent(); |
| |
| // Attach the asyncCause to the youngest frame. |
| if (i == 0) { |
| stackChain->back().asyncCause = asyncCauseAtom; |
| firstSavedFrameParent = currentSavedFrame; |
| } |
| } |
| |
| // This is the 1-based index of the oldest frame we care about. |
| size_t oldestFramePosition = stackChain->length(); |
| RootedSavedFrame parentFrame(cx, nullptr); |
| |
| if (currentSavedFrame == nullptr && |
| asyncStack->compartment() == cx->compartment()) { |
| // If we consumed the full async stack, and the stack is in the same |
| // compartment as the one requested, we don't need to rebuild the full |
| // chain again using the lookup objects, we can just reference the |
| // existing chain and change the asyncCause on the younger frame. |
| oldestFramePosition = 1; |
| parentFrame = firstSavedFrameParent; |
| } else if (maxFrameCount == 0 && |
| oldestFramePosition == ASYNC_STACK_MAX_FRAME_COUNT) { |
| // If we captured the maximum number of frames and the caller requested |
| // no specific limit, we only return half of them. This means that for |
| // the next iterations, it's likely we can use the optimization above. |
| oldestFramePosition = ASYNC_STACK_MAX_FRAME_COUNT / 2; |
| } |
| |
| // Iterate through |stackChain| in reverse order and get or create the |
| // actual SavedFrame instances. |
| for (size_t i = oldestFramePosition; i != 0; i--) { |
| SavedFrame::HandleLookup lookup = stackChain[i-1]; |
| lookup->parent = parentFrame; |
| parentFrame.set(getOrCreateSavedFrame(cx, lookup)); |
| if (!parentFrame) |
| return false; |
| } |
| |
| adoptedStack.set(parentFrame); |
| return true; |
| } |
| |
| SavedFrame* |
| SavedStacks::getOrCreateSavedFrame(JSContext* cx, SavedFrame::HandleLookup lookup) |
| { |
| const SavedFrame::Lookup& lookupInstance = lookup.get(); |
| DependentAddPtr<SavedFrame::Set> p(cx, frames, lookupInstance); |
| if (p) |
| return *p; |
| |
| RootedSavedFrame frame(cx, createFrameFromLookup(cx, lookup)); |
| if (!frame) |
| return nullptr; |
| |
| if (!p.add(cx, frames, lookupInstance, frame)) |
| return nullptr; |
| |
| return frame; |
| } |
| |
| SavedFrame* |
| SavedStacks::createFrameFromLookup(JSContext* cx, SavedFrame::HandleLookup lookup) |
| { |
| RootedSavedFrame frame(cx, SavedFrame::create(cx)); |
| if (!frame) |
| return nullptr; |
| frame->initFromLookup(lookup); |
| |
| if (!FreezeObject(cx, frame)) |
| return nullptr; |
| |
| return frame; |
| } |
| |
| /* |
| * Remove entries from the table whose JSScript is being collected. |
| */ |
| void |
| SavedStacks::sweepPCLocationMap() |
| { |
| for (PCLocationMap::Enum e(pcLocationMap); !e.empty(); e.popFront()) { |
| PCKey key = e.front().key(); |
| JSScript* script = key.script.get(); |
| if (IsAboutToBeFinalizedUnbarriered(&script)) { |
| e.removeFront(); |
| } else if (script != key.script.get()) { |
| key.script = script; |
| e.rekeyFront(key); |
| } |
| } |
| } |
| |
| bool |
| SavedStacks::getLocation(JSContext* cx, const FrameIter& iter, MutableHandleLocationValue locationp) |
| { |
| // We should only ever be caching location values for scripts in this |
| // compartment. Otherwise, we would get dead cross-compartment scripts in |
| // the cache because our compartment's sweep method isn't called when their |
| // compartment gets collected. |
| assertSameCompartment(cx, this, iter.compartment()); |
| |
| // When we have a |JSScript| for this frame, use a potentially memoized |
| // location from our PCLocationMap and copy it into |locationp|. When we do |
| // not have a |JSScript| for this frame (asm.js frames), we take a slow path |
| // that doesn't employ memoization, and update |locationp|'s slots directly. |
| |
| if (!iter.hasScript()) { |
| if (const char16_t* displayURL = iter.scriptDisplayURL()) { |
| locationp->source = AtomizeChars(cx, displayURL, js_strlen(displayURL)); |
| } else { |
| const char* filename = iter.scriptFilename() ? iter.scriptFilename() : ""; |
| locationp->source = Atomize(cx, filename, strlen(filename)); |
| } |
| if (!locationp->source) |
| return false; |
| |
| locationp->line = iter.computeLine(&locationp->column); |
| // XXX: Make the column 1-based as in other browsers, instead of 0-based |
| // which is how SpiderMonkey stores it internally. This will be |
| // unnecessary once bug 1144340 is fixed. |
| locationp->column++; |
| return true; |
| } |
| |
| RootedScript script(cx, iter.script()); |
| jsbytecode* pc = iter.pc(); |
| |
| PCKey key(script, pc); |
| PCLocationMap::AddPtr p = pcLocationMap.lookupForAdd(key); |
| |
| if (!p) { |
| RootedAtom source(cx); |
| if (const char16_t* displayURL = iter.scriptDisplayURL()) { |
| source = AtomizeChars(cx, displayURL, js_strlen(displayURL)); |
| } else { |
| const char* filename = script->filename() ? script->filename() : ""; |
| source = Atomize(cx, filename, strlen(filename)); |
| } |
| if (!source) |
| return false; |
| |
| uint32_t column; |
| uint32_t line = PCToLineNumber(script, pc, &column); |
| |
| // Make the column 1-based. See comment above. |
| LocationValue value(source, line, column + 1); |
| if (!pcLocationMap.add(p, key, value)) { |
| ReportOutOfMemory(cx); |
| return false; |
| } |
| } |
| |
| locationp.set(p->value()); |
| return true; |
| } |
| |
| void |
| SavedStacks::chooseSamplingProbability(JSCompartment* compartment) |
| { |
| GlobalObject* global = compartment->maybeGlobal(); |
| if (!global) |
| return; |
| |
| GlobalObject::DebuggerVector* dbgs = global->getDebuggers(); |
| if (!dbgs || dbgs->empty()) |
| return; |
| |
| mozilla::DebugOnly<Debugger**> begin = dbgs->begin(); |
| mozilla::DebugOnly<bool> foundAnyDebuggers = false; |
| |
| double probability = 0; |
| for (Debugger** dbgp = dbgs->begin(); dbgp < dbgs->end(); dbgp++) { |
| // The set of debuggers had better not change while we're iterating, |
| // such that the vector gets reallocated. |
| MOZ_ASSERT(dbgs->begin() == begin); |
| |
| if ((*dbgp)->trackingAllocationSites && (*dbgp)->enabled) { |
| foundAnyDebuggers = true; |
| probability = std::max((*dbgp)->allocationSamplingProbability, |
| probability); |
| } |
| } |
| MOZ_ASSERT(foundAnyDebuggers); |
| |
| bernoulli.setProbability(probability); |
| } |
| |
| JSObject* |
| SavedStacksMetadataCallback(JSContext* cx, JSObject* target) |
| { |
| RootedObject obj(cx, target); |
| |
| SavedStacks& stacks = cx->compartment()->savedStacks(); |
| if (!stacks.bernoulli.trial()) |
| return nullptr; |
| |
| AutoEnterOOMUnsafeRegion oomUnsafe; |
| RootedSavedFrame frame(cx); |
| if (!stacks.saveCurrentStack(cx, &frame)) |
| oomUnsafe.crash("SavedStacksMetadataCallback"); |
| |
| if (!Debugger::onLogAllocationSite(cx, obj, frame, JS_GetCurrentEmbedderTime())) |
| oomUnsafe.crash("SavedStacksMetadataCallback"); |
| |
| MOZ_ASSERT_IF(frame, !frame->is<WrapperObject>()); |
| return frame; |
| } |
| |
| #ifdef JS_CRASH_DIAGNOSTICS |
| void |
| CompartmentChecker::check(SavedStacks* stacks) |
| { |
| if (&compartment->savedStacks() != stacks) { |
| printf("*** Compartment SavedStacks mismatch: %p vs. %p\n", |
| (void*) &compartment->savedStacks(), stacks); |
| MOZ_CRASH(); |
| } |
| } |
| #endif /* JS_CRASH_DIAGNOSTICS */ |
| |
| /* static */ ReconstructedSavedFramePrincipals ReconstructedSavedFramePrincipals::IsSystem; |
| /* static */ ReconstructedSavedFramePrincipals ReconstructedSavedFramePrincipals::IsNotSystem; |
| |
| } /* namespace js */ |
| |
| namespace JS { |
| namespace ubi { |
| |
| bool |
| ConcreteStackFrame<SavedFrame>::isSystem() const |
| { |
| auto trustedPrincipals = get().runtimeFromAnyThread()->trustedPrincipals(); |
| return get().getPrincipals() == trustedPrincipals || |
| get().getPrincipals() == &js::ReconstructedSavedFramePrincipals::IsSystem; |
| } |
| |
| bool |
| ConcreteStackFrame<SavedFrame>::constructSavedFrameStack(JSContext* cx, |
| MutableHandleObject outSavedFrameStack) |
| const |
| { |
| outSavedFrameStack.set(&get()); |
| if (!cx->compartment()->wrap(cx, outSavedFrameStack)) { |
| outSavedFrameStack.set(nullptr); |
| return false; |
| } |
| return true; |
| } |
| |
| // A `mozilla::Variant` matcher that converts the inner value of a |
| // `JS::ubi::AtomOrTwoByteChars` string to a `JSAtom*`. |
| struct MOZ_STACK_CLASS AtomizingMatcher |
| { |
| using ReturnType = JSAtom*; |
| |
| JSContext* cx; |
| size_t length; |
| |
| explicit AtomizingMatcher(JSContext* cx, size_t length) |
| : cx(cx) |
| , length(length) |
| { } |
| |
| JSAtom* match(JSAtom* atom) { |
| MOZ_ASSERT(atom); |
| return atom; |
| } |
| |
| JSAtom* match(const char16_t* chars) { |
| MOZ_ASSERT(chars); |
| return AtomizeChars(cx, chars, length); |
| } |
| }; |
| |
| bool ConstructSavedFrameStackSlow(JSContext* cx, JS::ubi::StackFrame& frame, |
| MutableHandleObject outSavedFrameStack) |
| { |
| SavedFrame::AutoLookupVector stackChain(cx); |
| Rooted<JS::ubi::StackFrame> ubiFrame(cx, frame); |
| |
| while (ubiFrame.get()) { |
| // Convert the source and functionDisplayName strings to atoms. |
| |
| js::RootedAtom source(cx); |
| AtomizingMatcher atomizer(cx, ubiFrame.get().sourceLength()); |
| source = ubiFrame.get().source().match(atomizer); |
| if (!source) |
| return false; |
| |
| js::RootedAtom functionDisplayName(cx); |
| auto nameLength = ubiFrame.get().functionDisplayNameLength(); |
| if (nameLength > 0) { |
| AtomizingMatcher atomizer(cx, nameLength); |
| functionDisplayName = ubiFrame.get().functionDisplayName().match(atomizer); |
| if (!functionDisplayName) |
| return false; |
| } |
| |
| auto principals = js::ReconstructedSavedFramePrincipals::getSingleton(ubiFrame.get()); |
| |
| if (!stackChain->emplaceBack(source, ubiFrame.get().line(), ubiFrame.get().column(), |
| functionDisplayName, /* asyncCause */ nullptr, |
| /* parent */ nullptr, principals)) |
| { |
| ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| ubiFrame = ubiFrame.get().parent(); |
| } |
| |
| js::RootedSavedFrame parentFrame(cx); |
| for (size_t i = stackChain->length(); i != 0; i--) { |
| SavedFrame::HandleLookup lookup = stackChain[i-1]; |
| lookup->parent = parentFrame; |
| parentFrame = cx->compartment()->savedStacks().getOrCreateSavedFrame(cx, lookup); |
| if (!parentFrame) |
| return false; |
| } |
| |
| outSavedFrameStack.set(parentFrame); |
| return true; |
| } |
| |
| |
| } // namespace ubi |
| } // namespace JS |