/* -*- 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 frontend_SharedContext_h
#define frontend_SharedContext_h

#include "jsatom.h"
#include "jsopcode.h"
#include "jspubtd.h"
#include "jsscript.h"
#include "jstypes.h"

#include "builtin/ModuleObject.h"
#include "ds/InlineTable.h"
#include "frontend/ParseNode.h"
#include "frontend/TokenStream.h"
#include "vm/EnvironmentObject.h"

namespace js {
namespace frontend {

enum class StatementKind : uint8_t
{
    Label,
    Block,
    If,
    Switch,
    With,
    Catch,
    Try,
    Finally,
    ForLoopLexicalHead,
    ForLoop,
    ForInLoop,
    ForOfLoop,
    DoLoop,
    WhileLoop,

    // Used only by BytecodeEmitter.
    Spread
};

static inline bool
StatementKindIsLoop(StatementKind kind)
{
    return kind == StatementKind::ForLoop ||
           kind == StatementKind::ForInLoop ||
           kind == StatementKind::ForOfLoop ||
           kind == StatementKind::DoLoop ||
           kind == StatementKind::WhileLoop ||
           kind == StatementKind::Spread;
}

static inline bool
StatementKindIsUnlabeledBreakTarget(StatementKind kind)
{
    return StatementKindIsLoop(kind) || kind == StatementKind::Switch;
}

// A base class for nestable structures in the frontend, such as statements
// and scopes.
template <typename Concrete>
class MOZ_STACK_CLASS Nestable
{
    Concrete** stack_;
    Concrete*  enclosing_;

  protected:
    explicit Nestable(Concrete** stack)
      : stack_(stack),
        enclosing_(*stack)
    {
        *stack_ = static_cast<Concrete*>(this);
    }

    // These method are protected. Some derived classes, such as ParseContext,
    // do not expose the ability to walk the stack.
    Concrete* enclosing() const {
        return enclosing_;
    }

    template <typename Predicate /* (Concrete*) -> bool */>
    static Concrete* findNearest(Concrete* it, Predicate predicate) {
        while (it && !predicate(it))
            it = it->enclosing();
        return it;
    }

    template <typename T>
    static T* findNearest(Concrete* it) {
        while (it && !it->template is<T>())
            it = it->enclosing();
        return it ? &it->template as<T>() : nullptr;
    }

    template <typename T, typename Predicate /* (T*) -> bool */>
    static T* findNearest(Concrete* it, Predicate predicate) {
        while (it && (!it->template is<T>() || !predicate(&it->template as<T>())))
            it = it->enclosing();
        return it ? &it->template as<T>() : nullptr;
    }

  public:
    ~Nestable() {
        MOZ_ASSERT(*stack_ == static_cast<Concrete*>(this));
        *stack_ = enclosing_;
    }
};

// These flags apply to both global and function contexts.
class AnyContextFlags
{
    // This class's data is all private and so only visible to these friends.
    friend class SharedContext;

    // True if "use strict"; appears in the body instead of being inherited.
    bool hasExplicitUseStrict:1;

    // The (static) bindings of this script need to support dynamic name
    // read/write access. Here, 'dynamic' means dynamic dictionary lookup on
    // the scope chain for a dynamic set of keys. The primary examples are:
    //  - direct eval
    //  - function::
    //  - with
    // since both effectively allow any name to be accessed. Non-examples are:
    //  - upvars of nested functions
    //  - function statement
    // since the set of assigned name is known dynamically.
    //
    // Note: access through the arguments object is not considered dynamic
    // binding access since it does not go through the normal name lookup
    // mechanism. This is debatable and could be changed (although care must be
    // taken not to turn off the whole 'arguments' optimization). To answer the
    // more general "is this argument aliased" question, script->needsArgsObj
    // should be tested (see JSScript::argIsAliased).
    bool bindingsAccessedDynamically:1;

    // Whether this script, or any of its inner scripts contains a debugger
    // statement which could potentially read or write anywhere along the
    // scope chain.
    bool hasDebuggerStatement:1;

    // A direct eval occurs in the body of the script.
    bool hasDirectEval:1;

  public:
    AnyContextFlags()
     :  hasExplicitUseStrict(false),
        bindingsAccessedDynamically(false),
        hasDebuggerStatement(false),
        hasDirectEval(false)
    { }
};

class FunctionContextFlags
{
    // This class's data is all private and so only visible to these friends.
    friend class FunctionBox;

    // This function does something that can extend the set of bindings in its
    // call objects --- it does a direct eval in non-strict code, or includes a
    // function statement (as opposed to a function definition).
    //
    // This flag is *not* inherited by enclosed or enclosing functions; it
    // applies only to the function in whose flags it appears.
    //
    bool hasExtensibleScope:1;

