/* 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_ArgumentsObject_h
#define vm_ArgumentsObject_h

#include "ds/BitArray.h"
#include "gc/Barrier.h"
#include "gc/GCArray.h"
#include "vm/NativeObject.h"

namespace js {

class AbstractFramePtr;
class ArgumentsObject;
class ScriptFrameIter;

namespace jit {
class JitFrameLayout;
}  // namespace jit

// RareArgumentsData stores the deleted-elements bits for an arguments object.
// Because |delete arguments[i]| is uncommon, we allocate this data the first
// time an element is deleted.
class RareArgumentsData {
  // Pointer to an array of bits indicating, for every argument in
  // [0, initialLength) whether the element has been deleted. See
  // ArgumentsObject::isElementDeleted comment.
  size_t deletedBits_[1];

  using BitArray = ExternalBitArray<size_t>;
  using ConstBitArray = ExternalBitArray<const size_t>;

  RareArgumentsData() = default;

 public:
  RareArgumentsData(const RareArgumentsData&) = delete;
  void operator=(const RareArgumentsData&) = delete;
  static RareArgumentsData* create(JSContext* cx, ArgumentsObject* obj);
  static size_t bytesRequired(size_t numActuals);

  bool isElementDeleted(size_t len, size_t i) const {
    MOZ_ASSERT(i < len);
    return ConstBitArray(deletedBits_, len).get(i);
  }
  void markElementDeleted(size_t len, size_t i) {
    MOZ_ASSERT(i < len);
    BitArray(deletedBits_, len).set(i);
  }
};

// ArgumentsData stores the initial indexed arguments provided to a function
// call. It is used to store arguments[i] -- up until the corresponding
// property is modified, when the relevant value is flagged to memorialize the
// modification.
struct ArgumentsData {
  RareArgumentsData* rareData = nullptr;

  /*
   * This array holds either the current argument value or the magic
   * forwarding value. The latter means that the function has both a
   * CallObject and an ArgumentsObject AND the particular formal variable is
   * aliased by the CallObject. In such cases, the CallObject holds the
   * canonical value so any element access to the arguments object should load
   * the value out of the CallObject (which is pointed to by MAYBE_CALL_SLOT).
   */
  GCOwnedArray<Value> args;

  /*
   * numArgs = std::max(numFormalArgs, numActualArgs)
   * The array 'args' has numArgs elements.
   */
  explicit ArgumentsData(uint32_t numArgs);

  uint32_t numArgs() const { return args.size(); }

  /* For jit use: */
  static constexpr ptrdiff_t offsetOfArgs() {
    return offsetof(ArgumentsData, args) +
           GCOwnedArray<Value>::offsetOfElements();
  }

  static size_t bytesRequired(size_t numArgs) {
    return offsetof(ArgumentsData, args) +
           GCOwnedArray<Value>::bytesRequired(numArgs);
  }
};

// Maximum supported value of arguments.length. This bounds the
// maximum number of arguments that can be supplied to a spread call
// or Function.prototype.apply.  This value also bounds the number of
// elements parsed in an array initializer.  NB: keep this in sync
// with the copy in builtin/SelfHostingDefines.h.
static const unsigned ARGS_LENGTH_MAX = 500 * 1000;

// Maximum number of arguments supported in jitcode. This bounds the
// maximum number of arguments that can be supplied to a call, spread call,
// or Function.prototype.apply without entering the VM. We limit the
// number of parameters we can handle to a number that does not risk
// us allocating too much stack, notably on Windows where there is a
// 4K guard page that has to be touched to extend the stack. The value
// "3000" is the size of the guard page minus an arbitrary, but large,
// safety margin. See bug 1351278.
static const uint32_t JIT_ARGS_LENGTH_MAX = 3000 / sizeof(JS::Value);

static_assert(JIT_ARGS_LENGTH_MAX <= ARGS_LENGTH_MAX,
              "maximum jit arguments should be <= maximum arguments");

