diff -r 000000000000 -r 6474c204b198 js/src/vm/ArgumentsObject.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/vm/ArgumentsObject.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,613 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jsinfer.h" + +#ifdef JS_ION +#include "jit/IonFrames.h" +#endif +#include "vm/GlobalObject.h" +#include "vm/Stack.h" + +#include "jsobjinlines.h" + +#include "vm/Stack-inl.h" + +using namespace js; +using namespace js::gc; + +static void +CopyStackFrameArguments(const AbstractFramePtr frame, HeapValue *dst, unsigned totalArgs) +{ + JS_ASSERT_IF(frame.isInterpreterFrame(), !frame.asInterpreterFrame()->runningInJit()); + + JS_ASSERT(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, JSObject *obj, + ArgumentsData *data) +{ + JSScript *script = frame.script(); + if (frame.fun()->isHeavyweight() && script->argsObjAliasesFormals()) { + obj->initFixedSlot(MAYBE_CALL_SLOT, ObjectValue(frame.callObj())); + for (AliasedFormalIter fi(script); fi; fi++) + data->args[fi.frameIndex()] = JS::MagicValueUint32(fi.scopeSlot()); + } +} + +#if defined(JS_ION) +/* static */ void +ArgumentsObject::MaybeForwardToCallObject(jit::IonJSFrameLayout *frame, HandleObject callObj, + JSObject *obj, ArgumentsData *data) +{ + JSFunction *callee = jit::CalleeTokenToFunction(frame->calleeToken()); + JSScript *script = callee->nonLazyScript(); + if (callee->isHeavyweight() && script->argsObjAliasesFormals()) { + JS_ASSERT(callObj && callObj->is()); + obj->initFixedSlot(MAYBE_CALL_SLOT, ObjectValue(*callObj.get())); + for (AliasedFormalIter fi(script); fi; fi++) + data->args[fi.frameIndex()] = JS::MagicValueUint32(fi.scopeSlot()); + } +} +#endif + +struct CopyFrameArgs +{ + AbstractFramePtr frame_; + + CopyFrameArgs(AbstractFramePtr frame) + : frame_(frame) + { } + + void copyArgs(JSContext *, HeapValue *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(JSObject *obj, ArgumentsData *data) { + ArgumentsObject::MaybeForwardToCallObject(frame_, obj, data); + } +}; + +#if defined(JS_ION) +struct CopyIonJSFrameArgs +{ + jit::IonJSFrameLayout *frame_; + HandleObject callObj_; + + CopyIonJSFrameArgs(jit::IonJSFrameLayout *frame, HandleObject callObj) + : frame_(frame), callObj_(callObj) + { } + + void copyArgs(JSContext *, HeapValue *dstBase, unsigned totalArgs) const { + unsigned numActuals = frame_->numActualArgs(); + unsigned numFormals = jit::CalleeTokenToFunction(frame_->calleeToken())->nargs(); + JS_ASSERT(numActuals <= totalArgs); + JS_ASSERT(numFormals <= totalArgs); + JS_ASSERT(Max(numActuals, numFormals) == totalArgs); + + /* Copy all arguments. */ + Value *src = frame_->argv() + 1; /* +1 to skip this. */ + Value *end = src + numActuals; + HeapValue *dst = dstBase; + while (src != end) + (dst++)->init(*src++); + + if (numActuals < numFormals) { + HeapValue *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(JSObject *obj, ArgumentsData *data) { + ArgumentsObject::MaybeForwardToCallObject(frame_, callObj_, obj, data); + } +}; +#endif + +struct CopyScriptFrameIterArgs +{ + ScriptFrameIter &iter_; + + CopyScriptFrameIterArgs(ScriptFrameIter &iter) + : iter_(iter) + { } + + void copyArgs(JSContext *cx, HeapValue *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_.callee()->nargs(); + JS_ASSERT(numActuals <= totalArgs); + JS_ASSERT(numFormals <= totalArgs); + JS_ASSERT(Max(numActuals, numFormals) == totalArgs); + + if (numActuals < numFormals) { + HeapValue *dst = dstBase + numActuals, *dstEnd = dstBase + totalArgs; + while (dst != dstEnd) + (dst++)->init(UndefinedValue()); + } + } + + /* + * Ion frames are copying every argument onto the stack, other locations are + * invalid. + */ + void maybeForwardToCallObject(JSObject *obj, ArgumentsData *data) { + if (!iter_.isJit()) + ArgumentsObject::MaybeForwardToCallObject(iter_.abstractFramePtr(), obj, data); + } +}; + +template +/* static */ ArgumentsObject * +ArgumentsObject::create(JSContext *cx, HandleScript script, HandleFunction callee, unsigned numActuals, + CopyArgs ©) +{ + RootedObject proto(cx, callee->global().getOrCreateObjectPrototype(cx)); + if (!proto) + return nullptr; + + bool strict = callee->strict(); + const Class *clasp = strict ? &StrictArgumentsObject::class_ : &NormalArgumentsObject::class_; + + RootedTypeObject type(cx, cx->getNewType(clasp, proto.get())); + if (!type) + return nullptr; + + JSObject *metadata = nullptr; + if (!NewObjectMetadata(cx, &metadata)) + return nullptr; + + RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(proto), + proto->getParent(), metadata, FINALIZE_KIND, + BaseShape::INDEXED)); + if (!shape) + return nullptr; + + unsigned numFormals = callee->nargs(); + unsigned numDeletedWords = NumWordsForBitArrayOfLength(numActuals); + unsigned numArgs = Max(numActuals, numFormals); + unsigned numBytes = offsetof(ArgumentsData, args) + + numDeletedWords * sizeof(size_t) + + numArgs * sizeof(Value); + + ArgumentsData *data = (ArgumentsData *)cx->malloc_(numBytes); + if (!data) + return nullptr; + + JSObject *obj = JSObject::create(cx, FINALIZE_KIND, GetInitialHeap(GenericObject, clasp), + shape, type); + if (!obj) { + js_free(data); + return nullptr; + } + + data->numArgs = numArgs; + data->callee.init(ObjectValue(*callee.get())); + data->script = script; + + /* Copy [0, numArgs) into data->slots. */ + HeapValue *dst = data->args, *dstEnd = data->args + numArgs; + copy.copyArgs(cx, dst, numArgs); + + data->deletedBits = reinterpret_cast(dstEnd); + ClearAllBitArrayElements(data->deletedBits, numDeletedWords); + + obj->initFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(numActuals << PACKED_BITS_COUNT)); + obj->initFixedSlot(DATA_SLOT, PrivateValue(data)); + + copy.maybeForwardToCallObject(obj, data); + + ArgumentsObject &argsobj = obj->as(); + JS_ASSERT(argsobj.initialLength() == numActuals); + JS_ASSERT(!argsobj.hasOverriddenLength()); + return &argsobj; +} + +ArgumentsObject * +ArgumentsObject::createExpected(JSContext *cx, AbstractFramePtr frame) +{ + JS_ASSERT(frame.script()->needsArgsObj()); + RootedScript script(cx, frame.script()); + RootedFunction callee(cx, frame.callee()); + CopyFrameArgs copy(frame); + ArgumentsObject *argsobj = create(cx, script, callee, frame.numActualArgs(), copy); + if (!argsobj) + return nullptr; + + frame.initArgsObj(*argsobj); + return argsobj; +} + +ArgumentsObject * +ArgumentsObject::createUnexpected(JSContext *cx, ScriptFrameIter &iter) +{ + RootedScript script(cx, iter.script()); + RootedFunction callee(cx, iter.callee()); + CopyScriptFrameIterArgs copy(iter); + return create(cx, script, callee, iter.numActualArgs(), copy); +} + +ArgumentsObject * +ArgumentsObject::createUnexpected(JSContext *cx, AbstractFramePtr frame) +{ + RootedScript script(cx, frame.script()); + RootedFunction callee(cx, frame.callee()); + CopyFrameArgs copy(frame); + return create(cx, script, callee, frame.numActualArgs(), copy); +} + +#if defined(JS_ION) +ArgumentsObject * +ArgumentsObject::createForIon(JSContext *cx, jit::IonJSFrameLayout *frame, HandleObject scopeChain) +{ + jit::CalleeToken token = frame->calleeToken(); + JS_ASSERT(jit::CalleeTokenIsFunction(token)); + RootedScript script(cx, jit::ScriptFromCalleeToken(token)); + RootedFunction callee(cx, jit::CalleeTokenToFunction(token)); + RootedObject callObj(cx, scopeChain->is() ? scopeChain.get() : nullptr); + CopyIonJSFrameArgs copy(frame, callObj); + return create(cx, script, callee, frame->numActualArgs(), copy); +} +#endif + +static bool +args_delProperty(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded) +{ + ArgumentsObject &argsobj = obj->as(); + if (JSID_IS_INT(id)) { + unsigned arg = unsigned(JSID_TO_INT(id)); + if (arg < argsobj.initialLength() && !argsobj.isElementDeleted(arg)) + argsobj.markElementDeleted(arg); + } else if (JSID_IS_ATOM(id, cx->names().length)) { + argsobj.markLengthOverridden(); + } else if (JSID_IS_ATOM(id, cx->names().callee)) { + argsobj.as().clearCallee(); + } + *succeeded = true; + return true; +} + +static bool +ArgGetter(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) +{ + if (!obj->is()) + return true; + + NormalArgumentsObject &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 { + JS_ASSERT(JSID_IS_ATOM(id, cx->names().callee)); + if (!argsobj.callee().isMagic(JS_OVERWRITTEN_CALLEE)) + vp.set(argsobj.callee()); + } + return true; +} + +static bool +ArgSetter(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp) +{ + if (!obj->is()) + return true; + + unsigned attrs; + if (!baseops::GetAttributes(cx, obj, id, &attrs)) + return false; + JS_ASSERT(!(attrs & JSPROP_READONLY)); + attrs &= (JSPROP_ENUMERATE | JSPROP_PERMANENT); /* only valid attributes */ + + NormalArgumentsObject &argsobj = obj->as(); + RootedScript script(cx, argsobj.containingScript()); + + if (JSID_IS_INT(id)) { + unsigned arg = unsigned(JSID_TO_INT(id)); + if (arg < argsobj.initialLength() && !argsobj.isElementDeleted(arg)) { + argsobj.setElement(cx, arg, vp); + if (arg < script->functionNonDelazifying()->nargs()) + types::TypeScript::SetArgument(cx, script, arg, vp); + return true; + } + } else { + JS_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 one + * backed by the default Object getter and setter. Note that we rely on + * args_delProperty to clear the corresponding reserved slot so the GC can + * collect its value. 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. + */ + bool succeeded; + return baseops::DeleteGeneric(cx, obj, id, &succeeded) && + baseops::DefineGeneric(cx, obj, id, vp, nullptr, nullptr, attrs); +} + +static bool +args_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) +{ + objp.set(nullptr); + + Rooted argsobj(cx, &obj->as()); + + unsigned attrs = JSPROP_SHARED | JSPROP_SHADOWABLE; + 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->callee().isMagic(JS_OVERWRITTEN_CALLEE)) + return true; + } + + if (!baseops::DefineGeneric(cx, argsobj, id, UndefinedHandleValue, ArgGetter, ArgSetter, attrs)) + return false; + + objp.set(argsobj); + return true; +} + +static bool +args_enumerate(JSContext *cx, HandleObject obj) +{ + Rooted argsobj(cx, &obj->as()); + RootedId id(cx); + + /* + * Trigger reflection in args_resolve using a series of js_LookupProperty + * calls. + */ + int argc = int(argsobj->initialLength()); + for (int i = -2; i != argc; i++) { + id = (i == -2) + ? NameToId(cx->names().length) + : (i == -1) + ? NameToId(cx->names().callee) + : INT_TO_JSID(i); + + RootedObject pobj(cx); + RootedShape prop(cx); + if (!baseops::LookupProperty(cx, argsobj, id, &pobj, &prop)) + return false; + } + return true; +} + +static bool +StrictArgGetter(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) +{ + if (!obj->is()) + return true; + + StrictArgumentsObject &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 { + JS_ASSERT(JSID_IS_ATOM(id, cx->names().length)); + if (!argsobj.hasOverriddenLength()) + vp.setInt32(argsobj.initialLength()); + } + return true; +} + +static bool +StrictArgSetter(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp) +{ + if (!obj->is()) + return true; + + unsigned attrs; + if (!baseops::GetAttributes(cx, obj, id, &attrs)) + return false; + JS_ASSERT(!(attrs & JSPROP_READONLY)); + attrs &= (JSPROP_ENUMERATE | JSPROP_PERMANENT); /* only valid attributes */ + + Rooted argsobj(cx, &obj->as()); + + if (JSID_IS_INT(id)) { + unsigned arg = unsigned(JSID_TO_INT(id)); + if (arg < argsobj->initialLength()) { + argsobj->setElement(cx, arg, vp); + return true; + } + } else { + JS_ASSERT(JSID_IS_ATOM(id, cx->names().length)); + } + + /* + * For simplicity we use delete/define to replace the property with one + * backed by the default Object getter and setter. Note that we rely on + * args_delProperty to clear the corresponding reserved slot so the GC can + * collect its value. + */ + bool succeeded; + return baseops::DeleteGeneric(cx, argsobj, id, &succeeded) && + baseops::DefineGeneric(cx, argsobj, id, vp, nullptr, nullptr, attrs); +} + +static bool +strictargs_resolve(JSContext *cx, HandleObject obj, HandleId id, MutableHandleObject objp) +{ + objp.set(nullptr); + + Rooted argsobj(cx, &obj->as()); + + unsigned attrs = JSPROP_SHARED | JSPROP_SHADOWABLE; + PropertyOp getter = StrictArgGetter; + StrictPropertyOp setter = StrictArgSetter; + + 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) && !JSID_IS_ATOM(id, cx->names().caller)) + return true; + + attrs = JSPROP_PERMANENT | JSPROP_GETTER | JSPROP_SETTER | JSPROP_SHARED; + getter = CastAsPropertyOp(argsobj->global().getThrowTypeError()); + setter = CastAsStrictPropertyOp(argsobj->global().getThrowTypeError()); + } + + if (!baseops::DefineGeneric(cx, argsobj, id, UndefinedHandleValue, getter, setter, attrs)) + return false; + + objp.set(argsobj); + return true; +} + +static bool +strictargs_enumerate(JSContext *cx, HandleObject obj) +{ + Rooted argsobj(cx, &obj->as()); + + /* + * Trigger reflection in strictargs_resolve using a series of + * js_LookupProperty calls. + */ + RootedObject pobj(cx); + RootedShape prop(cx); + RootedId id(cx); + + // length + id = NameToId(cx->names().length); + if (!baseops::LookupProperty(cx, argsobj, id, &pobj, &prop)) + return false; + + // callee + id = NameToId(cx->names().callee); + if (!baseops::LookupProperty(cx, argsobj, id, &pobj, &prop)) + return false; + + // caller + id = NameToId(cx->names().caller); + if (!baseops::LookupProperty(cx, argsobj, id, &pobj, &prop)) + return false; + + for (uint32_t i = 0, argc = argsobj->initialLength(); i < argc; i++) { + id = INT_TO_JSID(i); + if (!baseops::LookupProperty(cx, argsobj, id, &pobj, &prop)) + return false; + } + + return true; +} + +void +ArgumentsObject::finalize(FreeOp *fop, JSObject *obj) +{ + fop->free_(reinterpret_cast(obj->as().data())); +} + +void +ArgumentsObject::trace(JSTracer *trc, JSObject *obj) +{ + ArgumentsObject &argsobj = obj->as(); + ArgumentsData *data = argsobj.data(); + MarkValue(trc, &data->callee, js_callee_str); + MarkValueRange(trc, data->numArgs, data->args, js_arguments_str); + MarkScriptUnbarriered(trc, &data->script, "script"); +} + +/* + * 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 Class NormalArgumentsObject::class_ = { + "Arguments", + JSCLASS_NEW_RESOLVE | JSCLASS_IMPLEMENTS_BARRIERS | + JSCLASS_HAS_RESERVED_SLOTS(NormalArgumentsObject::RESERVED_SLOTS) | + JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | JSCLASS_BACKGROUND_FINALIZE, + JS_PropertyStub, /* addProperty */ + args_delProperty, + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + args_enumerate, + reinterpret_cast(args_resolve), + JS_ConvertStub, + ArgumentsObject::finalize, + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + ArgumentsObject::trace +}; + +/* + * Strict mode arguments is significantly less magical than non-strict mode + * arguments, so it is represented by a different class while sharing some + * functionality. + */ +const Class StrictArgumentsObject::class_ = { + "Arguments", + JSCLASS_NEW_RESOLVE | JSCLASS_IMPLEMENTS_BARRIERS | + JSCLASS_HAS_RESERVED_SLOTS(StrictArgumentsObject::RESERVED_SLOTS) | + JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | JSCLASS_BACKGROUND_FINALIZE, + JS_PropertyStub, /* addProperty */ + args_delProperty, + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + strictargs_enumerate, + reinterpret_cast(strictargs_resolve), + JS_ConvertStub, + ArgumentsObject::finalize, + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + ArgumentsObject::trace +};