Raw File
Scope.h
/* -*- 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/. */

#ifndef vm_Scope_h
#define vm_Scope_h

#include "mozilla/Maybe.h"
#include "mozilla/Variant.h"

#include "jsobj.h"
#include "jsopcode.h"

#include "gc/Heap.h"
#include "gc/Policy.h"
#include "js/UbiNode.h"
#include "js/UniquePtr.h"
#include "vm/Xdr.h"

namespace js {

class ModuleObject;

enum class BindingKind : uint8_t
{
    Import,
    FormalParameter,
    Var,
    Let,
    Const,

    // So you think named lambda callee names are consts? Nope! They don't
    // throw when being assigned to in sloppy mode.
    NamedLambdaCallee
};

static inline bool
BindingKindIsLexical(BindingKind kind)
{
    return kind == BindingKind::Let || kind == BindingKind::Const;
}

enum class ScopeKind : uint8_t
{
    // FunctionScope
    Function,

    // VarScope
    FunctionBodyVar,
    ParameterExpressionVar,

    // LexicalScope
    Lexical,
    SimpleCatch,
    Catch,
    NamedLambda,
    StrictNamedLambda,

    // WithScope
    With,

    // EvalScope
    Eval,
    StrictEval,

    // GlobalScope
    Global,
    NonSyntactic,

    // ModuleScope
    Module
};

static inline bool
ScopeKindIsCatch(ScopeKind kind)
{
    return kind == ScopeKind::SimpleCatch || kind == ScopeKind::Catch;
}

const char* BindingKindString(BindingKind kind);
const char* ScopeKindString(ScopeKind kind);

class BindingName
{
    // A JSAtom* with its low bit used as a tag for whether it is closed over
    // (i.e., exists in the environment shape).
    uintptr_t bits_;

    static const uintptr_t ClosedOverFlag = 0x1;
    static const uintptr_t FlagMask = 0x1;

  public:
    BindingName()
      : bits_(0)
    { }

    BindingName(JSAtom* name, bool closedOver)
      : bits_(uintptr_t(name) | (closedOver ? ClosedOverFlag : 0x0))
    { }

    JSAtom* name() const {
        return reinterpret_cast<JSAtom*>(bits_ & ~FlagMask);
    }

    bool closedOver() const {
        return bits_ & ClosedOverFlag;
    }

    void trace(JSTracer* trc);
};

class BindingLocation
{
  public:
    enum class Kind {
        Global,
        Argument,
        Frame,
        Environment,
        Import,
        NamedLambdaCallee
    };

  private:
    Kind kind_;
    uint32_t slot_;

    BindingLocation(Kind kind, uint32_t slot)
      : kind_(kind),
        slot_(slot)
    { }

  public:
    static BindingLocation Global() {
        return BindingLocation(Kind::Global, UINT32_MAX);
    }

    static BindingLocation Argument(uint16_t slot) {
        return BindingLocation(Kind::Argument, slot);
    }

    static BindingLocation Frame(uint32_t slot) {
        MOZ_ASSERT(slot < LOCALNO_LIMIT);
        return BindingLocation(Kind::Frame, slot);
    }

    static BindingLocation Environment(uint32_t slot) {
        MOZ_ASSERT(slot < ENVCOORD_SLOT_LIMIT);
        return BindingLocation(Kind::Environment, slot);
    }

    static BindingLocation Import() {
        return BindingLocation(Kind::Import, UINT32_MAX);
    }

    static BindingLocation NamedLambdaCallee() {
        return BindingLocation(Kind::NamedLambdaCallee, UINT32_MAX);
    }

    bool operator==(const BindingLocation& other) const {
        return kind_ == other.kind_ && slot_ == other.slot_;
    }

    bool operator!=(const BindingLocation& other) const {
        return !operator==(other);
    }

    Kind kind() const {
        return kind_;
    }

    uint32_t slot() const {
        MOZ_ASSERT(kind_ == Kind::Frame || kind_ == Kind::Environment);
        return slot_;
    }

    uint16_t argumentSlot() const {
        MOZ_ASSERT(kind_ == Kind::Argument);
        return mozilla::AssertedCast<uint16_t>(slot_);
    }
};

//
// The base class of all Scopes.
//
class Scope : public js::gc::TenuredCell
{
    friend class GCMarker;

    // The kind determines data_.
    ScopeKind kind_;

    // The enclosing scope or nullptr.
    GCPtrScope enclosing_;

    // If there are any aliased bindings, the shape for the
    // EnvironmentObject. Otherwise nullptr.
    GCPtrShape environmentShape_;

  protected:
    uintptr_t data_;

    Scope(ScopeKind kind, Scope* enclosing, Shape* environmentShape)
      : kind_(kind),
        enclosing_(enclosing),
        environmentShape_(environmentShape),
        data_(0)
    { }

    static Scope* create(ExclusiveContext* cx, ScopeKind kind, HandleScope enclosing,
                         HandleShape envShape);

    template <typename T, typename D>
    static Scope* create(ExclusiveContext* cx, ScopeKind kind, HandleScope enclosing,
                         HandleShape envShape, mozilla::UniquePtr<T, D> data);

