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

/* Fundamental operations on objects. */

#ifndef vm_ObjectOperations_h
#define vm_ObjectOperations_h

#include "mozilla/Attributes.h" // MOZ_ALWAYS_INLINE

#include <stdint.h> // uint32_t

#include "jsapi.h" // JSPROP_ENUMERATE, JS::PropertyDescriptor

#include "js/Class.h" // JS::ObjectOpResult
#include "js/Id.h" // INT_TO_JSID, jsid, JSID_INT_MAX, SYMBOL_TO_JSID
#include "js/RootingAPI.h" // JS::Handle, JS::MutableHandle, JS::Rooted
#include "js/Value.h" // JS::Value
#include "vm/JSContext.h" // JSContext
#include "vm/JSObject.h" // JSObject
#include "vm/StringType.h" // js::NameToId
#include "vm/SymbolType.h" // JS::Symbol

namespace js {

class PropertyName;

// The functions below are the fundamental operations on objects. See the
// comment about "Standard internal methods" in jsapi.h.

/*
 * ES6 [[GetPrototypeOf]]. Get obj's prototype, storing it in protop.
 *
 * If obj is definitely not a proxy, the infallible obj->getProto() can be used
 * instead. See the comment on JSObject::getTaggedProto().
 */
inline bool
GetPrototype(JSContext* cx, JS::Handle<JSObject*> obj, JS::MutableHandle<JSObject*> protop);

/*
 * ES6 [[SetPrototypeOf]]. Change obj's prototype to proto.
 *
 * Returns false on error, success of operation in *result. For example, if
 * obj is not extensible, its prototype is fixed. js::SetPrototype will return
 * true, because no exception is thrown for this; but *result will be false.
 */
extern bool
SetPrototype(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JSObject*> proto,
             JS::ObjectOpResult& result);

/* Convenience function: like the above, but throw on failure. */
extern bool
SetPrototype(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JSObject*> proto);

/*
 * ES6 [[IsExtensible]]. Extensible objects can have new properties defined on
 * them. Inextensible objects can't, and their [[Prototype]] slot is fixed as
 * well.
 */
inline bool
IsExtensible(JSContext* cx, JS::Handle<JSObject*> obj, bool* extensible);

/*
 * ES6 [[PreventExtensions]]. Attempt to change the [[Extensible]] bit on |obj|
 * to false.  Indicate success or failure through the |result| outparam, or
 * actual error through the return value.
 */
extern bool
PreventExtensions(JSContext* cx, JS::Handle<JSObject*> obj, JS::ObjectOpResult& result);

/* Convenience function. As above, but throw on failure. */
extern bool
PreventExtensions(JSContext* cx, JS::Handle<JSObject*> obj);

/*
 * ES6 [[GetOwnProperty]]. Get a description of one of obj's own properties.
 *
 * If no such property exists on obj, return true with desc.object() set to
 * null.
 */
extern bool
GetOwnPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                         JS::MutableHandle<JS::PropertyDescriptor> desc);

/* ES6 [[DefineOwnProperty]]. Define a property on obj. */
extern bool
DefineProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
               Handle<JS::PropertyDescriptor> desc, JS::ObjectOpResult& result);

/*
 * When the 'result' out-param is omitted, the behavior is the same as above, except
 * that any failure results in a TypeError.
 */
extern bool
DefineProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
               JS::Handle<JS::PropertyDescriptor> desc);

extern bool
DefineAccessorProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                       JS::Handle<JSObject*> getter, JS::Handle<JSObject*> setter, unsigned attrs,
                       JS::ObjectOpResult& result);

extern bool
DefineDataProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                   JS::Handle<JS::Value> value, unsigned attrs, JS::ObjectOpResult& result);

extern bool
DefineDataProperty(JSContext* cx, JS::Handle<JSObject*> obj, PropertyName* name, JS::Handle<JS::Value> value,
                   unsigned attrs, JS::ObjectOpResult& result);

extern bool
DefineDataElement(JSContext* cx, JS::Handle<JSObject*> obj, uint32_t index, JS::Handle<JS::Value> value,
                  unsigned attrs, JS::ObjectOpResult& result);

extern bool
DefineAccessorProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                       JS::Handle<JSObject*> getter, JS::Handle<JSObject*> setter,
                       unsigned attrs = JSPROP_ENUMERATE);

extern bool
DefineDataProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::Handle<JS::Value> value,
                   unsigned attrs = JSPROP_ENUMERATE);

extern bool
DefineDataProperty(JSContext* cx, JS::Handle<JSObject*> obj, PropertyName* name, JS::Handle<JS::Value> value,
                   unsigned attrs = JSPROP_ENUMERATE);

extern bool
DefineDataElement(JSContext* cx, JS::Handle<JSObject*> obj, uint32_t index, JS::Handle<JS::Value> value,
                  unsigned attrs = JSPROP_ENUMERATE);

/*
 * ES6 [[Has]]. Set *foundp to true if `id in obj` (that is, if obj has an own
 * or inherited property obj[id]), false otherwise.
 */
inline bool
HasProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, bool* foundp);

inline bool
HasProperty(JSContext* cx, JS::Handle<JSObject*> obj, PropertyName* name, bool* foundp);

/*
 * ES6 [[Get]]. Get the value of the property `obj[id]`, or undefined if no
 * such property exists.
 *
 * Typically obj == receiver; if obj != receiver then the caller is most likely
 * a proxy using GetProperty to finish a property get that started out as
 * `receiver[id]`, and we've already searched the prototype chain up to `obj`.
 */
inline bool
GetProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JS::Value> receiver,
            JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp);

