/* -*- 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/. */ /* Fundamental operations on objects. */ #ifndef vm_ObjectOperations_inl_h #define vm_ObjectOperations_inl_h #include "vm/ObjectOperations.h" #include "mozilla/Assertions.h" // MOZ_ASSERT #include "mozilla/Attributes.h" // MOZ_ALWAYS_INLINE #include "mozilla/Likely.h" // MOZ_UNLIKELY #include // uint32_t #include "jsapi.h" // JSPROP_ENUMERATE, JS::PropertyDescriptor #include "js/Class.h" // js::{Delete,Get,Has}PropertyOp, JSMayResolveOp, JS::ObjectOpResult #include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis #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::ObjectValue, JS::Value #include "proxy/Proxy.h" // js::Proxy #include "vm/JSContext.h" // JSContext #include "vm/JSObject.h" // JSObject #include "vm/NativeObject.h" // js::NativeObject, js::Native{Get,Has,Set}Property, js::NativeGetPropertyNoGC, js::Qualified #include "vm/ProxyObject.h" // js::ProxyObject #include "vm/StringType.h" // js::NameToId #include "vm/SymbolType.h" // JS::Symbol #include "vm/JSAtom-inl.h" // js::IndexToId namespace js { // 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 obj, JS::MutableHandle protop) { if (obj->hasDynamicPrototype()) { MOZ_ASSERT(obj->is()); return Proxy::getPrototype(cx, obj, protop); } protop.set(obj->staticPrototype()); return true; } /* * 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 obj, bool* extensible) { if (obj->is()) { MOZ_ASSERT(!cx->isHelperThreadContext()); return Proxy::isExtensible(cx, obj, extensible); } *extensible = obj->nonProxyIsExtensible(); // If the following assertion fails, there's somewhere else a missing // call to shrinkCapacityToInitializedLength() which needs to be found and // fixed. MOZ_ASSERT_IF(obj->isNative() && !*extensible, obj->as().getDenseInitializedLength() == obj->as().getDenseCapacity()); return true; } /* * 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 obj, JS::Handle id, bool* foundp) { if (HasPropertyOp op = obj->getOpsHasProperty()) { return op(cx, obj, id, foundp); } return NativeHasProperty(cx, obj.as(), id, foundp); } inline bool HasProperty(JSContext* cx, JS::Handle obj, PropertyName* name, bool* foundp) { JS::Rooted id(cx, NameToId(name)); return HasProperty(cx, obj, id, 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 obj, JS::Handle receiver, JS::Handle id, JS::MutableHandle vp) { if (GetPropertyOp op = obj->getOpsGetProperty()) { return op(cx, obj, receiver, id, vp); } return NativeGetProperty(cx, obj.as(), receiver, id, vp); } inline bool GetProperty(JSContext* cx, JS::Handle obj, JS::Handle receiver, PropertyName* name, JS::MutableHandle vp) { JS::Rooted id(cx, NameToId(name)); return GetProperty(cx, obj, receiver, id, vp); } inline bool GetProperty(JSContext* cx, JS::Handle obj, JS::Handle receiver, JS::Handle id, JS::MutableHandle vp) { JS::Rooted receiverValue(cx, JS::ObjectValue(*receiver)); return GetProperty(cx, obj, receiverValue, id, vp); } inline bool GetProperty(JSContext* cx, JS::Handle obj, JS::Handle receiver, PropertyName* name, JS::MutableHandle vp) { JS::Rooted receiverValue(cx, JS::ObjectValue(*receiver)); return GetProperty(cx, obj, receiverValue, name, vp); } inline bool GetElement(JSContext* cx, JS::Handle obj, JS::Handle receiver, uint32_t index, JS::MutableHandle vp) { JS::Rooted id(cx); if (!IndexToId(cx, index, &id)) { return false; } return GetProperty(cx, obj, receiver, id, vp); } inline bool GetElement(JSContext* cx, JS::Handle obj, JS::Handle receiver, uint32_t index, JS::MutableHandle vp) { JS::Rooted receiverValue(cx, JS::ObjectValue(*receiver)); return GetElement(cx, obj, receiverValue, index, vp); } inline bool GetPropertyNoGC(JSContext* cx, JSObject* obj, const JS::Value& receiver, jsid id, JS::Value* vp) { if (obj->getOpsGetProperty()) { return false; } return NativeGetPropertyNoGC(cx, &obj->as(), receiver, id, vp); } inline bool GetPropertyNoGC(JSContext* cx, JSObject* obj, const JS::Value& receiver, PropertyName* name, JS::Value* vp) { return GetPropertyNoGC(cx, obj, receiver, NameToId(name), vp); } inline bool GetElementNoGC(JSContext* cx, JSObject* obj, const JS::Value& receiver, uint32_t index, JS::Value* vp) { if (obj->getOpsGetProperty()) { return false; } if (index > JSID_INT_MAX) { return false; } return GetPropertyNoGC(cx, obj, receiver, INT_TO_JSID(index), vp); } static MOZ_ALWAYS_INLINE bool ClassMayResolveId(const JSAtomState& names, const JSClass* clasp, jsid id, JSObject* maybeObj) { MOZ_ASSERT_IF(maybeObj, maybeObj->getClass() == clasp); if (!clasp->getResolve()) { // Sanity check: we should only have a mayResolve hook if we have a // resolve hook. MOZ_ASSERT(!clasp->getMayResolve(), "Class with mayResolve hook but no resolve hook"); return false; } if (JSMayResolveOp mayResolve = clasp->getMayResolve()) { // Tell the analysis our mayResolve hooks won't trigger GC. JS::AutoSuppressGCAnalysis nogc; if (!mayResolve(names, id, maybeObj)) { return false; } } return true; } // 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 */) { MOZ_ASSERT(symbol->isInterestingSymbol()); jsid id = SYMBOL_TO_JSID(symbol); do { if (obj->maybeHasInterestingSymbolProperty() || MOZ_UNLIKELY( ClassMayResolveId(cx->names(), obj->getClass(), id, obj))) { if (holder) { *holder = obj; } return true; } obj = obj->staticPrototype(); } while (obj); return false; } // Like GetProperty but optimized for interesting symbol properties like // @@toStringTag. MOZ_ALWAYS_INLINE bool GetInterestingSymbolProperty( JSContext* cx, JS::Handle obj, JS::Symbol* sym, JS::MutableHandle vp) { JSObject* holder; if (!MaybeHasInterestingSymbolProperty(cx, obj, sym, &holder)) { #ifdef DEBUG JS::Rooted receiver(cx, JS::ObjectValue(*obj)); JS::Rooted id(cx, SYMBOL_TO_JSID(sym)); if (!GetProperty(cx, obj, receiver, id, vp)) { return false; } MOZ_ASSERT(vp.isUndefined()); #endif vp.setUndefined(); return true; } JS::Rooted holderRoot(cx, holder); JS::Rooted receiver(cx, JS::ObjectValue(*obj)); JS::Rooted id(cx, SYMBOL_TO_JSID(sym)); return GetProperty(cx, holderRoot, receiver, id, 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 obj, JS::Handle id, JS::Handle v, JS::Handle receiver, JS::ObjectOpResult& result) { if (obj->getOpsSetProperty()) { return JSObject::nonNativeSetProperty(cx, obj, id, v, receiver, result); } return NativeSetProperty(cx, obj.as(), id, v, receiver, result); } inline bool SetProperty(JSContext* cx, JS::Handle obj, JS::Handle id, JS::Handle v) { JS::Rooted receiver(cx, JS::ObjectValue(*obj)); JS::ObjectOpResult result; return SetProperty(cx, obj, id, v, receiver, result) && result.checkStrict(cx, obj, id); } inline bool SetProperty(JSContext* cx, JS::Handle obj, PropertyName* name, JS::Handle v, JS::Handle receiver, JS::ObjectOpResult& result) { JS::Rooted id(cx, NameToId(name)); return SetProperty(cx, obj, id, v, receiver, result); } inline bool SetProperty(JSContext* cx, JS::Handle obj, PropertyName* name, JS::Handle v) { JS::Rooted id(cx, NameToId(name)); JS::Rooted receiver(cx, JS::ObjectValue(*obj)); JS::ObjectOpResult result; return SetProperty(cx, obj, id, v, receiver, result) && result.checkStrict(cx, obj, id); } inline bool SetElement(JSContext* cx, JS::Handle obj, uint32_t index, JS::Handle v, JS::Handle receiver, JS::ObjectOpResult& result) { if (obj->getOpsSetProperty()) { return JSObject::nonNativeSetElement(cx, obj, index, v, receiver, result); } return NativeSetElement(cx, obj.as(), index, v, receiver, 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 obj, JS::Handle id, JS::Handle v, bool strict) { JS::Rooted receiver(cx, JS::ObjectValue(*obj)); JS::ObjectOpResult result; return SetProperty(cx, obj, id, v, receiver, result) && result.checkStrictModeError(cx, obj, id, strict); } /* * ES6 [[Delete]]. Equivalent to the JS code `delete obj[id]`. */ inline bool DeleteProperty(JSContext* cx, JS::Handle obj, JS::Handle id, JS::ObjectOpResult& result) { if (DeletePropertyOp op = obj->getOpsDeleteProperty()) { return op(cx, obj, id, result); } return NativeDeleteProperty(cx, obj.as(), id, result); } inline bool DeleteElement(JSContext* cx, JS::Handle obj, uint32_t index, JS::ObjectOpResult& result) { JS::Rooted id(cx); if (!IndexToId(cx, index, &id)) { return false; } return DeleteProperty(cx, obj, id, result); } } /* namespace js */ #endif /* vm_ObjectOperations_inl_h */