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 "js/Wrapper.h"
8 
9 #include "jsexn.h"
10 
11 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
12 #include "js/friend/WindowProxy.h"    // js::IsWindowProxy
13 #include "js/Object.h"                // JS::GetBuiltinClass
14 #include "js/Proxy.h"
15 #include "vm/ErrorObject.h"
16 #include "vm/JSContext.h"
17 #include "vm/ProxyObject.h"
18 #include "vm/Realm.h"
19 #include "vm/RegExpObject.h"
20 #include "vm/WrapperObject.h"
21 
22 #include "gc/Marking-inl.h"
23 #include "vm/JSObject-inl.h"
24 #include "vm/NativeObject-inl.h"
25 
26 using namespace js;
27 
finalizeInBackground(const Value & priv) const28 bool Wrapper::finalizeInBackground(const Value& priv) const {
29   if (!priv.isObject()) {
30     return true;
31   }
32 
33   /*
34    * Make the 'background-finalized-ness' of the wrapper the same as the
35    * wrapped object, to allow transplanting between them.
36    */
37   JSObject* wrapped = MaybeForwarded(&priv.toObject());
38   gc::AllocKind wrappedKind;
39   if (IsInsideNursery(wrapped)) {
40     JSRuntime* rt = wrapped->runtimeFromMainThread();
41     wrappedKind = wrapped->allocKindForTenure(rt->gc.nursery());
42   } else {
43     wrappedKind = wrapped->asTenured().getAllocKind();
44   }
45   return IsBackgroundFinalized(wrappedKind);
46 }
47 
getOwnPropertyDescriptor(JSContext * cx,HandleObject proxy,HandleId id,MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) const48 bool ForwardingProxyHandler::getOwnPropertyDescriptor(
49     JSContext* cx, HandleObject proxy, HandleId id,
50     MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) const {
51   assertEnteredPolicy(cx, proxy, id, GET | SET | GET_PROPERTY_DESCRIPTOR);
52   RootedObject target(cx, proxy->as<ProxyObject>().target());
53   return GetOwnPropertyDescriptor(cx, target, id, desc);
54 }
55 
defineProperty(JSContext * cx,HandleObject proxy,HandleId id,Handle<PropertyDescriptor> desc,ObjectOpResult & result) const56 bool ForwardingProxyHandler::defineProperty(JSContext* cx, HandleObject proxy,
57                                             HandleId id,
58                                             Handle<PropertyDescriptor> desc,
59                                             ObjectOpResult& result) const {
60   assertEnteredPolicy(cx, proxy, id, SET);
61   RootedObject target(cx, proxy->as<ProxyObject>().target());
62   return DefineProperty(cx, target, id, desc, result);
63 }
64 
ownPropertyKeys(JSContext * cx,HandleObject proxy,MutableHandleIdVector props) const65 bool ForwardingProxyHandler::ownPropertyKeys(
66     JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const {
67   assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
68   RootedObject target(cx, proxy->as<ProxyObject>().target());
69   return GetPropertyKeys(
70       cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
71 }
72 
delete_(JSContext * cx,HandleObject proxy,HandleId id,ObjectOpResult & result) const73 bool ForwardingProxyHandler::delete_(JSContext* cx, HandleObject proxy,
74                                      HandleId id,
75                                      ObjectOpResult& result) const {
76   assertEnteredPolicy(cx, proxy, id, SET);
77   RootedObject target(cx, proxy->as<ProxyObject>().target());
78   return DeleteProperty(cx, target, id, result);
79 }
80 
enumerate(JSContext * cx,HandleObject proxy,MutableHandleIdVector props) const81 bool ForwardingProxyHandler::enumerate(JSContext* cx, HandleObject proxy,
82                                        MutableHandleIdVector props) const {
83   assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
84   MOZ_ASSERT(
85       !hasPrototype());  // Should never be called if there's a prototype.
86   RootedObject target(cx, proxy->as<ProxyObject>().target());
87   return EnumerateProperties(cx, target, props);
88 }
89 
getPrototype(JSContext * cx,HandleObject proxy,MutableHandleObject protop) const90 bool ForwardingProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
91                                           MutableHandleObject protop) const {
92   RootedObject target(cx, proxy->as<ProxyObject>().target());
93   return GetPrototype(cx, target, protop);
94 }
95 
setPrototype(JSContext * cx,HandleObject proxy,HandleObject proto,ObjectOpResult & result) const96 bool ForwardingProxyHandler::setPrototype(JSContext* cx, HandleObject proxy,
97                                           HandleObject proto,
98                                           ObjectOpResult& result) const {
99   RootedObject target(cx, proxy->as<ProxyObject>().target());
100   return SetPrototype(cx, target, proto, result);
101 }
102 
getPrototypeIfOrdinary(JSContext * cx,HandleObject proxy,bool * isOrdinary,MutableHandleObject protop) const103 bool ForwardingProxyHandler::getPrototypeIfOrdinary(
104     JSContext* cx, HandleObject proxy, bool* isOrdinary,
105     MutableHandleObject protop) const {
106   RootedObject target(cx, proxy->as<ProxyObject>().target());
107   return GetPrototypeIfOrdinary(cx, target, isOrdinary, protop);
108 }
109 
setImmutablePrototype(JSContext * cx,HandleObject proxy,bool * succeeded) const110 bool ForwardingProxyHandler::setImmutablePrototype(JSContext* cx,
111                                                    HandleObject proxy,
112                                                    bool* succeeded) const {
113   RootedObject target(cx, proxy->as<ProxyObject>().target());
114   return SetImmutablePrototype(cx, target, succeeded);
115 }
116 
preventExtensions(JSContext * cx,HandleObject proxy,ObjectOpResult & result) const117 bool ForwardingProxyHandler::preventExtensions(JSContext* cx,
118                                                HandleObject proxy,
119                                                ObjectOpResult& result) const {
120   RootedObject target(cx, proxy->as<ProxyObject>().target());
121   return PreventExtensions(cx, target, result);
122 }
123 
isExtensible(JSContext * cx,HandleObject proxy,bool * extensible) const124 bool ForwardingProxyHandler::isExtensible(JSContext* cx, HandleObject proxy,
125                                           bool* extensible) const {
126   RootedObject target(cx, proxy->as<ProxyObject>().target());
127   return IsExtensible(cx, target, extensible);
128 }
129 
has(JSContext * cx,HandleObject proxy,HandleId id,bool * bp) const130 bool ForwardingProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id,
131                                  bool* bp) const {
132   assertEnteredPolicy(cx, proxy, id, GET);
133   MOZ_ASSERT(
134       !hasPrototype());  // Should never be called if there's a prototype.
135   RootedObject target(cx, proxy->as<ProxyObject>().target());
136   return HasProperty(cx, target, id, bp);
137 }
138 
get(JSContext * cx,HandleObject proxy,HandleValue receiver,HandleId id,MutableHandleValue vp) const139 bool ForwardingProxyHandler::get(JSContext* cx, HandleObject proxy,
140                                  HandleValue receiver, HandleId id,
141                                  MutableHandleValue vp) const {
142   assertEnteredPolicy(cx, proxy, id, GET);
143   RootedObject target(cx, proxy->as<ProxyObject>().target());
144   return GetProperty(cx, target, receiver, id, vp);
145 }
146 
set(JSContext * cx,HandleObject proxy,HandleId id,HandleValue v,HandleValue receiver,ObjectOpResult & result) const147 bool ForwardingProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id,
148                                  HandleValue v, HandleValue receiver,
149                                  ObjectOpResult& result) const {
150   assertEnteredPolicy(cx, proxy, id, SET);
151   RootedObject target(cx, proxy->as<ProxyObject>().target());
152   return SetProperty(cx, target, id, v, receiver, result);
153 }
154 
call(JSContext * cx,HandleObject proxy,const CallArgs & args) const155 bool ForwardingProxyHandler::call(JSContext* cx, HandleObject proxy,
156                                   const CallArgs& args) const {
157   assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
158   RootedValue target(cx, proxy->as<ProxyObject>().private_());
159 
160   InvokeArgs iargs(cx);
161   if (!FillArgumentsFromArraylike(cx, iargs, args)) {
162     return false;
163   }
164 
165   return js::Call(cx, target, args.thisv(), iargs, args.rval());
166 }
167 
construct(JSContext * cx,HandleObject proxy,const CallArgs & args) const168 bool ForwardingProxyHandler::construct(JSContext* cx, HandleObject proxy,
169                                        const CallArgs& args) const {
170   assertEnteredPolicy(cx, proxy, JSID_VOID, CALL);
171 
172   RootedValue target(cx, proxy->as<ProxyObject>().private_());
173   if (!IsConstructor(target)) {
174     ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, target,
175                      nullptr);
176     return false;
177   }
178 
179   ConstructArgs cargs(cx);
180   if (!FillArgumentsFromArraylike(cx, cargs, args)) {
181     return false;
182   }
183 
184   RootedObject obj(cx);
185   if (!Construct(cx, target, cargs, args.newTarget(), &obj)) {
186     return false;
187   }
188 
189   args.rval().setObject(*obj);
190   return true;
191 }
192 
hasOwn(JSContext * cx,HandleObject proxy,HandleId id,bool * bp) const193 bool ForwardingProxyHandler::hasOwn(JSContext* cx, HandleObject proxy,
194                                     HandleId id, bool* bp) const {
195   assertEnteredPolicy(cx, proxy, id, GET);
196   RootedObject target(cx, proxy->as<ProxyObject>().target());
197   return HasOwnProperty(cx, target, id, bp);
198 }
199 
getOwnEnumerablePropertyKeys(JSContext * cx,HandleObject proxy,MutableHandleIdVector props) const200 bool ForwardingProxyHandler::getOwnEnumerablePropertyKeys(
201     JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const {
202   assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
203   RootedObject target(cx, proxy->as<ProxyObject>().target());
204   return GetPropertyKeys(cx, target, JSITER_OWNONLY, props);
205 }
206 
nativeCall(JSContext * cx,IsAcceptableThis test,NativeImpl impl,const CallArgs & args) const207 bool ForwardingProxyHandler::nativeCall(JSContext* cx, IsAcceptableThis test,
208                                         NativeImpl impl,
209                                         const CallArgs& args) const {
210   args.setThis(
211       ObjectValue(*args.thisv().toObject().as<ProxyObject>().target()));
212   if (!test(args.thisv())) {
213     ReportIncompatible(cx, args);
214     return false;
215   }
216 
217   return CallNativeImpl(cx, impl, args);
218 }
219 
hasInstance(JSContext * cx,HandleObject proxy,MutableHandleValue v,bool * bp) const220 bool ForwardingProxyHandler::hasInstance(JSContext* cx, HandleObject proxy,
221                                          MutableHandleValue v, bool* bp) const {
222   assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
223   RootedObject target(cx, proxy->as<ProxyObject>().target());
224   return HasInstance(cx, target, v, bp);
225 }
226 
getBuiltinClass(JSContext * cx,HandleObject proxy,ESClass * cls) const227 bool ForwardingProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy,
228                                              ESClass* cls) const {
229   RootedObject target(cx, proxy->as<ProxyObject>().target());
230   return JS::GetBuiltinClass(cx, target, cls);
231 }
232 
isArray(JSContext * cx,HandleObject proxy,JS::IsArrayAnswer * answer) const233 bool ForwardingProxyHandler::isArray(JSContext* cx, HandleObject proxy,
234                                      JS::IsArrayAnswer* answer) const {
235   RootedObject target(cx, proxy->as<ProxyObject>().target());
236   return IsArray(cx, target, answer);
237 }
238 
className(JSContext * cx,HandleObject proxy) const239 const char* ForwardingProxyHandler::className(JSContext* cx,
240                                               HandleObject proxy) const {
241   assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
242   RootedObject target(cx, proxy->as<ProxyObject>().target());
243   return GetObjectClassName(cx, target);
244 }
245 
fun_toString(JSContext * cx,HandleObject proxy,bool isToSource) const246 JSString* ForwardingProxyHandler::fun_toString(JSContext* cx,
247                                                HandleObject proxy,
248                                                bool isToSource) const {
249   assertEnteredPolicy(cx, proxy, JSID_VOID, GET);
250   RootedObject target(cx, proxy->as<ProxyObject>().target());
251   return fun_toStringHelper(cx, target, isToSource);
252 }
253 
regexp_toShared(JSContext * cx,HandleObject proxy) const254 RegExpShared* ForwardingProxyHandler::regexp_toShared(
255     JSContext* cx, HandleObject proxy) const {
256   RootedObject target(cx, proxy->as<ProxyObject>().target());
257   return RegExpToShared(cx, target);
258 }
259 
boxedValue_unbox(JSContext * cx,HandleObject proxy,MutableHandleValue vp) const260 bool ForwardingProxyHandler::boxedValue_unbox(JSContext* cx, HandleObject proxy,
261                                               MutableHandleValue vp) const {
262   RootedObject target(cx, proxy->as<ProxyObject>().target());
263   return Unbox(cx, target, vp);
264 }
265 
isCallable(JSObject * obj) const266 bool ForwardingProxyHandler::isCallable(JSObject* obj) const {
267   JSObject* target = obj->as<ProxyObject>().target();
268   return target->isCallable();
269 }
270 
isConstructor(JSObject * obj) const271 bool ForwardingProxyHandler::isConstructor(JSObject* obj) const {
272   JSObject* target = obj->as<ProxyObject>().target();
273   return target->isConstructor();
274 }
275 
New(JSContext * cx,JSObject * obj,const Wrapper * handler,const WrapperOptions & options)276 JSObject* Wrapper::New(JSContext* cx, JSObject* obj, const Wrapper* handler,
277                        const WrapperOptions& options) {
278   // If this is a cross-compartment wrapper allocate it in the compartment's
279   // first global. See Compartment::globalForNewCCW.
280   mozilla::Maybe<AutoRealm> ar;
281   if (handler->isCrossCompartmentWrapper()) {
282     ar.emplace(cx, &cx->compartment()->globalForNewCCW());
283   }
284   RootedValue priv(cx, ObjectValue(*obj));
285   return NewProxyObject(cx, handler, priv, options.proto(), options);
286 }
287 
Renew(JSObject * existing,JSObject * obj,const Wrapper * handler)288 JSObject* Wrapper::Renew(JSObject* existing, JSObject* obj,
289                          const Wrapper* handler) {
290   existing->as<ProxyObject>().renew(handler, ObjectValue(*obj));
291   return existing;
292 }
293 
wrappedObject(JSObject * wrapper)294 JSObject* Wrapper::wrappedObject(JSObject* wrapper) {
295   MOZ_ASSERT(wrapper->is<WrapperObject>());
296   JSObject* target = wrapper->as<ProxyObject>().target();
297 
298   if (target) {
299     // A cross-compartment wrapper should never wrap a CCW. We rely on this
300     // in the wrapper handlers (we use AutoRealm on our return value, and
301     // AutoRealm cannot be used with CCWs).
302     MOZ_ASSERT_IF(IsCrossCompartmentWrapper(wrapper),
303                   !IsCrossCompartmentWrapper(target));
304 
305 #ifdef DEBUG
306     // An incremental GC will eventually mark the targets of black wrappers
307     // black but while it is in progress we can observe gray targets.
308     if (!wrapper->runtimeFromMainThread()->gc.isIncrementalGCInProgress() &&
309         wrapper->isMarkedBlack()) {
310       JS::AssertObjectIsNotGray(target);
311     }
312 #endif
313 
314     // Unmark wrapper targets that should be black in case an incremental GC
315     // hasn't marked them the correct color yet.
316     JS::ExposeObjectToActiveJS(target);
317   }
318 
319   return target;
320 }
321 
UncheckedUnwrapWithoutExpose(JSObject * wrapped)322 JS_PUBLIC_API JSObject* js::UncheckedUnwrapWithoutExpose(JSObject* wrapped) {
323   while (true) {
324     if (!wrapped->is<WrapperObject>() || MOZ_UNLIKELY(IsWindowProxy(wrapped))) {
325       break;
326     }
327     wrapped = wrapped->as<WrapperObject>().target();
328 
329     // This can be called from when getting a weakmap key delegate() on a
330     // wrapper whose referent has been moved while it is still unmarked.
331     if (wrapped) {
332       wrapped = MaybeForwarded(wrapped);
333     }
334   }
335   return wrapped;
336 }
337 
UncheckedUnwrap(JSObject * wrapped,bool stopAtWindowProxy,unsigned * flagsp)338 JS_PUBLIC_API JSObject* js::UncheckedUnwrap(JSObject* wrapped,
339                                             bool stopAtWindowProxy,
340                                             unsigned* flagsp) {
341   MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
342   MOZ_ASSERT(CurrentThreadCanAccessRuntime(wrapped->runtimeFromAnyThread()));
343 
344   unsigned flags = 0;
345   while (true) {
346     if (!wrapped->is<WrapperObject>() ||
347         MOZ_UNLIKELY(stopAtWindowProxy && IsWindowProxy(wrapped))) {
348       break;
349     }
350     flags |= Wrapper::wrapperHandler(wrapped)->flags();
351     wrapped = Wrapper::wrappedObject(wrapped);
352   }
353   if (flagsp) {
354     *flagsp = flags;
355   }
356   return wrapped;
357 }
358 
CheckedUnwrapStatic(JSObject * obj)359 JS_PUBLIC_API JSObject* js::CheckedUnwrapStatic(JSObject* obj) {
360   while (true) {
361     JSObject* wrapper = obj;
362     obj = UnwrapOneCheckedStatic(obj);
363     if (!obj || obj == wrapper) {
364       return obj;
365     }
366   }
367 }
368 
UnwrapOneCheckedStatic(JSObject * obj)369 JS_PUBLIC_API JSObject* js::UnwrapOneCheckedStatic(JSObject* obj) {
370   MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
371   MOZ_ASSERT(CurrentThreadCanAccessRuntime(obj->runtimeFromAnyThread()));
372 
373   // Note: callers that care about WindowProxy unwrapping should use
374   // CheckedUnwrapDynamic or UnwrapOneCheckedDynamic instead of this. We don't
375   // unwrap WindowProxy here to preserve legacy behavior and for consistency
376   // with CheckedUnwrapDynamic's default stopAtWindowProxy = true.
377   if (!obj->is<WrapperObject>() || MOZ_UNLIKELY(IsWindowProxy(obj))) {
378     return obj;
379   }
380 
381   const Wrapper* handler = Wrapper::wrapperHandler(obj);
382   return handler->hasSecurityPolicy() ? nullptr : Wrapper::wrappedObject(obj);
383 }
384 
CheckedUnwrapDynamic(JSObject * obj,JSContext * cx,bool stopAtWindowProxy)385 JS_PUBLIC_API JSObject* js::CheckedUnwrapDynamic(JSObject* obj, JSContext* cx,
386                                                  bool stopAtWindowProxy) {
387   RootedObject wrapper(cx, obj);
388   while (true) {
389     JSObject* unwrapped =
390         UnwrapOneCheckedDynamic(wrapper, cx, stopAtWindowProxy);
391     if (!unwrapped || unwrapped == wrapper) {
392       return unwrapped;
393     }
394     wrapper = unwrapped;
395   }
396 }
397 
UnwrapOneCheckedDynamic(HandleObject obj,JSContext * cx,bool stopAtWindowProxy)398 JS_PUBLIC_API JSObject* js::UnwrapOneCheckedDynamic(HandleObject obj,
399                                                     JSContext* cx,
400                                                     bool stopAtWindowProxy) {
401   MOZ_ASSERT(!JS::RuntimeHeapIsCollecting());
402   MOZ_ASSERT(CurrentThreadCanAccessRuntime(obj->runtimeFromAnyThread()));
403   // We should know who's asking.
404   MOZ_ASSERT(cx);
405   MOZ_ASSERT(cx->realm());
406 
407   if (!obj->is<WrapperObject>() ||
408       MOZ_UNLIKELY(stopAtWindowProxy && IsWindowProxy(obj))) {
409     return obj;
410   }
411 
412   const Wrapper* handler = Wrapper::wrapperHandler(obj);
413   if (!handler->hasSecurityPolicy() ||
414       handler->dynamicCheckedUnwrapAllowed(obj, cx)) {
415     return Wrapper::wrappedObject(obj);
416   }
417 
418   return nullptr;
419 }
420 
ReportAccessDenied(JSContext * cx)421 void js::ReportAccessDenied(JSContext* cx) {
422   JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
423                             JSMSG_OBJECT_ACCESS_DENIED);
424 }
425 
426 const char Wrapper::family = 0;
427 const Wrapper Wrapper::singleton((unsigned)0);
428 const Wrapper Wrapper::singletonWithPrototype((unsigned)0, true);
429 JSObject* const Wrapper::defaultProto = TaggedProto::LazyProto;
430 
431 /* Compartments. */
432 
TransparentObjectWrapper(JSContext * cx,HandleObject existing,HandleObject obj)433 JSObject* js::TransparentObjectWrapper(JSContext* cx, HandleObject existing,
434                                        HandleObject obj) {
435   // Allow wrapping outer window proxies.
436   MOZ_ASSERT(!obj->is<WrapperObject>() || IsWindowProxy(obj));
437   return Wrapper::New(cx, obj, &CrossCompartmentWrapper::singleton);
438 }
439 
~ErrorCopier()440 ErrorCopier::~ErrorCopier() {
441   JSContext* cx = ar->context();
442 
443   // The provenance of Debugger.DebuggeeWouldRun is the topmost locking
444   // debugger compartment; it should not be copied around.
445   if (ar->origin()->compartment() != cx->compartment() &&
446       cx->isExceptionPending() && !cx->isThrowingDebuggeeWouldRun()) {
447     RootedValue exc(cx);
448     if (cx->getPendingException(&exc) && exc.isObject() &&
449         exc.toObject().is<ErrorObject>()) {
450       RootedSavedFrame stack(cx, cx->getPendingExceptionStack());
451       cx->clearPendingException();
452       ar.reset();
453       Rooted<ErrorObject*> errObj(cx, &exc.toObject().as<ErrorObject>());
454       if (JSObject* copyobj = CopyErrorObject(cx, errObj)) {
455         RootedValue rootedCopy(cx, ObjectValue(*copyobj));
456         cx->setPendingException(rootedCopy, stack);
457       }
458     }
459   }
460 }
461