    template <typename ConcreteScope, XDRMode mode>
    static bool XDRSizedBindingNames(XDRState<mode>* xdr, Handle<ConcreteScope*> scope,
                                     MutableHandle<typename ConcreteScope::Data*> data);

    Shape* maybeCloneEnvironmentShape(JSContext* cx);

    template <typename T, typename D>
    void initData(mozilla::UniquePtr<T, D> data) {
        MOZ_ASSERT(!data_);
        data_ = reinterpret_cast<uintptr_t>(data.release());
    }

  public:
    static const JS::TraceKind TraceKind = JS::TraceKind::Scope;

    template <typename T>
    bool is() const {
        return kind_ == T::classScopeKind_;
    }

    template <typename T>
    T& as() {
        MOZ_ASSERT(this->is<T>());
        return *static_cast<T*>(this);
    }

    template <typename T>
    const T& as() const {
        MOZ_ASSERT(this->is<T>());
        return *static_cast<const T*>(this);
    }

    ScopeKind kind() const {
        return kind_;
    }

    Scope* enclosing() const {
        return enclosing_;
    }

    Shape* environmentShape() const {
        return environmentShape_;
    }

    bool hasEnvironment() const {
        switch (kind()) {
          case ScopeKind::With:
          case ScopeKind::Global:
          case ScopeKind::NonSyntactic:
            return true;
          default:
            // If there's a shape, an environment must be created for this scope.
            return environmentShape_ != nullptr;
        }
    }

    uint32_t chainLength() const;
    uint32_t environmentChainLength() const;

    template <typename T>
    bool hasOnChain() const {
        for (const Scope* it = this; it; it = it->enclosing()) {
            if (it->is<T>())
                return true;
        }
        return false;
    }

    bool hasOnChain(ScopeKind kind) const {
        for (const Scope* it = this; it; it = it->enclosing()) {
            if (it->kind() == kind)
                return true;
        }
        return false;
    }

    static Scope* clone(JSContext* cx, HandleScope scope, HandleScope enclosing);

    void traceChildren(JSTracer* trc);
    void finalize(FreeOp* fop);

    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;

    void dump();
};

//
// A lexical scope that holds let and const bindings. There are 4 kinds of
// LexicalScopes.
//
// Lexical
//   A plain lexical scope.
//
// SimpleCatch
//   Holds the single catch parameter of a catch block.
//
// Catch
//   Holds the catch parameters (and only the catch parameters) of a catch
//   block.
//
// NamedLambda
// StrictNamedLambda
//   Holds the single name of the callee for a named lambda expression.
//
// All kinds of LexicalScopes correspond to LexicalEnvironmentObjects on the
// environment chain.
//
class LexicalScope : public Scope
{
    friend class Scope;
    friend class BindingIter;

  public:
    // Data is public because it is created by the frontend. See
    // Parser<FullParseHandler>::newLexicalScopeData.
    struct Data
    {
        // Bindings are sorted by kind in both frames and environments.
        //
        //   lets - [0, constStart)
        // consts - [constStart, length)
        uint32_t constStart;
        uint32_t length;

        // Frame slots [0, nextFrameSlot) are live when this is the innermost
        // scope.
        uint32_t nextFrameSlot;

        // The array of tagged JSAtom* names, allocated beyond the end of the
        // struct.
        BindingName names[1];

        void trace(JSTracer* trc);
    };

    static size_t sizeOfData(uint32_t length) {
        return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
    }

    static LexicalScope* create(ExclusiveContext* cx, ScopeKind kind, Handle<Data*> data,
                                uint32_t firstFrameSlot, HandleScope enclosing);

    template <XDRMode mode>
    static bool XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
                    MutableHandleScope scope);

  protected:
    Data& data() {
        return *reinterpret_cast<Data*>(data_);
    }

    const Data& data() const {
        return *reinterpret_cast<Data*>(data_);
    }

    static uint32_t nextFrameSlot(Scope* start);

  public:
    uint32_t firstFrameSlot() const;

    uint32_t nextFrameSlot() const {
        return data().nextFrameSlot;
    }

    // Returns an empty shape for extensible global and non-syntactic lexical
    // scopes.
    static Shape* getEmptyExtensibleEnvironmentShape(ExclusiveContext* cx);
};

template <>
inline bool
Scope::is<LexicalScope>() const
{
    return kind_ == ScopeKind::Lexical ||
           kind_ == ScopeKind::SimpleCatch ||
           kind_ == ScopeKind::Catch ||
           kind_ == ScopeKind::NamedLambda ||
           kind_ == ScopeKind::StrictNamedLambda;
}

//
// Scope corresponding to a function. Holds formal parameter names and, if the
// function parameters contain no expressions that might possibly be
// evaluated, the function's var bindings.  For example, in these functions,
// the FunctionScope will store a/b/c bindings but not d/e/f bindings:
//
//   function f1(a, b) {
//     var c;
//     let e;
//     const f = 3;
//   }
//   function f2([a], b = 4, ...c) {
//     var d, e, f; // stored in VarScope
//   }
//
// Corresponds to CallObject on environment chain.
//
class FunctionScope : public Scope
{
    friend class GCMarker;
    friend class BindingIter;
    friend class PositionalFormalParameterIter;
    friend class Scope;
    static const ScopeKind classScopeKind_ = ScopeKind::Function;

