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