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