  public:
    // Data is public because it is created by the
    // frontend. See Parser<FullParseHandler>::newFunctionScopeData.
    struct Data
    {
        // The canonical function of the scope, as during a scope walk we
        // often query properties of the JSFunction (e.g., is the function an
        // arrow).
        GCPtrFunction canonicalFunction;

        // If parameter expressions are present, parameters act like lexical
        // bindings.
        bool hasParameterExprs;

        // Bindings are sorted by kind in both frames and environments.
        //
        // Positional formal parameter names are those that are not
        // destructured. They may be referred to by argument slots if
        // !script()->hasParameterExprs().
        //
        // An argument slot that needs to be skipped due to being destructured
        // or having defaults will have a nullptr name in the name array to
        // advance the argument slot.
        //
        // positional formals - [0, nonPositionalFormalStart)
        //      other formals - [nonPositionalParamStart, varStart)
        //               vars - [varStart, length)
        uint16_t nonPositionalFormalStart;
        uint16_t varStart;
        uint32_t length;

        // Frame slots [0, nextFrameSlot) are live when this is the innermost
        // scope.
        uint32_t nextFrameSlot;

        // The array of tagged JSAtom* names, allocated beyond the end of the
        // struct.
        BindingName names[1];

        void trace(JSTracer* trc);
    };

    static size_t sizeOfData(uint32_t length) {
        return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
    }

    static FunctionScope* create(ExclusiveContext* cx, Handle<Data*> data,
                                 bool hasParameterExprs, bool needsEnvironment,
                                 HandleFunction fun, HandleScope enclosing);

    static FunctionScope* clone(JSContext* cx, Handle<FunctionScope*> scope, HandleFunction fun,
                                HandleScope enclosing);

    template <XDRMode mode>
    static bool XDR(XDRState<mode>* xdr, HandleFunction fun, HandleScope enclosing,
                    MutableHandleScope scope);

  private:
    static UniquePtr<Data> copyData(ExclusiveContext* cx, Handle<Data*> data,
                                    bool hasParameterExprs, MutableHandleShape envShape);

    Data& data() {
        return *reinterpret_cast<Data*>(data_);
    }

    const Data& data() const {
        return *reinterpret_cast<Data*>(data_);
    }

  public:
    uint32_t nextFrameSlot() const {
        return data().nextFrameSlot;
    }

    JSFunction* canonicalFunction() const {
        return data().canonicalFunction;
    }

    JSScript* script() const;

    bool hasParameterExprs() const {
        return data().hasParameterExprs;
    }

    uint32_t numPositionalFormalParameters() const {
        return data().nonPositionalFormalStart;
    }

    static Shape* getEmptyEnvironmentShape(ExclusiveContext* cx, bool hasParameterExprs);
};

//
// Scope holding only vars. There are 2 kinds of VarScopes.
//
// FunctionBodyVar
//   Corresponds to the extra var scope present in functions with parameter
//   expressions. See examples in comment above FunctionScope.
//
// ParameterExpressionVar
//   Each parameter expression is evaluated in its own var environment. For
//   example, f() below will print 'fml', then 'global'. That's right.
//
//     var a = 'global';
//     function f(x = (eval(`var a = 'fml'`), a), y = a) {
//       print(x);
//       print(y);
//     };
//
// Corresponds to VarEnvironmentObject on environment chain.
//
class VarScope : public Scope
{
    friend class GCMarker;
    friend class BindingIter;
    friend class Scope;

  public:
    // Data is public because it is created by the
    // frontend. See Parser<FullParseHandler>::newVarScopeData.
    struct Data
    {
        // All bindings are vars.
        uint32_t length;

        // Frame slots [firstFrameSlot(), nextFrameSlot) are live when this is
        // the innermost scope.
        uint32_t nextFrameSlot;

        // The array of tagged JSAtom* names, allocated beyond the end of the
        // struct.
        BindingName names[1];

        void trace(JSTracer* trc);
    };

    static size_t sizeOfData(uint32_t length) {
        return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
    }

    static VarScope* create(ExclusiveContext* cx, ScopeKind kind, Handle<Data*> data,
                            uint32_t firstFrameSlot, bool needsEnvironment,
                            HandleScope enclosing);

    template <XDRMode mode>
    static bool XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
                    MutableHandleScope scope);

  private:
    static UniquePtr<Data> copyData(ExclusiveContext* cx, Handle<Data*> data,
                                    uint32_t firstFrameSlot, MutableHandleShape envShape);

    Data& data() {
        return *reinterpret_cast<Data*>(data_);
    }

    const Data& data() const {
        return *reinterpret_cast<Data*>(data_);
    }

  public:
    uint32_t firstFrameSlot() const;

    uint32_t nextFrameSlot() const {
        return data().nextFrameSlot;
    }

    static Shape* getEmptyEnvironmentShape(ExclusiveContext* cx);
};

