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 #include "jsapi.h"
8 #include "NamespaceImports.h"
9
10 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
11 #include "js/Proxy.h"
12 #include "proxy/DeadObjectProxy.h"
13 #include "vm/ProxyObject.h"
14 #include "vm/WellKnownAtom.h" // js_*_str
15 #include "vm/WrapperObject.h"
16
17 #include "vm/JSContext-inl.h"
18 #include "vm/JSObject-inl.h"
19
20 using namespace js;
21
22 using JS::IsArrayAnswer;
23
enter(JSContext * cx,HandleObject wrapper,HandleId id,Action act,bool mayThrow,bool * bp) const24 bool BaseProxyHandler::enter(JSContext* cx, HandleObject wrapper, HandleId id,
25 Action act, bool mayThrow, bool* bp) const {
26 *bp = true;
27 return true;
28 }
29
has(JSContext * cx,HandleObject proxy,HandleId id,bool * bp) const30 bool BaseProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id,
31 bool* bp) const {
32 assertEnteredPolicy(cx, proxy, id, GET);
33
34 // This method is not covered by any spec, but we follow ES 2016
35 // (February 11, 2016) 9.1.7.1 fairly closely.
36
37 // Step 2. (Step 1 is a superfluous assertion.)
38 // Non-standard: Use our faster hasOwn trap.
39 if (!hasOwn(cx, proxy, id, bp)) {
40 return false;
41 }
42
43 // Step 3.
44 if (*bp) {
45 return true;
46 }
47
48 // The spec calls this variable "parent", but that word has weird
49 // connotations in SpiderMonkey, so let's go with "proto".
50 // Step 4.
51 RootedObject proto(cx);
52 if (!GetPrototype(cx, proxy, &proto)) {
53 return false;
54 }
55
56 // Step 5.,5.a.
57 if (proto) {
58 return HasProperty(cx, proto, id, bp);
59 }
60
61 // Step 6.
62 *bp = false;
63 return true;
64 }
65
hasOwn(JSContext * cx,HandleObject proxy,HandleId id,bool * bp) const66 bool BaseProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id,
67 bool* bp) const {
68 assertEnteredPolicy(cx, proxy, id, GET);
69 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
70 if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
71 return false;
72 }
73 *bp = desc.isSome();
74 return true;
75 }
76
get(JSContext * cx,HandleObject proxy,HandleValue receiver,HandleId id,MutableHandleValue vp) const77 bool BaseProxyHandler::get(JSContext* cx, HandleObject proxy,
78 HandleValue receiver, HandleId id,
79 MutableHandleValue vp) const {
80 assertEnteredPolicy(cx, proxy, id, GET);
81
82 // This method is not covered by any spec, but we follow ES 2016
83 // (January 21, 2016) 9.1.8 fairly closely.
84
85 // Step 2. (Step 1 is a superfluous assertion.)
86 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
87 if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
88 return false;
89 }
90 if (desc.isSome()) {
91 desc->assertComplete();
92 }
93
94 // Step 3.
95 if (desc.isNothing()) {
96 // The spec calls this variable "parent", but that word has weird
97 // connotations in SpiderMonkey, so let's go with "proto".
98 // Step 3.a.
99 RootedObject proto(cx);
100 if (!GetPrototype(cx, proxy, &proto)) {
101 return false;
102 }
103
104 // Step 3.b.
105 if (!proto) {
106 vp.setUndefined();
107 return true;
108 }
109
110 // Step 3.c.
111 return GetProperty(cx, proto, receiver, id, vp);
112 }
113
114 // Step 4.
115 if (desc->isDataDescriptor()) {
116 vp.set(desc->value());
117 return true;
118 }
119
120 // Step 5.
121 MOZ_ASSERT(desc->isAccessorDescriptor());
122 RootedObject getter(cx, desc->getter());
123
124 // Step 6.
125 if (!getter) {
126 vp.setUndefined();
127 return true;
128 }
129
130 // Step 7.
131 RootedValue getterFunc(cx, ObjectValue(*getter));
132 return CallGetter(cx, receiver, getterFunc, vp);
133 }
134
set(JSContext * cx,HandleObject proxy,HandleId id,HandleValue v,HandleValue receiver,ObjectOpResult & result) const135 bool BaseProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id,
136 HandleValue v, HandleValue receiver,
137 ObjectOpResult& result) const {
138 assertEnteredPolicy(cx, proxy, id, SET);
139
140 // This method is not covered by any spec, but we follow ES6 draft rev 28
141 // (2014 Oct 14) 9.1.9 fairly closely, adapting it slightly for
142 // SpiderMonkey's particular foibles.
143
144 // Steps 2-3. (Step 1 is a superfluous assertion.)
145 Rooted<mozilla::Maybe<PropertyDescriptor>> ownDesc(cx);
146 if (!getOwnPropertyDescriptor(cx, proxy, id, &ownDesc)) {
147 return false;
148 }
149 if (ownDesc.isSome()) {
150 ownDesc->assertComplete();
151 }
152
153 // The rest is factored out into a separate function with a weird name.
154 // This algorithm continues just below.
155 return SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc,
156 result);
157 }
158
SetPropertyIgnoringNamedGetter(JSContext * cx,HandleObject obj,HandleId id,HandleValue v,HandleValue receiver,Handle<mozilla::Maybe<PropertyDescriptor>> ownDesc_,ObjectOpResult & result)159 bool js::SetPropertyIgnoringNamedGetter(
160 JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
161 HandleValue receiver, Handle<mozilla::Maybe<PropertyDescriptor>> ownDesc_,
162 ObjectOpResult& result) {
163 Rooted<PropertyDescriptor> ownDesc(cx);
164
165 // Step 4.
166 if (ownDesc_.isNothing()) {
167 // The spec calls this variable "parent", but that word has weird
168 // connotations in SpiderMonkey, so let's go with "proto".
169 RootedObject proto(cx);
170 if (!GetPrototype(cx, obj, &proto)) {
171 return false;
172 }
173 if (proto) {
174 return SetProperty(cx, proto, id, v, receiver, result);
175 }
176
177 // Step 4.d.
178 ownDesc.set(PropertyDescriptor::Data(
179 UndefinedValue(),
180 {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable,
181 JS::PropertyAttribute::Writable}));
182 } else {
183 ownDesc.set(*ownDesc_);
184 }
185
186 // Step 5.
187 if (ownDesc.isDataDescriptor()) {
188 // Steps 5.a-b.
189 if (!ownDesc.writable()) {
190 return result.fail(JSMSG_READ_ONLY);
191 }
192 if (!receiver.isObject()) {
193 return result.fail(JSMSG_SET_NON_OBJECT_RECEIVER);
194 }
195 RootedObject receiverObj(cx, &receiver.toObject());
196
197 // Steps 5.c-d.
198 Rooted<mozilla::Maybe<PropertyDescriptor>> existingDescriptor(cx);
199 if (!GetOwnPropertyDescriptor(cx, receiverObj, id, &existingDescriptor)) {
200 return false;
201 }
202
203 // Step 5.e.
204 if (existingDescriptor.isSome()) {
205 // Step 5.e.i.
206 if (existingDescriptor->isAccessorDescriptor()) {
207 return result.fail(JSMSG_OVERWRITING_ACCESSOR);
208 }
209
210 // Step 5.e.ii.
211 if (!existingDescriptor->writable()) {
212 return result.fail(JSMSG_READ_ONLY);
213 }
214 }
215
216 // Steps 5.e.iii-iv. and 5.f.i.
217 Rooted<PropertyDescriptor> desc(cx);
218 if (existingDescriptor.isSome()) {
219 desc = PropertyDescriptor::Empty();
220 desc.setValue(v);
221 } else {
222 desc = PropertyDescriptor::Data(v, {JS::PropertyAttribute::Configurable,
223 JS::PropertyAttribute::Enumerable,
224 JS::PropertyAttribute::Writable});
225 }
226 return DefineProperty(cx, receiverObj, id, desc, result);
227 }
228
229 // Step 6.
230 MOZ_ASSERT(ownDesc.isAccessorDescriptor());
231 RootedObject setter(cx);
232 if (ownDesc.hasSetter()) {
233 setter = ownDesc.setter();
234 }
235 if (!setter) {
236 return result.fail(JSMSG_GETTER_ONLY);
237 }
238 RootedValue setterValue(cx, ObjectValue(*setter));
239 if (!CallSetter(cx, receiver, setterValue, v)) {
240 return false;
241 }
242 return result.succeed();
243 }
244
getOwnEnumerablePropertyKeys(JSContext * cx,HandleObject proxy,MutableHandleIdVector props) const245 bool BaseProxyHandler::getOwnEnumerablePropertyKeys(
246 JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const {
247 assertEnteredPolicy(cx, proxy, JS::PropertyKey::Void(), ENUMERATE);
248 MOZ_ASSERT(props.length() == 0);
249
250 if (!ownPropertyKeys(cx, proxy, props)) {
251 return false;
252 }
253
254 /* Select only the enumerable properties through in-place iteration. */
255 RootedId id(cx);
256 size_t i = 0;
257 for (size_t j = 0, len = props.length(); j < len; j++) {
258 MOZ_ASSERT(i <= j);
259 id = props[j];
260 if (id.isSymbol()) {
261 continue;
262 }
263
264 AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET);
265 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
266 if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) {
267 return false;
268 }
269 if (desc.isSome()) {
270 desc->assertComplete();
271 }
272
273 if (desc.isSome() && desc->enumerable()) {
274 props[i++].set(id);
275 }
276 }
277
278 MOZ_ASSERT(i <= props.length());
279 if (!props.resize(i)) {
280 return false;
281 }
282
283 return true;
284 }
285
enumerate(JSContext * cx,HandleObject proxy,MutableHandleIdVector props) const286 bool BaseProxyHandler::enumerate(JSContext* cx, HandleObject proxy,
287 MutableHandleIdVector props) const {
288 assertEnteredPolicy(cx, proxy, JS::PropertyKey::Void(), ENUMERATE);
289
290 // GetPropertyKeys will invoke getOwnEnumerablePropertyKeys along the proto
291 // chain for us.
292 MOZ_ASSERT(props.empty());
293 return GetPropertyKeys(cx, proxy, 0, props);
294 }
295
call(JSContext * cx,HandleObject proxy,const CallArgs & args) const296 bool BaseProxyHandler::call(JSContext* cx, HandleObject proxy,
297 const CallArgs& args) const {
298 MOZ_CRASH("callable proxies should implement call trap");
299 }
300
construct(JSContext * cx,HandleObject proxy,const CallArgs & args) const301 bool BaseProxyHandler::construct(JSContext* cx, HandleObject proxy,
302 const CallArgs& args) const {
303 MOZ_CRASH("callable proxies should implement construct trap");
304 }
305
className(JSContext * cx,HandleObject proxy) const306 const char* BaseProxyHandler::className(JSContext* cx,
307 HandleObject proxy) const {
308 return proxy->isCallable() ? "Function" : "Object";
309 }
310
fun_toString(JSContext * cx,HandleObject proxy,bool isToSource) const311 JSString* BaseProxyHandler::fun_toString(JSContext* cx, HandleObject proxy,
312 bool isToSource) const {
313 if (proxy->isCallable()) {
314 return JS_NewStringCopyZ(cx, "function () {\n [native code]\n}");
315 }
316
317 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
318 JSMSG_INCOMPATIBLE_PROTO, js_Function_str,
319 js_toString_str, "object");
320 return nullptr;
321 }
322
regexp_toShared(JSContext * cx,HandleObject proxy) const323 RegExpShared* BaseProxyHandler::regexp_toShared(JSContext* cx,
324 HandleObject proxy) const {
325 MOZ_CRASH("This should have been a wrapped regexp");
326 }
327
boxedValue_unbox(JSContext * cx,HandleObject proxy,MutableHandleValue vp) const328 bool BaseProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy,
329 MutableHandleValue vp) const {
330 vp.setUndefined();
331 return true;
332 }
333
nativeCall(JSContext * cx,IsAcceptableThis test,NativeImpl impl,const CallArgs & args) const334 bool BaseProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test,
335 NativeImpl impl, const CallArgs& args) const {
336 ReportIncompatible(cx, args);
337 return false;
338 }
339
hasInstance(JSContext * cx,HandleObject proxy,MutableHandleValue v,bool * bp) const340 bool BaseProxyHandler::hasInstance(JSContext* cx, HandleObject proxy,
341 MutableHandleValue v, bool* bp) const {
342 assertEnteredPolicy(cx, proxy, JS::PropertyKey::Void(), GET);
343 cx->check(proxy, v);
344 return JS::InstanceofOperator(cx, proxy, v, bp);
345 }
346
getBuiltinClass(JSContext * cx,HandleObject proxy,ESClass * cls) const347 bool BaseProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
348 ESClass* cls) const {
349 *cls = ESClass::Other;
350 return true;
351 }
352
isArray(JSContext * cx,HandleObject proxy,IsArrayAnswer * answer) const353 bool BaseProxyHandler::isArray(JSContext* cx, HandleObject proxy,
354 IsArrayAnswer* answer) const {
355 *answer = IsArrayAnswer::NotArray;
356 return true;
357 }
358
trace(JSTracer * trc,JSObject * proxy) const359 void BaseProxyHandler::trace(JSTracer* trc, JSObject* proxy) const {}
360
finalize(JSFreeOp * fop,JSObject * proxy) const361 void BaseProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const {}
362
objectMoved(JSObject * proxy,JSObject * old) const363 size_t BaseProxyHandler::objectMoved(JSObject* proxy, JSObject* old) const {
364 return 0;
365 }
366
getPrototype(JSContext * cx,HandleObject proxy,MutableHandleObject protop) const367 bool BaseProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
368 MutableHandleObject protop) const {
369 MOZ_CRASH("must override getPrototype with dynamic prototype");
370 }
371
setPrototype(JSContext * cx,HandleObject proxy,HandleObject proto,ObjectOpResult & result) const372 bool BaseProxyHandler::setPrototype(JSContext* cx, HandleObject proxy,
373 HandleObject proto,
374 ObjectOpResult& result) const {
375 // Disallow sets of protos on proxies with dynamic prototypes but no hook.
376 // This keeps us away from the footgun of having the first proto set opt
377 // you out of having dynamic protos altogether.
378 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
379 JSMSG_CANT_SET_PROTO_OF, "incompatible Proxy");
380 return false;
381 }
382
setImmutablePrototype(JSContext * cx,HandleObject proxy,bool * succeeded) const383 bool BaseProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy,
384 bool* succeeded) const {
385 *succeeded = false;
386 return true;
387 }
388
getElements(JSContext * cx,HandleObject proxy,uint32_t begin,uint32_t end,ElementAdder * adder) const389 bool BaseProxyHandler::getElements(JSContext* cx, HandleObject proxy,
390 uint32_t begin, uint32_t end,
391 ElementAdder* adder) const {
392 assertEnteredPolicy(cx, proxy, JS::PropertyKey::Void(), GET);
393
394 return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder);
395 }
396
isCallable(JSObject * obj) const397 bool BaseProxyHandler::isCallable(JSObject* obj) const { return false; }
398
isConstructor(JSObject * obj) const399 bool BaseProxyHandler::isConstructor(JSObject* obj) const { return false; }
400
NukeNonCCWProxy(JSContext * cx,HandleObject proxy)401 JS_PUBLIC_API void js::NukeNonCCWProxy(JSContext* cx, HandleObject proxy) {
402 MOZ_ASSERT(proxy->is<ProxyObject>());
403 MOZ_ASSERT(!proxy->is<CrossCompartmentWrapperObject>());
404
405 // (NotifyGCNukeWrapper() only needs to be called on CCWs.)
406
407 // The proxy is about to be replaced, so we need to do any necessary
408 // cleanup first.
409 proxy->as<ProxyObject>().handler()->finalize(cx->defaultFreeOp(), proxy);
410
411 proxy->as<ProxyObject>().nuke();
412
413 MOZ_ASSERT(IsDeadProxyObject(proxy));
414 }
415
NukeRemovedCrossCompartmentWrapper(JSContext * cx,JSObject * wrapper)416 JS_PUBLIC_API void js::NukeRemovedCrossCompartmentWrapper(JSContext* cx,
417 JSObject* wrapper) {
418 MOZ_ASSERT(wrapper->is<CrossCompartmentWrapperObject>());
419
420 NotifyGCNukeWrapper(cx, wrapper);
421
422 // We don't need to call finalize here because the CCW finalizer doesn't do
423 // anything. Skipping finalize means that |wrapper| doesn't need to be rooted
424 // to pass the hazard analysis, which is needed because this method is called
425 // from some tricky places inside transplanting where rooting can be
426 // difficult.
427
428 wrapper->as<ProxyObject>().nuke();
429
430 MOZ_ASSERT(IsDeadProxyObject(wrapper));
431 }
432