/*
 * [SMDOC] ArgumentsObject
 *
 * ArgumentsObject instances represent |arguments| objects created to store
 * function arguments when a function is called.  It's expensive to create such
 * objects if they're never used, so they're only created when they are
 * potentially used.
 *
 * Arguments objects are complicated because, for non-strict mode code, they
 * must alias any named arguments which were provided to the function.  Gnarly
 * example:
 *
 *   function f(a, b, c, d)
 *   {
 *     arguments[0] = "seta";
 *     assertEq(a, "seta");
 *     b = "setb";
 *     assertEq(arguments[1], "setb");
 *     c = "setc";
 *     assertEq(arguments[2], undefined);
 *     arguments[3] = "setd";
 *     assertEq(d, undefined);
 *   }
 *   f("arga", "argb");
 *
 * ES5's strict mode behaves more sanely, and named arguments don't alias
 * elements of an arguments object.
 *
 * ArgumentsObject instances use the following reserved slots:
 *
 *   INITIAL_LENGTH_SLOT
 *     Stores the initial value of arguments.length, plus a bit indicating
 *     whether arguments.length and/or arguments[@@iterator] have been
 *     modified.  Use initialLength(), hasOverriddenLength(), and
 *     hasOverriddenIterator() to access these values.  If arguments.length has
 *     been modified, then the current value of arguments.length is stored in
 *     another slot associated with a new property.
 *   DATA_SLOT
 *     Stores an ArgumentsData*, described above.
 *   MAYBE_CALL_SLOT
 *     Stores the CallObject, if the callee has aliased bindings. See
 *     the ArgumentsData::args comment.
 *   CALLEE_SLOT
 *     Stores the initial arguments.callee. This value can be overridden on
 *     mapped arguments objects, see hasOverriddenCallee.
 */
class ArgumentsObject : public NativeObject {
 public:
  static const uint32_t INITIAL_LENGTH_SLOT = 0;
  static const uint32_t DATA_SLOT = 1;
  static const uint32_t MAYBE_CALL_SLOT = 2;
  static const uint32_t CALLEE_SLOT = 3;

  static const uint32_t LENGTH_OVERRIDDEN_BIT = 0x1;
  static const uint32_t ITERATOR_OVERRIDDEN_BIT = 0x2;
  static const uint32_t ELEMENT_OVERRIDDEN_BIT = 0x4;
  static const uint32_t CALLEE_OVERRIDDEN_BIT = 0x8;
  static const uint32_t FORWARDED_ARGUMENTS_BIT = 0x10;
  static const uint32_t PACKED_BITS_COUNT = 5;
  static const uint32_t PACKED_BITS_MASK = (1 << PACKED_BITS_COUNT) - 1;

  static_assert(ARGS_LENGTH_MAX <= (UINT32_MAX >> PACKED_BITS_COUNT),
                "Max arguments length must fit in available bits");

// Our ability to inline functions that use |arguments| is limited by
// the number of registers available to represent Value operands to
// CreateInlinedArgumentsObject.
#if defined(JS_CODEGEN_X86)
  static const uint32_t MaxInlinedArgs = 1;
#else
  static const uint32_t MaxInlinedArgs = 3;
#endif

 protected:
  template <typename CopyArgs>
  static ArgumentsObject* create(JSContext* cx, HandleFunction callee,
                                 unsigned numActuals, CopyArgs& copy);

  ArgumentsData* data() const {
    return reinterpret_cast<ArgumentsData*>(
        getFixedSlot(DATA_SLOT).toPrivate());
  }

  RareArgumentsData* maybeRareData() const { return data()->rareData; }

  [[nodiscard]] bool createRareData(JSContext* cx);

  RareArgumentsData* getOrCreateRareData(JSContext* cx) {
    if (!data()->rareData && !createRareData(cx)) {
      return nullptr;
    }
    return data()->rareData;
  }

  static bool obj_delProperty(JSContext* cx, HandleObject obj, HandleId id,
                              ObjectOpResult& result);

  static bool obj_mayResolve(const JSAtomState& names, jsid id, JSObject*);

 public:
  static const uint32_t RESERVED_SLOTS = 4;
  static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT4;

  /* Create an arguments object for a frame that is expecting them. */
  static ArgumentsObject* createExpected(JSContext* cx, AbstractFramePtr frame);

