js/src/vm/ScopeObject.h

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/vm/ScopeObject.h	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,956 @@
     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_ScopeObject_h
    1.11 +#define vm_ScopeObject_h
    1.12 +
    1.13 +#include "jscntxt.h"
    1.14 +#include "jsobj.h"
    1.15 +#include "jsweakmap.h"
    1.16 +
    1.17 +#include "gc/Barrier.h"
    1.18 +#include "vm/ProxyObject.h"
    1.19 +
    1.20 +namespace js {
    1.21 +
    1.22 +namespace frontend { struct Definition; }
    1.23 +
    1.24 +class StaticWithObject;
    1.25 +
    1.26 +/*****************************************************************************/
    1.27 +
    1.28 +/*
    1.29 + * All function scripts have an "enclosing static scope" that refers to the
    1.30 + * innermost enclosing let or function in the program text. This allows full
    1.31 + * reconstruction of the lexical scope for debugging or compiling efficient
    1.32 + * access to variables in enclosing scopes. The static scope is represented at
    1.33 + * runtime by a tree of compiler-created objects representing each scope:
    1.34 + *  - a StaticBlockObject is created for 'let' and 'catch' scopes
    1.35 + *  - a JSFunction+JSScript+Bindings trio is created for function scopes
    1.36 + * (These objects are primarily used to clone objects scopes for the
    1.37 + * dynamic scope chain.)
    1.38 + *
    1.39 + * There is an additional scope for named lambdas. E.g., in:
    1.40 + *
    1.41 + *   (function f() { var x; function g() { } })
    1.42 + *
    1.43 + * g's innermost enclosing scope will first be the function scope containing
    1.44 + * 'x', enclosed by a scope containing only the name 'f'. (This separate scope
    1.45 + * is necessary due to the fact that declarations in the function scope shadow
    1.46 + * (dynamically, in the case of 'eval') the lambda name.)
    1.47 + *
    1.48 + * There are two limitations to the current lexical nesting information:
    1.49 + *
    1.50 + *  - 'with' is completely absent; this isn't a problem for the current use
    1.51 + *    cases since 'with' causes every static scope to be on the dynamic scope
    1.52 + *    chain (so the debugger can find everything) and inhibits all upvar
    1.53 + *    optimization.
    1.54 + *
    1.55 + *  - The "enclosing static scope" chain stops at 'eval'. For example in:
    1.56 + *      let (x) { eval("function f() {}") }
    1.57 + *    f does not have an enclosing static scope. This is fine for current uses
    1.58 + *    for the same reason as 'with'.
    1.59 + *
    1.60 + * (See also AssertDynamicScopeMatchesStaticScope.)
    1.61 + */
    1.62 +template <AllowGC allowGC>
    1.63 +class StaticScopeIter
    1.64 +{
    1.65 +    typename MaybeRooted<JSObject*, allowGC>::RootType obj;
    1.66 +    bool onNamedLambda;
    1.67 +
    1.68 +  public:
    1.69 +    StaticScopeIter(ExclusiveContext *cx, JSObject *obj)
    1.70 +      : obj(cx, obj), onNamedLambda(false)
    1.71 +    {
    1.72 +        JS_STATIC_ASSERT(allowGC == CanGC);
    1.73 +        JS_ASSERT_IF(obj, obj->is<StaticBlockObject>() || obj->is<StaticWithObject>() ||
    1.74 +                     obj->is<JSFunction>());
    1.75 +    }
    1.76 +
    1.77 +    StaticScopeIter(JSObject *obj)
    1.78 +      : obj((ExclusiveContext *) nullptr, obj), onNamedLambda(false)
    1.79 +    {
    1.80 +        JS_STATIC_ASSERT(allowGC == NoGC);
    1.81 +        JS_ASSERT_IF(obj, obj->is<StaticBlockObject>() || obj->is<StaticWithObject>() ||
    1.82 +                     obj->is<JSFunction>());
    1.83 +    }
    1.84 +
    1.85 +    bool done() const;
    1.86 +    void operator++(int);
    1.87 +
    1.88 +    /* Return whether this static scope will be on the dynamic scope chain. */
    1.89 +    bool hasDynamicScopeObject() const;
    1.90 +    Shape *scopeShape() const;
    1.91 +
    1.92 +    enum Type { WITH, BLOCK, FUNCTION, NAMED_LAMBDA };
    1.93 +    Type type() const;
    1.94 +
    1.95 +    StaticBlockObject &block() const;
    1.96 +    StaticWithObject &staticWith() const;
    1.97 +    JSScript *funScript() const;
    1.98 +};
    1.99 +
   1.100 +/*****************************************************************************/
   1.101 +
   1.102 +/*
   1.103 + * A "scope coordinate" describes how to get from head of the scope chain to a
   1.104 + * given lexically-enclosing variable. A scope coordinate has two dimensions:
   1.105 + *  - hops: the number of scope objects on the scope chain to skip
   1.106 + *  - slot: the slot on the scope object holding the variable's value
   1.107 + */
   1.108 +class ScopeCoordinate
   1.109 +{
   1.110 +    uint32_t hops_;
   1.111 +    uint32_t slot_;
   1.112 +
   1.113 +    /*
   1.114 +     * Technically, hops_/slot_ are SCOPECOORD_(HOPS|SLOT)_BITS wide.  Since
   1.115 +     * ScopeCoordinate is a temporary value, don't bother with a bitfield as
   1.116 +     * this only adds overhead.
   1.117 +     */
   1.118 +    static_assert(SCOPECOORD_HOPS_BITS <= 32, "We have enough bits below");
   1.119 +    static_assert(SCOPECOORD_SLOT_BITS <= 32, "We have enough bits below");
   1.120 +
   1.121 +  public:
   1.122 +    inline ScopeCoordinate(jsbytecode *pc)
   1.123 +      : hops_(GET_SCOPECOORD_HOPS(pc)), slot_(GET_SCOPECOORD_SLOT(pc + SCOPECOORD_HOPS_LEN))
   1.124 +    {
   1.125 +        JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD);
   1.126 +    }
   1.127 +
   1.128 +    inline ScopeCoordinate() {}
   1.129 +
   1.130 +    void setHops(uint32_t hops) { JS_ASSERT(hops < SCOPECOORD_HOPS_LIMIT); hops_ = hops; }
   1.131 +    void setSlot(uint32_t slot) { JS_ASSERT(slot < SCOPECOORD_SLOT_LIMIT); slot_ = slot; }
   1.132 +
   1.133 +    uint32_t hops() const { JS_ASSERT(hops_ < SCOPECOORD_HOPS_LIMIT); return hops_; }
   1.134 +    uint32_t slot() const { JS_ASSERT(slot_ < SCOPECOORD_SLOT_LIMIT); return slot_; }
   1.135 +};
   1.136 +
   1.137 +/*
   1.138 + * Return a shape representing the static scope containing the variable
   1.139 + * accessed by the ALIASEDVAR op at 'pc'.
   1.140 + */
   1.141 +extern Shape *
   1.142 +ScopeCoordinateToStaticScopeShape(JSScript *script, jsbytecode *pc);
   1.143 +
   1.144 +/* Return the name being accessed by the given ALIASEDVAR op. */
   1.145 +extern PropertyName *
   1.146 +ScopeCoordinateName(ScopeCoordinateNameCache &cache, JSScript *script, jsbytecode *pc);
   1.147 +
   1.148 +/* Return the function script accessed by the given ALIASEDVAR op, or nullptr. */
   1.149 +extern JSScript *
   1.150 +ScopeCoordinateFunctionScript(JSScript *script, jsbytecode *pc);
   1.151 +
   1.152 +/*****************************************************************************/
   1.153 +
   1.154 +/*
   1.155 + * Scope objects
   1.156 + *
   1.157 + * Scope objects are technically real JSObjects but only belong on the scope
   1.158 + * chain (that is, fp->scopeChain() or fun->environment()). The hierarchy of
   1.159 + * scope objects is:
   1.160 + *
   1.161 + *   JSObject                   Generic object
   1.162 + *     \
   1.163 + *   ScopeObject                Engine-internal scope
   1.164 + *     \   \   \
   1.165 + *      \   \  DeclEnvObject    Holds name of recursive/heavyweight named lambda
   1.166 + *       \   \
   1.167 + *        \  CallObject         Scope of entire function or strict eval
   1.168 + *         \
   1.169 + *   NestedScopeObject          Scope created for a statement
   1.170 + *     \   \  \
   1.171 + *      \   \ StaticWithObject  Template for "with" object in static scope chain
   1.172 + *       \   \
   1.173 + *        \  DynamicWithObject  Run-time "with" object on scope chain
   1.174 + *         \
   1.175 + *   BlockObject                Shared interface of cloned/static block objects
   1.176 + *     \   \
   1.177 + *      \  ClonedBlockObject    let, switch, catch, for
   1.178 + *       \
   1.179 + *       StaticBlockObject      See NB
   1.180 + *
   1.181 + * This hierarchy represents more than just the interface hierarchy: reserved
   1.182 + * slots in base classes are fixed for all derived classes. Thus, for example,
   1.183 + * ScopeObject::enclosingScope() can simply access a fixed slot without further
   1.184 + * dynamic type information.
   1.185 + *
   1.186 + * NB: Static block objects are a special case: these objects are created at
   1.187 + * compile time to hold the shape/binding information from which block objects
   1.188 + * are cloned at runtime. These objects should never escape into the wild and
   1.189 + * support a restricted set of ScopeObject operations.
   1.190 + *
   1.191 + * See also "Debug scope objects" below.
   1.192 + */
   1.193 +
   1.194 +class ScopeObject : public JSObject
   1.195 +{
   1.196 +  protected:
   1.197 +    static const uint32_t SCOPE_CHAIN_SLOT = 0;
   1.198 +
   1.199 +  public:
   1.200 +    /*
   1.201 +     * Since every scope chain terminates with a global object and GlobalObject
   1.202 +     * does not derive ScopeObject (it has a completely different layout), the
   1.203 +     * enclosing scope of a ScopeObject is necessarily non-null.
   1.204 +     */
   1.205 +    inline JSObject &enclosingScope() const {
   1.206 +        return getFixedSlot(SCOPE_CHAIN_SLOT).toObject();
   1.207 +    }
   1.208 +
   1.209 +    void setEnclosingScope(HandleObject obj);
   1.210 +
   1.211 +    /*
   1.212 +     * Get or set an aliased variable contained in this scope. Unaliased
   1.213 +     * variables should instead access the stack frame. Aliased variable access
   1.214 +     * is primarily made through JOF_SCOPECOORD ops which is why these members
   1.215 +     * take a ScopeCoordinate instead of just the slot index.
   1.216 +     */
   1.217 +    inline const Value &aliasedVar(ScopeCoordinate sc);
   1.218 +
   1.219 +    inline void setAliasedVar(JSContext *cx, ScopeCoordinate sc, PropertyName *name, const Value &v);
   1.220 +
   1.221 +    /* For jit access. */
   1.222 +    static size_t offsetOfEnclosingScope() {
   1.223 +        return getFixedSlotOffset(SCOPE_CHAIN_SLOT);
   1.224 +    }
   1.225 +
   1.226 +    static size_t enclosingScopeSlot() {
   1.227 +        return SCOPE_CHAIN_SLOT;
   1.228 +    }
   1.229 +};
   1.230 +
   1.231 +class CallObject : public ScopeObject
   1.232 +{
   1.233 +    static const uint32_t CALLEE_SLOT = 1;
   1.234 +
   1.235 +    static CallObject *
   1.236 +    create(JSContext *cx, HandleScript script, HandleObject enclosing, HandleFunction callee);
   1.237 +
   1.238 +  public:
   1.239 +    static const Class class_;
   1.240 +
   1.241 +    /* These functions are internal and are exposed only for JITs. */
   1.242 +
   1.243 +    /*
   1.244 +     * Construct a bare-bones call object given a shape, a non-singleton type,
   1.245 +     * and slots pointer.  The call object must be further initialized to be
   1.246 +     * usable.
   1.247 +     */
   1.248 +    static CallObject *
   1.249 +    create(JSContext *cx, HandleShape shape, HandleTypeObject type, HeapSlot *slots);
   1.250 +
   1.251 +    /*
   1.252 +     * Construct a bare-bones call object given a shape and slots pointer, and
   1.253 +     * make it have singleton type.  The call object must be initialized to be
   1.254 +     * usable.
   1.255 +     */
   1.256 +    static CallObject *
   1.257 +    createSingleton(JSContext *cx, HandleShape shape, HeapSlot *slots);
   1.258 +
   1.259 +    static CallObject *
   1.260 +    createTemplateObject(JSContext *cx, HandleScript script, gc::InitialHeap heap);
   1.261 +
   1.262 +    static const uint32_t RESERVED_SLOTS = 2;
   1.263 +
   1.264 +    static CallObject *createForFunction(JSContext *cx, HandleObject enclosing, HandleFunction callee);
   1.265 +
   1.266 +    static CallObject *createForFunction(JSContext *cx, AbstractFramePtr frame);
   1.267 +    static CallObject *createForStrictEval(JSContext *cx, AbstractFramePtr frame);
   1.268 +
   1.269 +    /* True if this is for a strict mode eval frame. */
   1.270 +    bool isForEval() const {
   1.271 +        JS_ASSERT(getFixedSlot(CALLEE_SLOT).isObjectOrNull());
   1.272 +        JS_ASSERT_IF(getFixedSlot(CALLEE_SLOT).isObject(),
   1.273 +                     getFixedSlot(CALLEE_SLOT).toObject().is<JSFunction>());
   1.274 +        return getFixedSlot(CALLEE_SLOT).isNull();
   1.275 +    }
   1.276 +
   1.277 +    /*
   1.278 +     * Returns the function for which this CallObject was created. (This may
   1.279 +     * only be called if !isForEval.)
   1.280 +     */
   1.281 +    JSFunction &callee() const {
   1.282 +        return getFixedSlot(CALLEE_SLOT).toObject().as<JSFunction>();
   1.283 +    }
   1.284 +
   1.285 +    /* Get/set the aliased variable referred to by 'bi'. */
   1.286 +    const Value &aliasedVar(AliasedFormalIter fi) {
   1.287 +        return getSlot(fi.scopeSlot());
   1.288 +    }
   1.289 +    inline void setAliasedVar(JSContext *cx, AliasedFormalIter fi, PropertyName *name,
   1.290 +                              const Value &v);
   1.291 +
   1.292 +    /*
   1.293 +     * When an aliased var (var accessed by nested closures) is also aliased by
   1.294 +     * the arguments object, it must of course exist in one canonical location
   1.295 +     * and that location is always the CallObject. For this to work, the
   1.296 +     * ArgumentsObject stores special MagicValue in its array for forwarded-to-
   1.297 +     * CallObject variables. This MagicValue's payload is the slot of the
   1.298 +     * CallObject to access.
   1.299 +     */
   1.300 +    const Value &aliasedVarFromArguments(const Value &argsValue) {
   1.301 +        return getSlot(argsValue.magicUint32());
   1.302 +    }
   1.303 +    inline void setAliasedVarFromArguments(JSContext *cx, const Value &argsValue, jsid id,
   1.304 +                                           const Value &v);
   1.305 +
   1.306 +    /* For jit access. */
   1.307 +    static size_t offsetOfCallee() {
   1.308 +        return getFixedSlotOffset(CALLEE_SLOT);
   1.309 +    }
   1.310 +
   1.311 +    static size_t calleeSlot() {
   1.312 +        return CALLEE_SLOT;
   1.313 +    }
   1.314 +};
   1.315 +
   1.316 +class DeclEnvObject : public ScopeObject
   1.317 +{
   1.318 +    // Pre-allocated slot for the named lambda.
   1.319 +    static const uint32_t LAMBDA_SLOT = 1;
   1.320 +
   1.321 +  public:
   1.322 +    static const uint32_t RESERVED_SLOTS = 2;
   1.323 +    static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND;
   1.324 +
   1.325 +    static const Class class_;
   1.326 +
   1.327 +    static DeclEnvObject *
   1.328 +    createTemplateObject(JSContext *cx, HandleFunction fun, gc::InitialHeap heap);
   1.329 +
   1.330 +    static DeclEnvObject *create(JSContext *cx, HandleObject enclosing, HandleFunction callee);
   1.331 +
   1.332 +    static inline size_t lambdaSlot() {
   1.333 +        return LAMBDA_SLOT;
   1.334 +    }
   1.335 +};
   1.336 +
   1.337 +class NestedScopeObject : public ScopeObject
   1.338 +{
   1.339 +  public:
   1.340 +    /*
   1.341 +     * A refinement of enclosingScope that returns nullptr if the enclosing
   1.342 +     * scope is not a NestedScopeObject.
   1.343 +     */
   1.344 +    inline NestedScopeObject *enclosingNestedScope() const;
   1.345 +
   1.346 +    // Return true if this object is a compile-time scope template.
   1.347 +    inline bool isStatic() { return !getProto(); }
   1.348 +
   1.349 +    // Return the static scope corresponding to this scope chain object.
   1.350 +    inline NestedScopeObject* staticScope() {
   1.351 +        JS_ASSERT(!isStatic());
   1.352 +        return &getProto()->as<NestedScopeObject>();
   1.353 +    }
   1.354 +
   1.355 +    // At compile-time it's possible for the scope chain to be null.
   1.356 +    JSObject *enclosingScopeForStaticScopeIter() {
   1.357 +        return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
   1.358 +    }
   1.359 +
   1.360 +    void initEnclosingNestedScope(JSObject *obj) {
   1.361 +        JS_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined());
   1.362 +        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj));
   1.363 +    }
   1.364 +
   1.365 +    /*
   1.366 +     * The parser uses 'enclosingNestedScope' as the prev-link in the
   1.367 +     * pc->staticScope stack. Note: in the case of hoisting, this prev-link will
   1.368 +     * not ultimately be the same as enclosingNestedScope;
   1.369 +     * initEnclosingNestedScope must be called separately in the
   1.370 +     * emitter. 'reset' is just for asserting stackiness.
   1.371 +     */
   1.372 +    void initEnclosingNestedScopeFromParser(NestedScopeObject *prev) {
   1.373 +        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev));
   1.374 +    }
   1.375 +
   1.376 +    void resetEnclosingNestedScopeFromParser() {
   1.377 +        setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue());
   1.378 +    }
   1.379 +};
   1.380 +
   1.381 +// With scope template objects on the static scope chain.
   1.382 +class StaticWithObject : public NestedScopeObject
   1.383 +{
   1.384 +  public:
   1.385 +    static const unsigned RESERVED_SLOTS = 1;
   1.386 +    static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT2_BACKGROUND;
   1.387 +
   1.388 +    static const Class class_;
   1.389 +
   1.390 +    static StaticWithObject *create(ExclusiveContext *cx);
   1.391 +};
   1.392 +
   1.393 +// With scope objects on the run-time scope chain.
   1.394 +class DynamicWithObject : public NestedScopeObject
   1.395 +{
   1.396 +    static const unsigned OBJECT_SLOT = 1;
   1.397 +    static const unsigned THIS_SLOT = 2;
   1.398 +
   1.399 +  public:
   1.400 +    static const unsigned RESERVED_SLOTS = 3;
   1.401 +    static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND;
   1.402 +
   1.403 +    static const Class class_;
   1.404 +
   1.405 +    static DynamicWithObject *
   1.406 +    create(JSContext *cx, HandleObject object, HandleObject enclosing, HandleObject staticWith);
   1.407 +
   1.408 +    StaticWithObject& staticWith() const {
   1.409 +        return getProto()->as<StaticWithObject>();
   1.410 +    }
   1.411 +
   1.412 +    /* Return the 'o' in 'with (o)'. */
   1.413 +    JSObject &object() const {
   1.414 +        return getReservedSlot(OBJECT_SLOT).toObject();
   1.415 +    }
   1.416 +
   1.417 +    /* Return object for the 'this' class hook. */
   1.418 +    JSObject &withThis() const {
   1.419 +        return getReservedSlot(THIS_SLOT).toObject();
   1.420 +    }
   1.421 +};
   1.422 +
   1.423 +class BlockObject : public NestedScopeObject
   1.424 +{
   1.425 +  protected:
   1.426 +    static const unsigned DEPTH_SLOT = 1;
   1.427 +
   1.428 +  public:
   1.429 +    static const unsigned RESERVED_SLOTS = 2;
   1.430 +    static const gc::AllocKind FINALIZE_KIND = gc::FINALIZE_OBJECT4_BACKGROUND;
   1.431 +
   1.432 +    static const Class class_;
   1.433 +
   1.434 +    /* Return the abstract stack depth right before entering this nested scope. */
   1.435 +    uint32_t stackDepth() const {
   1.436 +        return getReservedSlot(DEPTH_SLOT).toPrivateUint32();
   1.437 +    }
   1.438 +
   1.439 +    /* Return the number of variables associated with this block. */
   1.440 +    uint32_t numVariables() const {
   1.441 +        // TODO: propertyCount() is O(n), use O(1) lastProperty()->slot() instead
   1.442 +        return propertyCount();
   1.443 +    }
   1.444 +
   1.445 +  protected:
   1.446 +    /* Blocks contain an object slot for each slot i: 0 <= i < slotCount. */
   1.447 +    const Value &slotValue(unsigned i) {
   1.448 +        return getSlotRef(RESERVED_SLOTS + i);
   1.449 +    }
   1.450 +
   1.451 +    void setSlotValue(unsigned i, const Value &v) {
   1.452 +        setSlot(RESERVED_SLOTS + i, v);
   1.453 +    }
   1.454 +};
   1.455 +
   1.456 +class StaticBlockObject : public BlockObject
   1.457 +{
   1.458 +    static const unsigned LOCAL_OFFSET_SLOT = 1;
   1.459 +
   1.460 +  public:
   1.461 +    static StaticBlockObject *create(ExclusiveContext *cx);
   1.462 +
   1.463 +    /* See StaticScopeIter comment. */
   1.464 +    JSObject *enclosingStaticScope() const {
   1.465 +        return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
   1.466 +    }
   1.467 +
   1.468 +    /*
   1.469 +     * Return the index (in the range [0, numVariables()) corresponding to the
   1.470 +     * given shape of a block object.
   1.471 +     */
   1.472 +    uint32_t shapeToIndex(const Shape &shape) {
   1.473 +        uint32_t slot = shape.slot();
   1.474 +        JS_ASSERT(slot - RESERVED_SLOTS < numVariables());
   1.475 +        return slot - RESERVED_SLOTS;
   1.476 +    }
   1.477 +
   1.478 +    /*
   1.479 +     * A refinement of enclosingStaticScope that returns nullptr if the enclosing
   1.480 +     * static scope is a JSFunction.
   1.481 +     */
   1.482 +    inline StaticBlockObject *enclosingBlock() const;
   1.483 +
   1.484 +    uint32_t localOffset() {
   1.485 +        return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32();
   1.486 +    }
   1.487 +
   1.488 +    // Return the local corresponding to the 'var'th binding where 'var' is in the
   1.489 +    // range [0, numVariables()).
   1.490 +    uint32_t blockIndexToLocalIndex(uint32_t index) {
   1.491 +        JS_ASSERT(index < numVariables());
   1.492 +        return getReservedSlot(LOCAL_OFFSET_SLOT).toPrivateUint32() + index;
   1.493 +    }
   1.494 +
   1.495 +    // Return the slot corresponding to local variable 'local', where 'local' is
   1.496 +    // in the range [localOffset(), localOffset() + numVariables()).  The result is
   1.497 +    // in the range [RESERVED_SLOTS, RESERVED_SLOTS + numVariables()).
   1.498 +    uint32_t localIndexToSlot(uint32_t local) {
   1.499 +        JS_ASSERT(local >= localOffset());
   1.500 +        local -= localOffset();
   1.501 +        JS_ASSERT(local < numVariables());
   1.502 +        return RESERVED_SLOTS + local;
   1.503 +    }
   1.504 +
   1.505 +    /*
   1.506 +     * A let binding is aliased if accessed lexically by nested functions or
   1.507 +     * dynamically through dynamic name lookup (eval, with, function::, etc).
   1.508 +     */
   1.509 +    bool isAliased(unsigned i) {
   1.510 +        return slotValue(i).isTrue();
   1.511 +    }
   1.512 +
   1.513 +    /*
   1.514 +     * A static block object is cloned (when entering the block) iff some
   1.515 +     * variable of the block isAliased.
   1.516 +     */
   1.517 +    bool needsClone() {
   1.518 +        return !getFixedSlot(RESERVED_SLOTS).isFalse();
   1.519 +    }
   1.520 +
   1.521 +    /* Frontend-only functions ***********************************************/
   1.522 +
   1.523 +    /* Initialization functions for above fields. */
   1.524 +    void setAliased(unsigned i, bool aliased) {
   1.525 +        JS_ASSERT_IF(i > 0, slotValue(i-1).isBoolean());
   1.526 +        setSlotValue(i, BooleanValue(aliased));
   1.527 +        if (aliased && !needsClone()) {
   1.528 +            setSlotValue(0, MagicValue(JS_BLOCK_NEEDS_CLONE));
   1.529 +            JS_ASSERT(needsClone());
   1.530 +        }
   1.531 +    }
   1.532 +
   1.533 +    void setLocalOffset(uint32_t offset) {
   1.534 +        JS_ASSERT(getReservedSlot(LOCAL_OFFSET_SLOT).isUndefined());
   1.535 +        initReservedSlot(LOCAL_OFFSET_SLOT, PrivateUint32Value(offset));
   1.536 +    }
   1.537 +
   1.538 +    /*
   1.539 +     * Frontend compilation temporarily uses the object's slots to link
   1.540 +     * a let var to its associated Definition parse node.
   1.541 +     */
   1.542 +    void setDefinitionParseNode(unsigned i, frontend::Definition *def) {
   1.543 +        JS_ASSERT(slotValue(i).isUndefined());
   1.544 +        setSlotValue(i, PrivateValue(def));
   1.545 +    }
   1.546 +
   1.547 +    frontend::Definition *definitionParseNode(unsigned i) {
   1.548 +        Value v = slotValue(i);
   1.549 +        return reinterpret_cast<frontend::Definition *>(v.toPrivate());
   1.550 +    }
   1.551 +
   1.552 +    /*
   1.553 +     * While ScopeCoordinate can generally reference up to 2^24 slots, block objects have an
   1.554 +     * additional limitation that all slot indices must be storable as uint16_t short-ids in the
   1.555 +     * associated Shape. If we could remove the block dependencies on shape->shortid, we could
   1.556 +     * remove INDEX_LIMIT.
   1.557 +     */
   1.558 +    static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16);
   1.559 +
   1.560 +    static Shape *addVar(ExclusiveContext *cx, Handle<StaticBlockObject*> block, HandleId id,
   1.561 +                         unsigned index, bool *redeclared);
   1.562 +};
   1.563 +
   1.564 +class ClonedBlockObject : public BlockObject
   1.565 +{
   1.566 +  public:
   1.567 +    static ClonedBlockObject *create(JSContext *cx, Handle<StaticBlockObject *> block,
   1.568 +                                     AbstractFramePtr frame);
   1.569 +
   1.570 +    /* The static block from which this block was cloned. */
   1.571 +    StaticBlockObject &staticBlock() const {
   1.572 +        return getProto()->as<StaticBlockObject>();
   1.573 +    }
   1.574 +
   1.575 +    /* Assuming 'put' has been called, return the value of the ith let var. */
   1.576 +    const Value &var(unsigned i, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) {
   1.577 +        JS_ASSERT_IF(checkAliasing, staticBlock().isAliased(i));
   1.578 +        return slotValue(i);
   1.579 +    }
   1.580 +
   1.581 +    void setVar(unsigned i, const Value &v, MaybeCheckAliasing checkAliasing = CHECK_ALIASING) {
   1.582 +        JS_ASSERT_IF(checkAliasing, staticBlock().isAliased(i));
   1.583 +        setSlotValue(i, v);
   1.584 +    }
   1.585 +
   1.586 +    /* Copy in all the unaliased formals and locals. */
   1.587 +    void copyUnaliasedValues(AbstractFramePtr frame);
   1.588 +};
   1.589 +
   1.590 +template<XDRMode mode>
   1.591 +bool
   1.592 +XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope,
   1.593 +                     StaticBlockObject **objp);
   1.594 +
   1.595 +template<XDRMode mode>
   1.596 +bool
   1.597 +XDRStaticWithObject(XDRState<mode> *xdr, HandleObject enclosingScope,
   1.598 +                    StaticWithObject **objp);
   1.599 +
   1.600 +extern JSObject *
   1.601 +CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle<NestedScopeObject*> src);
   1.602 +
   1.603 +/*****************************************************************************/
   1.604 +
   1.605 +class ScopeIterKey;
   1.606 +class ScopeIterVal;
   1.607 +
   1.608 +/*
   1.609 + * A scope iterator describes the active scopes enclosing the current point of
   1.610 + * execution for a single frame, proceeding from inner to outer. Here, "frame"
   1.611 + * means a single activation of: a function, eval, or global code. By design,
   1.612 + * ScopeIter exposes *all* scopes, even those that have been optimized away
   1.613 + * (i.e., no ScopeObject was created when entering the scope and thus there is
   1.614 + * no ScopeObject on fp->scopeChain representing the scope).
   1.615 + *
   1.616 + * Note: ScopeIter iterates over all scopes *within* a frame which means that
   1.617 + * all scopes are ScopeObjects. In particular, the GlobalObject enclosing
   1.618 + * global code (and any random objects passed as scopes to Execute) will not
   1.619 + * be included.
   1.620 + */
   1.621 +class ScopeIter
   1.622 +{
   1.623 +    friend class ScopeIterKey;
   1.624 +    friend class ScopeIterVal;
   1.625 +
   1.626 +  public:
   1.627 +    enum Type { Call, Block, With, StrictEvalScope };
   1.628 +
   1.629 +  private:
   1.630 +    JSContext *cx;
   1.631 +    AbstractFramePtr frame_;
   1.632 +    RootedObject cur_;
   1.633 +    Rooted<NestedScopeObject *> staticScope_;
   1.634 +    Type type_;
   1.635 +    bool hasScopeObject_;
   1.636 +
   1.637 +    void settle();
   1.638 +
   1.639 +    /* ScopeIter does not have value semantics. */
   1.640 +    ScopeIter(const ScopeIter &si) MOZ_DELETE;
   1.641 +
   1.642 +    ScopeIter(JSContext *cx) MOZ_DELETE;
   1.643 +
   1.644 +  public:
   1.645 +
   1.646 +    /* Constructing from a copy of an existing ScopeIter. */
   1.647 +    ScopeIter(const ScopeIter &si, JSContext *cx
   1.648 +              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
   1.649 +
   1.650 +    /* Constructing from AbstractFramePtr places ScopeIter on the innermost scope. */
   1.651 +    ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
   1.652 +              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
   1.653 +
   1.654 +    /*
   1.655 +     * Without a stack frame, the resulting ScopeIter is done() with
   1.656 +     * enclosingScope() as given.
   1.657 +     */
   1.658 +    ScopeIter(JSObject &enclosingScope, JSContext *cx
   1.659 +              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
   1.660 +
   1.661 +    ScopeIter(const ScopeIterVal &hashVal, JSContext *cx
   1.662 +              MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
   1.663 +
   1.664 +    bool done() const { return !frame_; }
   1.665 +
   1.666 +    /* If done(): */
   1.667 +
   1.668 +    JSObject &enclosingScope() const { JS_ASSERT(done()); return *cur_; }
   1.669 +
   1.670 +    /* If !done(): */
   1.671 +
   1.672 +    ScopeIter &operator++();
   1.673 +
   1.674 +    AbstractFramePtr frame() const { JS_ASSERT(!done()); return frame_; }
   1.675 +    Type type() const { JS_ASSERT(!done()); return type_; }
   1.676 +    bool hasScopeObject() const { JS_ASSERT(!done()); return hasScopeObject_; }
   1.677 +    ScopeObject &scope() const;
   1.678 +    NestedScopeObject* staticScope() const { return staticScope_; }
   1.679 +
   1.680 +    StaticBlockObject &staticBlock() const {
   1.681 +        JS_ASSERT(type() == Block);
   1.682 +        return staticScope_->as<StaticBlockObject>();
   1.683 +    }
   1.684 +
   1.685 +    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   1.686 +};
   1.687 +
   1.688 +class ScopeIterKey
   1.689 +{
   1.690 +    friend class ScopeIterVal;
   1.691 +
   1.692 +    AbstractFramePtr frame_;
   1.693 +    JSObject *cur_;
   1.694 +    NestedScopeObject *staticScope_;
   1.695 +    ScopeIter::Type type_;
   1.696 +    bool hasScopeObject_;
   1.697 +
   1.698 +  public:
   1.699 +    ScopeIterKey(const ScopeIter &si)
   1.700 +      : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
   1.701 +        hasScopeObject_(si.hasScopeObject_) {}
   1.702 +
   1.703 +    AbstractFramePtr frame() const { return frame_; }
   1.704 +    JSObject *cur() const { return cur_; }
   1.705 +    NestedScopeObject *staticScope() const { return staticScope_; }
   1.706 +    ScopeIter::Type type() const { return type_; }
   1.707 +    bool hasScopeObject() const { return hasScopeObject_; }
   1.708 +    JSObject *enclosingScope() const { return cur_; }
   1.709 +    JSObject *&enclosingScope() { return cur_; }
   1.710 +
   1.711 +    /* For use as hash policy */
   1.712 +    typedef ScopeIterKey Lookup;
   1.713 +    static HashNumber hash(ScopeIterKey si);
   1.714 +    static bool match(ScopeIterKey si1, ScopeIterKey si2);
   1.715 +    bool operator!=(const ScopeIterKey &other) const {
   1.716 +        return frame_ != other.frame_ ||
   1.717 +               cur_ != other.cur_ ||
   1.718 +               staticScope_ != other.staticScope_ ||
   1.719 +               type_ != other.type_;
   1.720 +    }
   1.721 +    static void rekey(ScopeIterKey &k, const ScopeIterKey& newKey) {
   1.722 +        k = newKey;
   1.723 +    }
   1.724 +};
   1.725 +
   1.726 +class ScopeIterVal
   1.727 +{
   1.728 +    friend class ScopeIter;
   1.729 +    friend class DebugScopes;
   1.730 +
   1.731 +    AbstractFramePtr frame_;
   1.732 +    RelocatablePtr<JSObject> cur_;
   1.733 +    RelocatablePtr<NestedScopeObject> staticScope_;
   1.734 +    ScopeIter::Type type_;
   1.735 +    bool hasScopeObject_;
   1.736 +
   1.737 +    static void staticAsserts();
   1.738 +
   1.739 +  public:
   1.740 +    ScopeIterVal(const ScopeIter &si)
   1.741 +      : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
   1.742 +        hasScopeObject_(si.hasScopeObject_) {}
   1.743 +
   1.744 +    AbstractFramePtr frame() const { return frame_; }
   1.745 +};
   1.746 +
   1.747 +/*****************************************************************************/
   1.748 +
   1.749 +/*
   1.750 + * Debug scope objects
   1.751 + *
   1.752 + * The debugger effectively turns every opcode into a potential direct eval.
   1.753 + * Naively, this would require creating a ScopeObject for every call/block
   1.754 + * scope and using JSOP_GETALIASEDVAR for every access. To optimize this, the
   1.755 + * engine assumes there is no debugger and optimizes scope access and creation
   1.756 + * accordingly. When the debugger wants to perform an unexpected eval-in-frame
   1.757 + * (or other, similar dynamic-scope-requiring operations), fp->scopeChain is
   1.758 + * now incomplete: it may not contain all, or any, of the ScopeObjects to
   1.759 + * represent the current scope.
   1.760 + *
   1.761 + * To resolve this, the debugger first calls GetDebugScopeFor(Function|Frame)
   1.762 + * to synthesize a "debug scope chain". A debug scope chain is just a chain of
   1.763 + * objects that fill in missing scopes and protect the engine from unexpected
   1.764 + * access. (The latter means that some debugger operations, like redefining a
   1.765 + * lexical binding, can fail when a true eval would succeed.) To do both of
   1.766 + * these things, GetDebugScopeFor* creates a new proxy DebugScopeObject to sit
   1.767 + * in front of every existing ScopeObject.
   1.768 + *
   1.769 + * GetDebugScopeFor* ensures the invariant that the same DebugScopeObject is
   1.770 + * always produced for the same underlying scope (optimized or not!). This is
   1.771 + * maintained by some bookkeeping information stored in DebugScopes.
   1.772 + */
   1.773 +
   1.774 +extern JSObject *
   1.775 +GetDebugScopeForFunction(JSContext *cx, HandleFunction fun);
   1.776 +
   1.777 +extern JSObject *
   1.778 +GetDebugScopeForFrame(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
   1.779 +
   1.780 +/* Provides debugger access to a scope. */
   1.781 +class DebugScopeObject : public ProxyObject
   1.782 +{
   1.783 +    /*
   1.784 +     * The enclosing scope on the dynamic scope chain. This slot is analogous
   1.785 +     * to the SCOPE_CHAIN_SLOT of a ScopeObject.
   1.786 +     */
   1.787 +    static const unsigned ENCLOSING_EXTRA = 0;
   1.788 +
   1.789 +    /*
   1.790 +     * NullValue or a dense array holding the unaliased variables of a function
   1.791 +     * frame that has been popped.
   1.792 +     */
   1.793 +    static const unsigned SNAPSHOT_EXTRA = 1;
   1.794 +
   1.795 +  public:
   1.796 +    static DebugScopeObject *create(JSContext *cx, ScopeObject &scope, HandleObject enclosing);
   1.797 +
   1.798 +    ScopeObject &scope() const;
   1.799 +    JSObject &enclosingScope() const;
   1.800 +
   1.801 +    /* May only be called for proxies to function call objects. */
   1.802 +    JSObject *maybeSnapshot() const;
   1.803 +    void initSnapshot(JSObject &snapshot);
   1.804 +
   1.805 +    /* Currently, the 'declarative' scopes are Call and Block. */
   1.806 +    bool isForDeclarative() const;
   1.807 +
   1.808 +    // Get a property by 'id', but returns sentinel values instead of throwing
   1.809 +    // on exceptional cases.
   1.810 +    bool getMaybeSentinelValue(JSContext *cx, HandleId id, MutableHandleValue vp);
   1.811 +};
   1.812 +
   1.813 +/* Maintains per-compartment debug scope bookkeeping information. */
   1.814 +class DebugScopes
   1.815 +{
   1.816 +    /* The map from (non-debug) scopes to debug scopes. */
   1.817 +    typedef WeakMap<EncapsulatedPtrObject, RelocatablePtrObject> ObjectWeakMap;
   1.818 +    ObjectWeakMap proxiedScopes;
   1.819 +    static MOZ_ALWAYS_INLINE void proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map,
   1.820 +                                                               const EncapsulatedPtrObject &key);
   1.821 +
   1.822 +    /*
   1.823 +     * The map from live frames which have optimized-away scopes to the
   1.824 +     * corresponding debug scopes.
   1.825 +     */
   1.826 +    typedef HashMap<ScopeIterKey,
   1.827 +                    ReadBarriered<DebugScopeObject>,
   1.828 +                    ScopeIterKey,
   1.829 +                    RuntimeAllocPolicy> MissingScopeMap;
   1.830 +    MissingScopeMap missingScopes;
   1.831 +    class MissingScopesRef;
   1.832 +    static MOZ_ALWAYS_INLINE void missingScopesPostWriteBarrier(JSRuntime *rt, MissingScopeMap *map,
   1.833 +                                                               const ScopeIterKey &key);
   1.834 +
   1.835 +    /*
   1.836 +     * The map from scope objects of live frames to the live frame. This map
   1.837 +     * updated lazily whenever the debugger needs the information. In between
   1.838 +     * two lazy updates, liveScopes becomes incomplete (but not invalid, onPop*
   1.839 +     * removes scopes as they are popped). Thus, two consecutive debugger lazy
   1.840 +     * updates of liveScopes need only fill in the new scopes.
   1.841 +     */
   1.842 +    typedef HashMap<ScopeObject *,
   1.843 +                    ScopeIterVal,
   1.844 +                    DefaultHasher<ScopeObject *>,
   1.845 +                    RuntimeAllocPolicy> LiveScopeMap;
   1.846 +    LiveScopeMap liveScopes;
   1.847 +    static MOZ_ALWAYS_INLINE void liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map,
   1.848 +                                                            ScopeObject *key);
   1.849 +
   1.850 +  public:
   1.851 +    DebugScopes(JSContext *c);
   1.852 +    ~DebugScopes();
   1.853 +
   1.854 +  private:
   1.855 +    bool init();
   1.856 +
   1.857 +    static DebugScopes *ensureCompartmentData(JSContext *cx);
   1.858 +
   1.859 +  public:
   1.860 +    void mark(JSTracer *trc);
   1.861 +    void sweep(JSRuntime *rt);
   1.862 +#if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL)
   1.863 +    void checkHashTablesAfterMovingGC(JSRuntime *rt);
   1.864 +#endif
   1.865 +
   1.866 +    static DebugScopeObject *hasDebugScope(JSContext *cx, ScopeObject &scope);
   1.867 +    static bool addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope);
   1.868 +
   1.869 +    static DebugScopeObject *hasDebugScope(JSContext *cx, const ScopeIter &si);
   1.870 +    static bool addDebugScope(JSContext *cx, const ScopeIter &si, DebugScopeObject &debugScope);
   1.871 +
   1.872 +    static bool updateLiveScopes(JSContext *cx);
   1.873 +    static ScopeIterVal *hasLiveScope(ScopeObject &scope);
   1.874 +
   1.875 +    // In debug-mode, these must be called whenever exiting a scope that might
   1.876 +    // have stack-allocated locals.
   1.877 +    static void onPopCall(AbstractFramePtr frame, JSContext *cx);
   1.878 +    static void onPopBlock(JSContext *cx, const ScopeIter &si);
   1.879 +    static void onPopBlock(JSContext *cx, AbstractFramePtr frame, jsbytecode *pc);
   1.880 +    static void onPopWith(AbstractFramePtr frame);
   1.881 +    static void onPopStrictEvalScope(AbstractFramePtr frame);
   1.882 +    static void onCompartmentLeaveDebugMode(JSCompartment *c);
   1.883 +};
   1.884 +
   1.885 +}  /* namespace js */
   1.886 +
   1.887 +template<>
   1.888 +inline bool
   1.889 +JSObject::is<js::NestedScopeObject>() const
   1.890 +{
   1.891 +    return is<js::BlockObject>() || is<js::StaticWithObject>() || is<js::DynamicWithObject>();
   1.892 +}
   1.893 +
   1.894 +template<>
   1.895 +inline bool
   1.896 +JSObject::is<js::ScopeObject>() const
   1.897 +{
   1.898 +    return is<js::CallObject>() || is<js::DeclEnvObject>() || is<js::NestedScopeObject>();
   1.899 +}
   1.900 +
   1.901 +template<>
   1.902 +inline bool
   1.903 +JSObject::is<js::DebugScopeObject>() const
   1.904 +{
   1.905 +    extern bool js_IsDebugScopeSlow(js::ProxyObject *proxy);
   1.906 +
   1.907 +    // Note: don't use is<ProxyObject>() here -- it also matches subclasses!
   1.908 +    return hasClass(&js::ProxyObject::uncallableClass_) &&
   1.909 +           js_IsDebugScopeSlow(&const_cast<JSObject*>(this)->as<js::ProxyObject>());
   1.910 +}
   1.911 +
   1.912 +template<>
   1.913 +inline bool
   1.914 +JSObject::is<js::ClonedBlockObject>() const
   1.915 +{
   1.916 +    return is<js::BlockObject>() && !!getProto();
   1.917 +}
   1.918 +
   1.919 +template<>
   1.920 +inline bool
   1.921 +JSObject::is<js::StaticBlockObject>() const
   1.922 +{
   1.923 +    return is<js::BlockObject>() && !getProto();
   1.924 +}
   1.925 +
   1.926 +inline JSObject *
   1.927 +JSObject::enclosingScope()
   1.928 +{
   1.929 +    return is<js::ScopeObject>()
   1.930 +           ? &as<js::ScopeObject>().enclosingScope()
   1.931 +           : is<js::DebugScopeObject>()
   1.932 +           ? &as<js::DebugScopeObject>().enclosingScope()
   1.933 +           : getParent();
   1.934 +}
   1.935 +
   1.936 +namespace js {
   1.937 +
   1.938 +inline const Value &
   1.939 +ScopeObject::aliasedVar(ScopeCoordinate sc)
   1.940 +{
   1.941 +    JS_ASSERT(is<CallObject>() || is<ClonedBlockObject>());
   1.942 +    return getSlot(sc.slot());
   1.943 +}
   1.944 +
   1.945 +inline NestedScopeObject *
   1.946 +NestedScopeObject::enclosingNestedScope() const
   1.947 +{
   1.948 +    JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
   1.949 +    return obj && obj->is<NestedScopeObject>() ? &obj->as<NestedScopeObject>() : nullptr;
   1.950 +}
   1.951 +
   1.952 +#ifdef DEBUG
   1.953 +bool
   1.954 +AnalyzeEntrainedVariables(JSContext *cx, HandleScript script);
   1.955 +#endif
   1.956 +
   1.957 +} // namespace js
   1.958 +
   1.959 +#endif /* vm_ScopeObject_h */

mercurial