ArgumentsObject.h (21084B)
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 #ifndef vm_ArgumentsObject_h 8 #define vm_ArgumentsObject_h 9 10 #include "mozilla/MemoryReporting.h" 11 12 #include "gc/Barrier.h" 13 #include "gc/GCArray.h" 14 #include "util/BitArray.h" 15 #include "vm/NativeObject.h" 16 17 namespace js { 18 19 class AbstractFramePtr; 20 class ArgumentsObject; 21 class ScriptFrameIter; 22 23 namespace jit { 24 class JitFrameLayout; 25 } // namespace jit 26 27 // RareArgumentsData stores the deleted-elements bits for an arguments object. 28 // Because |delete arguments[i]| is uncommon, we allocate this data the first 29 // time an element is deleted. 30 class RareArgumentsData { 31 // Pointer to an array of bits indicating, for every argument in 32 // [0, initialLength) whether the element has been deleted. See 33 // ArgumentsObject::isElementDeleted comment. 34 size_t deletedBits_[1]; 35 36 RareArgumentsData() = default; 37 RareArgumentsData(const RareArgumentsData&) = delete; 38 void operator=(const RareArgumentsData&) = delete; 39 40 public: 41 static RareArgumentsData* create(JSContext* cx, ArgumentsObject* obj); 42 static size_t bytesRequired(size_t numActuals); 43 44 bool isElementDeleted(size_t len, size_t i) const { 45 MOZ_ASSERT(i < len); 46 return IsBitArrayElementSet(deletedBits_, len, i); 47 } 48 void markElementDeleted(size_t len, size_t i) { 49 MOZ_ASSERT(i < len); 50 SetBitArrayElement(deletedBits_, len, i); 51 } 52 }; 53 54 // ArgumentsData stores the initial indexed arguments provided to a function 55 // call. It is used to store arguments[i] -- up until the corresponding 56 // property is modified, when the relevant value is flagged to memorialize the 57 // modification. 58 struct ArgumentsData { 59 RareArgumentsData* rareData = nullptr; 60 61 /* 62 * This array holds either the current argument value or the magic 63 * forwarding value. The latter means that the function has both a 64 * CallObject and an ArgumentsObject AND the particular formal variable is 65 * aliased by the CallObject. In such cases, the CallObject holds the 66 * canonical value so any element access to the arguments object should load 67 * the value out of the CallObject (which is pointed to by MAYBE_CALL_SLOT). 68 */ 69 GCOwnedArray<Value> args; 70 71 /* 72 * numArgs = std::max(numFormalArgs, numActualArgs) 73 * The array 'args' has numArgs elements. 74 */ 75 explicit ArgumentsData(uint32_t numArgs); 76 77 uint32_t numArgs() const { return args.size(); } 78 79 /* For jit use: */ 80 static constexpr ptrdiff_t offsetOfArgs() { 81 return offsetof(ArgumentsData, args) + 82 GCOwnedArray<Value>::offsetOfElements(); 83 } 84 85 static size_t bytesRequired(size_t numArgs) { 86 return offsetof(ArgumentsData, args) + 87 GCOwnedArray<Value>::bytesRequired(numArgs); 88 } 89 }; 90 91 // Maximum supported value of arguments.length. This bounds the 92 // maximum number of arguments that can be supplied to a spread call 93 // or Function.prototype.apply. This value also bounds the number of 94 // elements parsed in an array initializer. NB: keep this in sync 95 // with the copy in builtin/SelfHostingDefines.h. 96 static const unsigned ARGS_LENGTH_MAX = 500 * 1000; 97 98 // Maximum number of arguments supported in jitcode. This bounds the 99 // maximum number of arguments that can be supplied to a spread call 100 // or Function.prototype.apply without entering the VM. We limit the 101 // number of parameters we can handle to a number that does not risk 102 // us allocating too much stack, notably on Windows where there is a 103 // 4K guard page that has to be touched to extend the stack. The value 104 // "3000" is the size of the guard page minus an arbitrary, but large, 105 // safety margin. See bug 1351278. 106 static const uint32_t JIT_ARGS_LENGTH_MAX = 3000 / sizeof(JS::Value); 107 108 static_assert(JIT_ARGS_LENGTH_MAX <= ARGS_LENGTH_MAX, 109 "maximum jit arguments should be <= maximum arguments"); 110 111 /* 112 * [SMDOC] ArgumentsObject 113 * 114 * ArgumentsObject instances represent |arguments| objects created to store 115 * function arguments when a function is called. It's expensive to create such 116 * objects if they're never used, so they're only created when they are 117 * potentially used. 118 * 119 * Arguments objects are complicated because, for non-strict mode code, they 120 * must alias any named arguments which were provided to the function. Gnarly 121 * example: 122 * 123 * function f(a, b, c, d) 124 * { 125 * arguments[0] = "seta"; 126 * assertEq(a, "seta"); 127 * b = "setb"; 128 * assertEq(arguments[1], "setb"); 129 * c = "setc"; 130 * assertEq(arguments[2], undefined); 131 * arguments[3] = "setd"; 132 * assertEq(d, undefined); 133 * } 134 * f("arga", "argb"); 135 * 136 * ES5's strict mode behaves more sanely, and named arguments don't alias 137 * elements of an arguments object. 138 * 139 * ArgumentsObject instances use the following reserved slots: 140 * 141 * INITIAL_LENGTH_SLOT 142 * Stores the initial value of arguments.length, plus a bit indicating 143 * whether arguments.length and/or arguments[@@iterator] have been 144 * modified. Use initialLength(), hasOverriddenLength(), and 145 * hasOverriddenIterator() to access these values. If arguments.length has 146 * been modified, then the current value of arguments.length is stored in 147 * another slot associated with a new property. 148 * DATA_SLOT 149 * Stores an ArgumentsData*, described above. 150 * MAYBE_CALL_SLOT 151 * Stores the CallObject, if the callee has aliased bindings. See 152 * the ArgumentsData::args comment. 153 * CALLEE_SLOT 154 * Stores the initial arguments.callee. This value can be overridden on 155 * mapped arguments objects, see hasOverriddenCallee. 156 */ 157 class ArgumentsObject : public NativeObject { 158 public: 159 static const uint32_t INITIAL_LENGTH_SLOT = 0; 160 static const uint32_t DATA_SLOT = 1; 161 static const uint32_t MAYBE_CALL_SLOT = 2; 162 static const uint32_t CALLEE_SLOT = 3; 163 164 static const uint32_t LENGTH_OVERRIDDEN_BIT = 0x1; 165 static const uint32_t ITERATOR_OVERRIDDEN_BIT = 0x2; 166 static const uint32_t ELEMENT_OVERRIDDEN_BIT = 0x4; 167 static const uint32_t CALLEE_OVERRIDDEN_BIT = 0x8; 168 static const uint32_t FORWARDED_ARGUMENTS_BIT = 0x10; 169 static const uint32_t PACKED_BITS_COUNT = 5; 170 static const uint32_t PACKED_BITS_MASK = (1 << PACKED_BITS_COUNT) - 1; 171 172 static_assert(ARGS_LENGTH_MAX <= (UINT32_MAX >> PACKED_BITS_COUNT), 173 "Max arguments length must fit in available bits"); 174 175 // Our ability to inline functions that use |arguments| is limited by 176 // the number of registers available to represent Value operands to 177 // CreateInlinedArgumentsObject. 178 #if defined(JS_CODEGEN_X86) 179 static const uint32_t MaxInlinedArgs = 1; 180 #else 181 static const uint32_t MaxInlinedArgs = 3; 182 #endif 183 184 protected: 185 template <typename CopyArgs> 186 static ArgumentsObject* create(JSContext* cx, HandleFunction callee, 187 unsigned numActuals, CopyArgs& copy); 188 189 ArgumentsData* data() const { 190 return reinterpret_cast<ArgumentsData*>( 191 getFixedSlot(DATA_SLOT).toPrivate()); 192 } 193 194 RareArgumentsData* maybeRareData() const { return data()->rareData; } 195 196 [[nodiscard]] bool createRareData(JSContext* cx); 197 198 RareArgumentsData* getOrCreateRareData(JSContext* cx) { 199 if (!data()->rareData && !createRareData(cx)) { 200 return nullptr; 201 } 202 return data()->rareData; 203 } 204 205 static bool obj_delProperty(JSContext* cx, HandleObject obj, HandleId id, 206 ObjectOpResult& result); 207 208 static bool obj_mayResolve(const JSAtomState& names, jsid id, JSObject*); 209 210 public: 211 static const uint32_t RESERVED_SLOTS = 4; 212 static const gc::AllocKind FINALIZE_KIND = gc::AllocKind::OBJECT4_BACKGROUND; 213 214 /* Create an arguments object for a frame that is expecting them. */ 215 static ArgumentsObject* createExpected(JSContext* cx, AbstractFramePtr frame); 216 217 /* 218 * Purposefully disconnect the returned arguments object from the frame 219 * by always creating a new copy that does not alias formal parameters. 220 * This allows function-local analysis to determine that formals are 221 * not aliased and generally simplifies arguments objects. 222 */ 223 static ArgumentsObject* createUnexpected(JSContext* cx, 224 ScriptFrameIter& iter); 225 static ArgumentsObject* createUnexpected(JSContext* cx, 226 AbstractFramePtr frame); 227 228 static ArgumentsObject* createForIon(JSContext* cx, 229 jit::JitFrameLayout* frame, 230 HandleObject scopeChain); 231 static ArgumentsObject* createForInlinedIon(JSContext* cx, Value* args, 232 HandleFunction callee, 233 HandleObject scopeChain, 234 uint32_t numActuals); 235 static ArgumentsObject* createFromValueArray(JSContext* cx, 236 HandleValueArray argsArray, 237 HandleFunction callee, 238 HandleObject scopeChain, 239 uint32_t numActuals); 240 241 private: 242 template <typename CopyArgs> 243 static ArgumentsObject* finishPure(JSContext* cx, ArgumentsObject* obj, 244 JSFunction* callee, JSObject* callObj, 245 unsigned numActuals, CopyArgs& copy); 246 247 public: 248 /* 249 * Allocate ArgumentsData and fill reserved slots after allocating an 250 * ArgumentsObject in Ion code. 251 */ 252 static ArgumentsObject* finishForIonPure(JSContext* cx, 253 jit::JitFrameLayout* frame, 254 JSObject* scopeChain, 255 ArgumentsObject* obj); 256 257 /* 258 * Allocate ArgumentsData for inlined arguments and fill reserved slots after 259 * allocating an ArgumentsObject in Ion code. 260 */ 261 static ArgumentsObject* finishInlineForIonPure( 262 JSContext* cx, JSObject* rawCallObj, JSFunction* rawCallee, Value* args, 263 uint32_t numActuals, ArgumentsObject* obj); 264 265 static ArgumentsObject* createTemplateObject(JSContext* cx, bool mapped); 266 267 /* 268 * Return the initial length of the arguments. This may differ from the 269 * current value of arguments.length! 270 */ 271 uint32_t initialLength() const { 272 uint32_t argc = uint32_t(getFixedSlot(INITIAL_LENGTH_SLOT).toInt32()) >> 273 PACKED_BITS_COUNT; 274 MOZ_ASSERT(argc <= ARGS_LENGTH_MAX); 275 return argc; 276 } 277 278 bool hasFlags(uint32_t flags) const { 279 const Value& v = getFixedSlot(INITIAL_LENGTH_SLOT); 280 return v.toInt32() & flags; 281 } 282 283 // True iff arguments.length has been assigned or deleted. 284 bool hasOverriddenLength() const { return hasFlags(LENGTH_OVERRIDDEN_BIT); } 285 286 void markLengthOverridden() { 287 uint32_t v = 288 getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | LENGTH_OVERRIDDEN_BIT; 289 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v)); 290 } 291 292 // Create the default "length" property and set LENGTH_OVERRIDDEN_BIT. 293 static bool reifyLength(JSContext* cx, Handle<ArgumentsObject*> obj); 294 295 // True iff arguments[@@iterator] has been assigned or deleted. 296 bool hasOverriddenIterator() const { 297 return hasFlags(ITERATOR_OVERRIDDEN_BIT); 298 } 299 300 void markIteratorOverridden() { 301 uint32_t v = 302 getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | ITERATOR_OVERRIDDEN_BIT; 303 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v)); 304 } 305 306 // Create the default @@iterator property and set ITERATOR_OVERRIDDEN_BIT. 307 static bool reifyIterator(JSContext* cx, Handle<ArgumentsObject*> obj); 308 309 /* 310 * Return the arguments iterator function. 311 */ 312 static bool getArgumentsIterator(JSContext* cx, MutableHandleValue val); 313 314 // True iff any element has been assigned or deleted. 315 bool hasOverriddenElement() const { return hasFlags(ELEMENT_OVERRIDDEN_BIT); } 316 317 void markElementOverridden() { 318 uint32_t v = 319 getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | ELEMENT_OVERRIDDEN_BIT; 320 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v)); 321 } 322 323 private: 324 /* 325 * Because the arguments object is a real object, its elements may be 326 * deleted. This is implemented by setting a 'deleted' flag for the arg 327 * which is read by argument object resolve and getter/setter hooks. 328 * 329 * NB: an element, once deleted, stays deleted. Thus: 330 * 331 * function f(x) { delete arguments[0]; arguments[0] = 42; return x } 332 * assertEq(f(1), 1); 333 * 334 * This works because, once a property is deleted from an arguments object, 335 * it gets regular properties with regular getters/setters that don't alias 336 * ArgumentsData::args. 337 */ 338 bool isElementDeleted(uint32_t i) const { 339 MOZ_ASSERT(i < data()->numArgs()); 340 if (i >= initialLength()) { 341 return false; 342 } 343 bool result = maybeRareData() && 344 maybeRareData()->isElementDeleted(initialLength(), i); 345 MOZ_ASSERT_IF(result, hasOverriddenElement()); 346 return result; 347 } 348 349 protected: 350 bool markElementDeleted(JSContext* cx, uint32_t i); 351 352 public: 353 /* 354 * Return true iff the index is a valid element index for this arguments 355 * object. 356 * 357 * Returning true here doesn't imply that the element value can be read 358 * through |ArgumentsObject::element()|. For example unmapped arguments 359 * objects can have an element index property redefined without having marked 360 * the element as deleted. Instead use |maybeGetElement()| or manually check 361 * for |hasOverriddenElement()|. 362 */ 363 bool isElement(uint32_t i) const { 364 return i < initialLength() && !isElementDeleted(i); 365 } 366 367 /* 368 * An ArgumentsObject serves two roles: 369 * - a real object, accessed through regular object operations, e.g.., 370 * GetElement corresponding to 'arguments[i]'; 371 * - a VM-internal data structure, storing the value of arguments (formal 372 * and actual) that are accessed directly by the VM when a reading the 373 * value of a formal parameter. 374 * There are two ways to access the ArgumentsData::args corresponding to 375 * these two use cases: 376 * - object access should use elements(i) which will take care of 377 * forwarding when the value is the magic forwarding value; 378 * - VM argument access should use arg(i) which will assert that the 379 * value is not the magic forwarding value (since, if such forwarding was 380 * needed, the frontend should have emitted JSOp::GetAliasedVar). 381 */ 382 const Value& element(uint32_t i) const; 383 384 inline void setElement(uint32_t i, const Value& v); 385 386 const Value& arg(unsigned i) const { 387 MOZ_ASSERT(i < data()->numArgs()); 388 const Value& v = data()->args[i]; 389 MOZ_ASSERT(!v.isMagic()); 390 return v; 391 } 392 393 void setArg(unsigned i, const Value& v) { 394 MOZ_ASSERT(i < data()->numArgs()); 395 MOZ_ASSERT(!data()->args[i].isMagic()); 396 data()->args.setElement(this, i, v); 397 } 398 399 /* 400 * Test if an argument is forwarded, i.e. its actual value is stored in the 401 * CallObject and can't be directly read from |ArgumentsData::args|. 402 */ 403 bool argIsForwarded(unsigned i) const { 404 MOZ_ASSERT(i < data()->numArgs()); 405 const Value& v = data()->args[i]; 406 MOZ_ASSERT_IF(IsMagicScopeSlotValue(v), anyArgIsForwarded()); 407 return IsMagicScopeSlotValue(v); 408 } 409 410 bool anyArgIsForwarded() const { return hasFlags(FORWARDED_ARGUMENTS_BIT); } 411 412 void markArgumentForwarded() { 413 uint32_t v = 414 getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | FORWARDED_ARGUMENTS_BIT; 415 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v)); 416 } 417 418 /* 419 * Attempt to speedily and efficiently access the i-th element of this 420 * arguments object. Return true if the element was speedily returned. 421 * Return false if the element must be looked up more slowly using 422 * getProperty or some similar method. The second overload copies the 423 * elements [start, start + count) into the locations starting at 'vp'. 424 * 425 * NB: Returning false does not indicate error! 426 */ 427 bool maybeGetElement(uint32_t i, MutableHandleValue vp) { 428 if (i >= initialLength() || hasOverriddenElement()) { 429 return false; 430 } 431 vp.set(element(i)); 432 return true; 433 } 434 435 inline bool maybeGetElements(uint32_t start, uint32_t count, js::Value* vp); 436 437 /* 438 * Measures things hanging off this ArgumentsObject that are counted by the 439 * |miscSize| argument in JSObject::sizeOfExcludingThis(). 440 */ 441 size_t sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const { 442 if (!data()) { // Template arguments objects have no data. 443 return 0; 444 } 445 return mallocSizeOf(data()) + mallocSizeOf(maybeRareData()); 446 } 447 size_t sizeOfData() const { 448 return ArgumentsData::bytesRequired(data()->numArgs()) + 449 (maybeRareData() ? RareArgumentsData::bytesRequired(initialLength()) 450 : 0); 451 } 452 453 static void finalize(JS::GCContext* gcx, JSObject* obj); 454 static void trace(JSTracer* trc, JSObject* obj); 455 static size_t objectMoved(JSObject* dst, JSObject* src); 456 457 /* For jit use: */ 458 static size_t getDataSlotOffset() { return getFixedSlotOffset(DATA_SLOT); } 459 static size_t getInitialLengthSlotOffset() { 460 return getFixedSlotOffset(INITIAL_LENGTH_SLOT); 461 } 462 463 static Value MagicEnvSlotValue(uint32_t slot) { 464 // When forwarding slots to a backing CallObject, the slot numbers are 465 // stored as uint32 magic values. This raises an ambiguity if we have 466 // also copied JS_OPTIMIZED_OUT magic from a JIT frame or 467 // JS_UNINITIALIZED_LEXICAL magic on the CallObject. To distinguish 468 // normal magic values (those with a JSWhyMagic) and uint32 magic 469 // values, we add the maximum JSWhyMagic value to the slot 470 // number. This is safe as ARGS_LENGTH_MAX is well below UINT32_MAX. 471 static_assert(UINT32_MAX - JS_WHY_MAGIC_COUNT > ARGS_LENGTH_MAX); 472 return JS::MagicValueUint32(slot + JS_WHY_MAGIC_COUNT); 473 } 474 static uint32_t SlotFromMagicScopeSlotValue(const Value& v) { 475 static_assert(UINT32_MAX - JS_WHY_MAGIC_COUNT > ARGS_LENGTH_MAX); 476 return v.magicUint32() - JS_WHY_MAGIC_COUNT; 477 } 478 static bool IsMagicScopeSlotValue(const Value& v) { 479 return v.isMagic() && v.magicUint32() > JS_WHY_MAGIC_COUNT; 480 } 481 482 static void MaybeForwardToCallObject(AbstractFramePtr frame, 483 ArgumentsObject* obj, 484 ArgumentsData* data); 485 static void MaybeForwardToCallObject(JSFunction* callee, JSObject* callObj, 486 ArgumentsObject* obj, 487 ArgumentsData* data); 488 }; 489 490 class MappedArgumentsObject : public ArgumentsObject { 491 static const JSClassOps classOps_; 492 static const ClassExtension classExt_; 493 static const ObjectOps objectOps_; 494 495 public: 496 static const JSClass class_; 497 498 JSFunction& callee() const { 499 return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>(); 500 } 501 502 bool hasOverriddenCallee() const { return hasFlags(CALLEE_OVERRIDDEN_BIT); } 503 504 void markCalleeOverridden() { 505 uint32_t v = 506 getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | CALLEE_OVERRIDDEN_BIT; 507 setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v)); 508 } 509 510 static size_t getCalleeSlotOffset() { 511 return getFixedSlotOffset(CALLEE_SLOT); 512 } 513 514 // Create the default "callee" property and set CALLEE_OVERRIDDEN_BIT. 515 static bool reifyCallee(JSContext* cx, Handle<MappedArgumentsObject*> obj); 516 517 private: 518 static bool obj_enumerate(JSContext* cx, HandleObject obj); 519 static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id, 520 bool* resolvedp); 521 static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, 522 Handle<JS::PropertyDescriptor> desc, 523 ObjectOpResult& result); 524 }; 525 526 class UnmappedArgumentsObject : public ArgumentsObject { 527 static const JSClassOps classOps_; 528 static const ClassExtension classExt_; 529 530 public: 531 static const JSClass class_; 532 533 private: 534 static bool obj_enumerate(JSContext* cx, HandleObject obj); 535 static bool obj_resolve(JSContext* cx, HandleObject obj, HandleId id, 536 bool* resolvedp); 537 }; 538 539 extern bool MappedArgGetter(JSContext* cx, HandleObject obj, HandleId id, 540 MutableHandleValue vp); 541 542 extern bool MappedArgSetter(JSContext* cx, HandleObject obj, HandleId id, 543 HandleValue v, ObjectOpResult& result); 544 545 extern bool UnmappedArgGetter(JSContext* cx, HandleObject obj, HandleId id, 546 MutableHandleValue vp); 547 548 extern bool UnmappedArgSetter(JSContext* cx, HandleObject obj, HandleId id, 549 HandleValue v, ObjectOpResult& result); 550 551 } // namespace js 552 553 template <> 554 inline bool JSObject::is<js::ArgumentsObject>() const { 555 return is<js::MappedArgumentsObject>() || is<js::UnmappedArgumentsObject>(); 556 } 557 558 #endif /* vm_ArgumentsObject_h */