  /*
   * Purposefully disconnect the returned arguments object from the frame
   * by always creating a new copy that does not alias formal parameters.
   * This allows function-local analysis to determine that formals are
   * not aliased and generally simplifies arguments objects.
   */
  static ArgumentsObject* createUnexpected(JSContext* cx,
                                           ScriptFrameIter& iter);
  static ArgumentsObject* createUnexpected(JSContext* cx,
                                           AbstractFramePtr frame);

  static ArgumentsObject* createForIon(JSContext* cx,
                                       jit::JitFrameLayout* frame,
                                       HandleObject scopeChain);
  static ArgumentsObject* createForInlinedIon(JSContext* cx, Value* args,
                                              HandleFunction callee,
                                              HandleObject scopeChain,
                                              uint32_t numActuals);
  static ArgumentsObject* createFromValueArray(JSContext* cx,
                                               HandleValueArray argsArray,
                                               HandleFunction callee,
                                               HandleObject scopeChain,
                                               uint32_t numActuals);

 private:
  template <typename CopyArgs>
  static ArgumentsObject* finishPure(JSContext* cx, ArgumentsObject* obj,
                                     JSFunction* callee, JSObject* callObj,
                                     unsigned numActuals, CopyArgs& copy);

 public:
  /*
   * Allocate ArgumentsData and fill reserved slots after allocating an
   * ArgumentsObject in Ion code.
   */
  static ArgumentsObject* finishForIonPure(JSContext* cx,
                                           jit::JitFrameLayout* frame,
                                           JSObject* scopeChain,
                                           ArgumentsObject* obj);

  /*
   * Allocate ArgumentsData for inlined arguments and fill reserved slots after
   * allocating an ArgumentsObject in Ion code.
   */
  static ArgumentsObject* finishInlineForIonPure(
      JSContext* cx, JSObject* rawCallObj, JSFunction* rawCallee, Value* args,
      uint32_t numActuals, ArgumentsObject* obj);

  static ArgumentsObject* createTemplateObject(JSContext* cx, bool mapped);

  /*
   * Return the initial length of the arguments.  This may differ from the
   * current value of arguments.length!
   */
  uint32_t initialLength() const {
    uint32_t argc = uint32_t(getFixedSlot(INITIAL_LENGTH_SLOT).toInt32()) >>
                    PACKED_BITS_COUNT;
    MOZ_ASSERT(argc <= ARGS_LENGTH_MAX);
    return argc;
  }

