Bug 1842773 - Part 32: Allow constructing growable SharedArrayBuffers. r=sfink
[gecko.git] / js / src / vm / ArgumentsObject.cpp
blob1bd2bb144c984b8b4641e609f692e273e4069fc0
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/. */
7 #include "vm/ArgumentsObject-inl.h"
9 #include "mozilla/Maybe.h"
10 #include "mozilla/PodOperations.h"
12 #include <algorithm>
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"
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"
26 using namespace js;
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;
34 /* static */
35 RareArgumentsData* RareArgumentsData::create(JSContext* cx,
36 ArgumentsObject* obj) {
37 size_t bytes = RareArgumentsData::bytesRequired(obj->initialLength());
39 uint8_t* data = AllocateCellBuffer<uint8_t>(cx, obj, bytes);
40 if (!data) {
41 return nullptr;
44 mozilla::PodZero(data, bytes);
46 AddCellMemory(obj, bytes, MemoryUse::RareArgumentsData);
48 return new (data) RareArgumentsData();
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));
57 bool ArgumentsObject::createRareData(JSContext* cx) {
58 MOZ_ASSERT(!data()->rareData);
60 RareArgumentsData* rareData = RareArgumentsData::create(cx, this);
61 if (!rareData) {
62 return false;
65 data()->rareData = rareData;
66 markElementOverridden();
67 return true;
70 bool ArgumentsObject::markElementDeleted(JSContext* cx, uint32_t i) {
71 RareArgumentsData* data = getOrCreateRareData(cx);
72 if (!data) {
73 return false;
76 data->markElementDeleted(initialLength(), i);
77 return true;
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();
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();
116 struct CopyFrameArgs {
117 AbstractFramePtr frame_;
119 explicit CopyFrameArgs(AbstractFramePtr frame) : frame_(frame) {}
121 void copyActualArgs(ArgumentsObject* owner, GCOwnedArray<Value>& args,
122 unsigned numActuals) const {
123 MOZ_ASSERT_IF(frame_.isInterpreterFrame(),
124 !frame_.asInterpreterFrame()->runningInJit());
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++);
138 * If a call object exists and the arguments object aliases formals, the
139 * call object is the canonical location for formals.
141 void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) {
142 ArgumentsObject::MaybeForwardToCallObject(frame_, obj, data);
146 struct CopyJitFrameArgs {
147 jit::JitFrameLayout* frame_;
148 HandleObject callObj_;
150 CopyJitFrameArgs(jit::JitFrameLayout* frame, HandleObject callObj)
151 : frame_(frame), callObj_(callObj) {}
153 void copyActualArgs(ArgumentsObject* owner, GCOwnedArray<Value>& args,
154 unsigned numActuals) const {
155 MOZ_ASSERT(frame_->numActualArgs() == numActuals);
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++);
168 * If a call object exists and the arguments object aliases formals, the
169 * call object is the canonical location for formals.
171 void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) {
172 JSFunction* callee = jit::CalleeTokenToFunction(frame_->calleeToken());
173 ArgumentsObject::MaybeForwardToCallObject(callee, callObj_, obj, data);
177 struct CopyScriptFrameIterArgs {
178 ScriptFrameIter& iter_;
179 RootedValueVector actualArgs_;
181 explicit CopyScriptFrameIterArgs(JSContext* cx, ScriptFrameIter& iter)
182 : iter_(iter), actualArgs_(cx) {}
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;
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;
199 void copyActualArgs(ArgumentsObject* owner, GCOwnedArray<Value>& args,
200 unsigned numActuals) const {
201 MOZ_ASSERT(actualArgs_.length() == numActuals);
203 args.withOwner(owner, [&](auto& args) {
204 auto* dst = args.begin();
205 for (Value v : actualArgs_) {
206 (dst++)->init(v);
212 * Ion frames are copying every argument onto the stack, other locations are
213 * invalid.
215 void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) {
216 if (!iter_.isIon()) {
217 ArgumentsObject::MaybeForwardToCallObject(iter_.abstractFramePtr(), obj,
218 data);
223 struct CopyInlinedArgs {
224 HandleValueArray args_;
225 HandleObject callObj_;
226 HandleFunction callee_;
228 CopyInlinedArgs(HandleValueArray args, HandleObject callObj,
229 HandleFunction callee)
230 : args_(args), callObj_(callObj), callee_(callee) {}
232 void copyActualArgs(ArgumentsObject* owner, GCOwnedArray<Value>& args,
233 unsigned numActuals) const {
234 MOZ_ASSERT(numActuals <= args_.length());
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]);
245 * If a call object exists and the arguments object aliases formals, the
246 * call object is the canonical location for formals.
248 void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) {
249 ArgumentsObject::MaybeForwardToCallObject(callee_, callObj_, obj, data);
253 ArgumentsObject* ArgumentsObject::createTemplateObject(JSContext* cx,
254 bool mapped) {
255 const JSClass* clasp = mapped ? &MappedArgumentsObject::class_
256 : &UnmappedArgumentsObject::class_;
258 RootedObject proto(cx, &cx->global()->getObjectPrototype());
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;
268 AutoSetNewObjectMetadata metadata(cx);
269 auto* obj = NativeObject::create<ArgumentsObject>(cx, FINALIZE_KIND,
270 gc::Heap::Tenured, shape);
271 if (!obj) {
272 return nullptr;
275 obj->initFixedSlot(ArgumentsObject::DATA_SLOT, PrivateValue(nullptr));
276 return obj;
279 ArgumentsObject* GlobalObject::maybeArgumentsTemplateObject(bool mapped) const {
280 return mapped ? data().mappedArgumentsTemplate
281 : data().unmappedArgumentsTemplate;
284 /* static */
285 ArgumentsObject* GlobalObject::getOrCreateArgumentsTemplateObject(JSContext* cx,
286 bool mapped) {
287 GlobalObjectData& data = cx->global()->data();
288 HeapPtr<ArgumentsObject*>& obj =
289 mapped ? data.mappedArgumentsTemplate : data.unmappedArgumentsTemplate;
291 ArgumentsObject* templateObj = obj;
292 if (templateObj) {
293 return templateObj;
296 templateObj = ArgumentsObject::createTemplateObject(cx, mapped);
297 if (!templateObj) {
298 return nullptr;
301 obj.init(templateObj);
302 return templateObj;
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());
313 bool mapped = callee->baseScript()->hasMappedArgsObj();
314 ArgumentsObject* templateObj =
315 GlobalObject::getOrCreateArgumentsTemplateObject(cx, mapped);
316 if (!templateObj) {
317 return nullptr;
320 Rooted<SharedShape*> shape(cx, templateObj->sharedShape());
322 unsigned numFormals = callee->nargs();
323 unsigned numArgs = std::max(numActuals, numFormals);
324 unsigned numBytes = ArgumentsData::bytesRequired(numArgs);
326 AutoSetNewObjectMetadata metadata(cx);
327 auto* obj = NativeObject::create<ArgumentsObject>(cx, FINALIZE_KIND,
328 gc::Heap::Default, shape);
329 if (!obj) {
330 return nullptr;
333 ArgumentsData* data = reinterpret_cast<ArgumentsData*>(
334 AllocateCellBuffer<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;
341 new (data) ArgumentsData(numArgs);
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));
348 // Copy [0, numActuals) into data->args.
349 copy.copyActualArgs(obj, data->args, numActuals);
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());
358 copy.maybeForwardToCallObject(obj, data);
360 MOZ_ASSERT(obj->initialLength() == numActuals);
361 MOZ_ASSERT(!obj->hasOverriddenLength());
362 return obj;
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;
375 frame.initArgsObj(*argsobj);
376 return argsobj;
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;
386 return create(cx, callee, iter.numActualArgs(), copy);
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);
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);
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);
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);
429 return createFromValueArray(cx, argsArray, callee, scopeChain, numActuals);
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);
441 ArgumentsData* data = reinterpret_cast<ArgumentsData*>(
442 AllocateCellBuffer<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;
451 new (data) ArgumentsData(numArgs);
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));
460 copy.copyActualArgs(obj, data->args, numActuals);
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());
469 if (callObj && callee->needsCallObject()) {
470 copy.maybeForwardToCallObject(obj, data);
473 MOZ_ASSERT(obj->initialLength() == numActuals);
474 MOZ_ASSERT(!obj->hasOverriddenLength());
475 return obj;
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;
487 JSFunction* callee = jit::CalleeTokenToFunction(frame->calleeToken());
488 RootedObject callObj(cx, scopeChain->is<CallObject>() ? scopeChain : nullptr);
489 CopyJitFrameArgs copy(frame, callObj);
491 unsigned numActuals = frame->numActualArgs();
493 return finishPure(cx, obj, callee, callObj, numActuals, copy);
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;
504 MOZ_ASSERT(numActuals <= MaxInlinedArgs);
506 RootedObject callObj(cx, rawCallObj);
507 RootedFunction callee(cx, rawCallee);
508 RootedExternalValueArray rootedArgs(cx, numActuals, args);
509 HandleValueArray argsArray =
510 HandleValueArray::fromMarkedLocation(numActuals, args);
512 CopyInlinedArgs copy(argsArray, callObj, callee);
514 return finishPure(cx, obj, callee, callObj, numActuals, copy);
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;
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();
535 return result.succeed();
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;
547 return id.isInt() || id.isWellKnownSymbol(JS::SymbolCode::iterator);
550 bool js::MappedArgGetter(JSContext* cx, HandleObject obj, HandleId id,
551 MutableHandleValue vp) {
552 MappedArgumentsObject& argsobj = obj->as<MappedArgumentsObject>();
553 if (id.isInt()) {
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.
558 unsigned arg = unsigned(id.toInt());
559 if (argsobj.isElement(arg)) {
560 vp.set(argsobj.element(arg));
562 } else if (id.isAtom(cx->names().length)) {
563 if (!argsobj.hasOverriddenLength()) {
564 vp.setInt32(argsobj.initialLength());
566 } else {
567 MOZ_ASSERT(id.isAtom(cx->names().callee));
568 if (!argsobj.hasOverriddenCallee()) {
569 vp.setObject(argsobj.callee());
572 return true;
575 bool js::MappedArgSetter(JSContext* cx, HandleObject obj, HandleId id,
576 HandleValue v, ObjectOpResult& result) {
577 Handle<MappedArgumentsObject*> argsobj = obj.as<MappedArgumentsObject>();
579 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
580 if (!GetOwnPropertyDescriptor(cx, argsobj, id, &desc)) {
581 return false;
583 MOZ_ASSERT(desc.isSome());
584 MOZ_ASSERT(desc->isDataDescriptor());
585 MOZ_ASSERT(desc->writable());
586 MOZ_ASSERT(!desc->resolving());
588 if (id.isInt()) {
589 unsigned arg = unsigned(id.toInt());
590 if (argsobj->isElement(arg)) {
591 argsobj->setElement(arg, v);
592 return result.succeed();
594 } else {
595 MOZ_ASSERT(id.isAtom(cx->names().length) || id.isAtom(cx->names().callee));
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.
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);
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);
622 /* static */
623 bool ArgumentsObject::reifyLength(JSContext* cx, Handle<ArgumentsObject*> obj) {
624 if (obj->hasOverriddenLength()) {
625 return true;
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;
634 obj->markLengthOverridden();
635 return true;
638 /* static */
639 bool ArgumentsObject::reifyIterator(JSContext* cx,
640 Handle<ArgumentsObject*> obj) {
641 if (obj->hasOverriddenIterator()) {
642 return true;
645 RootedId iteratorId(cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator));
646 RootedValue val(cx);
647 if (!ArgumentsObject::getArgumentsIterator(cx, &val)) {
648 return false;
650 if (!NativeDefineDataProperty(cx, obj, iteratorId, val, JSPROP_RESOLVING)) {
651 return false;
654 obj->markIteratorOverridden();
655 return true;
658 /* static */
659 bool MappedArgumentsObject::reifyCallee(JSContext* cx,
660 Handle<MappedArgumentsObject*> obj) {
661 if (obj->hasOverriddenCallee()) {
662 return true;
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;
671 obj->markCalleeOverridden();
672 return true;
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());
682 if (!NativeObject::addCustomDataProperty(cx, obj, id, flags)) {
683 return false;
686 *resolvedp = true;
687 return true;
690 /* static */
691 bool MappedArgumentsObject::obj_resolve(JSContext* cx, HandleObject obj,
692 HandleId id, bool* resolvedp) {
693 Rooted<MappedArgumentsObject*> argsobj(cx, &obj->as<MappedArgumentsObject>());
695 if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
696 if (argsobj->hasOverriddenIterator()) {
697 return true;
700 if (!reifyIterator(cx, argsobj)) {
701 return false;
703 *resolvedp = true;
704 return true;
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;
715 flags.setFlag(PropertyFlag::Enumerable);
716 } else if (id.isAtom(cx->names().length)) {
717 if (argsobj->hasOverriddenLength()) {
718 return true;
720 } else {
721 if (!id.isAtom(cx->names().callee)) {
722 return true;
725 if (argsobj->hasOverriddenCallee()) {
726 return true;
730 return ResolveArgumentsProperty(cx, argsobj, id, flags, resolvedp);
733 /* static */
734 bool MappedArgumentsObject::obj_enumerate(JSContext* cx, HandleObject obj) {
735 Rooted<MappedArgumentsObject*> argsobj(cx, &obj->as<MappedArgumentsObject>());
737 RootedId id(cx);
738 bool found;
740 // Trigger reflection.
741 id = NameToId(cx->names().length);
742 if (!HasOwnProperty(cx, argsobj, id, &found)) {
743 return false;
746 id = NameToId(cx->names().callee);
747 if (!HasOwnProperty(cx, argsobj, id, &found)) {
748 return false;
751 id = PropertyKey::Symbol(cx->wellKnownSymbols().iterator);
752 if (!HasOwnProperty(cx, argsobj, id, &found)) {
753 return false;
756 for (unsigned i = 0; i < argsobj->initialLength(); i++) {
757 id = PropertyKey::Int(i);
758 if (!HasOwnProperty(cx, argsobj, id, &found)) {
759 return false;
763 return true;
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.
774 // This exists in order to let JS code change the configurable/enumerable
775 // attributes for these properties.
777 // Note: because this preserves the default mapped-arguments behavior, we
778 // don't need to mark elements as overridden or deleted.
780 MOZ_ASSERT(id.isInt());
781 MOZ_ASSERT(obj->isElement(id.toInt()));
782 MOZ_ASSERT(!obj->containsDenseElement(id.toInt()));
784 MOZ_ASSERT(!desc.isAccessorDescriptor());
786 // Mapped properties aren't used when defining a non-writable property.
787 MOZ_ASSERT(!desc.hasWritable() || desc.writable());
789 // First, resolve the property to simplify the code below.
790 PropertyResult prop;
791 if (!NativeLookupOwnProperty<CanGC>(cx, obj, id, &prop)) {
792 return false;
795 MOZ_ASSERT(prop.isNativeProperty());
797 PropertyInfo propInfo = prop.propertyInfo();
798 MOZ_ASSERT(propInfo.writable());
799 MOZ_ASSERT(propInfo.isCustomDataProperty());
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.
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();
812 if (desc.hasEnumerable()) {
813 enumerable = desc.enumerable();
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);
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;
830 return result.succeed();
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 Rooted<MappedArgumentsObject*> argsobj(cx, &obj->as<MappedArgumentsObject>());
842 // Steps 2-3.
843 bool isMapped = false;
844 if (id.isInt()) {
845 unsigned arg = unsigned(id.toInt());
846 isMapped = argsobj->isElement(arg);
849 // Step 4.
850 Rooted<PropertyDescriptor> newArgDesc(cx, desc);
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);
861 } else {
862 // In this case the live mapping is supposed to keep working.
863 defineMapped = true;
867 // Step 6. NativeDefineProperty will lookup [[Value]] for us.
868 if (defineMapped) {
869 if (!DefineMappedIndex(cx, argsobj, id, &newArgDesc, result)) {
870 return false;
872 } else {
873 if (!NativeDefineProperty(cx, obj.as<NativeObject>(), id, newArgDesc,
874 result)) {
875 return false;
878 // Step 7.
879 if (!result.ok()) {
880 return true;
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;
890 } else {
891 if (desc.hasValue()) {
892 argsobj->setElement(arg, desc.value());
894 if (desc.hasWritable() && !desc.writable()) {
895 if (!argsobj->markElementDeleted(cx, arg)) {
896 return false;
902 // Step 9.
903 return result.succeed();
906 bool js::UnmappedArgGetter(JSContext* cx, HandleObject obj, HandleId id,
907 MutableHandleValue vp) {
908 UnmappedArgumentsObject& argsobj = obj->as<UnmappedArgumentsObject>();
910 if (id.isInt()) {
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.
915 unsigned arg = unsigned(id.toInt());
916 if (argsobj.isElement(arg)) {
917 vp.set(argsobj.element(arg));
919 } else {
920 MOZ_ASSERT(id.isAtom(cx->names().length));
921 if (!argsobj.hasOverriddenLength()) {
922 vp.setInt32(argsobj.initialLength());
925 return true;
928 bool js::UnmappedArgSetter(JSContext* cx, HandleObject obj, HandleId id,
929 HandleValue v, ObjectOpResult& result) {
930 Handle<UnmappedArgumentsObject*> argsobj = obj.as<UnmappedArgumentsObject>();
932 Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
933 if (!GetOwnPropertyDescriptor(cx, argsobj, id, &desc)) {
934 return false;
936 MOZ_ASSERT(desc.isSome());
937 MOZ_ASSERT(desc->isDataDescriptor());
938 MOZ_ASSERT(desc->writable());
939 MOZ_ASSERT(!desc->resolving());
941 if (id.isInt()) {
942 unsigned arg = unsigned(id.toInt());
943 if (arg < argsobj->initialLength()) {
944 argsobj->setElement(arg, v);
945 return result.succeed();
947 } else {
948 MOZ_ASSERT(id.isAtom(cx->names().length));
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.
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);
963 /* static */
964 bool UnmappedArgumentsObject::obj_resolve(JSContext* cx, HandleObject obj,
965 HandleId id, bool* resolvedp) {
966 Rooted<UnmappedArgumentsObject*> argsobj(cx,
967 &obj->as<UnmappedArgumentsObject>());
969 if (id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
970 if (argsobj->hasOverriddenIterator()) {
971 return true;
974 if (!reifyIterator(cx, argsobj)) {
975 return false;
977 *resolvedp = true;
978 return true;
981 if (id.isAtom(cx->names().callee)) {
982 RootedObject throwTypeError(
983 cx, GlobalObject::getOrCreateThrowTypeError(cx, cx->global()));
984 if (!throwTypeError) {
985 return false;
988 unsigned attrs = JSPROP_RESOLVING | JSPROP_PERMANENT;
989 if (!NativeDefineAccessorProperty(cx, argsobj, id, throwTypeError,
990 throwTypeError, attrs)) {
991 return false;
994 *resolvedp = true;
995 return true;
998 PropertyFlags flags = {PropertyFlag::CustomDataProperty,
999 PropertyFlag::Configurable, PropertyFlag::Writable};
1000 if (id.isInt()) {
1001 uint32_t arg = uint32_t(id.toInt());
1002 if (!argsobj->isElement(arg)) {
1003 return true;
1006 flags.setFlag(PropertyFlag::Enumerable);
1007 } else if (id.isAtom(cx->names().length)) {
1008 if (argsobj->hasOverriddenLength()) {
1009 return true;
1011 } else {
1012 return true;
1015 return ResolveArgumentsProperty(cx, argsobj, id, flags, resolvedp);
1018 /* static */
1019 bool UnmappedArgumentsObject::obj_enumerate(JSContext* cx, HandleObject obj) {
1020 Rooted<UnmappedArgumentsObject*> argsobj(cx,
1021 &obj->as<UnmappedArgumentsObject>());
1023 RootedId id(cx);
1024 bool found;
1026 // Trigger reflection.
1027 id = NameToId(cx->names().length);
1028 if (!HasOwnProperty(cx, argsobj, id, &found)) {
1029 return false;
1032 id = NameToId(cx->names().callee);
1033 if (!HasOwnProperty(cx, argsobj, id, &found)) {
1034 return false;
1037 id = PropertyKey::Symbol(cx->wellKnownSymbols().iterator);
1038 if (!HasOwnProperty(cx, argsobj, id, &found)) {
1039 return false;
1042 for (unsigned i = 0; i < argsobj->initialLength(); i++) {
1043 id = PropertyKey::Int(i);
1044 if (!HasOwnProperty(cx, argsobj, id, &found)) {
1045 return false;
1049 return true;
1052 void ArgumentsObject::finalize(JS::GCContext* gcx, JSObject* obj) {
1053 MOZ_ASSERT(!IsInsideNursery(obj));
1054 ArgumentsObject& argsobj = obj->as<ArgumentsObject>();
1055 if (argsobj.data()) {
1056 gcx->free_(&argsobj, argsobj.maybeRareData(),
1057 RareArgumentsData::bytesRequired(argsobj.initialLength()),
1058 MemoryUse::RareArgumentsData);
1059 gcx->free_(&argsobj, argsobj.data(),
1060 ArgumentsData::bytesRequired(argsobj.data()->numArgs()),
1061 MemoryUse::ArgumentsData);
1065 void ArgumentsObject::trace(JSTracer* trc, JSObject* obj) {
1066 ArgumentsObject& argsobj = obj->as<ArgumentsObject>();
1067 // Template objects have no ArgumentsData.
1068 if (ArgumentsData* data = argsobj.data()) {
1069 data->args.trace(trc);
1073 /* static */
1074 size_t ArgumentsObject::objectMoved(JSObject* dst, JSObject* src) {
1075 ArgumentsObject* ndst = &dst->as<ArgumentsObject>();
1076 const ArgumentsObject* nsrc = &src->as<ArgumentsObject>();
1077 MOZ_ASSERT(ndst->data() == nsrc->data());
1079 if (!IsInsideNursery(src)) {
1080 return 0;
1083 Nursery& nursery = dst->runtimeFromMainThread()->gc.nursery();
1085 size_t nbytesTotal = 0;
1087 ArgumentsData* data = nsrc->data();
1088 uint32_t nDataBytes = ArgumentsData::bytesRequired(nsrc->data()->numArgs());
1089 Nursery::WasBufferMoved result = nursery.maybeMoveBufferOnPromotion(
1090 &data, dst, nDataBytes, MemoryUse::ArgumentsData);
1091 if (result == Nursery::BufferMoved) {
1092 ndst->initFixedSlot(DATA_SLOT, PrivateValue(data));
1093 nbytesTotal += nDataBytes;
1096 if (RareArgumentsData* rareData = nsrc->maybeRareData()) {
1097 uint32_t nRareBytes =
1098 RareArgumentsData::bytesRequired(nsrc->initialLength());
1099 Nursery::WasBufferMoved result = nursery.maybeMoveBufferOnPromotion(
1100 &rareData, dst, nRareBytes, MemoryUse::RareArgumentsData);
1101 if (result == Nursery::BufferMoved) {
1102 ndst->data()->rareData = rareData;
1103 nbytesTotal += nRareBytes;
1107 return nbytesTotal;
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.
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
1129 const js::ClassExtension MappedArgumentsObject::classExt_ = {
1130 ArgumentsObject::objectMoved, // objectMovedOp
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
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_};
1157 * Unmapped arguments is significantly less magical than mapped arguments, so
1158 * it is represented by a different class while sharing some functionality.
1160 const JSClassOps UnmappedArgumentsObject::classOps_ = {
1161 nullptr, // addProperty
1162 ArgumentsObject::obj_delProperty, // delProperty
1163 UnmappedArgumentsObject::obj_enumerate, // enumerate
1164 nullptr, // newEnumerate
1165 UnmappedArgumentsObject::obj_resolve, // resolve
1166 ArgumentsObject::obj_mayResolve, // mayResolve
1167 ArgumentsObject::finalize, // finalize
1168 nullptr, // call
1169 nullptr, // construct
1170 ArgumentsObject::trace, // trace
1173 const js::ClassExtension UnmappedArgumentsObject::classExt_ = {
1174 ArgumentsObject::objectMoved, // objectMovedOp
1177 const JSClass UnmappedArgumentsObject::class_ = {
1178 "Arguments",
1179 JSCLASS_DELAY_METADATA_BUILDER |
1180 JSCLASS_HAS_RESERVED_SLOTS(UnmappedArgumentsObject::RESERVED_SLOTS) |
1181 JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
1182 JSCLASS_SKIP_NURSERY_FINALIZE | JSCLASS_BACKGROUND_FINALIZE,
1183 &UnmappedArgumentsObject::classOps_, nullptr,
1184 &UnmappedArgumentsObject::classExt_};