    // Technically, every function has a binding named 'arguments'. Internally,
    // this binding is only added when 'arguments' is mentioned by the function
    // body. This flag indicates whether 'arguments' has been bound either
    // through implicit use:
    //   function f() { return arguments }
    // or explicit redeclaration:
    //   function f() { var arguments; return arguments }
    //
    // Note 1: overwritten arguments (function() { arguments = 3 }) will cause
    // this flag to be set but otherwise require no special handling:
    // 'arguments' is just a local variable and uses of 'arguments' will just
    // read the local's current slot which may have been assigned. The only
    // special semantics is that the initial value of 'arguments' is the
    // arguments object (not undefined, like normal locals).
    //
    // Note 2: if 'arguments' is bound as a formal parameter, there will be an
    // 'arguments' in Bindings, but, as the "LOCAL" in the name indicates, this
    // flag will not be set. This is because, as a formal, 'arguments' will
    // have no special semantics: the initial value is unconditionally the
    // actual argument (or undefined if nactual < nformal).
    //
    bool argumentsHasLocalBinding:1;

    // In many cases where 'arguments' has a local binding (as described above)
    // we do not need to actually create an arguments object in the function
    // prologue: instead we can analyze how 'arguments' is used (using the
    // simple dataflow analysis in analyzeSSA) to determine that uses of
    // 'arguments' can just read from the stack frame directly. However, the
    // dataflow analysis only looks at how JSOP_ARGUMENTS is used, so it will
    // be unsound in several cases. The frontend filters out such cases by
    // setting this flag which eagerly sets script->needsArgsObj to true.
    //
    bool definitelyNeedsArgsObj:1;

    bool needsHomeObject:1;
    bool isDerivedClassConstructor:1;

    // Whether this function has a .this binding. If true, we need to emit
    // JSOP_FUNCTIONTHIS in the prologue to initialize it.
    bool hasThisBinding:1;

    // Whether this function has nested functions.
    bool hasInnerFunctions:1;

  public:
    FunctionContextFlags()
     :  hasExtensibleScope(false),
        argumentsHasLocalBinding(false),
        definitelyNeedsArgsObj(false),
        needsHomeObject(false),
        isDerivedClassConstructor(false),
        hasThisBinding(false),
        hasInnerFunctions(false)
    { }
};

// List of directives that may be encountered in a Directive Prologue (ES5 15.1).
class Directives
{
    bool strict_;
    bool asmJS_;

  public:
    explicit Directives(bool strict) : strict_(strict), asmJS_(false) {}
    explicit Directives(ParseContext* parent);

    void setStrict() { strict_ = true; }
    bool strict() const { return strict_; }

    void setAsmJS() { asmJS_ = true; }
    bool asmJS() const { return asmJS_; }

    Directives& operator=(Directives rhs) {
        strict_ = rhs.strict_;
        asmJS_ = rhs.asmJS_;
        return *this;
    }
    bool operator==(const Directives& rhs) const {
        return strict_ == rhs.strict_ && asmJS_ == rhs.asmJS_;
    }
    bool operator!=(const Directives& rhs) const {
        return !(*this == rhs);
    }
};

// The kind of this-binding for the current scope. Note that arrow functions
// (and generator expression lambdas) have a lexical this-binding so their
// ThisBinding is the same as the ThisBinding of their enclosing scope and can
// be any value.
enum class ThisBinding { Global, Function, Module };

class GlobalSharedContext;
class EvalSharedContext;
class ModuleSharedContext;

/*
 * The struct SharedContext is part of the current parser context (see
 * ParseContext). It stores information that is reused between the parser and
 * the bytecode emitter.
 */
class SharedContext
{
  public:
    ExclusiveContext* const context;
    AnyContextFlags anyCxFlags;
    bool strictScript;
    bool localStrict;
    bool extraWarnings;

  protected:
    enum class Kind {
        ObjectBox,
        Global,
        Eval,
        Module
    };

    Kind kind_;

    ThisBinding thisBinding_;

    bool allowNewTarget_;
    bool allowSuperProperty_;
    bool allowSuperCall_;
    bool inWith_;
    bool needsThisTDZChecks_;

    void computeAllowSyntax(Scope* scope);
    void computeInWith(Scope* scope);
    void computeThisBinding(Scope* scope);

