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