summaryrefslogtreecommitdiffstats
path: root/js/src/vm/SavedStacks.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/SavedStacks.cpp')
-rw-r--r--js/src/vm/SavedStacks.cpp1758
1 files changed, 1758 insertions, 0 deletions
diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp
new file mode 100644
index 000000000..84be0f221
--- /dev/null
+++ b/js/src/vm/SavedStacks.cpp
@@ -0,0 +1,1758 @@
+/* -*- 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/Policy.h"
+#include "gc/Rooting.h"
+#include "js/CharacterEncoding.h"
+#include "js/Vector.h"
+#include "vm/Debugger.h"
+#include "vm/SavedFrame.h"
+#include "vm/SPSProfiler.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 uint32_t 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();
+}
+
+void
+LiveSavedFrameCache::trace(JSTracer* trc)
+{
+ if (!initialized())
+ return;
+
+ for (auto* entry = frames->begin(); entry < 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 */ bool
+SavedFrame::HashPolicy::hasHash(const Lookup& l)
+{
+ return SavedFramePtrHasher::hasHash(l.parent);
+}
+
+/* static */ bool
+SavedFrame::HashPolicy::ensureHash(const Lookup& l)
+{
+ return SavedFramePtrHasher::ensureHash(l.parent);
+}
+
+/* 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)
+{
+ MOZ_ASSERT(existing);
+
+ 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 ClassOps SavedFrameClassOps = {
+ 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
+};
+
+const ClassSpec SavedFrame::classSpec_ = {
+ GenericCreateConstructor<SavedFrame::construct, 0, gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype,
+ SavedFrame::staticFunctions,
+ nullptr,
+ SavedFrame::protoFunctions,
+ SavedFrame::protoAccessors,
+ SavedFrame::finishSavedFrameInit,
+ ClassSpec::DontDefineConstructor
+};
+
+/* 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 |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &SavedFrameClassOps,
+ &SavedFrame::classSpec_
+};
+
+/* 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)
+{
+ MOZ_ASSERT(fop->onMainThread());
+ JSPrincipals* p = obj->as<SavedFrame>().getPrincipals();
+ if (p) {
+ JSRuntime* rt = obj->runtimeFromMainThread();
+ JS_DropPrincipals(rt->contextFromMainThread(), 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
+ // `SavedStacksMetadataBuilder` 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,
+ TenuredObject));
+ if (!frameObj)
+ return nullptr;
+
+ return &frameObj->as<SavedFrame>();
+}
+
+bool
+SavedFrame::isSelfHosted(JSContext* cx)
+{
+ JSAtom* source = getSource();
+ return source == cx->names().selfHosted;
+}
+
+/* static */ bool
+SavedFrame::construct(JSContext* cx, unsigned argc, Value* vp)
+{
+ JS_ReportErrorNumberASCII(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(cx)) &&
+ 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 MOZ_MUST_USE bool
+SavedFrame_checkThis(JSContext* cx, CallArgs& args, const char* fnName,
+ MutableHandleObject frame)
+{
+ const Value& thisValue = args.thisv();
+
+ if (!thisValue.isObject()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
+ InformalValueTypeName(thisValue));
+ return false;
+ }
+
+ JSObject* thisObject = CheckedUnwrap(&thisValue.toObject());
+ if (!thisObject || !thisObject->is<SavedFrame>()) {
+ JS_ReportErrorNumberASCII(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 (!SavedFrame::isSavedFrameAndNotProto(*thisObject)) {
+ JS_ReportErrorNumberASCII(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 (!SavedFrame_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;
+
+ MOZ_RELEASE_ASSERT(cx->compartment());
+ if (obj)
+ MOZ_RELEASE_ASSERT(obj->compartment());
+
+ // 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_RELEASE_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 */)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_RELEASE_ASSERT(cx->compartment());
+
+ 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 */)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_RELEASE_ASSERT(cx->compartment());
+ 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 */)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_RELEASE_ASSERT(cx->compartment());
+ 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 */)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_RELEASE_ASSERT(cx->compartment());
+
+ 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 unused_ /* = SavedFrameSelfHosted::Include */)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_RELEASE_ASSERT(cx->compartment());
+
+ AutoMaybeEnterFrameCompartment ac(cx, savedFrame);
+ bool skippedAsync;
+ // This function is always called with self-hosted frames excluded by
+ // GetValueIfNotCached in dom/bindings/Exceptions.cpp. However, we want
+ // to include them because our Promise implementation causes us to have
+ // the async cause on a self-hosted frame. So we just ignore the
+ // parameter and always include self-hosted frames.
+ js::RootedSavedFrame frame(cx, UnwrapSavedFrame(cx, savedFrame, SavedFrameSelfHosted::Include,
+ 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 */)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_RELEASE_ASSERT(cx->compartment());
+
+ 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 */)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_RELEASE_ASSERT(cx->compartment());
+
+ 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;
+}
+
+static bool
+FormatSpiderMonkeyStackFrame(JSContext* cx, js::StringBuffer& sb,
+ js::HandleSavedFrame frame, size_t indent,
+ bool skippedAsync)
+{
+ RootedString asyncCause(cx, frame->getAsyncCause());
+ if (!asyncCause && skippedAsync)
+ asyncCause.set(cx->names().Async);
+
+ js::RootedAtom name(cx, frame->getFunctionDisplayName());
+ return (!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');
+}
+
+static bool
+FormatV8StackFrame(JSContext* cx, js::StringBuffer& sb,
+ js::HandleSavedFrame frame, size_t indent, bool lastFrame)
+{
+ js::RootedAtom name(cx, frame->getFunctionDisplayName());
+ return sb.appendN(' ', indent + 4)
+ && sb.append('a')
+ && sb.append('t')
+ && sb.append(' ')
+ && (!name || (sb.append(name) &&
+ sb.append(' ') &&
+ sb.append('(')))
+ && sb.append(frame->getSource())
+ && sb.append(':')
+ && NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb)
+ && sb.append(':')
+ && NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb)
+ && (!name || sb.append(')'))
+ && (lastFrame || sb.append('\n'));
+}
+
+JS_PUBLIC_API(bool)
+BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp,
+ size_t indent, js::StackFormat format)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_RELEASE_ASSERT(cx->compartment());
+
+ js::StringBuffer sb(cx);
+
+ if (format == js::StackFormat::Default)
+ format = cx->stackFormat();
+ MOZ_ASSERT(format != js::StackFormat::Default);
+
+ // 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(cx));
+
+ parent = frame->getParent();
+ bool skippedNextAsync;
+ js::RootedSavedFrame nextFrame(cx, js::GetFirstSubsumedFrame(cx, parent,
+ SavedFrameSelfHosted::Exclude, skippedNextAsync));
+
+ switch (format) {
+ case js::StackFormat::SpiderMonkey:
+ if (!FormatSpiderMonkeyStackFrame(cx, sb, frame, indent, skippedAsync))
+ return false;
+ break;
+ case js::StackFormat::V8:
+ if (!FormatV8StackFrame(cx, sb, frame, indent, !nextFrame))
+ return false;
+ break;
+ case js::StackFormat::Default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected value");
+ break;
+ }
+
+ frame = nextFrame;
+ skippedAsync = skippedNextAsync;
+ } while (frame);
+ }
+
+ JSString* str = sb.finishString();
+ if (!str)
+ return false;
+ assertSameCompartment(cx, str);
+ stringp.set(str);
+ return true;
+}
+
+JS_PUBLIC_API(bool)
+IsSavedFrame(JSObject* obj)
+{
+ if (!obj)
+ return false;
+
+ JSObject* unwrapped = js::CheckedUnwrap(obj);
+ if (!unwrapped)
+ return false;
+
+ return js::SavedFrame::isSavedFrameAndNotProto(*unwrapped);
+}
+
+} /* 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()
+{
+ return frames.init() &&
+ pcLocationMap.init();
+}
+
+bool
+SavedStacks::saveCurrentStack(JSContext* cx, MutableHandleSavedFrame frame,
+ JS::StackCapture&& capture /* = JS::StackCapture(JS::AllFrames()) */)
+{
+ MOZ_ASSERT(initialized());
+ MOZ_RELEASE_ASSERT(cx->compartment());
+ assertSameCompartment(cx, this);
+
+ if (creatingSavedFrame ||
+ cx->isExceptionPending() ||
+ !cx->global() ||
+ !cx->global()->isStandardClassResolved(JSProto_Object))
+ {
+ frame.set(nullptr);
+ return true;
+ }
+
+ AutoSPSEntry psuedoFrame(cx->runtime(), "js::SavedStacks::saveCurrentStack");
+ FrameIter iter(cx);
+ return insertFrames(cx, iter, frame, mozilla::Move(capture));
+}
+
+bool
+SavedStacks::copyAsyncStack(JSContext* cx, HandleObject asyncStack, HandleString asyncCause,
+ MutableHandleSavedFrame adoptedStack, uint32_t maxFrameCount)
+{
+ MOZ_ASSERT(initialized());
+ MOZ_RELEASE_ASSERT(cx->compartment());
+ assertSameCompartment(cx, this);
+
+ RootedObject asyncStackObj(cx, CheckedUnwrap(asyncStack));
+ MOZ_RELEASE_ASSERT(asyncStackObj);
+ MOZ_RELEASE_ASSERT(js::SavedFrame::isSavedFrameAndNotProto(*asyncStackObj));
+ RootedSavedFrame frame(cx, &asyncStackObj->as<js::SavedFrame>());
+
+ return adoptAsyncStack(cx, frame, asyncCause, adoptedStack, maxFrameCount);
+}
+
+void
+SavedStacks::sweep()
+{
+ frames.sweep();
+ pcLocationMap.sweep();
+}
+
+void
+SavedStacks::trace(JSTracer* trc)
+{
+ pcLocationMap.trace(trc);
+}
+
+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) +
+ pcLocationMap.sizeOfExcludingThis(mallocSizeOf);
+}
+
+// Given that we have captured a stqck frame with the given principals and
+// source, return true if the requested `StackCapture` has been satisfied and
+// stack walking can halt. Return false otherwise (and stack walking and frame
+// capturing should continue).
+static inline bool
+captureIsSatisfied(JSContext* cx, JSPrincipals* principals, const JSAtom* source,
+ JS::StackCapture& capture)
+{
+ class Matcher
+ {
+ JSContext* cx_;
+ JSPrincipals* framePrincipals_;
+ const JSAtom* frameSource_;
+
+ public:
+ Matcher(JSContext* cx, JSPrincipals* principals, const JSAtom* source)
+ : cx_(cx)
+ , framePrincipals_(principals)
+ , frameSource_(source)
+ { }
+
+ bool match(JS::FirstSubsumedFrame& target) {
+ auto subsumes = cx_->runtime()->securityCallbacks->subsumes;
+ return (!subsumes || subsumes(target.principals, framePrincipals_)) &&
+ (!target.ignoreSelfHosted || frameSource_ != cx_->names().selfHosted);
+ }
+
+ bool match(JS::MaxFrames& target) {
+ return target.maxFrames == 1;
+ }
+
+ bool match(JS::AllFrames&) {
+ return false;
+ }
+ };
+
+ Matcher m(cx, principals, source);
+ return capture.match(m);
+}
+
+bool
+SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFrame frame,
+ JS::StackCapture&& capture)
+{
+ // 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.
+ AutoCompartment ac(cx, iter.compartment());
+ const char* cause = activation.asyncCause();
+ UTF8Chars utf8Chars(cause, strlen(cause));
+ size_t twoByteCharsLen = 0;
+ char16_t* twoByteChars = UTF8CharsToNewTwoByteCharsZ(cx, utf8Chars,
+ &twoByteCharsLen).get();
+ if (!twoByteChars)
+ return false;
+
+ // We expect that there will be a relatively small set of
+ // asyncCause reasons ("setTimeout", "promise", etc.), so we
+ // atomize the cause here in hopes of being able to benefit
+ // from reuse.
+ asyncCause = JS_AtomizeUCStringN(cx, twoByteChars, twoByteCharsLen);
+ js_free(twoByteChars);
+ if (!asyncCause)
+ return false;
+ asyncActivation = &activation;
+ }
+ }
+
+ Rooted<LocationValue> 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 (capture.is<JS::AllFrames>())
+ parentIsInCache = iter.hasCachedSavedFrame();
+
+ auto principals = iter.compartment()->principals();
+ auto displayAtom = iter.isFunctionFrame() ? iter.functionDisplayAtom() : nullptr;
+ if (!stackChain->emplaceBack(location.source(),
+ location.line(),
+ location.column(),
+ displayAtom,
+ nullptr,
+ nullptr,
+ principals,
+ LiveSavedFrameCache::getFramePtr(iter),
+ iter.pc(),
+ &activation))
+ {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ if (captureIsSatisfied(cx, principals, location.source(), capture)) {
+ // 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;
+ }
+
+ ++iter;
+
+ if (parentIsInCache &&
+ !iter.done() &&
+ iter.hasCachedSavedFrame())
+ {
+ auto* cache = activation.getLiveSavedFrameCache(cx);
+ if (!cache)
+ return false;
+ cache->find(cx, iter, &cachedFrame);
+ if (cachedFrame)
+ break;
+ }
+
+ if (capture.is<JS::MaxFrames>())
+ capture.as<JS::MaxFrames>().maxFrames--;
+ }
+
+ // 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 && !capture.is<JS::FirstSubsumedFrame>()) {
+ uint32_t maxAsyncFrames = capture.is<JS::MaxFrames>()
+ ? capture.as<JS::MaxFrames>().maxFrames
+ : ASYNC_STACK_MAX_FRAME_COUNT;
+ if (!adoptAsyncStack(cx, asyncStack, asyncCause, &parentFrame, maxAsyncFrames))
+ 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 (capture.is<JS::AllFrames>() && 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,
+ uint32_t 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.
+ uint32_t 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 (uint32_t 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) {
+ MOZ_ASSERT(*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;
+}
+
+bool
+SavedStacks::getLocation(JSContext* cx, const FrameIter& iter,
+ MutableHandle<LocationValue> 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 (wasm 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.displayURL()) {
+ locationp.setSource(AtomizeChars(cx, displayURL, js_strlen(displayURL)));
+ } else {
+ const char* filename = iter.filename() ? iter.filename() : "";
+ locationp.setSource(Atomize(cx, filename, strlen(filename)));
+ }
+ if (!locationp.source())
+ return false;
+
+ uint32_t column = 0;
+ locationp.setLine(iter.computeLine(&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.setColumn(column + 1);
+ 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.displayURL()) {
+ 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<ReadBarriered<Debugger*>*> begin = dbgs->begin();
+ mozilla::DebugOnly<bool> foundAnyDebuggers = false;
+
+ double probability = 0;
+ for (auto 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);
+
+ if (!bernoulliSeeded) {
+ mozilla::Array<uint64_t, 2> seed;
+ GenerateXorShift128PlusSeed(seed);
+ bernoulli.setRandomState(seed[0], seed[1]);
+ bernoulliSeeded = true;
+ }
+
+ bernoulli.setProbability(probability);
+}
+
+JSObject*
+SavedStacks::MetadataBuilder::build(JSContext* cx, HandleObject target,
+ AutoEnterOOMUnsafeRegion& oomUnsafe) const
+{
+ RootedObject obj(cx, target);
+
+ SavedStacks& stacks = cx->compartment()->savedStacks();
+ if (!stacks.bernoulli.trial())
+ return nullptr;
+
+ RootedSavedFrame frame(cx);
+ if (!stacks.saveCurrentStack(cx, &frame))
+ oomUnsafe.crash("SavedStacksMetadataBuilder");
+
+ if (!Debugger::onLogAllocationSite(cx, obj, frame, JS_GetCurrentEmbedderTime()))
+ oomUnsafe.crash("SavedStacksMetadataBuilder");
+
+ MOZ_ASSERT_IF(frame, !frame->is<WrapperObject>());
+ return frame;
+}
+
+const SavedStacks::MetadataBuilder SavedStacks::metadataBuilder;
+
+#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;
+
+UTF8CharsZ
+BuildUTF8StackString(JSContext* cx, HandleObject stack)
+{
+ RootedString stackStr(cx);
+ if (!JS::BuildStackString(cx, stack, &stackStr))
+ return UTF8CharsZ();
+
+ char* chars = JS_EncodeStringToUTF8(cx, stackStr);
+ return UTF8CharsZ(chars, strlen(chars));
+}
+
+} /* 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
+{
+ 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);
+ }
+};
+
+JS_PUBLIC_API(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