  public:
    SharedContext(ExclusiveContext* cx, Kind kind, Directives directives, bool extraWarnings)
      : context(cx),
        anyCxFlags(),
        strictScript(directives.strict()),
        localStrict(false),
        extraWarnings(extraWarnings),
        kind_(kind),
        thisBinding_(ThisBinding::Global),
        allowNewTarget_(false),
        allowSuperProperty_(false),
        allowSuperCall_(false),
        inWith_(false),
        needsThisTDZChecks_(false)
    { }

    // If this is the outermost SharedContext, the Scope that encloses
    // it. Otherwise nullptr.
    virtual Scope* compilationEnclosingScope() const = 0;

    virtual ObjectBox* toObjectBox() { return nullptr; }
    bool isObjectBox() { return toObjectBox(); }
    bool isFunctionBox() { return isObjectBox() && toObjectBox()->isFunctionBox(); }
    inline FunctionBox* asFunctionBox();
    bool isModuleContext() { return kind_ == Kind::Module; }
    inline ModuleSharedContext* asModuleContext();
    bool isGlobalContext() { return kind_ == Kind::Global; }
    inline GlobalSharedContext* asGlobalContext();
    bool isEvalContext() { return kind_ == Kind::Eval; }
    inline EvalSharedContext* asEvalContext();

    ThisBinding thisBinding()          const { return thisBinding_; }

    bool allowNewTarget()              const { return allowNewTarget_; }
    bool allowSuperProperty()          const { return allowSuperProperty_; }
    bool allowSuperCall()              const { return allowSuperCall_; }
    bool inWith()                      const { return inWith_; }
    bool needsThisTDZChecks()          const { return needsThisTDZChecks_; }

    bool hasExplicitUseStrict()        const { return anyCxFlags.hasExplicitUseStrict; }
    bool bindingsAccessedDynamically() const { return anyCxFlags.bindingsAccessedDynamically; }
    bool hasDebuggerStatement()        const { return anyCxFlags.hasDebuggerStatement; }
    bool hasDirectEval()               const { return anyCxFlags.hasDirectEval; }

    void setExplicitUseStrict()           { anyCxFlags.hasExplicitUseStrict        = true; }
    void setBindingsAccessedDynamically() { anyCxFlags.bindingsAccessedDynamically = true; }
    void setHasDebuggerStatement()        { anyCxFlags.hasDebuggerStatement        = true; }
    void setHasDirectEval()               { anyCxFlags.hasDirectEval               = true; }

    inline bool allBindingsClosedOver();

    bool strict() const {
        return strictScript || localStrict;
    }
    bool setLocalStrictMode(bool strict) {
        bool retVal = localStrict;
        localStrict = strict;
        return retVal;
    }

    // JSOPTION_EXTRA_WARNINGS warnings or strict mode errors.
    bool needStrictChecks() const {
        return strict() || extraWarnings;
    }

    bool isDotVariable(JSAtom* atom) const {
        return atom == context->names().dotGenerator || atom == context->names().dotThis;
    }
};

class MOZ_STACK_CLASS GlobalSharedContext : public SharedContext
{
    ScopeKind scopeKind_;

  public:
    Rooted<GlobalScope::Data*> bindings;

    GlobalSharedContext(ExclusiveContext* cx, ScopeKind scopeKind, Directives directives,
                        bool extraWarnings)
      : SharedContext(cx, Kind::Global, directives, extraWarnings),
        scopeKind_(scopeKind),
        bindings(cx)
    {
        MOZ_ASSERT(scopeKind == ScopeKind::Global || scopeKind == ScopeKind::NonSyntactic);
        thisBinding_ = ThisBinding::Global;
    }

    Scope* compilationEnclosingScope() const override {
        return nullptr;
    }

    ScopeKind scopeKind() const {
        return scopeKind_;
    }
};

inline GlobalSharedContext*
SharedContext::asGlobalContext()
{
    MOZ_ASSERT(isGlobalContext());
    return static_cast<GlobalSharedContext*>(this);
}

class MOZ_STACK_CLASS EvalSharedContext : public SharedContext
{
    RootedScope enclosingScope_;

  public:
    Rooted<EvalScope::Data*> bindings;

    EvalSharedContext(ExclusiveContext* cx, JSObject* enclosingEnv, Scope* enclosingScope,
                      Directives directives, bool extraWarnings);

    Scope* compilationEnclosingScope() const override {
        return enclosingScope_;
    }
};

inline EvalSharedContext*
SharedContext::asEvalContext()
{
    MOZ_ASSERT(isEvalContext());
    return static_cast<EvalSharedContext*>(this);
}

class FunctionBox : public ObjectBox, public SharedContext
{
    // The parser handles tracing the fields below via the ObjectBox linked
    // list.