inline bool
GetProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JS::Value> receiver,
            PropertyName* name, JS::MutableHandle<JS::Value> vp);

inline bool
GetProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JSObject*> receiver,
            JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp);

inline bool
GetProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JSObject*> receiver,
            PropertyName* name, JS::MutableHandle<JS::Value> vp);

inline bool
GetElement(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JS::Value> receiver,
           uint32_t index, JS::MutableHandle<JS::Value> vp);

inline bool
GetElement(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<JSObject*> receiver,
           uint32_t index, JS::MutableHandle<JS::Value> vp);

inline bool
GetPropertyNoGC(JSContext* cx, JSObject* obj, const JS::Value& receiver, jsid id, JS::Value* vp);

inline bool
GetPropertyNoGC(JSContext* cx, JSObject* obj, const JS::Value& receiver, PropertyName* name,
                JS::Value* vp);

inline bool
GetElementNoGC(JSContext* cx, JSObject* obj, const JS::Value& receiver, uint32_t index,
               JS::Value* vp);

// Returns whether |obj| or an object on its proto chain may have an interesting
// symbol property (see JSObject::hasInterestingSymbolProperty). If it returns
// true, *holder is set to the object that may have this property.
MOZ_ALWAYS_INLINE bool
MaybeHasInterestingSymbolProperty(JSContext* cx, JSObject* obj, JS::Symbol* symbol,
                                  JSObject** holder = nullptr);

// Like GetProperty but optimized for interesting symbol properties like
// @@toStringTag.
MOZ_ALWAYS_INLINE bool
GetInterestingSymbolProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Symbol* sym,
                             JS::MutableHandle<JS::Value> vp);

/*
 * ES6 [[Set]]. Carry out the assignment `obj[id] = v`.
 *
 * The `receiver` argument has to do with how [[Set]] interacts with the
 * prototype chain and proxies. It's hard to explain and ES6 doesn't really
 * try. Long story short, if you just want bog-standard assignment, pass
 * `ObjectValue(*obj)` as receiver. Or better, use one of the signatures that
 * doesn't have a receiver parameter.
 *
 * Callers pass obj != receiver e.g. when a proxy is involved, obj is the
 * proxy's target, and the proxy is using SetProperty to finish an assignment
 * that started out as `receiver[id] = v`, by delegating it to obj.
 */
inline bool
SetProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::Handle<JS::Value> v,
            JS::Handle<JS::Value> receiver, JS::ObjectOpResult& result);

inline bool
SetProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
            JS::Handle<JS::Value> v);

inline bool
SetProperty(JSContext* cx, JS::Handle<JSObject*> obj, PropertyName* name, JS::Handle<JS::Value> v,
            JS::Handle<JS::Value> receiver, JS::ObjectOpResult& result);

inline bool
SetProperty(JSContext* cx, JS::Handle<JSObject*> obj, PropertyName* name, JS::Handle<JS::Value> v);

inline bool
SetElement(JSContext* cx, JS::Handle<JSObject*> obj, uint32_t index, JS::Handle<JS::Value> v,
           JS::Handle<JS::Value> receiver, JS::ObjectOpResult& result);

/*
 * ES6 draft rev 31 (15 Jan 2015) 7.3.3 Put (O, P, V, Throw), except that on
 * success, the spec says this is supposed to return a boolean value, which we
 * don't bother doing.
 */
inline bool
PutProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::Handle<JS::Value> v,
            bool strict);

/*
 * ES6 [[Delete]]. Equivalent to the JS code `delete obj[id]`.
 */
inline bool
DeleteProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
               JS::ObjectOpResult& result);

inline bool
DeleteElement(JSContext* cx, JS::Handle<JSObject*> obj, uint32_t index,
              JS::ObjectOpResult& result);

/*** SpiderMonkey nonstandard internal methods ***************************************************/

/**
 * If |obj| (underneath any functionally-transparent wrapper proxies) has as
 * its [[GetPrototypeOf]] trap the ordinary [[GetPrototypeOf]] behavior defined
 * for ordinary objects, set |*isOrdinary = true| and store |obj|'s prototype
 * in |result|.  Otherwise set |*isOrdinary = false|.  In case of error, both
 * outparams have unspecified value.
 */
extern bool
GetPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> obj, bool* isOrdinary,
                       JS::MutableHandle<JSObject*> protop);

/*
 * Attempt to make |obj|'s [[Prototype]] immutable, such that subsequently
 * trying to change it will not work.  If an internal error occurred,
 * returns false.  Otherwise, |*succeeded| is set to true iff |obj|'s
 * [[Prototype]] is now immutable.
 */
extern bool
SetImmutablePrototype(JSContext* cx, JS::Handle<JSObject*> obj, bool* succeeded);

extern bool
GetPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                      MutableHandle<JS::PropertyDescriptor> desc);

/*
 * Deprecated. A version of HasProperty that also returns the object on which
 * the property was found (but that information is unreliable for proxies), and
 * the Shape of the property, if native.
 */
extern bool
LookupProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
               JS::MutableHandle<JSObject*> objp, MutableHandle<PropertyResult> propp);

inline bool
LookupProperty(JSContext* cx, JS::Handle<JSObject*> obj, PropertyName* name,
               JS::MutableHandle<JSObject*> objp, MutableHandle<PropertyResult> propp)
{
    JS::Rooted<jsid> id(cx, NameToId(name));
    return LookupProperty(cx, obj, id, objp, propp);
}

/* Set *result to tell whether obj has an own property with the given id. */
extern bool
HasOwnProperty(JSContext* cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, bool* result);

} /* namespace js */

#endif /* vm_ObjectOperations_h */