template <>
inline bool
Scope::is<VarScope>() const
{
    return kind_ == ScopeKind::FunctionBodyVar || kind_ == ScopeKind::ParameterExpressionVar;
}

//
// Scope corresponding to both the global object scope and the global lexical
// scope.
//
// Both are extensible and are singletons across <script> tags, so these
// scopes are a fragment of the names in global scope. In other words, two
// global scripts may have two different GlobalScopes despite having the same
// GlobalObject.
//
// There are 2 kinds of GlobalScopes.
//
// Global
//   Corresponds to a GlobalObject and its global LexicalEnvironmentObject on
//   the environment chain.
//
// NonSyntactic
//   Corresponds to a non-GlobalObject created by the embedding on the
//   environment chain. This distinction is important for optimizations.
//
class GlobalScope : public Scope
{
    friend class Scope;
    friend class BindingIter;

  public:
    // Data is public because it is created by the frontend. See
    // Parser<FullParseHandler>::newGlobalScopeData.
    struct Data
    {
        // Bindings are sorted by kind.
        //
        // top-level funcs - [0, varStart)
        //            vars - [varStart, letStart)
        //            lets - [letStart, constStart)
        //          consts - [constStart, length)
        uint32_t varStart;
        uint32_t letStart;
        uint32_t constStart;
        uint32_t length;

        // The array of tagged JSAtom* names, allocated beyond the end of the
        // struct.
        BindingName names[1];

        void trace(JSTracer* trc);
    };

    static size_t sizeOfData(uint32_t length) {
        return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
    }

    static GlobalScope* create(ExclusiveContext* cx, ScopeKind kind, Handle<Data*> data);

    static GlobalScope* createEmpty(ExclusiveContext* cx, ScopeKind kind) {
        return create(cx, kind, nullptr);
    }

    static GlobalScope* clone(JSContext* cx, Handle<GlobalScope*> scope, ScopeKind kind);

    template <XDRMode mode>
    static bool XDR(XDRState<mode>* xdr, ScopeKind kind, MutableHandleScope scope);

  private:
    static UniquePtr<Data> copyData(ExclusiveContext* cx, Handle<Data*> data);

    Data& data() {
        return *reinterpret_cast<Data*>(data_);
    }

    const Data& data() const {
        return *reinterpret_cast<Data*>(data_);
    }

  public:
    bool isSyntactic() const {
        return kind() != ScopeKind::NonSyntactic;
    }

    bool hasBindings() const {
        return data().length > 0;
    }
};

template <>
inline bool
Scope::is<GlobalScope>() const
{
    return kind_ == ScopeKind::Global || kind_ == ScopeKind::NonSyntactic;
}

//
// Scope of a 'with' statement. Has no bindings.
//
// Corresponds to a WithEnvironmentObject on the environment chain.
class WithScope : public Scope
{
    friend class Scope;
    static const ScopeKind classScopeKind_ = ScopeKind::With;

  public:
    static WithScope* create(ExclusiveContext* cx, HandleScope enclosing);
};

//
// Scope of an eval. Holds var bindings. There are 2 kinds of EvalScopes.
//
// StrictEval
//   A strict eval. Corresponds to a VarEnvironmentObject, where its var
//   bindings lives.
//
// Eval
//   A sloppy eval. This is an empty scope, used only in the frontend, to
//   detect redeclaration errors. It has no Environment. Any `var`s declared
//   in the eval code are bound on the nearest enclosing var environment.
//
class EvalScope : public Scope
{
    friend class Scope;
    friend class BindingIter;

  public:
    // Data is public because it is created by the frontend. See
    // Parser<FullParseHandler>::newEvalScopeData.
    struct Data
    {
        // All bindings in an eval script are 'var' bindings. The implicit
        // lexical scope around the eval is present regardless of strictness
        // and is its own LexicalScope. However, we need to track top-level
        // functions specially for redeclaration checks.
        //
        // top-level funcs - [0, varStart)
        //            vars - [varStart, length)
        uint32_t varStart;
        uint32_t length;

        // Frame slots [0, nextFrameSlot) are live when this is the innermost
        // scope.
        uint32_t nextFrameSlot;

        // The array of tagged JSAtom* names, allocated beyond the end of the
        // struct.
        BindingName names[1];

        void trace(JSTracer* trc);
    };

    static size_t sizeOfData(uint32_t length) {
        return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
    }

    static EvalScope* create(ExclusiveContext* cx, ScopeKind kind, Handle<Data*> data,
                             HandleScope enclosing);

