/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "vm/ArgumentsObject-inl.h" #include "mozilla/PodOperations.h" #include #include "gc/FreeOp.h" #include "jit/CalleeToken.h" #include "jit/JitFrames.h" #include "util/BitArray.h" #include "vm/AsyncFunction.h" #include "vm/GlobalObject.h" #include "vm/Stack.h" #include "gc/Nursery-inl.h" #include "vm/FrameIter-inl.h" // js::FrameIter::unaliasedForEachActual #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Stack-inl.h" using namespace js; /* static */ size_t RareArgumentsData::bytesRequired(size_t numActuals) { size_t extraBytes = NumWordsForBitArrayOfLength(numActuals) * sizeof(size_t); return offsetof(RareArgumentsData, deletedBits_) + extraBytes; } /* static */ RareArgumentsData* RareArgumentsData::create(JSContext* cx, ArgumentsObject* obj) { size_t bytes = RareArgumentsData::bytesRequired(obj->initialLength()); uint8_t* data = AllocateObjectBuffer(cx, obj, bytes); if (!data) { return nullptr; } mozilla::PodZero(data, bytes); AddCellMemory(obj, bytes, MemoryUse::RareArgumentsData); return new (data) RareArgumentsData(); } bool ArgumentsObject::createRareData(JSContext* cx) { MOZ_ASSERT(!data()->rareData); RareArgumentsData* rareData = RareArgumentsData::create(cx, this); if (!rareData) { return false; } data()->rareData = rareData; return true; } bool ArgumentsObject::markElementDeleted(JSContext* cx, uint32_t i) { RareArgumentsData* data = getOrCreateRareData(cx); if (!data) { return false; } data->markElementDeleted(initialLength(), i); return true; } static void CopyStackFrameArguments(const AbstractFramePtr frame, GCPtrValue* dst, unsigned totalArgs) { MOZ_ASSERT_IF(frame.isInterpreterFrame(), !frame.asInterpreterFrame()->runningInJit()); MOZ_ASSERT(std::max(frame.numActualArgs(), frame.numFormalArgs()) == totalArgs); /* Copy arguments. */ Value* src = frame.argv(); Value* end = src + totalArgs; while (src != end) { (dst++)->init(*src++); } } /* static */ void ArgumentsObject::MaybeForwardToCallObject(AbstractFramePtr frame, ArgumentsObject* obj, ArgumentsData* data) { JSScript* script = frame.script(); if (frame.callee()->needsCallObject() && script->argumentsAliasesFormals()) { obj->initFixedSlot(MAYBE_CALL_SLOT, ObjectValue(frame.callObj())); for (PositionalFormalParameterIter fi(script); fi; fi++) { if (fi.closedOver()) { data->args[fi.argumentSlot()] = MagicEnvSlotValue(fi.location().slot()); } } } } /* static */ void ArgumentsObject::MaybeForwardToCallObject(jit::JitFrameLayout* frame, HandleObject callObj, ArgumentsObject* obj, ArgumentsData* data) { JSFunction* callee = jit::CalleeTokenToFunction(frame->calleeToken()); JSScript* script = callee->nonLazyScript(); if (callee->needsCallObject() && script->argumentsAliasesFormals()) { MOZ_ASSERT(callObj && callObj->is()); obj->initFixedSlot(MAYBE_CALL_SLOT, ObjectValue(*callObj.get())); for (PositionalFormalParameterIter fi(script); fi; fi++) { if (fi.closedOver()) { data->args[fi.argumentSlot()] = MagicEnvSlotValue(fi.location().slot()); } } } } struct CopyFrameArgs { AbstractFramePtr frame_; explicit CopyFrameArgs(AbstractFramePtr frame) : frame_(frame) {} void copyArgs(JSContext*, GCPtrValue* dst, unsigned totalArgs) const { CopyStackFrameArguments(frame_, dst, totalArgs); } /* * If a call object exists and the arguments object aliases formals, the * call object is the canonical location for formals. */ void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) { ArgumentsObject::MaybeForwardToCallObject(frame_, obj, data); } }; struct CopyJitFrameArgs { jit::JitFrameLayout* frame_; HandleObject callObj_; CopyJitFrameArgs(jit::JitFrameLayout* frame, HandleObject callObj) : frame_(frame), callObj_(callObj) {} void copyArgs(JSContext*, GCPtrValue* dstBase, unsigned totalArgs) const { unsigned numActuals = frame_->numActualArgs(); unsigned numFormals = jit::CalleeTokenToFunction(frame_->calleeToken())->nargs(); MOZ_ASSERT(numActuals <= totalArgs); MOZ_ASSERT(numFormals <= totalArgs); MOZ_ASSERT(std::max(numActuals, numFormals) == totalArgs); /* Copy all arguments. */ Value* src = frame_->argv() + 1; /* +1 to skip this. */ Value* end = src + numActuals; GCPtrValue* dst = dstBase; while (src != end) { (dst++)->init(*src++); } if (numActuals < numFormals) { GCPtrValue* dstEnd = dstBase + totalArgs; while (dst != dstEnd) { (dst++)->init(UndefinedValue()); } } } /* * If a call object exists and the arguments object aliases formals, the * call object is the canonical location for formals. */ void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) { ArgumentsObject::MaybeForwardToCallObject(frame_, callObj_, obj, data); } }; struct CopyScriptFrameIterArgs { ScriptFrameIter& iter_; explicit CopyScriptFrameIterArgs(ScriptFrameIter& iter) : iter_(iter) {} void copyArgs(JSContext* cx, GCPtrValue* dstBase, unsigned totalArgs) const { /* Copy actual arguments. */ iter_.unaliasedForEachActual(cx, CopyToHeap(dstBase)); /* Define formals which are not part of the actuals. */ unsigned numActuals = iter_.numActualArgs(); unsigned numFormals = iter_.calleeTemplate()->nargs(); MOZ_ASSERT(numActuals <= totalArgs); MOZ_ASSERT(numFormals <= totalArgs); MOZ_ASSERT(std::max(numActuals, numFormals) == totalArgs); if (numActuals < numFormals) { GCPtrValue* dst = dstBase + numActuals; GCPtrValue* dstEnd = dstBase + totalArgs; while (dst != dstEnd) { (dst++)->init(UndefinedValue()); } } } /* * Ion frames are copying every argument onto the stack, other locations are * invalid. */ void maybeForwardToCallObject(ArgumentsObject* obj, ArgumentsData* data) { if (!iter_.isIon()) { ArgumentsObject::MaybeForwardToCallObject(iter_.abstractFramePtr(), obj, data); } } }; ArgumentsObject* ArgumentsObject::createTemplateObject(JSContext* cx, bool mapped) { const JSClass* clasp = mapped ? &MappedArgumentsObject::class_ : &UnmappedArgumentsObject::class_; RootedObject proto( cx, GlobalObject::getOrCreateObjectPrototype(cx, cx->global())); if (!proto) { return nullptr; } RootedObjectGroup group( cx, ObjectGroup::defaultNewGroup(cx, clasp, TaggedProto(proto.get()))); if (!group) { return nullptr; } RootedShape shape( cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(proto), FINALIZE_KIND, BaseShape::INDEXED)); if (!shape) { return nullptr; } AutoSetNewObjectMetadata metadata(cx); JSObject* base; JS_TRY_VAR_OR_RETURN_NULL( cx, base, NativeObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, group)); ArgumentsObject* obj = &base->as(); obj->initFixedSlot(ArgumentsObject::DATA_SLOT, PrivateValue(nullptr)); return obj; } ArgumentsObject* Realm::maybeArgumentsTemplateObject(bool mapped) const { return mapped ? mappedArgumentsTemplate_ : unmappedArgumentsTemplate_; } ArgumentsObject* Realm::getOrCreateArgumentsTemplateObject(JSContext* cx, bool mapped) { WeakHeapPtr& obj = mapped ? mappedArgumentsTemplate_ : unmappedArgumentsTemplate_; ArgumentsObject* templateObj = obj; if (templateObj) { return templateObj; } templateObj = ArgumentsObject::createTemplateObject(cx, mapped); if (!templateObj) { return nullptr; } obj.set(templateObj); return templateObj; } template /* static */ ArgumentsObject* ArgumentsObject::create(JSContext* cx, HandleFunction callee, unsigned numActuals, CopyArgs& copy) { bool mapped = callee->baseScript()->hasMappedArgsObj(); ArgumentsObject* templateObj = cx->realm()->getOrCreateArgumentsTemplateObject(cx, mapped); if (!templateObj) { return nullptr; } RootedShape shape(cx, templateObj->lastProperty()); RootedObjectGroup group(cx, templateObj->group()); unsigned numFormals = callee->nargs(); unsigned numArgs = std::max(numActuals, numFormals); unsigned numBytes = ArgumentsData::bytesRequired(numArgs); Rooted obj(cx); ArgumentsData* data = nullptr; { // The copyArgs call below can allocate objects, so add this block scope // to make sure we set the metadata for this arguments object first. AutoSetNewObjectMetadata metadata(cx); JSObject* base; JS_TRY_VAR_OR_RETURN_NULL( cx, base, NativeObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, group)); obj = &base->as(); data = reinterpret_cast( AllocateObjectBuffer(cx, obj, numBytes)); if (!data) { // Make the object safe for GC. obj->initFixedSlot(DATA_SLOT, PrivateValue(nullptr)); return nullptr; } data->numArgs = numArgs; data->rareData = nullptr; // Initialize |args| with a pattern that is safe for GC tracing. for (unsigned i = 0; i < numArgs; i++) { data->args[i].init(UndefinedValue()); } InitReservedSlot(obj, DATA_SLOT, data, numBytes, MemoryUse::ArgumentsData); obj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee)); } MOZ_ASSERT(data != nullptr); /* Copy [0, numArgs) into data->slots. */ copy.copyArgs(cx, data->args, numArgs); obj->initFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(numActuals << PACKED_BITS_COUNT)); copy.maybeForwardToCallObject(obj, data); MOZ_ASSERT(obj->initialLength() == numActuals); MOZ_ASSERT(!obj->hasOverriddenLength()); return obj; } ArgumentsObject* ArgumentsObject::createExpected(JSContext* cx, AbstractFramePtr frame) { MOZ_ASSERT(frame.script()->needsArgsObj()); RootedFunction callee(cx, frame.callee()); CopyFrameArgs copy(frame); ArgumentsObject* argsobj = create(cx, callee, frame.numActualArgs(), copy); if (!argsobj) { return nullptr; } frame.initArgsObj(*argsobj); return argsobj; } ArgumentsObject* ArgumentsObject::createUnexpected(JSContext* cx, ScriptFrameIter& iter) { RootedFunction callee(cx, iter.callee(cx)); CopyScriptFrameIterArgs copy(iter); return create(cx, callee, iter.numActualArgs(), copy); } ArgumentsObject* ArgumentsObject::createUnexpected(JSContext* cx, AbstractFramePtr frame) { RootedFunction callee(cx, frame.callee()); CopyFrameArgs copy(frame); return create(cx, callee, frame.numActualArgs(), copy); } ArgumentsObject* ArgumentsObject::createForIon(JSContext* cx, jit::JitFrameLayout* frame, HandleObject scopeChain) { jit::CalleeToken token = frame->calleeToken(); MOZ_ASSERT(jit::CalleeTokenIsFunction(token)); RootedFunction callee(cx, jit::CalleeTokenToFunction(token)); RootedObject callObj( cx, scopeChain->is() ? scopeChain.get() : nullptr); CopyJitFrameArgs copy(frame, callObj); return create(cx, callee, frame->numActualArgs(), copy); } /* static */ ArgumentsObject* ArgumentsObject::finishForIonPure(JSContext* cx, jit::JitFrameLayout* frame, JSObject* scopeChain, ArgumentsObject* obj) { // JIT code calls this directly (no callVM), because it's faster, so we're // not allowed to GC in here. AutoUnsafeCallWithABI unsafe; JSFunction* callee = jit::CalleeTokenToFunction(frame->calleeToken()); RootedObject callObj(cx, scopeChain->is() ? scopeChain : nullptr); CopyJitFrameArgs copy(frame, callObj); unsigned numActuals = frame->numActualArgs(); unsigned numFormals = callee->nargs(); unsigned numArgs = std::max(numActuals, numFormals); unsigned numBytes = ArgumentsData::bytesRequired(numArgs); ArgumentsData* data = reinterpret_cast( AllocateObjectBuffer(cx, obj, numBytes)); if (!data) { // Make the object safe for GC. Don't report OOM, the slow path will // retry the allocation. cx->recoverFromOutOfMemory(); obj->initFixedSlot(DATA_SLOT, PrivateValue(nullptr)); return nullptr; } data->numArgs = numArgs; data->rareData = nullptr; obj->initFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(numActuals << PACKED_BITS_COUNT)); obj->initFixedSlot(DATA_SLOT, PrivateValue(data)); AddCellMemory(obj, numBytes, MemoryUse::ArgumentsData); obj->initFixedSlot(MAYBE_CALL_SLOT, UndefinedValue()); obj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee)); copy.copyArgs(cx, data->args, numArgs); if (callObj && callee->needsCallObject()) { copy.maybeForwardToCallObject(obj, data); } MOZ_ASSERT(obj->initialLength() == numActuals); MOZ_ASSERT(!obj->hasOverriddenLength()); return obj; } /* static */ bool ArgumentsObject::obj_delProperty(JSContext* cx, HandleObject obj, HandleId id, ObjectOpResult& result) { ArgumentsObject& argsobj = obj->as(); if (JSID_IS_INT(id)) { unsigned arg = unsigned(JSID_TO_INT(id)); if (arg < argsobj.initialLength() && !argsobj.isElementDeleted(arg)) { if (!argsobj.markElementDeleted(cx, arg)) { return false; } } } else if (JSID_IS_ATOM(id, cx->names().length)) { argsobj.markLengthOverridden(); } else if (JSID_IS_ATOM(id, cx->names().callee)) { argsobj.as().markCalleeOverridden(); } else if (JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id) == cx->wellKnownSymbols().iterator) { argsobj.markIteratorOverridden(); } return result.succeed(); } /* static */ bool ArgumentsObject::obj_mayResolve(const JSAtomState& names, jsid id, JSObject*) { // Arguments might resolve indexes, Symbol.iterator, or length/callee. if (JSID_IS_ATOM(id)) { JSAtom* atom = JSID_TO_ATOM(id); uint32_t index; if (atom->isIndex(&index)) { return true; } return atom == names.length || atom == names.callee; } return id.isInt() || id.isWellKnownSymbol(JS::SymbolCode::iterator); } static bool MappedArgGetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp) { MappedArgumentsObject& argsobj = obj->as(); if (JSID_IS_INT(id)) { /* * arg can exceed the number of arguments if a script changed the * prototype to point to another Arguments object with a bigger argc. */ unsigned arg = unsigned(JSID_TO_INT(id)); if (arg < argsobj.initialLength() && !argsobj.isElementDeleted(arg)) { vp.set(argsobj.element(arg)); } } else if (JSID_IS_ATOM(id, cx->names().length)) { if (!argsobj.hasOverriddenLength()) { vp.setInt32(argsobj.initialLength()); } } else { MOZ_ASSERT(JSID_IS_ATOM(id, cx->names().callee)); if (!argsobj.hasOverriddenCallee()) { vp.setObject(argsobj.callee()); } } return true; } static bool MappedArgSetter(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, ObjectOpResult& result) { if (!obj->is()) { return result.succeed(); } Handle argsobj = obj.as(); Rooted desc(cx); if (!GetOwnPropertyDescriptor(cx, argsobj, id, &desc)) { return false; } MOZ_ASSERT(desc.object()); unsigned attrs = desc.attributes(); MOZ_ASSERT(!(attrs & JSPROP_READONLY)); attrs &= (JSPROP_ENUMERATE | JSPROP_PERMANENT); /* only valid attributes */ if (JSID_IS_INT(id)) { unsigned arg = unsigned(JSID_TO_INT(id)); if (arg < argsobj->initialLength() && !argsobj->isElementDeleted(arg)) { argsobj->setElement(arg, v); return result.succeed(); } } else { MOZ_ASSERT(JSID_IS_ATOM(id, cx->names().length) || JSID_IS_ATOM(id, cx->names().callee)); } /* * For simplicity we use delete/define to replace the property with a * simple data property. Note that we rely on ArgumentsObject::obj_delProperty * to set the corresponding override-bit. * Note also that we must define the property instead of setting it in case * the user has changed the prototype to an object that has a setter for * this id. */ ObjectOpResult ignored; return NativeDeleteProperty(cx, argsobj, id, ignored) && NativeDefineDataProperty(cx, argsobj, id, v, attrs, result); } /* static */ bool ArgumentsObject::getArgumentsIterator(JSContext* cx, MutableHandleValue val) { HandlePropertyName shName = cx->names().ArrayValues; RootedAtom name(cx, cx->names().values); return GlobalObject::getSelfHostedFunction(cx, cx->global(), shName, name, 0, val); } /* static */ bool ArgumentsObject::reifyLength(JSContext* cx, Handle obj) { if (obj->hasOverriddenLength()) { return true; } RootedId id(cx, NameToId(cx->names().length)); RootedValue val(cx, Int32Value(obj->initialLength())); if (!NativeDefineDataProperty(cx, obj, id, val, JSPROP_RESOLVING)) { return false; } obj->markLengthOverridden(); return true; } /* static */ bool ArgumentsObject::reifyIterator(JSContext* cx, Handle obj) { if (obj->hasOverriddenIterator()) { return true; } RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator)); RootedValue val(cx); if (!ArgumentsObject::getArgumentsIterator(cx, &val)) { return false; } if (!NativeDefineDataProperty(cx, obj, iteratorId, val, JSPROP_RESOLVING)) { return false; } obj->markIteratorOverridden(); return true; } /* static */ bool MappedArgumentsObject::obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { Rooted argsobj(cx, &obj->as()); if (JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id) == cx->wellKnownSymbols().iterator) { if (argsobj->hasOverriddenIterator()) { return true; } if (!reifyIterator(cx, argsobj)) { return false; } *resolvedp = true; return true; } unsigned attrs = JSPROP_RESOLVING; if (JSID_IS_INT(id)) { uint32_t arg = uint32_t(JSID_TO_INT(id)); if (arg >= argsobj->initialLength() || argsobj->isElementDeleted(arg)) { return true; } attrs |= JSPROP_ENUMERATE; } else if (JSID_IS_ATOM(id, cx->names().length)) { if (argsobj->hasOverriddenLength()) { return true; } } else { if (!JSID_IS_ATOM(id, cx->names().callee)) { return true; } if (argsobj->hasOverriddenCallee()) { return true; } } if (!NativeDefineAccessorProperty(cx, argsobj, id, MappedArgGetter, MappedArgSetter, attrs)) { return false; } *resolvedp = true; return true; } /* static */ bool MappedArgumentsObject::obj_enumerate(JSContext* cx, HandleObject obj) { Rooted argsobj(cx, &obj->as()); RootedId id(cx); bool found; // Trigger reflection. id = NameToId(cx->names().length); if (!HasOwnProperty(cx, argsobj, id, &found)) { return false; } id = NameToId(cx->names().callee); if (!HasOwnProperty(cx, argsobj, id, &found)) { return false; } id = SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator); if (!HasOwnProperty(cx, argsobj, id, &found)) { return false; } for (unsigned i = 0; i < argsobj->initialLength(); i++) { id = INT_TO_JSID(i); if (!HasOwnProperty(cx, argsobj, id, &found)) { return false; } } return true; } // ES 2017 draft 9.4.4.2 /* static */ bool MappedArgumentsObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, Handle desc, ObjectOpResult& result) { // Step 1. Rooted argsobj(cx, &obj->as()); // Steps 2-3. bool isMapped = false; if (JSID_IS_INT(id)) { unsigned arg = unsigned(JSID_TO_INT(id)); isMapped = arg < argsobj->initialLength() && !argsobj->isElementDeleted(arg); } // Step 4. Rooted newArgDesc(cx, desc); // Step 5. if (!desc.isAccessorDescriptor() && isMapped) { // Step 5.a. if (desc.hasWritable() && !desc.writable()) { if (!desc.hasValue()) { RootedValue v(cx, argsobj->element(JSID_TO_INT(id))); newArgDesc.setValue(v); } newArgDesc.setGetter(nullptr); newArgDesc.setSetter(nullptr); } else { // In this case the live mapping is supposed to keep working, // we have to pass along the Getter/Setter otherwise they are // overwritten. newArgDesc.setGetter(MappedArgGetter); newArgDesc.setSetter(MappedArgSetter); newArgDesc.value().setUndefined(); newArgDesc.attributesRef() |= JSPROP_IGNORE_VALUE; } } // Step 6. NativeDefineProperty will lookup [[Value]] for us. if (!NativeDefineProperty(cx, obj.as(), id, newArgDesc, result)) { return false; } // Step 7. if (!result.ok()) { return true; } // Step 8. if (isMapped) { unsigned arg = unsigned(JSID_TO_INT(id)); if (desc.isAccessorDescriptor()) { if (!argsobj->markElementDeleted(cx, arg)) { return false; } } else { if (desc.hasValue()) { argsobj->setElement(arg, desc.value()); } if (desc.hasWritable() && !desc.writable()) { if (!argsobj->markElementDeleted(cx, arg)) { return false; } } } } // Step 9. return result.succeed(); } static bool UnmappedArgGetter(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp) { UnmappedArgumentsObject& argsobj = obj->as(); if (JSID_IS_INT(id)) { /* * arg can exceed the number of arguments if a script changed the * prototype to point to another Arguments object with a bigger argc. */ unsigned arg = unsigned(JSID_TO_INT(id)); if (arg < argsobj.initialLength() && !argsobj.isElementDeleted(arg)) { vp.set(argsobj.element(arg)); } } else { MOZ_ASSERT(JSID_IS_ATOM(id, cx->names().length)); if (!argsobj.hasOverriddenLength()) { vp.setInt32(argsobj.initialLength()); } } return true; } static bool UnmappedArgSetter(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, ObjectOpResult& result) { if (!obj->is()) { return result.succeed(); } Handle argsobj = obj.as(); Rooted desc(cx); if (!GetOwnPropertyDescriptor(cx, argsobj, id, &desc)) { return false; } MOZ_ASSERT(desc.object()); unsigned attrs = desc.attributes(); MOZ_ASSERT(!(attrs & JSPROP_READONLY)); attrs &= (JSPROP_ENUMERATE | JSPROP_PERMANENT); /* only valid attributes */ if (JSID_IS_INT(id)) { unsigned arg = unsigned(JSID_TO_INT(id)); if (arg < argsobj->initialLength()) { argsobj->setElement(arg, v); return result.succeed(); } } else { MOZ_ASSERT(JSID_IS_ATOM(id, cx->names().length)); } /* * For simplicity we use delete/define to replace the property with a * simple data property. Note that we rely on ArgumentsObject::obj_delProperty * to set the corresponding override-bit. */ ObjectOpResult ignored; return NativeDeleteProperty(cx, argsobj, id, ignored) && NativeDefineDataProperty(cx, argsobj, id, v, attrs, result); } /* static */ bool UnmappedArgumentsObject::obj_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) { Rooted argsobj(cx, &obj->as()); if (JSID_IS_SYMBOL(id) && JSID_TO_SYMBOL(id) == cx->wellKnownSymbols().iterator) { if (argsobj->hasOverriddenIterator()) { return true; } if (!reifyIterator(cx, argsobj)) { return false; } *resolvedp = true; return true; } if (JSID_IS_ATOM(id, cx->names().callee)) { RootedObject throwTypeError( cx, GlobalObject::getOrCreateThrowTypeError(cx, cx->global())); if (!throwTypeError) { return false; } unsigned attrs = JSPROP_RESOLVING | JSPROP_PERMANENT | JSPROP_GETTER | JSPROP_SETTER; if (!NativeDefineAccessorProperty(cx, argsobj, id, throwTypeError, throwTypeError, attrs)) { return false; } *resolvedp = true; return true; } unsigned attrs = JSPROP_RESOLVING; if (JSID_IS_INT(id)) { uint32_t arg = uint32_t(JSID_TO_INT(id)); if (arg >= argsobj->initialLength() || argsobj->isElementDeleted(arg)) { return true; } attrs |= JSPROP_ENUMERATE; } else if (JSID_IS_ATOM(id, cx->names().length)) { if (argsobj->hasOverriddenLength()) { return true; } } else { return true; } if (!NativeDefineAccessorProperty(cx, argsobj, id, UnmappedArgGetter, UnmappedArgSetter, attrs)) { return false; } *resolvedp = true; return true; } /* static */ bool UnmappedArgumentsObject::obj_enumerate(JSContext* cx, HandleObject obj) { Rooted argsobj(cx, &obj->as()); RootedId id(cx); bool found; // Trigger reflection. id = NameToId(cx->names().length); if (!HasOwnProperty(cx, argsobj, id, &found)) { return false; } id = NameToId(cx->names().callee); if (!HasOwnProperty(cx, argsobj, id, &found)) { return false; } id = SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator); if (!HasOwnProperty(cx, argsobj, id, &found)) { return false; } for (unsigned i = 0; i < argsobj->initialLength(); i++) { id = INT_TO_JSID(i); if (!HasOwnProperty(cx, argsobj, id, &found)) { return false; } } return true; } void ArgumentsObject::finalize(JSFreeOp* fop, JSObject* obj) { MOZ_ASSERT(!IsInsideNursery(obj)); ArgumentsObject& argsobj = obj->as(); if (argsobj.data()) { fop->free_(&argsobj, argsobj.maybeRareData(), RareArgumentsData::bytesRequired(argsobj.initialLength()), MemoryUse::RareArgumentsData); fop->free_(&argsobj, argsobj.data(), ArgumentsData::bytesRequired(argsobj.data()->numArgs), MemoryUse::ArgumentsData); } } void ArgumentsObject::trace(JSTracer* trc, JSObject* obj) { ArgumentsObject& argsobj = obj->as(); if (ArgumentsData* data = argsobj.data()) { // Template objects have no ArgumentsData. TraceRange(trc, data->numArgs, data->begin(), js_arguments_str); } } /* static */ size_t ArgumentsObject::objectMoved(JSObject* dst, JSObject* src) { ArgumentsObject* ndst = &dst->as(); const ArgumentsObject* nsrc = &src->as(); MOZ_ASSERT(ndst->data() == nsrc->data()); if (!IsInsideNursery(src)) { return 0; } Nursery& nursery = dst->runtimeFromMainThread()->gc.nursery(); size_t nbytesTotal = 0; uint32_t nDataBytes = ArgumentsData::bytesRequired(nsrc->data()->numArgs); if (!nursery.isInside(nsrc->data())) { nursery.removeMallocedBufferDuringMinorGC(nsrc->data()); } else { AutoEnterOOMUnsafeRegion oomUnsafe; uint8_t* data = nsrc->zone()->pod_malloc(nDataBytes); if (!data) { oomUnsafe.crash( "Failed to allocate ArgumentsObject data while tenuring."); } ndst->initFixedSlot(DATA_SLOT, PrivateValue(data)); mozilla::PodCopy(data, reinterpret_cast(nsrc->data()), nDataBytes); nbytesTotal += nDataBytes; } AddCellMemory(ndst, nDataBytes, MemoryUse::ArgumentsData); if (RareArgumentsData* srcRareData = nsrc->maybeRareData()) { uint32_t nbytes = RareArgumentsData::bytesRequired(nsrc->initialLength()); if (!nursery.isInside(srcRareData)) { nursery.removeMallocedBufferDuringMinorGC(srcRareData); } else { AutoEnterOOMUnsafeRegion oomUnsafe; uint8_t* dstRareData = nsrc->zone()->pod_malloc(nbytes); if (!dstRareData) { oomUnsafe.crash( "Failed to allocate RareArgumentsData data while tenuring."); } ndst->data()->rareData = (RareArgumentsData*)dstRareData; mozilla::PodCopy(dstRareData, reinterpret_cast(srcRareData), nbytes); nbytesTotal += nbytes; } AddCellMemory(ndst, nbytes, MemoryUse::RareArgumentsData); } return nbytesTotal; } /* * The classes below collaborate to lazily reflect and synchronize actual * argument values, argument count, and callee function object stored in a * stack frame with their corresponding property values in the frame's * arguments object. */ const JSClassOps MappedArgumentsObject::classOps_ = { nullptr, // addProperty ArgumentsObject::obj_delProperty, // delProperty MappedArgumentsObject::obj_enumerate, // enumerate nullptr, // newEnumerate MappedArgumentsObject::obj_resolve, // resolve ArgumentsObject::obj_mayResolve, // mayResolve ArgumentsObject::finalize, // finalize nullptr, // call nullptr, // hasInstance nullptr, // construct ArgumentsObject::trace, // trace }; const js::ClassExtension MappedArgumentsObject::classExt_ = { ArgumentsObject::objectMoved, // objectMovedOp }; const ObjectOps MappedArgumentsObject::objectOps_ = { nullptr, // lookupProperty MappedArgumentsObject::obj_defineProperty, // defineProperty nullptr, // hasProperty nullptr, // getProperty nullptr, // setProperty nullptr, // getOwnPropertyDescriptor nullptr, // deleteProperty nullptr, // getElements nullptr, // funToString }; const JSClass MappedArgumentsObject::class_ = { "Arguments", JSCLASS_DELAY_METADATA_BUILDER | JSCLASS_HAS_RESERVED_SLOTS(MappedArgumentsObject::RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | JSCLASS_SKIP_NURSERY_FINALIZE | JSCLASS_BACKGROUND_FINALIZE, &MappedArgumentsObject::classOps_, nullptr, &MappedArgumentsObject::classExt_, &MappedArgumentsObject::objectOps_}; /* * Unmapped arguments is significantly less magical than mapped arguments, so * it is represented by a different class while sharing some functionality. */ const JSClassOps UnmappedArgumentsObject::classOps_ = { nullptr, // addProperty ArgumentsObject::obj_delProperty, // delProperty UnmappedArgumentsObject::obj_enumerate, // enumerate nullptr, // newEnumerate UnmappedArgumentsObject::obj_resolve, // resolve ArgumentsObject::obj_mayResolve, // mayResolve ArgumentsObject::finalize, // finalize nullptr, // call nullptr, // hasInstance nullptr, // construct ArgumentsObject::trace, // trace }; const js::ClassExtension UnmappedArgumentsObject::classExt_ = { ArgumentsObject::objectMoved, // objectMovedOp }; const JSClass UnmappedArgumentsObject::class_ = { "Arguments", JSCLASS_DELAY_METADATA_BUILDER | JSCLASS_HAS_RESERVED_SLOTS(UnmappedArgumentsObject::RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | JSCLASS_SKIP_NURSERY_FINALIZE | JSCLASS_BACKGROUND_FINALIZE, &UnmappedArgumentsObject::classOps_, nullptr, &UnmappedArgumentsObject::classExt_};