    Scope* enclosingScope_;

    // Names from the named lambda scope, if a named lambda.
    LexicalScope::Data* namedLambdaBindings_;

    // Names from the function scope.
    FunctionScope::Data* functionScopeBindings_;

    // Names from the extra 'var' scope of the function, if the parameter list
    // has expressions.
    VarScope::Data* extraVarScopeBindings_;

    void initWithEnclosingScope(Scope* enclosingScope);

  public:
    ParseNode*      functionNode;           /* back pointer used by asm.js for error messages */
    uint32_t        bufStart;
    uint32_t        bufEnd;
    uint32_t        startLine;
    uint32_t        startColumn;
    uint32_t        preludeStart;
    uint16_t        length;

    uint8_t         generatorKindBits_;     /* The GeneratorKind of this function. */
    uint8_t         asyncKindBits_;         /* The FunctionAsyncKing of this function. */

    bool            isGenexpLambda:1;       /* lambda from generator expression */
    bool            hasDestructuringArgs:1; /* parameter list contains destructuring expression */
    bool            hasParameterExprs:1;    /* parameter list contains expressions */
    bool            hasDirectEvalInParameterExpr:1; /* parameter list contains direct eval */
    bool            hasDuplicateParameters:1; /* parameter list contains duplicate names */
    bool            useAsm:1;               /* see useAsmOrInsideUseAsm */
    bool            insideUseAsm:1;         /* see useAsmOrInsideUseAsm */
    bool            isAnnexB:1;             /* need to emit a synthesized Annex B assignment */
    bool            wasEmitted:1;           /* Bytecode has been emitted for this function. */

    // Fields for use in heuristics.
    bool            declaredArguments:1;    /* the Parser declared 'arguments' */
    bool            usesArguments:1;        /* contains a free use of 'arguments' */
    bool            usesApply:1;            /* contains an f.apply() call */
    bool            usesThis:1;             /* contains 'this' */
    bool            usesReturn:1;           /* contains a 'return' statement */
    bool            hasRest_:1;             /* has rest parameter */

    FunctionContextFlags funCxFlags;

    FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, JSFunction* fun,
                uint32_t preludeStart, Directives directives, bool extraWarnings,
                GeneratorKind generatorKind, FunctionAsyncKind asyncKind);

    MutableHandle<LexicalScope::Data*> namedLambdaBindings() {
        MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms());
        return MutableHandle<LexicalScope::Data*>::fromMarkedLocation(&namedLambdaBindings_);
    }

    MutableHandle<FunctionScope::Data*> functionScopeBindings() {
        MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms());
        return MutableHandle<FunctionScope::Data*>::fromMarkedLocation(&functionScopeBindings_);
    }

    MutableHandle<VarScope::Data*> extraVarScopeBindings() {
        MOZ_ASSERT(context->compartment()->runtimeFromAnyThread()->keepAtoms());
        return MutableHandle<VarScope::Data*>::fromMarkedLocation(&extraVarScopeBindings_);
    }

    void initFromLazyFunction();
    void initStandaloneFunction(Scope* enclosingScope);
    void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind);

    ObjectBox* toObjectBox() override { return this; }
    JSFunction* function() const { return &object->as<JSFunction>(); }

    Scope* compilationEnclosingScope() const override {
        // This method is used to distinguish the outermost SharedContext. If
        // a FunctionBox is the outermost SharedContext, it must be a lazy
        // function.
        MOZ_ASSERT_IF(function()->isInterpretedLazy(),
                      enclosingScope_ == function()->lazyScript()->enclosingScope());
        return enclosingScope_;
    }

    bool needsCallObjectRegardlessOfBindings() const {
        return hasExtensibleScope() ||
               needsHomeObject() ||
               isDerivedClassConstructor() ||
               isGenerator();
    }

    bool hasExtraBodyVarScope() const {
        return hasParameterExprs &&
               (extraVarScopeBindings_ ||
                needsExtraBodyVarEnvironmentRegardlessOfBindings());
    }

    bool needsExtraBodyVarEnvironmentRegardlessOfBindings() const {
        MOZ_ASSERT(hasParameterExprs);
        return hasExtensibleScope() || isGenerator();
    }

    bool isLikelyConstructorWrapper() const {
        return usesArguments && usesApply && usesThis && !usesReturn;
    }

    GeneratorKind generatorKind() const { return GeneratorKindFromBits(generatorKindBits_); }
    bool isGenerator() const { return generatorKind() != NotGenerator; }
    bool isLegacyGenerator() const { return generatorKind() == LegacyGenerator; }
    bool isStarGenerator() const { return generatorKind() == StarGenerator; }
    FunctionAsyncKind asyncKind() const { return AsyncKindFromBits(asyncKindBits_); }
    bool isAsync() const { return asyncKind() == AsyncFunction; }
    bool isArrow() const { return function()->isArrow(); }

    bool hasRest() const { return hasRest_; }
    void setHasRest() {
        hasRest_ = true;
    }

    void setGeneratorKind(GeneratorKind kind) {
        // A generator kind can be set at initialization, or when "yield" is
        // first seen.  In both cases the transition can only happen from
        // NotGenerator.
        MOZ_ASSERT(!isGenerator());
        generatorKindBits_ = GeneratorKindAsBits(kind);
    }

    bool hasExtensibleScope()        const { return funCxFlags.hasExtensibleScope; }
    bool hasThisBinding()            const { return funCxFlags.hasThisBinding; }
    bool argumentsHasLocalBinding()  const { return funCxFlags.argumentsHasLocalBinding; }
    bool definitelyNeedsArgsObj()    const { return funCxFlags.definitelyNeedsArgsObj; }
    bool needsHomeObject()           const { return funCxFlags.needsHomeObject; }
    bool isDerivedClassConstructor() const { return funCxFlags.isDerivedClassConstructor; }
    bool hasInnerFunctions()         const { return funCxFlags.hasInnerFunctions; }

    void setHasExtensibleScope()           { funCxFlags.hasExtensibleScope       = true; }
    void setHasThisBinding()               { funCxFlags.hasThisBinding           = true; }
    void setArgumentsHasLocalBinding()     { funCxFlags.argumentsHasLocalBinding = true; }
    void setDefinitelyNeedsArgsObj()       { MOZ_ASSERT(funCxFlags.argumentsHasLocalBinding);
                                             funCxFlags.definitelyNeedsArgsObj   = true; }
    void setNeedsHomeObject()              { MOZ_ASSERT(function()->allowSuperProperty());
                                             funCxFlags.needsHomeObject          = true; }
    void setDerivedClassConstructor()      { MOZ_ASSERT(function()->isClassConstructor());
                                             funCxFlags.isDerivedClassConstructor = true; }
    void setHasInnerFunctions()            { funCxFlags.hasInnerFunctions         = true; }

    bool hasSimpleParameterList() const {
        return !hasRest() && !hasParameterExprs && !hasDestructuringArgs;
    }

    bool hasMappedArgsObj() const {
        return !strict() && hasSimpleParameterList();
    }

    // Return whether this or an enclosing function is being parsed and
    // validated as asm.js. Note: if asm.js validation fails, this will be false
    // while the function is being reparsed. This flag can be used to disable
    // certain parsing features that are necessary in general, but unnecessary
    // for validated asm.js.
    bool useAsmOrInsideUseAsm() const {
        return useAsm || insideUseAsm;
    }

    void setStart(const TokenStream& tokenStream) {
        bufStart = tokenStream.currentToken().pos.begin;
        tokenStream.srcCoords.lineNumAndColumnIndex(bufStart, &startLine, &startColumn);
    }

    void trace(JSTracer* trc) override;
};

inline FunctionBox*
SharedContext::asFunctionBox()
{
    MOZ_ASSERT(isFunctionBox());
    return static_cast<FunctionBox*>(this);
}

class MOZ_STACK_CLASS ModuleSharedContext : public SharedContext
{
    RootedModuleObject module_;
    RootedScope enclosingScope_;

  public:
    Rooted<ModuleScope::Data*> bindings;
    ModuleBuilder& builder;

    ModuleSharedContext(ExclusiveContext* cx, ModuleObject* module, Scope* enclosingScope,
                        ModuleBuilder& builder);

    HandleModuleObject module() const { return module_; }
    Scope* compilationEnclosingScope() const override { return enclosingScope_; }
};

inline ModuleSharedContext*
SharedContext::asModuleContext()
{
    MOZ_ASSERT(isModuleContext());
    return static_cast<ModuleSharedContext*>(this);
}

// In generators, we treat all bindings as closed so that they get stored on
// the heap.  This way there is less information to copy off the stack when
// suspending, and back on when resuming.  It also avoids the need to create
// and invalidate DebugScope proxies for unaliased locals in a generator
// frame, as the generator frame will be copied out to the heap and released
// only by GC.
inline bool
SharedContext::allBindingsClosedOver()
{
    return bindingsAccessedDynamically() || (isFunctionBox() && asFunctionBox()->isGenerator());
}

} // namespace frontend
} // namespace js

#endif /* frontend_SharedContext_h */