/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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/. */ #ifndef vm_SavedFrame_h #define vm_SavedFrame_h #include "mozilla/Attributes.h" #include "gc/Policy.h" #include "js/GCHashTable.h" #include "js/Principals.h" #include "js/UbiNode.h" #include "vm/NativeObject.h" namespace js { class SavedFrame : public NativeObject { friend class SavedStacks; friend struct ::JSStructuredCloneReader; static const ClassSpec classSpec_; public: static const JSClass class_; static const JSClass protoClass_; static const JSPropertySpec protoAccessors[]; static const JSFunctionSpec protoFunctions[]; static const JSFunctionSpec staticFunctions[]; // Prototype methods and properties to be exposed to JS. static bool construct(JSContext* cx, unsigned argc, Value* vp); static bool sourceProperty(JSContext* cx, unsigned argc, Value* vp); static bool sourceIdProperty(JSContext* cx, unsigned argc, Value* vp); static bool lineProperty(JSContext* cx, unsigned argc, Value* vp); static bool columnProperty(JSContext* cx, unsigned argc, Value* vp); static bool functionDisplayNameProperty(JSContext* cx, unsigned argc, Value* vp); static bool asyncCauseProperty(JSContext* cx, unsigned argc, Value* vp); static bool asyncParentProperty(JSContext* cx, unsigned argc, Value* vp); static bool parentProperty(JSContext* cx, unsigned argc, Value* vp); static bool toStringMethod(JSContext* cx, unsigned argc, Value* vp); static void finalize(JS::GCContext* gcx, JSObject* obj); // Convenient getters for SavedFrame's reserved slots for use from C++. JSAtom* getSource(); uint32_t getSourceId(); uint32_t getLine(); uint32_t getColumn(); JSAtom* getFunctionDisplayName(); JSAtom* getAsyncCause(); SavedFrame* getParent() const; JSPrincipals* getPrincipals(); bool getMutedErrors(); bool isSelfHosted(JSContext* cx); bool isWasm(); // When isWasm(): uint32_t wasmFuncIndex(); uint32_t wasmBytecodeOffset(); // Iterator for use with C++11 range based for loops, eg: // // Rooted stack(cx, getSomeSavedFrameStack()); // for (Handle frame : SavedFrame::RootedRange(cx, stack)) { // ... // } // // Each frame yielded by `SavedFrame::RootedRange` is only a valid handle to // a rooted `SavedFrame` within the loop's block for a single loop // iteration. When the next iteration begins, the value is invalidated. class RootedRange; class MOZ_STACK_CLASS RootedIterator { friend class RootedRange; RootedRange* range_; // For use by RootedRange::end() only. explicit RootedIterator() : range_(nullptr) {} public: explicit RootedIterator(RootedRange& range) : range_(&range) {} Handle operator*() { MOZ_ASSERT(range_); return range_->frame_; } bool operator!=(const RootedIterator& rhs) const { // We should only ever compare to the null range, aka we are just // testing if this range is done. MOZ_ASSERT(rhs.range_ == nullptr); return range_->frame_ != nullptr; } inline void operator++(); }; class MOZ_STACK_CLASS RootedRange { friend class RootedIterator; Rooted frame_; public: RootedRange(JSContext* cx, Handle frame) : frame_(cx, frame) {} RootedIterator begin() { return RootedIterator(*this); } RootedIterator end() { return RootedIterator(); } }; struct Lookup; struct HashPolicy; typedef JS::GCHashSet, HashPolicy, SystemAllocPolicy> Set; private: static SavedFrame* create(JSContext* cx); [[nodiscard]] static bool finishSavedFrameInit(JSContext* cx, HandleObject ctor, HandleObject proto); void initFromLookup(JSContext* cx, Handle lookup); void initSource(JSAtom* source); void initSourceId(uint32_t id); void initLine(uint32_t line); void initColumn(uint32_t column); void initFunctionDisplayName(JSAtom* maybeName); void initAsyncCause(JSAtom* maybeCause); void initParent(SavedFrame* maybeParent); void initPrincipalsAlreadyHeldAndMutedErrors(JSPrincipals* principals, bool mutedErrors); void initPrincipalsAndMutedErrors(JSPrincipals* principals, bool mutedErrors); enum { // The reserved slots in the SavedFrame class. JSSLOT_SOURCE, JSSLOT_SOURCEID, JSSLOT_LINE, JSSLOT_COLUMN, JSSLOT_FUNCTIONDISPLAYNAME, JSSLOT_ASYNCCAUSE, JSSLOT_PARENT, JSSLOT_PRINCIPALS, // The total number of reserved slots in the SavedFrame class. JSSLOT_COUNT }; }; struct SavedFrame::HashPolicy { using Lookup = SavedFrame::Lookup; using SavedFramePtrHasher = StableCellHasher; using JSPrincipalsPtrHasher = PointerHasher; static bool maybeGetHash(const Lookup& l, HashNumber* hashOut); static bool ensureHash(const Lookup& l, HashNumber* hashOut); static HashNumber hash(const Lookup& lookup); static bool match(SavedFrame* existing, const Lookup& lookup); using Key = WeakHeapPtr; static void rekey(Key& key, const Key& newKey); private: static HashNumber calculateHash(const Lookup& lookup, HashNumber parentHash); }; } // namespace js namespace mozilla { template <> struct FallibleHashMethods { template static bool maybeGetHash(Lookup&& l, HashNumber* hashOut) { return js::SavedFrame::HashPolicy::maybeGetHash(std::forward(l), hashOut); } template static bool ensureHash(Lookup&& l, HashNumber* hashOut) { return js::SavedFrame::HashPolicy::ensureHash(std::forward(l), hashOut); } }; } // namespace mozilla namespace js { // Assert that if the given object is not null, that it must be either a // SavedFrame object or wrapper (Xray or CCW) around a SavedFrame object. inline void AssertObjectIsSavedFrameOrWrapper(JSContext* cx, HandleObject stack); // When we reconstruct a SavedFrame stack from a JS::ubi::StackFrame, we may not // have access to the principals that the original stack was captured // with. Instead, we use these two singleton principals based on whether // JS::ubi::StackFrame::isSystem or not. These singletons should never be passed // to the subsumes callback, and should be special cased with a shortcut before // that. struct ReconstructedSavedFramePrincipals : public JSPrincipals { explicit ReconstructedSavedFramePrincipals() : JSPrincipals() { MOZ_ASSERT(is(this)); this->refcount = 1; } [[nodiscard]] bool write(JSContext* cx, JSStructuredCloneWriter* writer) override { MOZ_ASSERT(false, "ReconstructedSavedFramePrincipals should never be exposed to " "embedders"); return false; } bool isSystemOrAddonPrincipal() override { MOZ_ASSERT(false, "ReconstructedSavedFramePrincipals should never be exposed to " "embedders"); return false; } static ReconstructedSavedFramePrincipals IsSystem; static ReconstructedSavedFramePrincipals IsNotSystem; // Return true if the given JSPrincipals* points to one of the // ReconstructedSavedFramePrincipals singletons, false otherwise. static bool is(JSPrincipals* p) { return p == &IsSystem || p == &IsNotSystem; } // Get the appropriate ReconstructedSavedFramePrincipals singleton for the // given JS::ubi::StackFrame that is being reconstructed as a SavedFrame // stack. static JSPrincipals* getSingleton(JS::ubi::StackFrame& f) { return f.isSystem() ? &IsSystem : &IsNotSystem; } }; inline void SavedFrame::RootedIterator::operator++() { MOZ_ASSERT(range_); range_->frame_ = range_->frame_->getParent(); } } // namespace js namespace JS { namespace ubi { using js::SavedFrame; // A concrete JS::ubi::StackFrame that is backed by a live SavedFrame object. template <> class ConcreteStackFrame : public BaseStackFrame { explicit ConcreteStackFrame(SavedFrame* ptr) : BaseStackFrame(ptr) {} SavedFrame& get() const { return *static_cast(ptr); } public: static void construct(void* storage, SavedFrame* ptr) { new (storage) ConcreteStackFrame(ptr); } StackFrame parent() const override { return get().getParent(); } uint32_t line() const override { return get().getLine(); } uint32_t column() const override { return get().getColumn(); } AtomOrTwoByteChars source() const override { auto source = get().getSource(); return AtomOrTwoByteChars(source); } uint32_t sourceId() const override { return get().getSourceId(); } AtomOrTwoByteChars functionDisplayName() const override { auto name = get().getFunctionDisplayName(); return AtomOrTwoByteChars(name); } void trace(JSTracer* trc) override { JSObject* prev = &get(); JSObject* next = prev; js::TraceRoot(trc, &next, "ConcreteStackFrame::ptr"); if (next != prev) { ptr = next; } } bool isSelfHosted(JSContext* cx) const override { return get().isSelfHosted(cx); } bool isSystem() const override; [[nodiscard]] bool constructSavedFrameStack( JSContext* cx, MutableHandleObject outSavedFrameStack) const override; }; } // namespace ubi } // namespace JS #endif // vm_SavedFrame_h