    template <XDRMode mode>
    static bool XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
                    MutableHandleScope scope);

  private:
    static UniquePtr<Data> copyData(ExclusiveContext* cx, ScopeKind scopeKind,
                                    Handle<Data*> data, MutableHandleShape envShape);

    Data& data() {
        return *reinterpret_cast<Data*>(data_);
    }

    const Data& data() const {
        return *reinterpret_cast<Data*>(data_);
    }

  public:
    // Starting a scope, the nearest var scope that a direct eval can
    // introduce vars on.
    static Scope* nearestVarScopeForDirectEval(Scope* scope);

    uint32_t nextFrameSlot() const {
        return data().nextFrameSlot;
    }

    bool strict() const {
        return kind() == ScopeKind::StrictEval;
    }

    bool hasBindings() const {
        return data().length > 0;
    }

    bool isNonGlobal() const {
        if (strict())
            return true;
        return !nearestVarScopeForDirectEval(enclosing())->is<GlobalScope>();
    }

    static Shape* getEmptyEnvironmentShape(ExclusiveContext* cx);
};

template <>
inline bool
Scope::is<EvalScope>() const
{
    return kind_ == ScopeKind::Eval || kind_ == ScopeKind::StrictEval;
}

//
// Scope corresponding to the toplevel script in an ES module.
//
// Like GlobalScopes, these scopes contain both vars and lexical bindings, as
// the treating of imports and exports requires putting them in one scope.
//
// Corresponds to a ModuleEnvironmentObject on the environment chain.
//
class ModuleScope : public Scope
{
    friend class GCMarker;
    friend class BindingIter;
    friend class Scope;
    static const ScopeKind classScopeKind_ = ScopeKind::Module;

  public:
    // Data is public because it is created by the frontend. See
    // Parser<FullParseHandler>::newModuleScopeData.
    struct Data
    {
        // The module of the scope.
        GCPtr<ModuleObject*> module;

        // Bindings are sorted by kind.
        //
        // imports - [0, varStart)
        //    vars - [varStart, letStart)
        //    lets - [letStart, constStart)
        //  consts - [constStart, length)
        uint32_t varStart;
        uint32_t letStart;
        uint32_t constStart;
        uint32_t length;

        // Frame slots [0, nextFrameSlot) are live when this is the innermost
        // scope.
        uint32_t nextFrameSlot;

        // The array of tagged JSAtom* names, allocated beyond the end of the
        // struct.
        BindingName names[1];

        void trace(JSTracer* trc);
    };

    static size_t sizeOfData(uint32_t length) {
        return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
    }

    static ModuleScope* create(ExclusiveContext* cx, Handle<Data*> data,
                               Handle<ModuleObject*> module, HandleScope enclosing);

  private:
    static UniquePtr<Data> copyData(ExclusiveContext* cx, Handle<Data*> data,
                                    MutableHandleShape envShape);

    Data& data() {
        return *reinterpret_cast<Data*>(data_);
    }

    const Data& data() const {
        return *reinterpret_cast<Data*>(data_);
    }

  public:
    uint32_t nextFrameSlot() const {
        return data().nextFrameSlot;
    }

    ModuleObject* module() const {
        return data().module;
    }

    JSScript* script() const;

    static Shape* getEmptyEnvironmentShape(ExclusiveContext* cx);
};

//
// An iterator for a Scope's bindings. This is the source of truth for frame
// and environment object layout.
//
// It may be placed in GC containers; for example:
//
//   for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
//     use(bi);
//     SomeMayGCOperation();
//     use(bi);
//   }
//
class BindingIter
{
  protected:
    // Bindings are sorted by kind. Because different Scopes have differently
    // laid out Data for packing, BindingIter must handle all binding kinds.
    //
    // Kind ranges:
    //
    //            imports - [0, positionalFormalStart)
    // positional formals - [positionalFormalStart, nonPositionalFormalStart)
    //      other formals - [nonPositionalParamStart, topLevelFunctionStart)
    //    top-level funcs - [topLevelFunctionStart, varStart)
    //               vars - [varStart, letStart)
    //               lets - [letStart, constStart)
    //             consts - [constStart, length)
    //
    // Access method when not closed over:
    //
    //            imports - name
    // positional formals - argument slot
    //      other formals - frame slot
    //    top-level funcs - frame slot
    //               vars - frame slot
    //               lets - frame slot
    //             consts - frame slot
    //
    // Access method when closed over:
    //
    //            imports - name
    // positional formals - environment slot or name
    //      other formals - environment slot or name
    //    top-level funcs - environment slot or name
    //               vars - environment slot or name
    //               lets - environment slot or name
    //             consts - environment slot or name
    uint32_t positionalFormalStart_;
    uint32_t nonPositionalFormalStart_;
    uint32_t topLevelFunctionStart_;
    uint32_t varStart_;
    uint32_t letStart_;
    uint32_t constStart_;
    uint32_t length_;

    uint32_t index_;

    enum Flags : uint8_t {
        CannotHaveSlots = 0,
        CanHaveArgumentSlots = 1 << 0,
        CanHaveFrameSlots = 1 << 1,
        CanHaveEnvironmentSlots = 1 << 2,

        // See comment in settle below.
        HasFormalParameterExprs = 1 << 3,
        IgnoreDestructuredFormalParameters = 1 << 4,

        // Truly I hate named lambdas.
        IsNamedLambda = 1 << 5
    };

    static const uint8_t CanHaveSlotsMask = 0x7;

    uint8_t flags_;
    uint16_t argumentSlot_;
    uint32_t frameSlot_;
    uint32_t environmentSlot_;

