ArgumentsObject.cpp (38444B)
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 "vm/ArgumentsObject-inl.h" 8 9 #include "mozilla/Maybe.h" 10 #include "mozilla/PodOperations.h" 11 12 #include <algorithm> 13 14 #include "gc/GCContext.h" 15 #include "jit/CalleeToken.h" 16 #include "jit/JitFrames.h" 17 #include "util/BitArray.h" 18 #include "vm/GlobalObject.h" 19 #include "vm/Stack.h" 20 21 #include "gc/Nursery-inl.h" 22 #include "vm/FrameIter-inl.h" // js::FrameIter::unaliasedForEachActual 23 #include "vm/NativeObject-inl.h" 24 #include "vm/Stack-inl.h" 25 26 using namespace js; 27 28 /* static */ 29 size_t RareArgumentsData::bytesRequired(size_t numActuals) { 30 size_t extraBytes = NumWordsForBitArrayOfLength(numActuals) * sizeof(size_t); 31 return offsetof(RareArgumentsData, deletedBits_) + extraBytes; 32 } 33 34 /* static */ 35 RareArgumentsData* RareArgumentsData::create(JSContext* cx, 36 ArgumentsObject* obj) { 37 size_t bytes = RareArgumentsData::bytesRequired(obj->initialLength()); 38 39 uint8_t* data = AllocNurseryOrMallocBuffer<uint8_t>(cx, obj, bytes); 40 if (!data) { 41 return nullptr; 42 } 43 44 mozilla::PodZero(data, bytes); 45 46 AddCellMemory(obj, bytes, MemoryUse::RareArgumentsData); 47 48 return new (data) RareArgumentsData(); 49 } 50 51 ArgumentsData::ArgumentsData(uint32_t numArgs) : args(numArgs) { 52 // |args| must be the last field. 53 static_assert(offsetof(ArgumentsData, args) + sizeof(args) == 54 sizeof(ArgumentsData)); 55 } 56 57 bool ArgumentsObject::createRareData(JSContext* cx) { 58 MOZ_ASSERT(!data()->rareData); 59 60 RareArgumentsData* rareData = RareArgumentsData::create(cx, this); 61 if (!rareData) { 62 return false; 63 } 64 65 data()->rareData = rareData; 66 markElementOverridden(); 67 return true; 68 } 69 70 bool ArgumentsObject::markElementDeleted(JSContext* cx, uint32_t i) { 71 RareArgumentsData* data = getOrCreateRareData(cx); 72 if (!data) { 73 return false; 74 } 75 76 data->markElementDeleted(initialLength(), i); 77 return true; 78 } 79 80 /* static */ 81 void ArgumentsObject::MaybeForwardToCallObject(AbstractFramePtr frame, 82 ArgumentsObject* obj, 83 ArgumentsData* data) { 84 JSScript* script = frame.script(); 85 if (frame.callee()->needsCallObject() && script->argsObjAliasesFormals()) { 86 obj->initFixedSlot(MAYBE_CALL_SLOT, ObjectValue(frame.callObj())); 87 for (PositionalFormalParameterIter fi(script); fi; fi++) { 88 if (fi.closedOver()) { 89 data->args.setElement(obj, fi.argumentSlot(), 90 MagicEnvSlotValue(fi.location().slot())); 91 obj->markArgumentForwarded(); 92 } 93 } 94 } 95 } 96 97 /* static */ 98 void ArgumentsObject::MaybeForwardToCallObject(JSFunction* callee, 99 JSObject* callObj, 100 ArgumentsObject* obj, 101 ArgumentsData* data) { 102 JSScript* script = callee->nonLazyScript(); 103 if (callee->needsCallObject() && script->argsObjAliasesFormals()) { 104 MOZ_ASSERT(callObj && callObj->is<CallObject>()); 105 obj->initFixedSlot(MAYBE_CALL_SLOT, ObjectValue(*callObj)); 106 for (PositionalFormalParameterIter fi(script); fi; fi++) { 107 if (fi.closedOver()) { 108 data->args.setElement(obj, fi.argumentSlot(), 109 MagicEnvSlotValue(fi.location().slot())); 110 obj->markArgumentForwarded(); 111 } 112 } 113 } 114 } 115 116 struct CopyFrameArgs { 117 AbstractFramePtr frame_; 118 119 explicit CopyFrameArgs(AbstractFramePtr frame) : frame_(frame) {} 120 121 void copyActualArgs(ArgumentsObject* owner, GCOwnedArray<Value>& args, 122 unsigned numActuals) const { 123 MOZ_ASSERT_IF(frame_.isInterpreterFrame(), 124 !frame_.asInterpreterFrame()->runningInJit()); 125 126 // Copy arguments. 127 Value* src = frame_.argv(); 128 Value* end = src + numActuals; 129 args.withOwner(owner, [&](auto& args) { 130 auto* dst = args.begin(); 131 while (src != end) { 132 (dst++)->init(*src++); 133 } 134 }); 135 } 136 137 /* 138 * If a call object exists and the arguments object aliases formals, the 139 * call object is the canonical location for formals. 140 */ 141 void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) { 142 ArgumentsObject::MaybeForwardToCallObject(frame_, obj, data); 143 } 144 }; 145 146 struct CopyJitFrameArgs { 147 jit::JitFrameLayout* frame_; 148 HandleObject callObj_; 149 150 CopyJitFrameArgs(jit::JitFrameLayout* frame, HandleObject callObj) 151 : frame_(frame), callObj_(callObj) {} 152 153 void copyActualArgs(ArgumentsObject* owner, GCOwnedArray<Value>& args, 154 unsigned numActuals) const { 155 MOZ_ASSERT(frame_->numActualArgs() == numActuals); 156 157 Value* src = frame_->actualArgs(); 158 Value* end = src + numActuals; 159 args.withOwner(owner, [&](auto& args) { 160 auto* dst = args.begin(); 161 while (src != end) { 162 (dst++)->init(*src++); 163 } 164 }); 165 } 166 167 /* 168 * If a call object exists and the arguments object aliases formals, the 169 * call object is the canonical location for formals. 170 */ 171 void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) { 172 JSFunction* callee = jit::CalleeTokenToFunction(frame_->calleeToken()); 173 ArgumentsObject::MaybeForwardToCallObject(callee, callObj_, obj, data); 174 } 175 }; 176 177 struct CopyScriptFrameIterArgs { 178 ScriptFrameIter& iter_; 179 RootedValueVector actualArgs_; 180 181 explicit CopyScriptFrameIterArgs(JSContext* cx, ScriptFrameIter& iter) 182 : iter_(iter), actualArgs_(cx) {} 183 184 // Used to copy arguments to actualArgs_ to simplify copyArgs and 185 // ArgumentsObject allocation. 186 [[nodiscard]] bool init(JSContext* cx) { 187 unsigned numActuals = iter_.numActualArgs(); 188 if (!actualArgs_.reserve(numActuals)) { 189 return false; 190 } 191 192 // Append actual arguments. 193 iter_.unaliasedForEachActual( 194 cx, [this](const Value& v) { actualArgs_.infallibleAppend(v); }); 195 MOZ_RELEASE_ASSERT(actualArgs_.length() == numActuals); 196 return true; 197 } 198 199 void copyActualArgs(ArgumentsObject* owner, GCOwnedArray<Value>& args, 200 unsigned numActuals) const { 201 MOZ_ASSERT(actualArgs_.length() == numActuals); 202 203 args.withOwner(owner, [&](auto& args) { 204 auto* dst = args.begin(); 205 for (Value v : actualArgs_) { 206 (dst++)->init(v); 207 } 208 }); 209 } 210 211 /* 212 * Ion frames are copying every argument onto the stack, other locations are 213 * invalid. 214 */ 215 void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) { 216 if (!iter_.isIon()) { 217 ArgumentsObject::MaybeForwardToCallObject(iter_.abstractFramePtr(), obj, 218 data); 219 } 220 } 221 }; 222 223 struct CopyInlinedArgs { 224 HandleValueArray args_; 225 HandleObject callObj_; 226 HandleFunction callee_; 227 228 CopyInlinedArgs(HandleValueArray args, HandleObject callObj, 229 HandleFunction callee) 230 : args_(args), callObj_(callObj), callee_(callee) {} 231 232 void copyActualArgs(ArgumentsObject* owner, GCOwnedArray<Value>& args, 233 unsigned numActuals) const { 234 MOZ_ASSERT(numActuals <= args_.length()); 235 236 args.withOwner(owner, [&](auto& args) { 237 auto* dst = args.begin(); 238 for (uint32_t i = 0; i < numActuals; i++) { 239 (dst++)->init(args_[i]); 240 } 241 }); 242 } 243 244 /* 245 * If a call object exists and the arguments object aliases formals, the 246 * call object is the canonical location for formals. 247 */ 248 void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) { 249 ArgumentsObject::MaybeForwardToCallObject(callee_, callObj_, obj, data); 250 } 251 }; 252 253 ArgumentsObject* ArgumentsObject::createTemplateObject(JSContext* cx, 254 bool mapped) { 255 const JSClass* clasp = mapped ? &MappedArgumentsObject::class_ 256 : &UnmappedArgumentsObject::class_; 257 258 RootedObject proto(cx, &cx->global()->getObjectPrototype()); 259 260 constexpr ObjectFlags objectFlags = {ObjectFlag::Indexed}; 261 Rooted<SharedShape*> shape(cx, SharedShape::getInitialShape( 262 cx, clasp, cx->realm(), TaggedProto(proto), 263 FINALIZE_KIND, objectFlags)); 264 if (!shape) { 265 return nullptr; 266 } 267 268 AutoSetNewObjectMetadata metadata(cx); 269 auto* obj = NativeObject::create<ArgumentsObject>(cx, FINALIZE_KIND, 270 gc::Heap::Tenured, shape); 271 if (!obj) { 272 return nullptr; 273 } 274 275 obj->initFixedSlot(ArgumentsObject::DATA_SLOT, PrivateValue(nullptr)); 276 return obj; 277 } 278 279 ArgumentsObject* GlobalObject::maybeArgumentsTemplateObject(bool mapped) const { 280 return mapped ? data().mappedArgumentsTemplate 281 : data().unmappedArgumentsTemplate; 282 } 283 284 /* static */ 285 ArgumentsObject* GlobalObject::getOrCreateArgumentsTemplateObject(JSContext* cx, 286 bool mapped) { 287 GlobalObjectData& data = cx->global()->data(); 288 GCPtr<ArgumentsObject*>& obj = 289 mapped ? data.mappedArgumentsTemplate : data.unmappedArgumentsTemplate; 290 291 ArgumentsObject* templateObj = obj; 292 if (templateObj) { 293 return templateObj; 294 } 295 296 templateObj = ArgumentsObject::createTemplateObject(cx, mapped); 297 if (!templateObj) { 298 return nullptr; 299 } 300 301 obj.init(templateObj); 302 return templateObj; 303 } 304 305 template <typename CopyArgs> 306 /* static */ 307 ArgumentsObject* ArgumentsObject::create(JSContext* cx, HandleFunction callee, 308 unsigned numActuals, CopyArgs& copy) { 309 // Self-hosted code should use the more efficient ArgumentsLength and 310 // GetArgument intrinsics instead of `arguments`. 311 MOZ_ASSERT(!callee->isSelfHostedBuiltin()); 312 313 bool mapped = callee->baseScript()->hasMappedArgsObj(); 314 ArgumentsObject* templateObj = 315 GlobalObject::getOrCreateArgumentsTemplateObject(cx, mapped); 316 if (!templateObj) { 317 return nullptr; 318 } 319 320 Rooted<SharedShape*> shape(cx, templateObj->sharedShape()); 321 322 unsigned numFormals = callee->nargs(); 323 unsigned numArgs = std::max(numActuals, numFormals); 324 unsigned numBytes = ArgumentsData::bytesRequired(numArgs); 325 326 AutoSetNewObjectMetadata metadata(cx); 327 auto* obj = NativeObject::create<ArgumentsObject>(cx, FINALIZE_KIND, 328 gc::Heap::Default, shape); 329 if (!obj) { 330 return nullptr; 331 } 332 333 ArgumentsData* data = reinterpret_cast<ArgumentsData*>( 334 AllocNurseryOrMallocBuffer<uint8_t>(cx, obj, numBytes)); 335 if (!data) { 336 // Make the object safe for GC. 337 obj->initFixedSlot(DATA_SLOT, PrivateValue(nullptr)); 338 return nullptr; 339 } 340 341 new (data) ArgumentsData(numArgs); 342 343 InitReservedSlot(obj, DATA_SLOT, data, numBytes, MemoryUse::ArgumentsData); 344 obj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee)); 345 obj->initFixedSlot(INITIAL_LENGTH_SLOT, 346 Int32Value(numActuals << PACKED_BITS_COUNT)); 347 348 // Copy [0, numActuals) into data->args. 349 copy.copyActualArgs(obj, data->args, numActuals); 350 351 // Fill in missing arguments with |undefined|. 352 data->args.withOwner(obj, [&](auto& args) { 353 for (size_t i = numActuals; i < numArgs; i++) { 354 args[i].init(UndefinedValue()); 355 } 356 }); 357 358 copy.maybeForwardToCallObject(obj, data); 359 360 MOZ_ASSERT(obj->initialLength() == numActuals); 361 MOZ_ASSERT(!obj->hasOverriddenLength()); 362 return obj; 363 } 364 365 ArgumentsObject* ArgumentsObject::createExpected(JSContext* cx, 366 AbstractFramePtr frame) { 367 MOZ_ASSERT(frame.script()->needsArgsObj()); 368 RootedFunction callee(cx, frame.callee()); 369 CopyFrameArgs copy(frame); 370 ArgumentsObject* argsobj = create(cx, callee, frame.numActualArgs(), copy); 371 if (!argsobj) { 372 return nullptr; 373 } 374 375 frame.initArgsObj(*argsobj); 376 return argsobj; 377 } 378 379 ArgumentsObject* ArgumentsObject::createUnexpected(JSContext* cx, 380 ScriptFrameIter& iter) { 381 RootedFunction callee(cx, iter.callee(cx)); 382 CopyScriptFrameIterArgs copy(cx, iter); 383 if (!copy.init(cx)) { 384 return nullptr; 385 } 386 return create(cx, callee, iter.numActualArgs(), copy); 387 } 388 389 ArgumentsObject* ArgumentsObject::createUnexpected(JSContext* cx, 390 AbstractFramePtr frame) { 391 RootedFunction callee(cx, frame.callee()); 392 CopyFrameArgs copy(frame); 393 return create(cx, callee, frame.numActualArgs(), copy); 394 } 395 396 ArgumentsObject* ArgumentsObject::createForIon(JSContext* cx, 397 jit::JitFrameLayout* frame, 398 HandleObject scopeChain) { 399 jit::CalleeToken token = frame->calleeToken(); 400 MOZ_ASSERT(jit::CalleeTokenIsFunction(token)); 401 RootedFunction callee(cx, jit::CalleeTokenToFunction(token)); 402 RootedObject callObj( 403 cx, scopeChain->is<CallObject>() ? scopeChain.get() : nullptr); 404 CopyJitFrameArgs copy(frame, callObj); 405 return create(cx, callee, frame->numActualArgs(), copy); 406 } 407 408 /* static */ 409 ArgumentsObject* ArgumentsObject::createFromValueArray( 410 JSContext* cx, HandleValueArray argsArray, HandleFunction callee, 411 HandleObject scopeChain, uint32_t numActuals) { 412 MOZ_ASSERT(numActuals <= MaxInlinedArgs); 413 RootedObject callObj( 414 cx, scopeChain->is<CallObject>() ? scopeChain.get() : nullptr); 415 CopyInlinedArgs copy(argsArray, callObj, callee); 416 return create(cx, callee, numActuals, copy); 417 } 418 419 /* static */ 420 ArgumentsObject* ArgumentsObject::createForInlinedIon(JSContext* cx, 421 Value* args, 422 HandleFunction callee, 423 HandleObject scopeChain, 424 uint32_t numActuals) { 425 RootedExternalValueArray rootedArgs(cx, numActuals, args); 426 HandleValueArray argsArray = 427 HandleValueArray::fromMarkedLocation(numActuals, args); 428 429 return createFromValueArray(cx, argsArray, callee, scopeChain, numActuals); 430 } 431 432 template <typename CopyArgs> 433 /* static */ 434 ArgumentsObject* ArgumentsObject::finishPure( 435 JSContext* cx, ArgumentsObject* obj, JSFunction* callee, JSObject* callObj, 436 unsigned numActuals, CopyArgs& copy) { 437 unsigned numFormals = callee->nargs(); 438 unsigned numArgs = std::max(numActuals, numFormals); 439 unsigned numBytes = ArgumentsData::bytesRequired(numArgs); 440 441 ArgumentsData* data = reinterpret_cast<ArgumentsData*>( 442 AllocNurseryOrMallocBuffer<uint8_t>(cx, obj, numBytes)); 443 if (!data) { 444 // Make the object safe for GC. Don't report OOM, the slow path will 445 // retry the allocation. 446 cx->recoverFromOutOfMemory(); 447 obj->initFixedSlot(DATA_SLOT, PrivateValue(nullptr)); 448 return nullptr; 449 } 450 451 new (data) ArgumentsData(numArgs); 452 453 obj->initFixedSlot(INITIAL_LENGTH_SLOT, 454 Int32Value(numActuals << PACKED_BITS_COUNT)); 455 obj->initFixedSlot(DATA_SLOT, PrivateValue(data)); 456 AddCellMemory(obj, numBytes, MemoryUse::ArgumentsData); 457 obj->initFixedSlot(MAYBE_CALL_SLOT, UndefinedValue()); 458 obj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee)); 459 460 copy.copyActualArgs(obj, data->args, numActuals); 461 462 // Fill in missing arguments with |undefined|. 463 data->args.withOwner(obj, [&](auto& args) { 464 for (size_t i = numActuals; i < numArgs; i++) { 465 args[i].init(UndefinedValue()); 466 } 467 }); 468 469 if (callObj && callee->needsCallObject()) { 470 copy.maybeForwardToCallObject(obj, data); 471 } 472 473 MOZ_ASSERT(obj->initialLength() == numActuals); 474 MOZ_ASSERT(!obj->hasOverriddenLength()); 475 return obj; 476 } 477 478 /* static */ 479 ArgumentsObject* ArgumentsObject::finishForIonPure(JSContext* cx, 480 jit::JitFrameLayout* frame, 481 JSObject* scopeChain, 482 ArgumentsObject* obj) { 483 // JIT code calls this directly (no callVM), because it's faster, so we're 484 // not allowed to GC in here. 485 AutoUnsafeCallWithABI unsafe; 486 487 JSFunction* callee = jit::CalleeTokenToFunction(frame->calleeToken()); 488 RootedObject callObj(cx, scopeChain->is<CallObject>() ? scopeChain : nullptr); 489 CopyJitFrameArgs copy(frame, callObj); 490 491 unsigned numActuals = frame->numActualArgs(); 492 493 return finishPure(cx, obj, callee, callObj, numActuals, copy); 494 } 495 496 /* static */ 497 ArgumentsObject* ArgumentsObject::finishInlineForIonPure( 498 JSContext* cx, JSObject* rawCallObj, JSFunction* rawCallee, Value* args, 499 uint32_t numActuals, ArgumentsObject* obj) { 500 // JIT code calls this directly (no callVM), because it's faster, so we're 501 // not allowed to GC in here. 502 AutoUnsafeCallWithABI unsafe; 503 504 MOZ_ASSERT(numActuals <= MaxInlinedArgs); 505 506 RootedObject callObj(cx, rawCallObj); 507 RootedFunction callee(cx, rawCallee); 508 RootedExternalValueArray rootedArgs(cx, numActuals, args); 509 HandleValueArray argsArray = 510 HandleValueArray::fromMarkedLocation(numActuals, args); 511 512 CopyInlinedArgs copy(argsArray, callObj, callee); 513 514 return finishPure(cx, obj, callee, callObj, numActuals, copy); 515 } 516 517 /* static */ 518 bool ArgumentsObject::obj_delProperty(JSContext* cx, HandleObject obj, 519 HandleId id, ObjectOpResult& result) { 520 ArgumentsObject& argsobj = obj->as<ArgumentsObject>(); 521 if (id.isInt()) { 522 unsigned arg = unsigned(id.toInt()); 523 if (argsobj.isElement(arg)) { 524 if (!argsobj.markElementDeleted(cx, arg)) { 525 return false; 526 } 527 } 528 } else if (id.isAtom(cx->names().length)) { 529 argsobj.markLengthOverridden(); 530 } else if (id.isAtom(cx->names().callee)) { 531 argsobj.as<MappedArgumentsObject>().markCalleeOverridden(); 532 } else if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) { 533 argsobj.markIteratorOverridden(); 534 } 535 return result.succeed(); 536 } 537 538 /* static */ 539 bool ArgumentsObject::obj_mayResolve(const JSAtomState& names, jsid id, 540 JSObject*) { 541 // Arguments might resolve indexes, Symbol.iterator, or length/callee. 542 if (id.isAtom()) { 543 JSAtom* atom = id.toAtom(); 544 return atom->isIndex() || atom == names.length || atom == names.callee; 545 } 546 547 return id.isInt() || id.isWellKnownSymbol(JS::SymbolCode::iterator); 548 } 549 550 bool js::MappedArgGetter(JSContext* cx, HandleObject obj, HandleId id, 551 MutableHandleValue vp) { 552 MappedArgumentsObject& argsobj = obj->as<MappedArgumentsObject>(); 553 if (id.isInt()) { 554 /* 555 * arg can exceed the number of arguments if a script changed the 556 * prototype to point to another Arguments object with a bigger argc. 557 */ 558 unsigned arg = unsigned(id.toInt()); 559 if (argsobj.isElement(arg)) { 560 vp.set(argsobj.element(arg)); 561 } 562 } else if (id.isAtom(cx->names().length)) { 563 if (!argsobj.hasOverriddenLength()) { 564 vp.setInt32(argsobj.initialLength()); 565 } 566 } else { 567 MOZ_ASSERT(id.isAtom(cx->names().callee)); 568 if (!argsobj.hasOverriddenCallee()) { 569 vp.setObject(argsobj.callee()); 570 } 571 } 572 return true; 573 } 574 575 bool js::MappedArgSetter(JSContext* cx, HandleObject obj, HandleId id, 576 HandleValue v, ObjectOpResult& result) { 577 Handle<MappedArgumentsObject*> argsobj = obj.as<MappedArgumentsObject>(); 578 579 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); 580 if (!GetOwnPropertyDescriptor(cx, argsobj, id, &desc)) { 581 return false; 582 } 583 MOZ_ASSERT(desc.isSome()); 584 MOZ_ASSERT(desc->isDataDescriptor()); 585 MOZ_ASSERT(desc->writable()); 586 MOZ_ASSERT(!desc->resolving()); 587 588 if (id.isInt()) { 589 unsigned arg = unsigned(id.toInt()); 590 if (argsobj->isElement(arg)) { 591 argsobj->setElement(arg, v); 592 return result.succeed(); 593 } 594 } else { 595 MOZ_ASSERT(id.isAtom(cx->names().length) || id.isAtom(cx->names().callee)); 596 } 597 598 /* 599 * For simplicity we use delete/define to replace the property with a 600 * simple data property. Note that we rely on ArgumentsObject::obj_delProperty 601 * to set the corresponding override-bit. 602 * Note also that we must define the property instead of setting it in case 603 * the user has changed the prototype to an object that has a setter for 604 * this id. 605 */ 606 Rooted<PropertyDescriptor> desc_(cx, *desc); 607 desc_.setValue(v); 608 ObjectOpResult ignored; 609 return NativeDeleteProperty(cx, argsobj, id, ignored) && 610 NativeDefineProperty(cx, argsobj, id, desc_, result); 611 } 612 613 /* static */ 614 bool ArgumentsObject::getArgumentsIterator(JSContext* cx, 615 MutableHandleValue val) { 616 Handle<PropertyName*> shName = cx->names().dollar_ArrayValues_; 617 Rooted<JSAtom*> name(cx, cx->names().values); 618 return GlobalObject::getSelfHostedFunction(cx, cx->global(), shName, name, 0, 619 val); 620 } 621 622 /* static */ 623 bool ArgumentsObject::reifyLength(JSContext* cx, Handle<ArgumentsObject*> obj) { 624 if (obj->hasOverriddenLength()) { 625 return true; 626 } 627 628 RootedId id(cx, NameToId(cx->names().length)); 629 RootedValue val(cx, Int32Value(obj->initialLength())); 630 if (!NativeDefineDataProperty(cx, obj, id, val, JSPROP_RESOLVING)) { 631 return false; 632 } 633 634 obj->markLengthOverridden(); 635 return true; 636 } 637 638 /* static */ 639 bool ArgumentsObject::reifyIterator(JSContext* cx, 640 Handle<ArgumentsObject*> obj) { 641 if (obj->hasOverriddenIterator()) { 642 return true; 643 } 644 645 RootedId iteratorId(cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator)); 646 RootedValue val(cx); 647 if (!ArgumentsObject::getArgumentsIterator(cx, &val)) { 648 return false; 649 } 650 if (!NativeDefineDataProperty(cx, obj, iteratorId, val, JSPROP_RESOLVING)) { 651 return false; 652 } 653 654 obj->markIteratorOverridden(); 655 return true; 656 } 657 658 /* static */ 659 bool MappedArgumentsObject::reifyCallee(JSContext* cx, 660 Handle<MappedArgumentsObject*> obj) { 661 if (obj->hasOverriddenCallee()) { 662 return true; 663 } 664 665 Rooted<PropertyKey> key(cx, NameToId(cx->names().callee)); 666 Rooted<Value> val(cx, ObjectValue(obj->callee())); 667 if (!NativeDefineDataProperty(cx, obj, key, val, JSPROP_RESOLVING)) { 668 return false; 669 } 670 671 obj->markCalleeOverridden(); 672 return true; 673 } 674 675 static bool ResolveArgumentsProperty(JSContext* cx, 676 Handle<ArgumentsObject*> obj, HandleId id, 677 PropertyFlags flags, bool* resolvedp) { 678 MOZ_ASSERT(id.isInt() || id.isAtom(cx->names().length) || 679 id.isAtom(cx->names().callee)); 680 MOZ_ASSERT(flags.isCustomDataProperty()); 681 682 if (!NativeObject::addCustomDataProperty(cx, obj, id, flags)) { 683 return false; 684 } 685 686 *resolvedp = true; 687 return true; 688 } 689 690 /* static */ 691 bool MappedArgumentsObject::obj_resolve(JSContext* cx, HandleObject obj, 692 HandleId id, bool* resolvedp) { 693 auto argsobj = obj.as<MappedArgumentsObject>(); 694 695 if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) { 696 if (argsobj->hasOverriddenIterator()) { 697 return true; 698 } 699 700 if (!reifyIterator(cx, argsobj)) { 701 return false; 702 } 703 *resolvedp = true; 704 return true; 705 } 706 707 PropertyFlags flags = {PropertyFlag::CustomDataProperty, 708 PropertyFlag::Configurable, PropertyFlag::Writable}; 709 if (id.isInt()) { 710 uint32_t arg = uint32_t(id.toInt()); 711 if (!argsobj->isElement(arg)) { 712 return true; 713 } 714 715 flags.setFlag(PropertyFlag::Enumerable); 716 } else if (id.isAtom(cx->names().length)) { 717 if (argsobj->hasOverriddenLength()) { 718 return true; 719 } 720 } else { 721 if (!id.isAtom(cx->names().callee)) { 722 return true; 723 } 724 725 if (argsobj->hasOverriddenCallee()) { 726 return true; 727 } 728 } 729 730 return ResolveArgumentsProperty(cx, argsobj, id, flags, resolvedp); 731 } 732 733 /* static */ 734 bool MappedArgumentsObject::obj_enumerate(JSContext* cx, HandleObject obj) { 735 auto argsobj = obj.as<MappedArgumentsObject>(); 736 737 RootedId id(cx); 738 bool found; 739 740 // Trigger reflection. 741 id = NameToId(cx->names().length); 742 if (!HasOwnProperty(cx, argsobj, id, &found)) { 743 return false; 744 } 745 746 id = NameToId(cx->names().callee); 747 if (!HasOwnProperty(cx, argsobj, id, &found)) { 748 return false; 749 } 750 751 id = PropertyKey::Symbol(cx->wellKnownSymbols().iterator); 752 if (!HasOwnProperty(cx, argsobj, id, &found)) { 753 return false; 754 } 755 756 for (unsigned i = 0; i < argsobj->initialLength(); i++) { 757 id = PropertyKey::Int(i); 758 if (!HasOwnProperty(cx, argsobj, id, &found)) { 759 return false; 760 } 761 } 762 763 return true; 764 } 765 766 static bool DefineMappedIndex(JSContext* cx, Handle<MappedArgumentsObject*> obj, 767 HandleId id, 768 MutableHandle<PropertyDescriptor> desc, 769 ObjectOpResult& result) { 770 // The custom data properties (see MappedArgGetter, MappedArgSetter) have to 771 // be (re)defined manually because PropertyDescriptor and NativeDefineProperty 772 // don't support these special properties. 773 // 774 // This exists in order to let JS code change the configurable/enumerable 775 // attributes for these properties. 776 // 777 // Note: because this preserves the default mapped-arguments behavior, we 778 // don't need to mark elements as overridden or deleted. 779 780 MOZ_ASSERT(id.isInt()); 781 MOZ_ASSERT(obj->isElement(id.toInt())); 782 MOZ_ASSERT(!obj->containsDenseElement(id.toInt())); 783 784 MOZ_ASSERT(!desc.isAccessorDescriptor()); 785 786 // Mapped properties aren't used when defining a non-writable property. 787 MOZ_ASSERT(!desc.hasWritable() || desc.writable()); 788 789 // First, resolve the property to simplify the code below. 790 PropertyResult prop; 791 if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) { 792 return false; 793 } 794 795 MOZ_ASSERT(prop.isNativeProperty()); 796 797 PropertyInfo propInfo = prop.propertyInfo(); 798 MOZ_ASSERT(propInfo.writable()); 799 MOZ_ASSERT(propInfo.isCustomDataProperty()); 800 801 // Change the property's attributes by implementing the relevant parts of 802 // ValidateAndApplyPropertyDescriptor (ES2021 draft, 10.1.6.3), in particular 803 // steps 4 and 9. 804 805 // Determine whether the property should be configurable and/or enumerable. 806 bool configurable = propInfo.configurable(); 807 bool enumerable = propInfo.enumerable(); 808 if (configurable) { 809 if (desc.hasConfigurable()) { 810 configurable = desc.configurable(); 811 } 812 if (desc.hasEnumerable()) { 813 enumerable = desc.enumerable(); 814 } 815 } else { 816 // Property is not configurable so disallow any attribute changes. 817 if ((desc.hasConfigurable() && desc.configurable()) || 818 (desc.hasEnumerable() && enumerable != desc.enumerable())) { 819 return result.fail(JSMSG_CANT_REDEFINE_PROP); 820 } 821 } 822 823 PropertyFlags flags = propInfo.flags(); 824 flags.setFlag(PropertyFlag::Configurable, configurable); 825 flags.setFlag(PropertyFlag::Enumerable, enumerable); 826 if (!NativeObject::changeCustomDataPropAttributes(cx, obj, id, flags)) { 827 return false; 828 } 829 830 return result.succeed(); 831 } 832 833 // ES 2017 draft 9.4.4.2 834 /* static */ 835 bool MappedArgumentsObject::obj_defineProperty(JSContext* cx, HandleObject obj, 836 HandleId id, 837 Handle<PropertyDescriptor> desc, 838 ObjectOpResult& result) { 839 // Step 1. 840 auto argsobj = obj.as<MappedArgumentsObject>(); 841 842 // Steps 2-3. 843 bool isMapped = false; 844 if (id.isInt()) { 845 unsigned arg = unsigned(id.toInt()); 846 isMapped = argsobj->isElement(arg); 847 } 848 849 // Step 4. 850 Rooted<PropertyDescriptor> newArgDesc(cx, desc); 851 852 // Step 5. 853 bool defineMapped = false; 854 if (!desc.isAccessorDescriptor() && isMapped) { 855 // Step 5.a. 856 if (desc.hasWritable() && !desc.writable()) { 857 if (!desc.hasValue()) { 858 RootedValue v(cx, argsobj->element(id.toInt())); 859 newArgDesc.setValue(v); 860 } 861 } else { 862 // In this case the live mapping is supposed to keep working. 863 defineMapped = true; 864 } 865 } 866 867 // Step 6. NativeDefineProperty will lookup [[Value]] for us. 868 if (defineMapped) { 869 if (!DefineMappedIndex(cx, argsobj, id, &newArgDesc, result)) { 870 return false; 871 } 872 } else { 873 if (!NativeDefineProperty(cx, obj.as<NativeObject>(), id, newArgDesc, 874 result)) { 875 return false; 876 } 877 } 878 // Step 7. 879 if (!result.ok()) { 880 return true; 881 } 882 883 // Step 8. 884 if (isMapped) { 885 unsigned arg = unsigned(id.toInt()); 886 if (desc.isAccessorDescriptor()) { 887 if (!argsobj->markElementDeleted(cx, arg)) { 888 return false; 889 } 890 } else { 891 if (desc.hasValue()) { 892 argsobj->setElement(arg, desc.value()); 893 } 894 if (desc.hasWritable() && !desc.writable()) { 895 if (!argsobj->markElementDeleted(cx, arg)) { 896 return false; 897 } 898 } 899 } 900 } 901 902 // Step 9. 903 return result.succeed(); 904 } 905 906 bool js::UnmappedArgGetter(JSContext* cx, HandleObject obj, HandleId id, 907 MutableHandleValue vp) { 908 UnmappedArgumentsObject& argsobj = obj->as<UnmappedArgumentsObject>(); 909 910 if (id.isInt()) { 911 /* 912 * arg can exceed the number of arguments if a script changed the 913 * prototype to point to another Arguments object with a bigger argc. 914 */ 915 unsigned arg = unsigned(id.toInt()); 916 if (argsobj.isElement(arg)) { 917 vp.set(argsobj.element(arg)); 918 } 919 } else { 920 MOZ_ASSERT(id.isAtom(cx->names().length)); 921 if (!argsobj.hasOverriddenLength()) { 922 vp.setInt32(argsobj.initialLength()); 923 } 924 } 925 return true; 926 } 927 928 bool js::UnmappedArgSetter(JSContext* cx, HandleObject obj, HandleId id, 929 HandleValue v, ObjectOpResult& result) { 930 Handle<UnmappedArgumentsObject*> argsobj = obj.as<UnmappedArgumentsObject>(); 931 932 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx); 933 if (!GetOwnPropertyDescriptor(cx, argsobj, id, &desc)) { 934 return false; 935 } 936 MOZ_ASSERT(desc.isSome()); 937 MOZ_ASSERT(desc->isDataDescriptor()); 938 MOZ_ASSERT(desc->writable()); 939 MOZ_ASSERT(!desc->resolving()); 940 941 if (id.isInt()) { 942 unsigned arg = unsigned(id.toInt()); 943 if (arg < argsobj->initialLength()) { 944 argsobj->setElement(arg, v); 945 return result.succeed(); 946 } 947 } else { 948 MOZ_ASSERT(id.isAtom(cx->names().length)); 949 } 950 951 /* 952 * For simplicity we use delete/define to replace the property with a 953 * simple data property. Note that we rely on ArgumentsObject::obj_delProperty 954 * to set the corresponding override-bit. 955 */ 956 Rooted<PropertyDescriptor> desc_(cx, *desc); 957 desc_.setValue(v); 958 ObjectOpResult ignored; 959 return NativeDeleteProperty(cx, argsobj, id, ignored) && 960 NativeDefineProperty(cx, argsobj, id, desc_, result); 961 } 962 963 /* static */ 964 bool UnmappedArgumentsObject::obj_resolve(JSContext* cx, HandleObject obj, 965 HandleId id, bool* resolvedp) { 966 auto argsobj = obj.as<UnmappedArgumentsObject>(); 967 968 if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) { 969 if (argsobj->hasOverriddenIterator()) { 970 return true; 971 } 972 973 if (!reifyIterator(cx, argsobj)) { 974 return false; 975 } 976 *resolvedp = true; 977 return true; 978 } 979 980 if (id.isAtom(cx->names().callee)) { 981 RootedObject throwTypeError( 982 cx, GlobalObject::getOrCreateThrowTypeError(cx, cx->global())); 983 if (!throwTypeError) { 984 return false; 985 } 986 987 unsigned attrs = JSPROP_RESOLVING | JSPROP_PERMANENT; 988 if (!NativeDefineAccessorProperty(cx, argsobj, id, throwTypeError, 989 throwTypeError, attrs)) { 990 return false; 991 } 992 993 *resolvedp = true; 994 return true; 995 } 996 997 PropertyFlags flags = {PropertyFlag::CustomDataProperty, 998 PropertyFlag::Configurable, PropertyFlag::Writable}; 999 if (id.isInt()) { 1000 uint32_t arg = uint32_t(id.toInt()); 1001 if (!argsobj->isElement(arg)) { 1002 return true; 1003 } 1004 1005 flags.setFlag(PropertyFlag::Enumerable); 1006 } else if (id.isAtom(cx->names().length)) { 1007 if (argsobj->hasOverriddenLength()) { 1008 return true; 1009 } 1010 } else { 1011 return true; 1012 } 1013 1014 return ResolveArgumentsProperty(cx, argsobj, id, flags, resolvedp); 1015 } 1016 1017 /* static */ 1018 bool UnmappedArgumentsObject::obj_enumerate(JSContext* cx, HandleObject obj) { 1019 auto argsobj = obj.as<UnmappedArgumentsObject>(); 1020 1021 RootedId id(cx); 1022 bool found; 1023 1024 // Trigger reflection. 1025 id = NameToId(cx->names().length); 1026 if (!HasOwnProperty(cx, argsobj, id, &found)) { 1027 return false; 1028 } 1029 1030 id = NameToId(cx->names().callee); 1031 if (!HasOwnProperty(cx, argsobj, id, &found)) { 1032 return false; 1033 } 1034 1035 id = PropertyKey::Symbol(cx->wellKnownSymbols().iterator); 1036 if (!HasOwnProperty(cx, argsobj, id, &found)) { 1037 return false; 1038 } 1039 1040 for (unsigned i = 0; i < argsobj->initialLength(); i++) { 1041 id = PropertyKey::Int(i); 1042 if (!HasOwnProperty(cx, argsobj, id, &found)) { 1043 return false; 1044 } 1045 } 1046 1047 return true; 1048 } 1049 1050 void ArgumentsObject::finalize(JS::GCContext* gcx, JSObject* obj) { 1051 MOZ_ASSERT(!IsInsideNursery(obj)); 1052 ArgumentsObject& argsobj = obj->as<ArgumentsObject>(); 1053 if (argsobj.data()) { 1054 gcx->free_(&argsobj, argsobj.maybeRareData(), 1055 RareArgumentsData::bytesRequired(argsobj.initialLength()), 1056 MemoryUse::RareArgumentsData); 1057 gcx->free_(&argsobj, argsobj.data(), 1058 ArgumentsData::bytesRequired(argsobj.data()->numArgs()), 1059 MemoryUse::ArgumentsData); 1060 } 1061 } 1062 1063 void ArgumentsObject::trace(JSTracer* trc, JSObject* obj) { 1064 ArgumentsObject& argsobj = obj->as<ArgumentsObject>(); 1065 // Template objects have no ArgumentsData. 1066 if (ArgumentsData* data = argsobj.data()) { 1067 data->args.trace(trc); 1068 } 1069 } 1070 1071 /* static */ 1072 size_t ArgumentsObject::objectMoved(JSObject* dst, JSObject* src) { 1073 ArgumentsObject* ndst = &dst->as<ArgumentsObject>(); 1074 const ArgumentsObject* nsrc = &src->as<ArgumentsObject>(); 1075 MOZ_ASSERT(ndst->data() == nsrc->data()); 1076 1077 if (!IsInsideNursery(src)) { 1078 return 0; 1079 } 1080 1081 Nursery& nursery = dst->runtimeFromMainThread()->gc.nursery(); 1082 1083 size_t nbytesTotal = 0; 1084 1085 ArgumentsData* data = nsrc->data(); 1086 uint32_t nDataBytes = ArgumentsData::bytesRequired(nsrc->data()->numArgs()); 1087 Nursery::WasBufferMoved result = 1088 nursery.maybeMoveNurseryOrMallocBufferOnPromotion( 1089 &data, dst, nDataBytes, MemoryUse::ArgumentsData); 1090 if (result == Nursery::BufferMoved) { 1091 ndst->initFixedSlot(DATA_SLOT, PrivateValue(data)); 1092 nbytesTotal += nDataBytes; 1093 } 1094 1095 if (RareArgumentsData* rareData = nsrc->maybeRareData()) { 1096 uint32_t nRareBytes = 1097 RareArgumentsData::bytesRequired(nsrc->initialLength()); 1098 Nursery::WasBufferMoved result = 1099 nursery.maybeMoveNurseryOrMallocBufferOnPromotion( 1100 &rareData, dst, nRareBytes, MemoryUse::RareArgumentsData); 1101 if (result == Nursery::BufferMoved) { 1102 ndst->data()->rareData = rareData; 1103 nbytesTotal += nRareBytes; 1104 } 1105 } 1106 1107 return nbytesTotal; 1108 } 1109 1110 /* 1111 * The classes below collaborate to lazily reflect and synchronize actual 1112 * argument values, argument count, and callee function object stored in a 1113 * stack frame with their corresponding property values in the frame's 1114 * arguments object. 1115 */ 1116 const JSClassOps MappedArgumentsObject::classOps_ = { 1117 nullptr, // addProperty 1118 ArgumentsObject::obj_delProperty, // delProperty 1119 MappedArgumentsObject::obj_enumerate, // enumerate 1120 nullptr, // newEnumerate 1121 MappedArgumentsObject::obj_resolve, // resolve 1122 ArgumentsObject::obj_mayResolve, // mayResolve 1123 ArgumentsObject::finalize, // finalize 1124 nullptr, // call 1125 nullptr, // construct 1126 ArgumentsObject::trace, // trace 1127 }; 1128 1129 const js::ClassExtension MappedArgumentsObject::classExt_ = { 1130 ArgumentsObject::objectMoved, // objectMovedOp 1131 }; 1132 1133 const ObjectOps MappedArgumentsObject::objectOps_ = { 1134 nullptr, // lookupProperty 1135 MappedArgumentsObject::obj_defineProperty, // defineProperty 1136 nullptr, // hasProperty 1137 nullptr, // getProperty 1138 nullptr, // setProperty 1139 nullptr, // getOwnPropertyDescriptor 1140 nullptr, // deleteProperty 1141 nullptr, // getElements 1142 nullptr, // funToString 1143 }; 1144 1145 const JSClass MappedArgumentsObject::class_ = { 1146 "Arguments", 1147 JSCLASS_DELAY_METADATA_BUILDER | 1148 JSCLASS_HAS_RESERVED_SLOTS(MappedArgumentsObject::RESERVED_SLOTS) | 1149 JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | 1150 JSCLASS_SKIP_NURSERY_FINALIZE | JSCLASS_BACKGROUND_FINALIZE, 1151 &MappedArgumentsObject::classOps_, 1152 nullptr, 1153 &MappedArgumentsObject::classExt_, 1154 &MappedArgumentsObject::objectOps_, 1155 }; 1156 1157 /* 1158 * Unmapped arguments is significantly less magical than mapped arguments, so 1159 * it is represented by a different class while sharing some functionality. 1160 */ 1161 const JSClassOps UnmappedArgumentsObject::classOps_ = { 1162 nullptr, // addProperty 1163 ArgumentsObject::obj_delProperty, // delProperty 1164 UnmappedArgumentsObject::obj_enumerate, // enumerate 1165 nullptr, // newEnumerate 1166 UnmappedArgumentsObject::obj_resolve, // resolve 1167 ArgumentsObject::obj_mayResolve, // mayResolve 1168 ArgumentsObject::finalize, // finalize 1169 nullptr, // call 1170 nullptr, // construct 1171 ArgumentsObject::trace, // trace 1172 }; 1173 1174 const js::ClassExtension UnmappedArgumentsObject::classExt_ = { 1175 ArgumentsObject::objectMoved, // objectMovedOp 1176 }; 1177 1178 const JSClass UnmappedArgumentsObject::class_ = { 1179 "Arguments", 1180 JSCLASS_DELAY_METADATA_BUILDER | 1181 JSCLASS_HAS_RESERVED_SLOTS(UnmappedArgumentsObject::RESERVED_SLOTS) | 1182 JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | 1183 JSCLASS_SKIP_NURSERY_FINALIZE | JSCLASS_BACKGROUND_FINALIZE, 1184 &UnmappedArgumentsObject::classOps_, 1185 nullptr, 1186 &UnmappedArgumentsObject::classExt_, 1187 };