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