    BindingName* names_;

    void init(uint32_t positionalFormalStart, uint32_t nonPositionalFormalStart,
              uint32_t topLevelFunctionStart, uint32_t varStart,
              uint32_t letStart, uint32_t constStart,
              uint8_t flags, uint32_t firstFrameSlot, uint32_t firstEnvironmentSlot,
              BindingName* names, uint32_t length)
    {
        positionalFormalStart_ = positionalFormalStart;
        nonPositionalFormalStart_ = nonPositionalFormalStart;
        topLevelFunctionStart_ = topLevelFunctionStart;
        varStart_ = varStart;
        letStart_ = letStart;
        constStart_ = constStart;
        length_ = length;
        index_ = 0;
        flags_ = flags;
        argumentSlot_ = 0;
        frameSlot_ = firstFrameSlot;
        environmentSlot_ = firstEnvironmentSlot;
        names_ = names;

        settle();
    }

    void init(LexicalScope::Data& data, uint32_t firstFrameSlot, uint8_t flags);
    void init(FunctionScope::Data& data, uint8_t flags);
    void init(VarScope::Data& data, uint32_t firstFrameSlot);
    void init(GlobalScope::Data& data);
    void init(EvalScope::Data& data, bool strict);
    void init(ModuleScope::Data& data);

    bool hasFormalParameterExprs() const {
        return flags_ & HasFormalParameterExprs;
    }

    bool ignoreDestructuredFormalParameters() const {
        return flags_ & IgnoreDestructuredFormalParameters;
    }

    bool isNamedLambda() const {
        return flags_ & IsNamedLambda;
    }

    void increment() {
        MOZ_ASSERT(!done());
        if (flags_ & CanHaveSlotsMask) {
            if (canHaveArgumentSlots()) {
                if (index_ < nonPositionalFormalStart_) {
                    MOZ_ASSERT(index_ >= positionalFormalStart_);
                    argumentSlot_++;
                }
            }
            if (closedOver()) {
                // Imports must not be given known slots. They are
                // indirect bindings.
                MOZ_ASSERT(kind() != BindingKind::Import);
                MOZ_ASSERT(canHaveEnvironmentSlots());
                environmentSlot_++;
            } else if (canHaveFrameSlots()) {
                // Usually positional formal parameters don't have frame
                // slots, except when there are parameter expressions, in
                // which case they act like lets.
                if (index_ >= nonPositionalFormalStart_ || (hasFormalParameterExprs() && name()))
                    frameSlot_++;
            }
        }
        index_++;
    }

    void settle() {
        if (ignoreDestructuredFormalParameters()) {
            while (!done() && !name())
                increment();
        }
    }

  public:
    explicit BindingIter(Scope* scope);
    explicit BindingIter(JSScript* script);

    BindingIter(LexicalScope::Data& data, uint32_t firstFrameSlot, bool isNamedLambda) {
        init(data, firstFrameSlot, isNamedLambda ? IsNamedLambda : 0);
    }

    BindingIter(FunctionScope::Data& data, bool hasParameterExprs) {
        init(data,
             IgnoreDestructuredFormalParameters |
             (hasParameterExprs ? HasFormalParameterExprs : 0));
    }

    BindingIter(VarScope::Data& data, uint32_t firstFrameSlot) {
        init(data, firstFrameSlot);
    }

    explicit BindingIter(GlobalScope::Data& data) {
        init(data);
    }

    explicit BindingIter(ModuleScope::Data& data) {
        init(data);
    }

    BindingIter(EvalScope::Data& data, bool strict) {
        init(data, strict);
    }

    explicit BindingIter(const BindingIter& bi) = default;

    bool done() const {
        return index_ == length_;
    }

    explicit operator bool() const {
        return !done();
    }

    void operator++(int) {
        increment();
        settle();
    }

    bool isLast() const {
        MOZ_ASSERT(!done());
        return index_ + 1 == length_;
    }

    bool canHaveArgumentSlots() const {
        return flags_ & CanHaveArgumentSlots;
    }

    bool canHaveFrameSlots() const {
        return flags_ & CanHaveFrameSlots;
    }

    bool canHaveEnvironmentSlots() const {
        return flags_ & CanHaveEnvironmentSlots;
    }

    JSAtom* name() const {
        MOZ_ASSERT(!done());
        return names_[index_].name();
    }

    bool closedOver() const {
        MOZ_ASSERT(!done());
        return names_[index_].closedOver();
    }

    BindingLocation location() const {
        MOZ_ASSERT(!done());
        if (!(flags_ & CanHaveSlotsMask))
            return BindingLocation::Global();
        if (index_ < positionalFormalStart_)
            return BindingLocation::Import();
        if (closedOver()) {
            MOZ_ASSERT(canHaveEnvironmentSlots());
            return BindingLocation::Environment(environmentSlot_);
        }
        if (index_ < nonPositionalFormalStart_ && canHaveArgumentSlots())
            return BindingLocation::Argument(argumentSlot_);
        if (canHaveFrameSlots())
            return BindingLocation::Frame(frameSlot_);
        MOZ_ASSERT(isNamedLambda());
        return BindingLocation::NamedLambdaCallee();
    }

