Proxy.cpp (35859B)
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/Proxy.h" 8 9 #include "mozilla/Attributes.h" 10 #include "mozilla/Maybe.h" 11 12 #include <string.h> 13 14 #include "js/friend/ErrorMessages.h" // JSMSG_* 15 #include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit, js::GetNativeStackLimit 16 #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowProxyIfWindow 17 #include "js/PropertySpec.h" 18 #include "js/Value.h" // JS::ObjectValue 19 #include "js/Wrapper.h" 20 #include "proxy/DeadObjectProxy.h" 21 #include "proxy/ScriptedProxyHandler.h" 22 #include "vm/Compartment.h" 23 #include "vm/Interpreter.h" // js::CallGetter 24 #include "vm/JSContext.h" 25 #include "vm/JSFunction.h" 26 #include "vm/JSObject.h" 27 #include "vm/WrapperObject.h" 28 29 #include "gc/Marking-inl.h" 30 #include "vm/JSObject-inl.h" 31 #include "vm/NativeObject-inl.h" 32 33 using namespace js; 34 35 // Used by private fields to manipulate the ProxyExpando: 36 // All the following methods are called iff the handler for the proxy 37 // returns true for useProxyExpandoObjectForPrivateFields. 38 static bool ProxySetOnExpando(JSContext* cx, HandleObject proxy, HandleId id, 39 HandleValue v, HandleValue receiver, 40 ObjectOpResult& result) { 41 MOZ_ASSERT(id.isPrivateName()); 42 43 // For BaseProxyHandler, private names are stored in the expando object. 44 RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull()); 45 46 // SetPrivateElementOperation checks for hasOwn first, which ensures the 47 // expando exsists. 48 // 49 // If we don't have an expando, then we're probably misusing debugger apis and 50 // should just throw. 51 if (!expando) { 52 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 53 JSMSG_SET_MISSING_PRIVATE); 54 return false; 55 } 56 57 Rooted<mozilla::Maybe<PropertyDescriptor>> ownDesc(cx); 58 if (!GetOwnPropertyDescriptor(cx, expando, id, &ownDesc)) { 59 return false; 60 } 61 if (ownDesc.isNothing()) { 62 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 63 JSMSG_SET_MISSING_PRIVATE); 64 return false; 65 } 66 67 RootedValue expandoValue(cx, proxy->as<ProxyObject>().expando()); 68 return SetPropertyIgnoringNamedGetter(cx, expando, id, v, expandoValue, 69 ownDesc, result); 70 } 71 72 static bool ProxyGetOwnPropertyDescriptorFromExpando( 73 JSContext* cx, HandleObject proxy, HandleId id, 74 MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) { 75 RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull()); 76 77 if (!expando) { 78 return true; 79 } 80 81 return GetOwnPropertyDescriptor(cx, expando, id, desc); 82 } 83 84 static bool ProxyGetOnExpando(JSContext* cx, HandleObject proxy, 85 HandleValue receiver, HandleId id, 86 MutableHandleValue vp) { 87 // For BaseProxyHandler, private names are stored in the expando object. 88 RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull()); 89 90 // We must have the expando, or GetPrivateElemOperation didn't call 91 // hasPrivate first. 92 if (!expando) { 93 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 94 JSMSG_GET_MISSING_PRIVATE); 95 return false; 96 } 97 98 // Because we controlled the creation of the expando, we know it's not a 99 // proxy, and so can safely call internal methods on it without worrying about 100 // exposing information about private names. 101 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); 102 if (!GetOwnPropertyDescriptor(cx, expando, id, &desc)) { 103 return false; 104 } 105 // We must have the object, same reasoning as the expando. 106 if (desc.isNothing()) { 107 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 108 JSMSG_SET_MISSING_PRIVATE); 109 return false; 110 } 111 112 // If the private name has a getter, delegate to that. 113 if (desc->hasGetter()) { 114 RootedValue getter(cx, JS::ObjectValue(*desc->getter())); 115 return js::CallGetter(cx, receiver, getter, vp); 116 } 117 118 MOZ_ASSERT(desc->hasValue()); 119 MOZ_ASSERT(desc->isDataDescriptor()); 120 121 vp.set(desc->value()); 122 return true; 123 } 124 125 static bool ProxyHasOnExpando(JSContext* cx, HandleObject proxy, HandleId id, 126 bool* bp) { 127 // For BaseProxyHandler, private names are stored in the expando object. 128 RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull()); 129 130 // If there is no expando object, then there is no private field. 131 if (!expando) { 132 *bp = false; 133 return true; 134 } 135 136 return HasOwnProperty(cx, expando, id, bp); 137 } 138 139 static bool ProxyDefineOnExpando(JSContext* cx, HandleObject proxy, HandleId id, 140 Handle<PropertyDescriptor> desc, 141 ObjectOpResult& result) { 142 MOZ_ASSERT(id.isPrivateName()); 143 144 // For BaseProxyHandler, private names are stored in the expando object. 145 RootedObject expando(cx, proxy->as<ProxyObject>().expando().toObjectOrNull()); 146 147 if (!expando) { 148 expando = NewPlainObjectWithProto(cx, nullptr); 149 if (!expando) { 150 return false; 151 } 152 153 proxy->as<ProxyObject>().setExpando(expando); 154 } 155 156 return DefineProperty(cx, expando, id, desc, result); 157 } 158 159 void js::AutoEnterPolicy::reportErrorIfExceptionIsNotPending(JSContext* cx, 160 HandleId id) { 161 if (JS_IsExceptionPending(cx)) { 162 return; 163 } 164 165 if (id.isVoid()) { 166 ReportAccessDenied(cx); 167 } else { 168 Throw(cx, id, JSMSG_PROPERTY_ACCESS_DENIED); 169 } 170 } 171 172 #ifdef DEBUG 173 void js::AutoEnterPolicy::recordEnter(JSContext* cx, HandleObject proxy, 174 HandleId id, Action act) { 175 if (allowed()) { 176 context = cx; 177 enteredProxy.emplace(proxy); 178 enteredId.emplace(id); 179 enteredAction = act; 180 prev = cx->enteredPolicy; 181 cx->enteredPolicy = this; 182 } 183 } 184 185 void js::AutoEnterPolicy::recordLeave() { 186 if (enteredProxy) { 187 MOZ_ASSERT(context->enteredPolicy == this); 188 context->enteredPolicy = prev; 189 } 190 } 191 192 JS_PUBLIC_API void js::assertEnteredPolicy(JSContext* cx, JSObject* proxy, 193 jsid id, 194 BaseProxyHandler::Action act) { 195 MOZ_ASSERT(proxy->is<ProxyObject>()); 196 MOZ_ASSERT(cx->enteredPolicy); 197 MOZ_ASSERT(cx->enteredPolicy->enteredProxy->get() == proxy); 198 MOZ_ASSERT(cx->enteredPolicy->enteredId->get() == id); 199 MOZ_ASSERT(cx->enteredPolicy->enteredAction & act); 200 } 201 #endif 202 203 bool Proxy::getOwnPropertyDescriptor( 204 JSContext* cx, HandleObject proxy, HandleId id, 205 MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) { 206 AutoCheckRecursionLimit recursion(cx); 207 if (!recursion.check(cx)) { 208 return false; 209 } 210 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 211 desc.reset(); // default result if we refuse to perform this action 212 AutoEnterPolicy policy(cx, handler, proxy, id, 213 BaseProxyHandler::GET_PROPERTY_DESCRIPTOR, true); 214 if (!policy.allowed()) { 215 return policy.returnValue(); 216 } 217 218 if (handler->useProxyExpandoObjectForPrivateFields() && id.isPrivateName()) { 219 return ProxyGetOwnPropertyDescriptorFromExpando(cx, proxy, id, desc); 220 } 221 return handler->getOwnPropertyDescriptor(cx, proxy, id, desc); 222 } 223 224 bool Proxy::defineProperty(JSContext* cx, HandleObject proxy, HandleId id, 225 Handle<PropertyDescriptor> desc, 226 ObjectOpResult& result) { 227 AutoCheckRecursionLimit recursion(cx); 228 if (!recursion.check(cx)) { 229 return false; 230 } 231 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 232 233 // We shouldn't be definining a private field if we are supposed to throw; 234 // this ought to have been caught by CheckPrivateField. 235 MOZ_ASSERT_IF(id.isPrivateName(), !handler->throwOnPrivateField()); 236 237 AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); 238 if (!policy.allowed()) { 239 if (!policy.returnValue()) { 240 return false; 241 } 242 return result.succeed(); 243 } 244 245 // Private field accesses have different semantics depending on the kind 246 // of proxy involved, and so take a different path compared to regular 247 // [[Get]] operations. For example, scripted handlers don't fire traps 248 // when accessing private fields (because of the WeakMap semantics) 249 if (id.isPrivateName() && handler->useProxyExpandoObjectForPrivateFields()) { 250 return ProxyDefineOnExpando(cx, proxy, id, desc, result); 251 } 252 253 return proxy->as<ProxyObject>().handler()->defineProperty(cx, proxy, id, desc, 254 result); 255 } 256 257 bool Proxy::ownPropertyKeys(JSContext* cx, HandleObject proxy, 258 MutableHandleIdVector props) { 259 AutoCheckRecursionLimit recursion(cx); 260 if (!recursion.check(cx)) { 261 return false; 262 } 263 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 264 AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey, 265 BaseProxyHandler::ENUMERATE, true); 266 if (!policy.allowed()) { 267 return policy.returnValue(); 268 } 269 return proxy->as<ProxyObject>().handler()->ownPropertyKeys(cx, proxy, props); 270 } 271 272 bool Proxy::delete_(JSContext* cx, HandleObject proxy, HandleId id, 273 ObjectOpResult& result) { 274 AutoCheckRecursionLimit recursion(cx); 275 if (!recursion.check(cx)) { 276 return false; 277 } 278 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 279 AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); 280 if (!policy.allowed()) { 281 bool ok = policy.returnValue(); 282 if (ok) { 283 result.succeed(); 284 } 285 return ok; 286 } 287 288 // Private names shouldn't take this path, as deleting a private name 289 // should be a syntax error. 290 MOZ_ASSERT(!id.isPrivateName()); 291 292 return proxy->as<ProxyObject>().handler()->delete_(cx, proxy, id, result); 293 } 294 295 JS_PUBLIC_API bool js::AppendUnique(JSContext* cx, MutableHandleIdVector base, 296 HandleIdVector others) { 297 RootedIdVector uniqueOthers(cx); 298 if (!uniqueOthers.reserve(others.length())) { 299 return false; 300 } 301 for (size_t i = 0; i < others.length(); ++i) { 302 bool unique = true; 303 for (size_t j = 0; j < base.length(); ++j) { 304 if (others[i].get() == base[j]) { 305 unique = false; 306 break; 307 } 308 } 309 if (unique) { 310 if (!uniqueOthers.append(others[i])) { 311 return false; 312 } 313 } 314 } 315 return base.appendAll(std::move(uniqueOthers)); 316 } 317 318 /* static */ 319 bool Proxy::getPrototype(JSContext* cx, HandleObject proxy, 320 MutableHandleObject proto) { 321 MOZ_ASSERT(proxy->hasDynamicPrototype()); 322 AutoCheckRecursionLimit recursion(cx); 323 if (!recursion.check(cx)) { 324 return false; 325 } 326 return proxy->as<ProxyObject>().handler()->getPrototype(cx, proxy, proto); 327 } 328 329 /* static */ 330 bool Proxy::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, 331 ObjectOpResult& result) { 332 MOZ_ASSERT(proxy->hasDynamicPrototype()); 333 AutoCheckRecursionLimit recursion(cx); 334 if (!recursion.check(cx)) { 335 return false; 336 } 337 return proxy->as<ProxyObject>().handler()->setPrototype(cx, proxy, proto, 338 result); 339 } 340 341 /* static */ 342 bool Proxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, 343 bool* isOrdinary, 344 MutableHandleObject proto) { 345 AutoCheckRecursionLimit recursion(cx); 346 if (!recursion.check(cx)) { 347 return false; 348 } 349 return proxy->as<ProxyObject>().handler()->getPrototypeIfOrdinary( 350 cx, proxy, isOrdinary, proto); 351 } 352 353 /* static */ 354 bool Proxy::setImmutablePrototype(JSContext* cx, HandleObject proxy, 355 bool* succeeded) { 356 AutoCheckRecursionLimit recursion(cx); 357 if (!recursion.check(cx)) { 358 return false; 359 } 360 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 361 return handler->setImmutablePrototype(cx, proxy, succeeded); 362 } 363 364 /* static */ 365 bool Proxy::preventExtensions(JSContext* cx, HandleObject proxy, 366 ObjectOpResult& result) { 367 AutoCheckRecursionLimit recursion(cx); 368 if (!recursion.check(cx)) { 369 return false; 370 } 371 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 372 return handler->preventExtensions(cx, proxy, result); 373 } 374 375 /* static */ 376 bool Proxy::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) { 377 AutoCheckRecursionLimit recursion(cx); 378 if (!recursion.check(cx)) { 379 return false; 380 } 381 return proxy->as<ProxyObject>().handler()->isExtensible(cx, proxy, 382 extensible); 383 } 384 385 bool Proxy::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) { 386 AutoCheckRecursionLimit recursion(cx); 387 if (!recursion.check(cx)) { 388 return false; 389 } 390 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 391 *bp = false; // default result if we refuse to perform this action 392 AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); 393 if (!policy.allowed()) { 394 return policy.returnValue(); 395 } 396 397 // Private names shouldn't take this path, but only hasOwn; 398 MOZ_ASSERT(!id.isPrivateName()); 399 400 if (handler->hasPrototype()) { 401 if (!handler->hasOwn(cx, proxy, id, bp)) { 402 return false; 403 } 404 if (*bp) { 405 return true; 406 } 407 408 RootedObject proto(cx); 409 if (!GetPrototype(cx, proxy, &proto)) { 410 return false; 411 } 412 if (!proto) { 413 return true; 414 } 415 416 return HasProperty(cx, proto, id, bp); 417 } 418 419 return handler->has(cx, proxy, id, bp); 420 } 421 422 bool js::ProxyHas(JSContext* cx, HandleObject proxy, HandleValue idVal, 423 bool* result) { 424 RootedId id(cx); 425 if (!ToPropertyKey(cx, idVal, &id)) { 426 return false; 427 } 428 429 return Proxy::has(cx, proxy, id, result); 430 } 431 432 bool Proxy::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) { 433 AutoCheckRecursionLimit recursion(cx); 434 if (!recursion.check(cx)) { 435 return false; 436 } 437 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 438 *bp = false; // default result if we refuse to perform this action 439 440 // If the handler is supposed to throw, we'll never have a private field so 441 // simply return, as we shouldn't throw an invalid security error when 442 // checking for the presence of a private field (WeakMap model). 443 if (id.isPrivateName() && handler->throwOnPrivateField()) { 444 return true; 445 } 446 447 AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); 448 if (!policy.allowed()) { 449 return policy.returnValue(); 450 } 451 452 // Private field accesses have different semantics depending on the kind 453 // of proxy involved, and so take a different path compared to regular 454 // [[Get]] operations. For example, scripted handlers don't fire traps 455 // when accessing private fields (because of the WeakMap semantics) 456 if (id.isPrivateName() && handler->useProxyExpandoObjectForPrivateFields()) { 457 return ProxyHasOnExpando(cx, proxy, id, bp); 458 } 459 460 return handler->hasOwn(cx, proxy, id, bp); 461 } 462 463 bool js::ProxyHasOwn(JSContext* cx, HandleObject proxy, HandleValue idVal, 464 bool* result) { 465 RootedId id(cx); 466 if (!ToPropertyKey(cx, idVal, &id)) { 467 return false; 468 } 469 470 return Proxy::hasOwn(cx, proxy, id, result); 471 } 472 473 static MOZ_ALWAYS_INLINE Value ValueToWindowProxyIfWindow(const Value& v, 474 JSObject* proxy) { 475 if (v.isObject() && v != ObjectValue(*proxy)) { 476 return ObjectValue(*ToWindowProxyIfWindow(&v.toObject())); 477 } 478 return v; 479 } 480 481 MOZ_ALWAYS_INLINE bool Proxy::getInternal(JSContext* cx, HandleObject proxy, 482 HandleValue receiver, HandleId id, 483 MutableHandleValue vp) { 484 MOZ_ASSERT_IF(receiver.isObject(), !IsWindow(&receiver.toObject())); 485 486 AutoCheckRecursionLimit recursion(cx); 487 if (!recursion.check(cx)) { 488 return false; 489 } 490 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 491 492 // Shouldn't have gotten here, as this should have been caught earlier. 493 MOZ_ASSERT_IF(id.isPrivateName(), !handler->throwOnPrivateField()); 494 495 vp.setUndefined(); // default result if we refuse to perform this action 496 AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true); 497 if (!policy.allowed()) { 498 return policy.returnValue(); 499 } 500 501 // Private field accesses have different semantics depending on the kind 502 // of proxy involved, and so take a different path compared to regular 503 // [[Get]] operations. For example, scripted handlers don't fire traps 504 // when accessing private fields (because of the WeakMap semantics) 505 if (id.isPrivateName() && handler->useProxyExpandoObjectForPrivateFields()) { 506 return ProxyGetOnExpando(cx, proxy, receiver, id, vp); 507 } 508 509 if (handler->hasPrototype()) { 510 bool own; 511 if (!handler->hasOwn(cx, proxy, id, &own)) { 512 return false; 513 } 514 if (!own) { 515 RootedObject proto(cx); 516 if (!GetPrototype(cx, proxy, &proto)) { 517 return false; 518 } 519 if (!proto) { 520 return true; 521 } 522 return GetProperty(cx, proto, receiver, id, vp); 523 } 524 } 525 526 return handler->get(cx, proxy, receiver, id, vp); 527 } 528 529 bool Proxy::get(JSContext* cx, HandleObject proxy, HandleValue receiver_, 530 HandleId id, MutableHandleValue vp) { 531 // Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers 532 // shouldn't have to know about the Window/WindowProxy distinction. 533 RootedValue receiver(cx, ValueToWindowProxyIfWindow(receiver_, proxy)); 534 return getInternal(cx, proxy, receiver, id, vp); 535 } 536 537 bool js::ProxyGetProperty(JSContext* cx, HandleObject proxy, HandleId id, 538 MutableHandleValue vp) { 539 RootedValue receiver(cx, ObjectValue(*proxy)); 540 return Proxy::getInternal(cx, proxy, receiver, id, vp); 541 } 542 543 bool js::ProxyGetPropertyByValue(JSContext* cx, HandleObject proxy, 544 HandleValue idVal, MutableHandleValue vp) { 545 RootedId id(cx); 546 if (!ToPropertyKey(cx, idVal, &id)) { 547 return false; 548 } 549 550 RootedValue receiver(cx, ObjectValue(*proxy)); 551 return Proxy::getInternal(cx, proxy, receiver, id, vp); 552 } 553 554 MOZ_ALWAYS_INLINE bool Proxy::setInternal(JSContext* cx, HandleObject proxy, 555 HandleId id, HandleValue v, 556 HandleValue receiver, 557 ObjectOpResult& result) { 558 MOZ_ASSERT_IF(receiver.isObject(), !IsWindow(&receiver.toObject())); 559 560 AutoCheckRecursionLimit recursion(cx); 561 if (!recursion.check(cx)) { 562 return false; 563 } 564 565 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 566 567 // Should have been handled already. 568 MOZ_ASSERT_IF(id.isPrivateName(), !handler->throwOnPrivateField()); 569 570 AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true); 571 if (!policy.allowed()) { 572 if (!policy.returnValue()) { 573 return false; 574 } 575 return result.succeed(); 576 } 577 578 // Private field accesses have different semantics depending on the kind 579 // of proxy involved, and so take a different path compared to regular 580 // [[Set]] operations. 581 // 582 // This doesn't interact with hasPrototype, as PrivateFields are always 583 // own propertiers, and so we never deal with prototype traversals. 584 if (id.isPrivateName() && handler->useProxyExpandoObjectForPrivateFields()) { 585 return ProxySetOnExpando(cx, proxy, id, v, receiver, result); 586 } 587 588 // Special case. See the comment on BaseProxyHandler::mHasPrototype. 589 if (handler->hasPrototype()) { 590 return handler->BaseProxyHandler::set(cx, proxy, id, v, receiver, result); 591 } 592 593 return handler->set(cx, proxy, id, v, receiver, result); 594 } 595 596 bool Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, 597 HandleValue receiver_, ObjectOpResult& result) { 598 // Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers 599 // shouldn't have to know about the Window/WindowProxy distinction. 600 RootedValue receiver(cx, ValueToWindowProxyIfWindow(receiver_, proxy)); 601 return setInternal(cx, proxy, id, v, receiver, result); 602 } 603 604 bool js::ProxySetProperty(JSContext* cx, HandleObject proxy, HandleId id, 605 HandleValue val, bool strict) { 606 ObjectOpResult result; 607 RootedValue receiver(cx, ObjectValue(*proxy)); 608 if (!Proxy::setInternal(cx, proxy, id, val, receiver, result)) { 609 return false; 610 } 611 return result.checkStrictModeError(cx, proxy, id, strict); 612 } 613 614 bool js::ProxySetPropertyByValue(JSContext* cx, HandleObject proxy, 615 HandleValue idVal, HandleValue val, 616 bool strict) { 617 RootedId id(cx); 618 if (!ToPropertyKey(cx, idVal, &id)) { 619 return false; 620 } 621 622 ObjectOpResult result; 623 RootedValue receiver(cx, ObjectValue(*proxy)); 624 if (!Proxy::setInternal(cx, proxy, id, val, receiver, result)) { 625 return false; 626 } 627 return result.checkStrictModeError(cx, proxy, id, strict); 628 } 629 630 bool Proxy::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, 631 MutableHandleIdVector props) { 632 AutoCheckRecursionLimit recursion(cx); 633 if (!recursion.check(cx)) { 634 return false; 635 } 636 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 637 AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey, 638 BaseProxyHandler::ENUMERATE, true); 639 if (!policy.allowed()) { 640 return policy.returnValue(); 641 } 642 return handler->getOwnEnumerablePropertyKeys(cx, proxy, props); 643 } 644 645 bool Proxy::enumerate(JSContext* cx, HandleObject proxy, 646 MutableHandleIdVector props) { 647 AutoCheckRecursionLimit recursion(cx); 648 if (!recursion.check(cx)) { 649 return false; 650 } 651 652 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 653 if (handler->hasPrototype()) { 654 if (!Proxy::getOwnEnumerablePropertyKeys(cx, proxy, props)) { 655 return false; 656 } 657 658 RootedObject proto(cx); 659 if (!GetPrototype(cx, proxy, &proto)) { 660 return false; 661 } 662 if (!proto) { 663 return true; 664 } 665 666 cx->check(proxy, proto); 667 668 RootedIdVector protoProps(cx); 669 if (!GetPropertyKeys(cx, proto, 0, &protoProps)) { 670 return false; 671 } 672 return AppendUnique(cx, props, protoProps); 673 } 674 675 AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey, 676 BaseProxyHandler::ENUMERATE, true); 677 678 // If the policy denies access but wants us to return true, we need 679 // to return an empty |props| list. 680 if (!policy.allowed()) { 681 MOZ_ASSERT(props.empty()); 682 return policy.returnValue(); 683 } 684 685 return handler->enumerate(cx, proxy, props); 686 } 687 688 bool Proxy::call(JSContext* cx, HandleObject proxy, const CallArgs& args) { 689 AutoCheckRecursionLimit recursion(cx); 690 if (!recursion.check(cx)) { 691 return false; 692 } 693 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 694 695 // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we 696 // can only set our default value once we're sure that we're not calling the 697 // trap. 698 AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey, 699 BaseProxyHandler::CALL, true); 700 if (!policy.allowed()) { 701 args.rval().setUndefined(); 702 return policy.returnValue(); 703 } 704 705 return handler->call(cx, proxy, args); 706 } 707 708 bool Proxy::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) { 709 AutoCheckRecursionLimit recursion(cx); 710 if (!recursion.check(cx)) { 711 return false; 712 } 713 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 714 715 // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we 716 // can only set our default value once we're sure that we're not calling the 717 // trap. 718 AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey, 719 BaseProxyHandler::CALL, true); 720 if (!policy.allowed()) { 721 args.rval().setUndefined(); 722 return policy.returnValue(); 723 } 724 725 return handler->construct(cx, proxy, args); 726 } 727 728 bool Proxy::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl, 729 const CallArgs& args) { 730 AutoCheckRecursionLimit recursion(cx); 731 if (!recursion.check(cx)) { 732 return false; 733 } 734 RootedObject proxy(cx, &args.thisv().toObject()); 735 // Note - we don't enter a policy here because our security architecture 736 // guards against nativeCall by overriding the trap itself in the right 737 // circumstances. 738 return proxy->as<ProxyObject>().handler()->nativeCall(cx, test, impl, args); 739 } 740 741 bool Proxy::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) { 742 AutoCheckRecursionLimit recursion(cx); 743 if (!recursion.check(cx)) { 744 return false; 745 } 746 return proxy->as<ProxyObject>().handler()->getBuiltinClass(cx, proxy, cls); 747 } 748 749 bool Proxy::isArray(JSContext* cx, HandleObject proxy, 750 JS::IsArrayAnswer* answer) { 751 AutoCheckRecursionLimit recursion(cx); 752 if (!recursion.check(cx)) { 753 return false; 754 } 755 return proxy->as<ProxyObject>().handler()->isArray(cx, proxy, answer); 756 } 757 758 const char* Proxy::className(JSContext* cx, HandleObject proxy) { 759 // Check for unbounded recursion, but don't signal an error; className 760 // needs to be infallible. 761 AutoCheckRecursionLimit recursion(cx); 762 if (!recursion.checkDontReport(cx)) { 763 return "too much recursion"; 764 } 765 766 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 767 AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey, 768 BaseProxyHandler::GET, /* mayThrow = */ false); 769 // Do the safe thing if the policy rejects. 770 if (!policy.allowed()) { 771 return handler->BaseProxyHandler::className(cx, proxy); 772 } 773 return handler->className(cx, proxy); 774 } 775 776 JSString* Proxy::fun_toString(JSContext* cx, HandleObject proxy, 777 bool isToSource) { 778 AutoCheckRecursionLimit recursion(cx); 779 if (!recursion.check(cx)) { 780 return nullptr; 781 } 782 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 783 AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey, 784 BaseProxyHandler::GET, /* mayThrow = */ false); 785 // Do the safe thing if the policy rejects. 786 if (!policy.allowed()) { 787 return handler->BaseProxyHandler::fun_toString(cx, proxy, isToSource); 788 } 789 return handler->fun_toString(cx, proxy, isToSource); 790 } 791 792 RegExpShared* Proxy::regexp_toShared(JSContext* cx, HandleObject proxy) { 793 AutoCheckRecursionLimit recursion(cx); 794 if (!recursion.check(cx)) { 795 return nullptr; 796 } 797 return proxy->as<ProxyObject>().handler()->regexp_toShared(cx, proxy); 798 } 799 800 bool Proxy::boxedValue_unbox(JSContext* cx, HandleObject proxy, 801 MutableHandleValue vp) { 802 AutoCheckRecursionLimit recursion(cx); 803 if (!recursion.check(cx)) { 804 return false; 805 } 806 return proxy->as<ProxyObject>().handler()->boxedValue_unbox(cx, proxy, vp); 807 } 808 809 JSObject* const TaggedProto::LazyProto = reinterpret_cast<JSObject*>(0x1); 810 811 /* static */ 812 bool Proxy::getElements(JSContext* cx, HandleObject proxy, uint32_t begin, 813 uint32_t end, ElementAdder* adder) { 814 AutoCheckRecursionLimit recursion(cx); 815 if (!recursion.check(cx)) { 816 return false; 817 } 818 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 819 AutoEnterPolicy policy(cx, handler, proxy, JS::VoidHandlePropertyKey, 820 BaseProxyHandler::GET, 821 /* mayThrow = */ true); 822 if (!policy.allowed()) { 823 if (policy.returnValue()) { 824 MOZ_ASSERT(!cx->isExceptionPending()); 825 return js::GetElementsWithAdder(cx, proxy, proxy, begin, end, adder); 826 } 827 return false; 828 } 829 return handler->getElements(cx, proxy, begin, end, adder); 830 } 831 832 /* static */ 833 void Proxy::trace(JSTracer* trc, JSObject* proxy) { 834 const BaseProxyHandler* handler = proxy->as<ProxyObject>().handler(); 835 handler->trace(trc, proxy); 836 } 837 838 static bool proxy_LookupProperty(JSContext* cx, HandleObject obj, HandleId id, 839 MutableHandleObject objp, 840 PropertyResult* propp) { 841 bool found; 842 if (!Proxy::has(cx, obj, id, &found)) { 843 return false; 844 } 845 846 if (found) { 847 propp->setProxyProperty(); 848 objp.set(obj); 849 } else { 850 propp->setNotFound(); 851 objp.set(nullptr); 852 } 853 return true; 854 } 855 856 static bool proxy_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id, 857 ObjectOpResult& result) { 858 if (!Proxy::delete_(cx, obj, id, result)) { 859 return false; 860 } 861 return SuppressDeletedProperty(cx, obj, id); // XXX is this necessary? 862 } 863 864 /* static */ 865 void ProxyObject::traceEdgeToTarget(JSTracer* trc, ProxyObject* obj) { 866 TraceCrossCompartmentEdge(trc, obj, obj->slotOfPrivate(), "proxy target"); 867 } 868 869 #ifdef DEBUG 870 static inline void CheckProxyIsInCCWMap(ProxyObject* proxy) { 871 if (proxy->zone()->isGCCompacting()) { 872 // Skip this check during compacting GC since objects' shapes and base 873 // shapes may be forwarded. It's not impossible to make this work, but 874 // requires adding a parallel lookupWrapper() path for this one case. 875 return; 876 } 877 878 JSObject* referent = MaybeForwarded(proxy->target()); 879 if (referent->compartment() != proxy->compartment()) { 880 // Assert that this proxy is tracked in the wrapper map. We maintain the 881 // invariant that the wrapped object is the key in the wrapper map. 882 ObjectWrapperMap::Ptr p = proxy->compartment()->lookupWrapper(referent); 883 MOZ_ASSERT(p); 884 MOZ_ASSERT(*p->value().unsafeGet() == proxy); 885 } 886 } 887 #endif 888 889 /* static */ 890 void ProxyObject::trace(JSTracer* trc, JSObject* obj) { 891 ProxyObject* proxy = &obj->as<ProxyObject>(); 892 893 TraceNullableEdge(trc, proxy->slotOfExpando(), "expando"); 894 895 #ifdef DEBUG 896 JSContext* cx = TlsContext.get(); 897 if (cx && cx->isStrictProxyCheckingEnabled() && proxy->is<WrapperObject>()) { 898 CheckProxyIsInCCWMap(proxy); 899 } 900 #endif 901 902 // Note: If you add new slots here, make sure to change 903 // nuke() to cope. 904 905 traceEdgeToTarget(trc, proxy); 906 907 size_t nreserved = proxy->numReservedSlots(); 908 for (size_t i = 0; i < nreserved; i++) { 909 /* 910 * The GC can use the second reserved slot to link the cross compartment 911 * wrappers into a linked list, in which case we don't want to trace it. 912 */ 913 if (proxy->is<CrossCompartmentWrapperObject>() && 914 i == CrossCompartmentWrapperObject::GrayLinkReservedSlot) { 915 continue; 916 } 917 TraceEdge(trc, proxy->reservedSlotPtr(i), "proxy_reserved"); 918 } 919 920 Proxy::trace(trc, obj); 921 } 922 923 static void proxy_Finalize(JS::GCContext* gcx, JSObject* obj) { 924 // Suppress a bogus warning about finalize(). 925 JS::AutoSuppressGCAnalysis nogc; 926 927 MOZ_ASSERT(obj->is<ProxyObject>()); 928 ProxyObject* proxy = &obj->as<ProxyObject>(); 929 proxy->handler()->finalize(gcx, obj); 930 931 if (!proxy->usingInlineValueArray() && proxy->isTenured()) { 932 auto* valArray = js::detail::GetProxyDataLayout(obj)->values(); 933 size_t size = 934 js::detail::ProxyValueArray::sizeOf(proxy->numReservedSlots()); 935 gcx->free_(obj, valArray, size, MemoryUse::ProxyExternalValueArray); 936 } 937 } 938 939 size_t js::proxy_ObjectMoved(JSObject* obj, JSObject* old) { 940 ProxyObject& proxy = obj->as<ProxyObject>(); 941 942 if (IsInsideNursery(old)) { 943 proxy.nurseryProxyTenured(&old->as<ProxyObject>()); 944 } 945 946 return proxy.handler()->objectMoved(obj, old); 947 } 948 949 void ProxyObject::nurseryProxyTenured(ProxyObject* old) { 950 if (old->usingInlineValueArray()) { 951 setInlineValueArray(); 952 return; 953 } 954 955 Nursery& nursery = runtimeFromMainThread()->gc.nursery(); 956 nursery.removeMallocedBufferDuringMinorGC(data.values()); 957 958 size_t size = detail::ProxyValueArray::sizeOf(numReservedSlots()); 959 AddCellMemory(this, size, MemoryUse::ProxyExternalValueArray); 960 } 961 962 const JSClassOps js::ProxyClassOps = { 963 nullptr, // addProperty 964 nullptr, // delProperty 965 nullptr, // enumerate 966 nullptr, // newEnumerate 967 nullptr, // resolve 968 nullptr, // mayResolve 969 proxy_Finalize, // finalize 970 nullptr, // call 971 nullptr, // construct 972 ProxyObject::trace, // trace 973 }; 974 975 const ClassExtension js::ProxyClassExtension = { 976 proxy_ObjectMoved, // objectMovedOp 977 }; 978 979 const ObjectOps js::ProxyObjectOps = { 980 proxy_LookupProperty, // lookupProperty 981 Proxy::defineProperty, // defineProperty 982 Proxy::has, // hasProperty 983 Proxy::get, // getProperty 984 Proxy::set, // setProperty 985 Proxy::getOwnPropertyDescriptor, // getOwnPropertyDescriptor 986 proxy_DeleteProperty, // deleteProperty 987 Proxy::getElements, // getElements 988 Proxy::fun_toString, // funToString 989 }; 990 991 static const JSFunctionSpec proxy_static_methods[] = { 992 JS_FN("revocable", proxy_revocable, 2, 0), 993 JS_FS_END, 994 }; 995 996 static const ClassSpec ProxyClassSpec = { 997 GenericCreateConstructor<js::proxy, 2, gc::AllocKind::FUNCTION>, 998 nullptr, 999 proxy_static_methods, 1000 nullptr, 1001 }; 1002 1003 const JSClass js::ProxyClass = PROXY_CLASS_DEF_WITH_CLASS_SPEC( 1004 "Proxy", 1005 JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy) | JSCLASS_HAS_RESERVED_SLOTS(2), 1006 &ProxyClassSpec); 1007 1008 JS_PUBLIC_API JSObject* js::NewProxyObject(JSContext* cx, 1009 const BaseProxyHandler* handler, 1010 HandleValue priv, JSObject* proto_, 1011 const ProxyOptions& options) { 1012 AssertHeapIsIdle(); 1013 CHECK_THREAD(cx); 1014 1015 // This can be called from the compartment wrap hooks while in a realm with a 1016 // gray global. Trigger the read barrier on the global to ensure this is 1017 // unmarked. 1018 cx->realm()->maybeGlobal(); 1019 1020 if (proto_ != TaggedProto::LazyProto) { 1021 cx->check(proto_); // |priv| might be cross-compartment. 1022 } 1023 1024 if (options.lazyProto()) { 1025 MOZ_ASSERT(!proto_); 1026 proto_ = TaggedProto::LazyProto; 1027 } 1028 1029 return ProxyObject::New(cx, handler, priv, TaggedProto(proto_), 1030 options.clasp()); 1031 } 1032 1033 void ProxyObject::renew(const BaseProxyHandler* handler, const Value& priv) { 1034 MOZ_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this)); 1035 MOZ_ASSERT(getClass() == &ProxyClass); 1036 MOZ_ASSERT(!IsWindowProxy(this)); 1037 MOZ_ASSERT(hasDynamicPrototype()); 1038 1039 setHandler(handler); 1040 setCrossCompartmentPrivate(priv); 1041 for (size_t i = 0; i < numReservedSlots(); i++) { 1042 setReservedSlot(i, UndefinedValue()); 1043 } 1044 } 1045 1046 // This implementation of HostEnsureCanAddPrivateElement is designed to work in 1047 // collaboration with Gecko to support the HTML implementation, which applies 1048 // only to Proxy type objects, and as a result we can simply provide proxy 1049 // handlers to correctly match the required semantics. 1050 bool DefaultHostEnsureCanAddPrivateElementCallback(JSContext* cx, 1051 HandleValue val) { 1052 if (!val.isObject()) { 1053 return true; 1054 } 1055 1056 Rooted<JSObject*> valObj(cx, &val.toObject()); 1057 if (!IsProxy(valObj)) { 1058 return true; 1059 } 1060 1061 if (GetProxyHandler(valObj)->throwOnPrivateField()) { 1062 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1063 JSMSG_ILLEGAL_PRIVATE_EXOTIC); 1064 return false; 1065 } 1066 return true; 1067 }