/* -*- 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/. */

#ifndef vm_SavedFrame_h
#define vm_SavedFrame_h

#include "mozilla/Attributes.h"

#include "js/GCHashTable.h"
#include "js/UbiNode.h"
#include "js/Wrapper.h"

namespace js {

class SavedFrame : public NativeObject {
    friend class SavedStacks;
    friend struct ::JSStructuredCloneReader;

    static const ClassSpec      classSpec_;

  public:
    static const Class          class_;
    static const Class          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 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(FreeOp* fop, JSObject* obj);

    // Convenient getters for SavedFrame's reserved slots for use from C++.
    JSAtom*       getSource();
    uint32_t      getLine();
    uint32_t      getColumn();
    JSAtom*       getFunctionDisplayName();
    JSAtom*       getAsyncCause();
    SavedFrame*   getParent() const;
    JSPrincipals* getPrincipals();
    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:
    //
    //     RootedSavedFrame stack(cx, getSomeSavedFrameStack());
    //     for (HandleSavedFrame 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) { }
        HandleSavedFrame 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;
        RootedSavedFrame frame_;

      public:
        RootedRange(JSContext* cx, HandleSavedFrame frame) : frame_(cx, frame) { }
        RootedIterator begin() { return RootedIterator(*this); }
        RootedIterator end() { return RootedIterator(); }
    };

    static bool isSavedFrameOrWrapper(JSObject& obj) {
        auto unwrapped = CheckedUnwrap(&obj);
        if (!unwrapped) {
            return false;
        }
        return unwrapped->is<SavedFrame>();
    }

    struct Lookup;
    struct HashPolicy;

    typedef JS::GCHashSet<ReadBarriered<SavedFrame*>,
                          HashPolicy,
                          SystemAllocPolicy> Set;

    class AutoLookupVector;

    class MOZ_STACK_CLASS HandleLookup {
        friend class AutoLookupVector;

        Lookup& lookup;

        explicit HandleLookup(Lookup& lookup) : lookup(lookup) { }

      public:
        inline Lookup& get() { return lookup; }
        inline Lookup* operator->() { return &lookup; }
    };

  private:
    static SavedFrame* create(JSContext* cx);
    static MOZ_MUST_USE bool finishSavedFrameInit(JSContext* cx, HandleObject ctor, HandleObject proto);
    void initFromLookup(JSContext* cx, HandleLookup lookup);
    void initSource(JSAtom* source);
    void initLine(uint32_t line);
    void initColumn(uint32_t column);
    void initFunctionDisplayName(JSAtom* maybeName);
    void initAsyncCause(JSAtom* maybeCause);
    void initParent(SavedFrame* maybeParent);
    void initPrincipalsAlreadyHeld(JSPrincipals* principals);
    void initPrincipals(JSPrincipals* principals);

    enum {
        // The reserved slots in the SavedFrame class.
        JSSLOT_SOURCE,
        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
{
    typedef SavedFrame::Lookup              Lookup;
    typedef MovableCellHasher<SavedFrame*>  SavedFramePtrHasher;
    typedef PointerHasher<JSPrincipals*> JSPrincipalsPtrHasher;

    static bool       hasHash(const Lookup& l);
    static bool       ensureHash(const Lookup& l);
    static HashNumber hash(const Lookup& lookup);
    static bool       match(SavedFrame* existing, const Lookup& lookup);

    typedef ReadBarriered<SavedFrame*> Key;
    static void rekey(Key& key, const Key& newKey);
};

} // namespace js

namespace mozilla {

template <>
struct FallibleHashMethods<js::SavedFrame::HashPolicy>
{
    template <typename Lookup> static bool hasHash(Lookup&& l) {
        return js::SavedFrame::HashPolicy::hasHash(std::forward<Lookup>(l));
    }
    template <typename Lookup> static bool ensureHash(Lookup&& l) {
        return js::SavedFrame::HashPolicy::ensureHash(std::forward<Lookup>(l));
    }
};

} // 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;
    }

    MOZ_MUST_USE bool write(JSContext* cx, JSStructuredCloneWriter* writer) 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<SavedFrame> : public BaseStackFrame {
    explicit ConcreteStackFrame(SavedFrame* ptr) : BaseStackFrame(ptr) { }
    SavedFrame& get() const { return *static_cast<SavedFrame*>(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);
    }

    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<SavedFrame>::ptr");
        if (next != prev) {
            ptr = next;
        }
    }

    bool isSelfHosted(JSContext* cx) const override {
        return get().isSelfHosted(cx);
    }

    bool isSystem() const override;

    MOZ_MUST_USE bool constructSavedFrameStack(JSContext* cx,
                                               MutableHandleObject outSavedFrameStack)
        const override;
};

} // namespace ubi
} // namespace JS

#endif // vm_SavedFrame_h