    BindingKind kind() const {
        MOZ_ASSERT(!done());
        if (index_ < positionalFormalStart_)
            return BindingKind::Import;
        if (index_ < topLevelFunctionStart_) {
            // When the parameter list has expressions, the parameters act
            // like lexical bindings and have TDZ.
            if (hasFormalParameterExprs())
                return BindingKind::Let;
            return BindingKind::FormalParameter;
        }
        if (index_ < letStart_)
            return BindingKind::Var;
        if (index_ < constStart_)
            return BindingKind::Let;
        if (isNamedLambda())
            return BindingKind::NamedLambdaCallee;
        return BindingKind::Const;
    }

    bool isTopLevelFunction() const {
        MOZ_ASSERT(!done());
        return index_ >= topLevelFunctionStart_ && index_ < varStart_;
    }

    bool hasArgumentSlot() const {
        MOZ_ASSERT(!done());
        if (hasFormalParameterExprs())
            return false;
        return index_ >= positionalFormalStart_ && index_ < nonPositionalFormalStart_;
    }

    uint16_t argumentSlot() const {
        MOZ_ASSERT(canHaveArgumentSlots());
        return mozilla::AssertedCast<uint16_t>(index_);
    }

    uint32_t nextFrameSlot() const {
        MOZ_ASSERT(canHaveFrameSlots());
        return frameSlot_;
    }

    uint32_t nextEnvironmentSlot() const {
        MOZ_ASSERT(canHaveEnvironmentSlots());
        return environmentSlot_;
    }

    void trace(JSTracer* trc);
};

void DumpBindings(JSContext* cx, Scope* scope);
JSAtom* FrameSlotName(JSScript* script, jsbytecode* pc);

//
// A refinement BindingIter that only iterates over positional formal
// parameters of a function.
//
class PositionalFormalParameterIter : public BindingIter
{
    void settle() {
        if (index_ >= nonPositionalFormalStart_)
            index_ = length_;
    }

  public:
    explicit PositionalFormalParameterIter(JSScript* script);

    void operator++(int) {
        BindingIter::operator++(1);
        settle();
    }

    bool isDestructured() const {
        return !name();
    }
};

//
// Iterator for walking the scope chain.
//
// It may be placed in GC containers; for example:
//
//   for (Rooted<ScopeIter> si(cx, ScopeIter(scope)); si; si++) {
//     use(si);
//     SomeMayGCOperation();
//     use(si);
//   }
//
class MOZ_STACK_CLASS ScopeIter
{
    Scope* scope_;

  public:
    explicit ScopeIter(Scope* scope)
      : scope_(scope)
    { }

    explicit ScopeIter(JSScript* script);

    explicit ScopeIter(const ScopeIter& si)
      : scope_(si.scope_)
    { }

    bool done() const {
        return !scope_;
    }

    explicit operator bool() const {
        return !done();
    }

    void operator++(int) {
        MOZ_ASSERT(!done());
        scope_ = scope_->enclosing();
    }

    Scope* scope() const {
        MOZ_ASSERT(!done());
        return scope_;
    }

    ScopeKind kind() const {
        MOZ_ASSERT(!done());
        return scope_->kind();
    }

    // Returns the shape of the environment if it is known. It is possible to
    // hasSyntacticEnvironment and to have no known shape, e.g., eval.
    Shape* environmentShape() const {
        return scope()->environmentShape();
    }

    // Returns whether this scope has a syntactic environment (i.e., an
    // Environment that isn't a non-syntactic With or NonSyntacticVariables)
    // on the environment chain.
    bool hasSyntacticEnvironment() const;

    void trace(JSTracer* trc) {
        if (scope_)
            TraceRoot(trc, &scope_, "scope iter scope");
    }
};

//
// Specializations of Rooted containers for the iterators.
//

template <typename Outer>
class BindingIterOperations
{
    const BindingIter& iter() const { return static_cast<const Outer*>(this)->get(); }

  public:
    bool done() const { return iter().done(); }
    explicit operator bool() const { return !done(); }
    bool isLast() const { return iter().isLast(); }
    bool canHaveArgumentSlots() const { return iter().canHaveArgumentSlots(); }
    bool canHaveFrameSlots() const { return iter().canHaveFrameSlots(); }
    bool canHaveEnvironmentSlots() const { return iter().canHaveEnvironmentSlots(); }
    JSAtom* name() const { return iter().name(); }
    bool closedOver() const { return iter().closedOver(); }
    BindingLocation location() const { return iter().location(); }
    BindingKind kind() const { return iter().kind(); }
    bool isTopLevelFunction() const { return iter().isTopLevelFunction(); }
    bool hasArgumentSlot() const { return iter().hasArgumentSlot(); }
    uint16_t argumentSlot() const { return iter().argumentSlot(); }
    uint32_t nextFrameSlot() const { return iter().nextFrameSlot(); }
    uint32_t nextEnvironmentSlot() const { return iter().nextEnvironmentSlot(); }
};