  bool hasFlags(uint32_t flags) const {
    const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT);
    return v.toInt32() & flags;
  }

  // True iff arguments.length has been assigned or deleted.
  bool hasOverriddenLength() const { return hasFlags(LENGTH_OVERRIDDEN_BIT); }

  void markLengthOverridden() {
    uint32_t v =
        getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | LENGTH_OVERRIDDEN_BIT;
    setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
  }

  // Create the default "length" property and set LENGTH_OVERRIDDEN_BIT.
  static bool reifyLength(JSContext* cx, Handle<ArgumentsObject*> obj);

  // True iff arguments[@@iterator] has been assigned or deleted.
  bool hasOverriddenIterator() const {
    return hasFlags(ITERATOR_OVERRIDDEN_BIT);
  }

  void markIteratorOverridden() {
    uint32_t v =
        getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | ITERATOR_OVERRIDDEN_BIT;
    setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
  }

  // Create the default @@iterator property and set ITERATOR_OVERRIDDEN_BIT.
  static bool reifyIterator(JSContext* cx, Handle<ArgumentsObject*> obj);

  /*
   * Return the arguments iterator function.
   */
  static bool getArgumentsIterator(JSContext* cx, MutableHandleValue val);

  // True iff any element has been assigned or deleted.
  bool hasOverriddenElement() const { return hasFlags(ELEMENT_OVERRIDDEN_BIT); }

  void markElementOverridden() {
    uint32_t v =
        getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | ELEMENT_OVERRIDDEN_BIT;
    setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
  }

 private:
  /*
   * Because the arguments object is a real object, its elements may be
   * deleted. This is implemented by setting a 'deleted' flag for the arg
   * which is read by argument object resolve and getter/setter hooks.
   *
   * NB: an element, once deleted, stays deleted. Thus:
   *
   *   function f(x) { delete arguments[0]; arguments[0] = 42; return x }
   *   assertEq(f(1), 1);
   *
   * This works because, once a property is deleted from an arguments object,
   * it gets regular properties with regular getters/setters that don't alias
   * ArgumentsData::args.
   */
  bool isElementDeleted(uint32_t i) const {
    MOZ_ASSERT(i < data()->numArgs());
    if (i >= initialLength()) {
      return false;
    }
    bool result = maybeRareData() &&
                  maybeRareData()->isElementDeleted(initialLength(), i);
    MOZ_ASSERT_IF(result, hasOverriddenElement());
    return result;
  }

 protected:
  bool markElementDeleted(JSContext* cx, uint32_t i);

 public:
  /*
   * Return true iff the index is a valid element index for this arguments
   * object.
   *
   * Returning true here doesn't imply that the element value can be read
   * through |ArgumentsObject::element()|. For example unmapped arguments
   * objects can have an element index property redefined without having marked
   * the element as deleted. Instead use |maybeGetElement()| or manually check
   * for |hasOverriddenElement()|.
   */
  bool isElement(uint32_t i) const {
    return i < initialLength() && !isElementDeleted(i);
  }

  /*
   * An ArgumentsObject serves two roles:
   *  - a real object, accessed through regular object operations, e.g..,
   *    GetElement corresponding to 'arguments[i]';
   *  - a VM-internal data structure, storing the value of arguments (formal
   *    and actual) that are accessed directly by the VM when a reading the
   *    value of a formal parameter.
   * There are two ways to access the ArgumentsData::args corresponding to
   * these two use cases:
   *  - object access should use elements(i) which will take care of
   *    forwarding when the value is the magic forwarding value;
   *  - VM argument access should use arg(i) which will assert that the
   *    value is not the magic forwarding value (since, if such forwarding was
   *    needed, the frontend should have emitted JSOp::GetAliasedVar).
   */
  const Value& element(uint32_t i) const;

  inline void setElement(uint32_t i, const Value& v);

  const Value& arg(unsigned i) const {
    MOZ_ASSERT(i < data()->numArgs());
    const Value& v = data()->args[i];
    MOZ_RELEASE_ASSERT(!v.isMagic());
    return v;
  }

  void setArg(unsigned i, const Value& v) {
    MOZ_ASSERT(i < data()->numArgs());
    MOZ_RELEASE_ASSERT(!data()->args[i].isMagic());
    data()->args.setElement(this, i, v);
  }

  /*
   * Test if an argument is forwarded, i.e. its actual value is stored in the
   * CallObject and can't be directly read from |ArgumentsData::args|.
   */
  bool argIsForwarded(unsigned i) const {
    MOZ_ASSERT(i < data()->numArgs());
    const Value& v = data()->args[i];
    MOZ_ASSERT_IF(IsMagicScopeSlotValue(v), anyArgIsForwarded());
    return IsMagicScopeSlotValue(v);
  }

  bool anyArgIsForwarded() const { return hasFlags(FORWARDED_ARGUMENTS_BIT); }

  void markArgumentForwarded() {
    uint32_t v =
        getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | FORWARDED_ARGUMENTS_BIT;
    setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
  }

  /*
   * Attempt to speedily and efficiently access the i-th element of this
   * arguments object.  Return true if the element was speedily returned.
   * Return false if the element must be looked up more slowly using
   * getProperty or some similar method. The second overload copies the
   * elements [start, start + count) into the locations starting at 'vp'.
   *
   * NB: Returning false does not indicate error!
   */
  bool maybeGetElement(uint32_t i, MutableHandleValue vp) {
    if (i >= initialLength() || hasOverriddenElement()) {
      return false;
    }
    vp.set(element(i));
    return true;
  }

  inline bool maybeGetElements(uint32_t start, uint32_t count, js::Value* vp);

  /*
   * Measures things hanging off this ArgumentsObject that are counted by the
   * |miscSize| argument in JSObject::sizeOfExcludingThis().
   */
  size_t sizeOfMisc() const;
  size_t sizeOfData() const;
  static void trace(JSTracer* trc, JSObject* obj);
  static size_t objectMoved(JSObject* dst, JSObject* src);

  /* For jit use: */
  static size_t getDataSlotOffset() { return getFixedSlotOffset(DATA_SLOT); }
  static size_t getInitialLengthSlotOffset() {
    return getFixedSlotOffset(INITIAL_LENGTH_SLOT);
  }

  static Value MagicEnvSlotValue(uint32_t slot) {
    // When forwarding slots to a backing CallObject, the slot numbers are
    // stored as uint32 magic values. This raises an ambiguity if we have
    // also copied JS_OPTIMIZED_OUT magic from a JIT frame or
    // JS_UNINITIALIZED_LEXICAL magic on the CallObject. To distinguish
    // normal magic values (those with a JSWhyMagic) and uint32 magic
    // values, we add the maximum JSWhyMagic value to the slot
    // number. This is safe as ARGS_LENGTH_MAX is well below UINT32_MAX.
    static_assert(UINT32_MAX - JS_WHY_MAGIC_COUNT > ARGS_LENGTH_MAX);
    return JS::MagicValueUint32(slot + JS_WHY_MAGIC_COUNT);
  }
  static uint32_t SlotFromMagicScopeSlotValue(const Value& v) {
    static_assert(UINT32_MAX - JS_WHY_MAGIC_COUNT > ARGS_LENGTH_MAX);
    return v.magicUint32() - JS_WHY_MAGIC_COUNT;
  }
  static bool IsMagicScopeSlotValue(const Value& v) {
    return v.isMagic() && v.magicUint32() > JS_WHY_MAGIC_COUNT;
  }

  static void MaybeForwardToCallObject(AbstractFramePtr frame,
                                       ArgumentsObject* obj,
                                       ArgumentsData* data);
  static void MaybeForwardToCallObject(JSFunction* callee, JSObject* callObj,
                                       ArgumentsObject* obj,
                                       ArgumentsData* data);
};

