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