js/src/vm/ArgumentsObject.h

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/vm/ArgumentsObject.h	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,317 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99:
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#ifndef vm_ArgumentsObject_h
    1.11 +#define vm_ArgumentsObject_h
    1.12 +
    1.13 +#include "mozilla/MemoryReporting.h"
    1.14 +
    1.15 +#include "jsobj.h"
    1.16 +
    1.17 +#include "gc/Barrier.h"
    1.18 +
    1.19 +namespace js {
    1.20 +
    1.21 +class AbstractFramePtr;
    1.22 +class ScriptFrameIter;
    1.23 +
    1.24 +namespace jit {
    1.25 +class IonJSFrameLayout;
    1.26 +}
    1.27 +
    1.28 +/*
    1.29 + * ArgumentsData stores the initial indexed arguments provided to the
    1.30 + * corresponding and that function itself.  It is used to store arguments[i]
    1.31 + * and arguments.callee -- up until the corresponding property is modified,
    1.32 + * when the relevant value is flagged to memorialize the modification.
    1.33 + */
    1.34 +struct ArgumentsData
    1.35 +{
    1.36 +    /*
    1.37 +     * numArgs = Max(numFormalArgs, numActualArgs)
    1.38 +     * The array 'args' has numArgs elements.
    1.39 +     */
    1.40 +    unsigned    numArgs;
    1.41 +
    1.42 +    /*
    1.43 +     * arguments.callee, or MagicValue(JS_OVERWRITTEN_CALLEE) if
    1.44 +     * arguments.callee has been modified.
    1.45 +     */
    1.46 +    HeapValue   callee;
    1.47 +
    1.48 +    /* The script for the function containing this arguments object. */
    1.49 +    JSScript    *script;
    1.50 +
    1.51 +    /*
    1.52 +     * Pointer to an array of bits indicating, for every argument in 'slots',
    1.53 +     * whether the element has been deleted. See isElementDeleted comment.
    1.54 +     */
    1.55 +    size_t      *deletedBits;
    1.56 +
    1.57 +    /*
    1.58 +     * This array holds either the current argument value or the magic
    1.59 +     * forwarding value. The latter means that the function has both a
    1.60 +     * CallObject and an ArgumentsObject AND the particular formal variable is
    1.61 +     * aliased by the CallObject. In such cases, the CallObject holds the
    1.62 +     * canonical value so any element access to the arguments object should load
    1.63 +     * the value out of the CallObject (which is pointed to by MAYBE_CALL_SLOT).
    1.64 +     */
    1.65 +    HeapValue   args[1];
    1.66 +
    1.67 +    /* For jit use: */
    1.68 +    static ptrdiff_t offsetOfArgs() { return offsetof(ArgumentsData, args); }
    1.69 +};
    1.70 +
    1.71 +// Maximum supported value of arguments.length. This bounds the maximum
    1.72 +// number of arguments that can be supplied to Function.prototype.apply.
    1.73 +// This value also bounds the number of elements parsed in an array
    1.74 +// initialiser.
    1.75 +static const unsigned ARGS_LENGTH_MAX = 500 * 1000;
    1.76 +
    1.77 +/*
    1.78 + * ArgumentsObject instances represent |arguments| objects created to store
    1.79 + * function arguments when a function is called.  It's expensive to create such
    1.80 + * objects if they're never used, so they're only created when they are
    1.81 + * potentially used.
    1.82 + *
    1.83 + * Arguments objects are complicated because, for non-strict mode code, they
    1.84 + * must alias any named arguments which were provided to the function.  Gnarly
    1.85 + * example:
    1.86 + *
    1.87 + *   function f(a, b, c, d)
    1.88 + *   {
    1.89 + *     arguments[0] = "seta";
    1.90 + *     assertEq(a, "seta");
    1.91 + *     b = "setb";
    1.92 + *     assertEq(arguments[1], "setb");
    1.93 + *     c = "setc";
    1.94 + *     assertEq(arguments[2], undefined);
    1.95 + *     arguments[3] = "setd";
    1.96 + *     assertEq(d, undefined);
    1.97 + *   }
    1.98 + *   f("arga", "argb");
    1.99 + *
   1.100 + * ES5's strict mode behaves more sanely, and named arguments don't alias
   1.101 + * elements of an arguments object.
   1.102 + *
   1.103 + * ArgumentsObject instances use the following reserved slots:
   1.104 + *
   1.105 + *   INITIAL_LENGTH_SLOT
   1.106 + *     Stores the initial value of arguments.length, plus a bit indicating
   1.107 + *     whether arguments.length has been modified.  Use initialLength() and
   1.108 + *     hasOverriddenLength() to access these values.  If arguments.length has
   1.109 + *     been modified, then the current value of arguments.length is stored in
   1.110 + *     another slot associated with a new property.
   1.111 + *   DATA_SLOT
   1.112 + *     Stores an ArgumentsData*, described above.
   1.113 + */
   1.114 +class ArgumentsObject : public JSObject
   1.115 +{
   1.116 +  protected:
   1.117 +    static const uint32_t INITIAL_LENGTH_SLOT = 0;
   1.118 +    static const uint32_t DATA_SLOT = 1;
   1.119 +    static const uint32_t MAYBE_CALL_SLOT = 2;
   1.120 +
   1.121 +  public:
   1.122 +    static const uint32_t LENGTH_OVERRIDDEN_BIT = 0x1;
   1.123 +    static const uint32_t PACKED_BITS_COUNT = 1;
   1.124 +
   1.125 +  protected:
   1.126 +    template <typename CopyArgs>
   1.127 +    static ArgumentsObject *create(JSContext *cx, HandleScript script, HandleFunction callee,
   1.128 +                                   unsigned numActuals, CopyArgs &copy);
   1.129 +
   1.130 +    ArgumentsData *data() const {
   1.131 +        return reinterpret_cast<ArgumentsData *>(getFixedSlot(DATA_SLOT).toPrivate());
   1.132 +    }
   1.133 +
   1.134 +  public:
   1.135 +    static const uint32_t RESERVED_SLOTS = 3;
   1.136 +    static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND;
   1.137 +
   1.138 +    /* Create an arguments object for a frame that is expecting them. */
   1.139 +    static ArgumentsObject *createExpected(JSContext *cx, AbstractFramePtr frame);
   1.140 +
   1.141 +    /*
   1.142 +     * Purposefully disconnect the returned arguments object from the frame
   1.143 +     * by always creating a new copy that does not alias formal parameters.
   1.144 +     * This allows function-local analysis to determine that formals are
   1.145 +     * not aliased and generally simplifies arguments objects.
   1.146 +     */
   1.147 +    static ArgumentsObject *createUnexpected(JSContext *cx, ScriptFrameIter &iter);
   1.148 +    static ArgumentsObject *createUnexpected(JSContext *cx, AbstractFramePtr frame);
   1.149 +#if defined(JS_ION)
   1.150 +    static ArgumentsObject *createForIon(JSContext *cx, jit::IonJSFrameLayout *frame,
   1.151 +                                         HandleObject scopeChain);
   1.152 +#endif
   1.153 +
   1.154 +    /*
   1.155 +     * Return the initial length of the arguments.  This may differ from the
   1.156 +     * current value of arguments.length!
   1.157 +     */
   1.158 +    uint32_t initialLength() const {
   1.159 +        uint32_t argc = uint32_t(getFixedSlot(INITIAL_LENGTH_SLOT).toInt32()) >> PACKED_BITS_COUNT;
   1.160 +        JS_ASSERT(argc <= ARGS_LENGTH_MAX);
   1.161 +        return argc;
   1.162 +    }
   1.163 +
   1.164 +    /* The script for the function containing this arguments object. */
   1.165 +    JSScript *containingScript() const {
   1.166 +        return data()->script;
   1.167 +    }
   1.168 +
   1.169 +    /* True iff arguments.length has been assigned or its attributes changed. */
   1.170 +    bool hasOverriddenLength() const {
   1.171 +        const Value &v = getFixedSlot(INITIAL_LENGTH_SLOT);
   1.172 +        return v.toInt32() & LENGTH_OVERRIDDEN_BIT;
   1.173 +    }
   1.174 +
   1.175 +    void markLengthOverridden() {
   1.176 +        uint32_t v = getFixedSlot(INITIAL_LENGTH_SLOT).toInt32() | LENGTH_OVERRIDDEN_BIT;
   1.177 +        setFixedSlot(INITIAL_LENGTH_SLOT, Int32Value(v));
   1.178 +    }
   1.179 +
   1.180 +    /*
   1.181 +     * Because the arguments object is a real object, its elements may be
   1.182 +     * deleted. This is implemented by setting a 'deleted' flag for the arg
   1.183 +     * which is read by argument object resolve and getter/setter hooks.
   1.184 +     *
   1.185 +     * NB: an element, once deleted, stays deleted. Thus:
   1.186 +     *
   1.187 +     *   function f(x) { delete arguments[0]; arguments[0] = 42; return x }
   1.188 +     *   assertEq(f(1), 1);
   1.189 +     *
   1.190 +     * This works because, once a property is deleted from an arguments object,
   1.191 +     * it gets regular properties with regular getters/setters that don't alias
   1.192 +     * ArgumentsData::slots.
   1.193 +     */
   1.194 +    bool isElementDeleted(uint32_t i) const {
   1.195 +        JS_ASSERT(i < data()->numArgs);
   1.196 +        if (i >= initialLength())
   1.197 +            return false;
   1.198 +        return IsBitArrayElementSet(data()->deletedBits, initialLength(), i);
   1.199 +    }
   1.200 +
   1.201 +    bool isAnyElementDeleted() const {
   1.202 +        return IsAnyBitArrayElementSet(data()->deletedBits, initialLength());
   1.203 +    }
   1.204 +
   1.205 +    void markElementDeleted(uint32_t i) {
   1.206 +        SetBitArrayElement(data()->deletedBits, initialLength(), i);
   1.207 +    }
   1.208 +
   1.209 +    /*
   1.210 +     * An ArgumentsObject serves two roles:
   1.211 +     *  - a real object, accessed through regular object operations, e.g..,
   1.212 +     *    JSObject::getElement corresponding to 'arguments[i]';
   1.213 +     *  - a VM-internal data structure, storing the value of arguments (formal
   1.214 +     *    and actual) that are accessed directly by the VM when a reading the
   1.215 +     *    value of a formal parameter.
   1.216 +     * There are two ways to access the ArgumentsData::args corresponding to
   1.217 +     * these two use cases:
   1.218 +     *  - object access should use elements(i) which will take care of
   1.219 +     *    forwarding when the value is the magic forwarding value;
   1.220 +     *  - VM argument access should use arg(i) which will assert that the
   1.221 +     *    value is not the magic forwarding value (since, if such forwarding was
   1.222 +     *    needed, the frontend should have emitted JSOP_GETALIASEDVAR).
   1.223 +     */
   1.224 +    const Value &element(uint32_t i) const;
   1.225 +
   1.226 +    inline void setElement(JSContext *cx, uint32_t i, const Value &v);
   1.227 +
   1.228 +    const Value &arg(unsigned i) const {
   1.229 +        JS_ASSERT(i < data()->numArgs);
   1.230 +        const Value &v = data()->args[i];
   1.231 +        JS_ASSERT(!v.isMagic());
   1.232 +        return v;
   1.233 +    }
   1.234 +
   1.235 +    void setArg(unsigned i, const Value &v) {
   1.236 +        JS_ASSERT(i < data()->numArgs);
   1.237 +        HeapValue &lhs = data()->args[i];
   1.238 +        JS_ASSERT(!lhs.isMagic());
   1.239 +        lhs = v;
   1.240 +    }
   1.241 +
   1.242 +    /*
   1.243 +     * Attempt to speedily and efficiently access the i-th element of this
   1.244 +     * arguments object.  Return true if the element was speedily returned.
   1.245 +     * Return false if the element must be looked up more slowly using
   1.246 +     * getProperty or some similar method. The second overload copies the
   1.247 +     * elements [start, start + count) into the locations starting at 'vp'.
   1.248 +     *
   1.249 +     * NB: Returning false does not indicate error!
   1.250 +     */
   1.251 +    bool maybeGetElement(uint32_t i, MutableHandleValue vp) {
   1.252 +        if (i >= initialLength() || isElementDeleted(i))
   1.253 +            return false;
   1.254 +        vp.set(element(i));
   1.255 +        return true;
   1.256 +    }
   1.257 +
   1.258 +    inline bool maybeGetElements(uint32_t start, uint32_t count, js::Value *vp);
   1.259 +
   1.260 +    /*
   1.261 +     * Measures things hanging off this ArgumentsObject that are counted by the
   1.262 +     * |miscSize| argument in JSObject::sizeOfExcludingThis().
   1.263 +     */
   1.264 +    size_t sizeOfMisc(mozilla::MallocSizeOf mallocSizeOf) const {
   1.265 +        return mallocSizeOf(data());
   1.266 +    }
   1.267 +
   1.268 +    static void finalize(FreeOp *fop, JSObject *obj);
   1.269 +    static void trace(JSTracer *trc, JSObject *obj);
   1.270 +
   1.271 +    /* For jit use: */
   1.272 +    static size_t getDataSlotOffset() {
   1.273 +        return getFixedSlotOffset(DATA_SLOT);
   1.274 +    }
   1.275 +    static size_t getInitialLengthSlotOffset() {
   1.276 +        return getFixedSlotOffset(INITIAL_LENGTH_SLOT);
   1.277 +    }
   1.278 +
   1.279 +    static void MaybeForwardToCallObject(AbstractFramePtr frame, JSObject *obj, ArgumentsData *data);
   1.280 +#if defined(JS_ION)
   1.281 +    static void MaybeForwardToCallObject(jit::IonJSFrameLayout *frame, HandleObject callObj,
   1.282 +                                         JSObject *obj, ArgumentsData *data);
   1.283 +#endif
   1.284 +};
   1.285 +
   1.286 +class NormalArgumentsObject : public ArgumentsObject
   1.287 +{
   1.288 +  public:
   1.289 +    static const Class class_;
   1.290 +
   1.291 +    /*
   1.292 +     * Stores arguments.callee, or MagicValue(JS_ARGS_HOLE) if the callee has
   1.293 +     * been cleared.
   1.294 +     */
   1.295 +    const js::Value &callee() const {
   1.296 +        return data()->callee;
   1.297 +    }
   1.298 +
   1.299 +    /* Clear the location storing arguments.callee's initial value. */
   1.300 +    void clearCallee() {
   1.301 +        data()->callee.set(zone(), MagicValue(JS_OVERWRITTEN_CALLEE));
   1.302 +    }
   1.303 +};
   1.304 +
   1.305 +class StrictArgumentsObject : public ArgumentsObject
   1.306 +{
   1.307 +  public:
   1.308 +    static const Class class_;
   1.309 +};
   1.310 +
   1.311 +} // namespace js
   1.312 +
   1.313 +template<>
   1.314 +inline bool
   1.315 +JSObject::is<js::ArgumentsObject>() const
   1.316 +{
   1.317 +    return is<js::NormalArgumentsObject>() || is<js::StrictArgumentsObject>();
   1.318 +}
   1.319 +
   1.320 +#endif /* vm_ArgumentsObject_h */

mercurial