class MappedArgumentsObject : public ArgumentsObject {
  static const JSClassOps classOps_;
  static const ClassExtension classExt_;
  static const ObjectOps objectOps_;

 public:
  static const JSClass class_;

  JSFunction& callee() const {
    return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
  }

  bool hasOverriddenCallee() const { return hasFlags(CALLEE_OVERRIDDEN_BIT); }

  void markCalleeOverridden() {
    uint32_t v =
        getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | CALLEE_OVERRIDDEN_BIT;
    setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
  }

  static size_t getCalleeSlotOffset() {
    return getFixedSlotOffset(CALLEE_SLOT);
  }

  // Create the default "callee" property and set CALLEE_OVERRIDDEN_BIT.
  static bool reifyCallee(JSContext* cx, Handle<MappedArgumentsObject*> obj);

 private:
  static bool obj_enumerate(JSContext* cx, HandleObject obj);
  static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id,
                          bool* resolvedp);
  static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
                                 Handle<JS::PropertyDescriptor> desc,
                                 ObjectOpResult& result);
};

class UnmappedArgumentsObject : public ArgumentsObject {
  static const JSClassOps classOps_;
  static const ClassExtension classExt_;

 public:
  static const JSClass class_;

 private:
  static bool obj_enumerate(JSContext* cx, HandleObject obj);
  static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id,
                          bool* resolvedp);
};

extern bool MappedArgGetter(JSContext* cx, HandleObject obj, HandleId id,
                            MutableHandleValue vp);

extern bool MappedArgSetter(JSContext* cx, HandleObject obj, HandleId id,
                            HandleValue v, ObjectOpResult& result);

extern bool UnmappedArgGetter(JSContext* cx, HandleObject obj, HandleId id,
                              MutableHandleValue vp);

extern bool UnmappedArgSetter(JSContext* cx, HandleObject obj, HandleId id,
                              HandleValue v, ObjectOpResult& result);

}  // namespace js

template <>
inline bool JSObject::is<js::ArgumentsObject>() const {
  return is<js::MappedArgumentsObject>() || is<js::UnmappedArgumentsObject>();
}

#endif /* vm_ArgumentsObject_h */
