ObjectOperations-inl.h (13568B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* Fundamental operations on objects. */ 8 9 #ifndef vm_ObjectOperations_inl_h 10 #define vm_ObjectOperations_inl_h 11 12 #include "vm/ObjectOperations.h" 13 14 #include "mozilla/Assertions.h" // MOZ_ASSERT 15 #include "mozilla/Attributes.h" // MOZ_ALWAYS_INLINE 16 #include "mozilla/Likely.h" // MOZ_UNLIKELY 17 18 #include <stdint.h> // uint32_t 19 20 #include "js/Class.h" // js::{Delete,Get,Has}PropertyOp, JSMayResolveOp, JS::ObjectOpResult 21 #include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis 22 #include "js/Id.h" // JS::PropertyKey, jsid 23 #include "js/RootingAPI.h" // JS::Handle, JS::MutableHandle, JS::Rooted 24 #include "js/Value.h" // JS::ObjectValue, JS::Value 25 #include "proxy/Proxy.h" // js::Proxy 26 #include "vm/JSContext.h" // JSContext 27 #include "vm/JSObject.h" // JSObject 28 #include "vm/NativeObject.h" // js::NativeObject, js::Native{Get,Has,Set}Property, js::NativeGetPropertyNoGC, js::Qualified 29 #include "vm/ProxyObject.h" // js::ProxyObject 30 #include "vm/StringType.h" // js::NameToId 31 #include "vm/SymbolType.h" // JS::Symbol 32 33 #include "vm/JSAtomUtils-inl.h" // js::PrimitiveValueToId, js::IndexToId 34 35 namespace js { 36 37 // The functions below are the fundamental operations on objects. See the 38 // comment about "Standard internal methods" in jsapi.h. 39 40 /* 41 * ES6 [[GetPrototypeOf]]. Get obj's prototype, storing it in protop. 42 * 43 * If obj is definitely not a proxy, the infallible obj->getProto() can be used 44 * instead. See the comment on JSObject::getTaggedProto(). 45 */ 46 inline bool GetPrototype(JSContext* cx, JS::Handle<JSObject*> obj, 47 JS::MutableHandle<JSObject*> protop) { 48 if (obj->hasDynamicPrototype()) { 49 MOZ_ASSERT(obj->is<ProxyObject>()); 50 return Proxy::getPrototype(cx, obj, protop); 51 } 52 53 protop.set(obj->staticPrototype()); 54 return true; 55 } 56 57 /* 58 * ES6 [[IsExtensible]]. Extensible objects can have new properties defined on 59 * them. Inextensible objects can't, and their [[Prototype]] slot is fixed as 60 * well. 61 */ 62 inline bool IsExtensible(JSContext* cx, JS::Handle<JSObject*> obj, 63 bool* extensible) { 64 if (obj->is<ProxyObject>()) { 65 return Proxy::isExtensible(cx, obj, extensible); 66 } 67 68 *extensible = obj->nonProxyIsExtensible(); 69 70 // If the following assertion fails, there's somewhere else a missing 71 // call to shrinkCapacityToInitializedLength() which needs to be found and 72 // fixed. 73 MOZ_ASSERT_IF(obj->is<NativeObject>() && !*extensible, 74 obj->as<NativeObject>().getDenseInitializedLength() == 75 obj->as<NativeObject>().getDenseCapacity()); 76 return true; 77 } 78 79 /* 80 * ES6 [[Has]]. Set *foundp to true if `id in obj` (that is, if obj has an own 81 * or inherited property obj[id]), false otherwise. 82 */ 83 inline bool HasProperty(JSContext* cx, JS::Handle<JSObject*> obj, 84 JS::Handle<jsid> id, bool* foundp) { 85 if (HasPropertyOp op = obj->getOpsHasProperty()) { 86 return op(cx, obj, id, foundp); 87 } 88 89 return NativeHasProperty(cx, obj.as<NativeObject>(), id, foundp); 90 } 91 92 inline bool HasProperty(JSContext* cx, JS::Handle<JSObject*> obj, 93 PropertyName* name, bool* foundp) { 94 JS::Rooted<jsid> id(cx, NameToId(name)); 95 return HasProperty(cx, obj, id, foundp); 96 } 97 98 /* 99 * ES6 [[Get]]. Get the value of the property `obj[id]`, or undefined if no 100 * such property exists. 101 * 102 * Typically obj == receiver; if obj != receiver then the caller is most likely 103 * a proxy using GetProperty to finish a property get that started out as 104 * `receiver[id]`, and we've already searched the prototype chain up to `obj`. 105 */ 106 inline bool GetProperty(JSContext* cx, JS::Handle<JSObject*> obj, 107 JS::Handle<JS::Value> receiver, JS::Handle<jsid> id, 108 JS::MutableHandle<JS::Value> vp) { 109 if (GetPropertyOp op = obj->getOpsGetProperty()) { 110 return op(cx, obj, receiver, id, vp); 111 } 112 113 return NativeGetProperty(cx, obj.as<NativeObject>(), receiver, id, vp); 114 } 115 116 inline bool GetProperty(JSContext* cx, JS::Handle<JSObject*> obj, 117 JS::Handle<JS::Value> receiver, PropertyName* name, 118 JS::MutableHandle<JS::Value> vp) { 119 JS::Rooted<jsid> id(cx, NameToId(name)); 120 return GetProperty(cx, obj, receiver, id, vp); 121 } 122 123 inline bool GetProperty(JSContext* cx, JS::Handle<JSObject*> obj, 124 JS::Handle<JSObject*> receiver, JS::Handle<jsid> id, 125 JS::MutableHandle<JS::Value> vp) { 126 JS::Rooted<JS::Value> receiverValue(cx, JS::ObjectValue(*receiver)); 127 return GetProperty(cx, obj, receiverValue, id, vp); 128 } 129 130 inline bool GetProperty(JSContext* cx, JS::Handle<JSObject*> obj, 131 JS::Handle<JSObject*> receiver, PropertyName* name, 132 JS::MutableHandle<JS::Value> vp) { 133 JS::Rooted<JS::Value> receiverValue(cx, JS::ObjectValue(*receiver)); 134 return GetProperty(cx, obj, receiverValue, name, vp); 135 } 136 137 inline bool GetElement(JSContext* cx, JS::Handle<JSObject*> obj, 138 JS::Handle<JS::Value> receiver, uint32_t index, 139 JS::MutableHandle<JS::Value> vp) { 140 JS::Rooted<jsid> id(cx); 141 if (!IndexToId(cx, index, &id)) { 142 return false; 143 } 144 145 return GetProperty(cx, obj, receiver, id, vp); 146 } 147 148 inline bool GetElement(JSContext* cx, JS::Handle<JSObject*> obj, 149 JS::Handle<JSObject*> receiver, uint32_t index, 150 JS::MutableHandle<JS::Value> vp) { 151 JS::Rooted<JS::Value> receiverValue(cx, JS::ObjectValue(*receiver)); 152 return GetElement(cx, obj, receiverValue, index, vp); 153 } 154 155 inline bool GetElementLargeIndex(JSContext* cx, JS::Handle<JSObject*> obj, 156 JS::Handle<JSObject*> receiver, uint64_t index, 157 JS::MutableHandle<JS::Value> vp) { 158 JS::Rooted<jsid> id(cx); 159 if (!IndexToId(cx, index, &id)) { 160 return false; 161 } 162 163 JS::Rooted<JS::Value> receiverValue(cx, JS::ObjectValue(*receiver)); 164 return GetProperty(cx, obj, receiverValue, id, vp); 165 } 166 167 inline bool GetPropertyNoGC(JSContext* cx, JSObject* obj, 168 const JS::Value& receiver, jsid id, JS::Value* vp) { 169 if (obj->getOpsGetProperty()) { 170 return false; 171 } 172 173 return NativeGetPropertyNoGC(cx, &obj->as<NativeObject>(), receiver, id, vp); 174 } 175 176 inline bool GetPropertyNoGC(JSContext* cx, JSObject* obj, 177 const JS::Value& receiver, PropertyName* name, 178 JS::Value* vp) { 179 return GetPropertyNoGC(cx, obj, receiver, NameToId(name), vp); 180 } 181 182 inline bool GetElementNoGC(JSContext* cx, JSObject* obj, 183 const JS::Value& receiver, uint32_t index, 184 JS::Value* vp) { 185 if (obj->getOpsGetProperty()) { 186 return false; 187 } 188 189 if (index > PropertyKey::IntMax) { 190 return false; 191 } 192 193 return GetPropertyNoGC(cx, obj, receiver, PropertyKey::Int(index), vp); 194 } 195 196 static MOZ_ALWAYS_INLINE bool ClassMayResolveId(const JSAtomState& names, 197 const JSClass* clasp, jsid id, 198 JSObject* maybeObj) { 199 MOZ_ASSERT_IF(maybeObj, maybeObj->getClass() == clasp); 200 201 if (!clasp->getResolve()) { 202 // Sanity check: we should only have a mayResolve hook if we have a 203 // resolve hook. 204 MOZ_ASSERT(!clasp->getMayResolve(), 205 "Class with mayResolve hook but no resolve hook"); 206 return false; 207 } 208 209 if (JSMayResolveOp mayResolve = clasp->getMayResolve()) { 210 // Tell the analysis our mayResolve hooks won't trigger GC. 211 JS::AutoSuppressGCAnalysis nogc; 212 if (!mayResolve(names, id, maybeObj)) { 213 return false; 214 } 215 } 216 217 return true; 218 } 219 220 // Returns whether |obj| or an object on its proto chain may have an interesting 221 // symbol property (see JSObject::hasInterestingSymbolProperty). If it returns 222 // true, *holder is set to the object that may have this property. 223 MOZ_ALWAYS_INLINE bool MaybeHasInterestingSymbolProperty( 224 JSContext* cx, JSObject* obj, JS::Symbol* symbol, 225 JSObject** holder /* = nullptr */) { 226 MOZ_ASSERT(symbol->isInterestingSymbol()); 227 228 jsid id = PropertyKey::Symbol(symbol); 229 do { 230 if (obj->maybeHasInterestingSymbolProperty() || 231 MOZ_UNLIKELY( 232 ClassMayResolveId(cx->names(), obj->getClass(), id, obj))) { 233 if (holder) { 234 *holder = obj; 235 } 236 return true; 237 } 238 obj = obj->staticPrototype(); 239 } while (obj); 240 241 return false; 242 } 243 244 // Like GetProperty but optimized for interesting symbol properties like 245 // @@toStringTag. 246 MOZ_ALWAYS_INLINE bool GetInterestingSymbolProperty( 247 JSContext* cx, JS::Handle<JSObject*> obj, JS::Symbol* sym, 248 JS::MutableHandle<JS::Value> vp) { 249 JSObject* holder; 250 if (!MaybeHasInterestingSymbolProperty(cx, obj, sym, &holder)) { 251 #ifdef DEBUG 252 JS::Rooted<JS::Value> receiver(cx, JS::ObjectValue(*obj)); 253 JS::Rooted<jsid> id(cx, PropertyKey::Symbol(sym)); 254 if (!GetProperty(cx, obj, receiver, id, vp)) { 255 return false; 256 } 257 MOZ_ASSERT(vp.isUndefined()); 258 #endif 259 260 vp.setUndefined(); 261 return true; 262 } 263 264 JS::Rooted<JSObject*> holderRoot(cx, holder); 265 JS::Rooted<JS::Value> receiver(cx, JS::ObjectValue(*obj)); 266 JS::Rooted<jsid> id(cx, PropertyKey::Symbol(sym)); 267 return GetProperty(cx, holderRoot, receiver, id, vp); 268 } 269 270 /* 271 * ES6 [[Set]]. Carry out the assignment `obj[id] = v`. 272 * 273 * The `receiver` argument has to do with how [[Set]] interacts with the 274 * prototype chain and proxies. It's hard to explain and ES6 doesn't really 275 * try. Long story short, if you just want bog-standard assignment, pass 276 * `ObjectValue(*obj)` as receiver. Or better, use one of the signatures that 277 * doesn't have a receiver parameter. 278 * 279 * Callers pass obj != receiver e.g. when a proxy is involved, obj is the 280 * proxy's target, and the proxy is using SetProperty to finish an assignment 281 * that started out as `receiver[id] = v`, by delegating it to obj. 282 */ 283 inline bool SetProperty(JSContext* cx, JS::Handle<JSObject*> obj, 284 JS::Handle<jsid> id, JS::Handle<JS::Value> v, 285 JS::Handle<JS::Value> receiver, 286 JS::ObjectOpResult& result) { 287 if (obj->getOpsSetProperty()) { 288 return JSObject::nonNativeSetProperty(cx, obj, id, v, receiver, result); 289 } 290 291 return NativeSetProperty<Qualified>(cx, obj.as<NativeObject>(), id, v, 292 receiver, result); 293 } 294 295 inline bool SetProperty(JSContext* cx, JS::Handle<JSObject*> obj, 296 JS::Handle<jsid> id, JS::Handle<JS::Value> v) { 297 JS::Rooted<JS::Value> receiver(cx, JS::ObjectValue(*obj)); 298 JS::ObjectOpResult result; 299 return SetProperty(cx, obj, id, v, receiver, result) && 300 result.checkStrict(cx, obj, id); 301 } 302 303 inline bool SetProperty(JSContext* cx, JS::Handle<JSObject*> obj, 304 PropertyName* name, JS::Handle<JS::Value> v, 305 JS::Handle<JS::Value> receiver, 306 JS::ObjectOpResult& result) { 307 JS::Rooted<jsid> id(cx, NameToId(name)); 308 return SetProperty(cx, obj, id, v, receiver, result); 309 } 310 311 inline bool SetProperty(JSContext* cx, JS::Handle<JSObject*> obj, 312 PropertyName* name, JS::Handle<JS::Value> v) { 313 JS::Rooted<jsid> id(cx, NameToId(name)); 314 JS::Rooted<JS::Value> receiver(cx, JS::ObjectValue(*obj)); 315 JS::ObjectOpResult result; 316 return SetProperty(cx, obj, id, v, receiver, result) && 317 result.checkStrict(cx, obj, id); 318 } 319 320 inline bool SetElement(JSContext* cx, JS::Handle<JSObject*> obj, uint32_t index, 321 JS::Handle<JS::Value> v, JS::Handle<JS::Value> receiver, 322 JS::ObjectOpResult& result) { 323 if (obj->getOpsSetProperty()) { 324 return JSObject::nonNativeSetElement(cx, obj, index, v, receiver, result); 325 } 326 327 return NativeSetElement(cx, obj.as<NativeObject>(), index, v, receiver, 328 result); 329 } 330 331 /* 332 * ES6 draft rev 31 (15 Jan 2015) 7.3.3 Put (O, P, V, Throw), except that on 333 * success, the spec says this is supposed to return a boolean value, which we 334 * don't bother doing. 335 */ 336 inline bool PutProperty(JSContext* cx, JS::Handle<JSObject*> obj, 337 JS::Handle<jsid> id, JS::Handle<JS::Value> v, 338 bool strict) { 339 JS::Rooted<JS::Value> receiver(cx, JS::ObjectValue(*obj)); 340 JS::ObjectOpResult result; 341 return SetProperty(cx, obj, id, v, receiver, result) && 342 result.checkStrictModeError(cx, obj, id, strict); 343 } 344 345 /* 346 * ES6 [[Delete]]. Equivalent to the JS code `delete obj[id]`. 347 */ 348 inline bool DeleteProperty(JSContext* cx, JS::Handle<JSObject*> obj, 349 JS::Handle<jsid> id, JS::ObjectOpResult& result) { 350 if (DeletePropertyOp op = obj->getOpsDeleteProperty()) { 351 return op(cx, obj, id, result); 352 } 353 354 return NativeDeleteProperty(cx, obj.as<NativeObject>(), id, result); 355 } 356 357 inline bool DeleteElement(JSContext* cx, JS::Handle<JSObject*> obj, 358 uint32_t index, JS::ObjectOpResult& result) { 359 JS::Rooted<jsid> id(cx); 360 if (!IndexToId(cx, index, &id)) { 361 return false; 362 } 363 364 return DeleteProperty(cx, obj, id, result); 365 } 366 367 } /* namespace js */ 368 369 #endif /* vm_ObjectOperations_inl_h */