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