template <typename Outer>
class MutableBindingIterOperations : public BindingIterOperations<Outer>
{
    BindingIter& iter() { return static_cast<Outer*>(this)->get(); }

  public:
    void operator++(int) { iter().operator++(1); }
};

template <typename Outer>
class ScopeIterOperations
{
    const ScopeIter& iter() const { return static_cast<const Outer*>(this)->get(); }

  public:
    bool done() const { return iter().done(); }
    explicit operator bool() const { return !done(); }
    Scope* scope() const { return iter().scope(); }
    ScopeKind kind() const { return iter().kind(); }
    Shape* environmentShape() const { return iter().environmentShape(); }
    bool hasSyntacticEnvironment() const { return iter().hasSyntacticEnvironment(); }
};

template <typename Outer>
class MutableScopeIterOperations : public ScopeIterOperations<Outer>
{
    ScopeIter& iter() { return static_cast<Outer*>(this)->get(); }

  public:
    void operator++(int) { iter().operator++(1); }
};

#define SPECIALIZE_ROOTING_CONTAINERS(Iter, BaseIter)                    \
    template <>                                                          \
    class RootedBase<Iter>                                               \
      : public Mutable##BaseIter##Operations<JS::Rooted<Iter>>           \
    { };                                                                 \
                                                                         \
    template <>                                                          \
    class MutableHandleBase<Iter>                                        \
      : public Mutable##BaseIter##Operations<JS::MutableHandle<Iter>>    \
    { };                                                                 \
                                                                         \
    template <>                                                          \
    class HandleBase<Iter>                                               \
      : public BaseIter##Operations<JS::Handle<Iter>>                    \
    { };                                                                 \
                                                                         \
    template <>                                                          \
    class PersistentRootedBase<Iter>                                     \
      : public Mutable##BaseIter##Operations<JS::PersistentRooted<Iter>> \
    { }

SPECIALIZE_ROOTING_CONTAINERS(BindingIter, BindingIter);
SPECIALIZE_ROOTING_CONTAINERS(PositionalFormalParameterIter, BindingIter);
SPECIALIZE_ROOTING_CONTAINERS(ScopeIter, ScopeIter);

#undef SPECIALIZE_ROOTING_CONTAINERS

//
// Allow using is<T> and as<T> on Rooted<Scope*> and Handle<Scope*>.
//

template <typename Outer>
struct ScopeCastOperation
{
    template <class U>
    JS::Handle<U*> as() const {
        const Outer& self = *static_cast<const Outer*>(this);
        MOZ_ASSERT_IF(self, self->template is<U>());
        return Handle<U*>::fromMarkedLocation(reinterpret_cast<U* const*>(self.address()));
    }
};

template <>
class RootedBase<Scope*> : public ScopeCastOperation<JS::Rooted<Scope*>>
{ };

template <>
class HandleBase<Scope*> : public ScopeCastOperation<JS::Handle<Scope*>>
{ };

template <>
class MutableHandleBase<Scope*> : public ScopeCastOperation<JS::MutableHandle<Scope*>>
{ };

} // namespace js

namespace JS {

template <>
struct GCPolicy<js::ScopeKind> : public IgnoreGCPolicy<js::ScopeKind>
{ };

template <typename T>
struct ScopeDataGCPolicy
{
    static T initial() {
        return nullptr;
    }

    static void trace(JSTracer* trc, T* vp, const char* name) {
        if (*vp)
            (*vp)->trace(trc);
    }
};

#define DEFINE_SCOPE_DATA_GCPOLICY(Data)                        \
    template <>                                                 \
    struct MapTypeToRootKind<Data*> {                           \
        static const RootKind kind = RootKind::Traceable;       \
    };                                                          \
    template <>                                                 \
    struct GCPolicy<Data*> : public ScopeDataGCPolicy<Data*>    \
    { }

DEFINE_SCOPE_DATA_GCPOLICY(js::LexicalScope::Data);
DEFINE_SCOPE_DATA_GCPOLICY(js::FunctionScope::Data);
DEFINE_SCOPE_DATA_GCPOLICY(js::VarScope::Data);
DEFINE_SCOPE_DATA_GCPOLICY(js::GlobalScope::Data);
DEFINE_SCOPE_DATA_GCPOLICY(js::EvalScope::Data);
DEFINE_SCOPE_DATA_GCPOLICY(js::ModuleScope::Data);

#undef DEFINE_SCOPE_DATA_GCPOLICY

namespace ubi {

template <>
class Concrete<js::Scope> : TracerConcrete<js::Scope>
{
  protected:
    explicit Concrete(js::Scope* ptr) : TracerConcrete<js::Scope>(ptr) { }

  public:
    static void construct(void* storage, js::Scope* ptr) {
        new (storage) Concrete(ptr);
    }

    CoarseType coarseType() const final { return CoarseType::Script; }

    Size size(mozilla::MallocSizeOf mallocSizeOf) const override;

    const char16_t* typeName() const override { return concreteTypeName; }
    static const char16_t concreteTypeName[];
};

} // namespace ubi
} // namespace JS

#endif // vm_Scope_h
back to top