| /* -*- 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 "jstypes.h" |
| #include "jsatom.h" |
| #include "jsopcode.h" |
| #include "jsscript.h" |
| #include "jsprvtd.h" |
| #include "jspubtd.h" |
| |
| #include "builtin/Module.h" |
| #include "frontend/ParseMaps.h" |
| #include "frontend/ParseNode.h" |
| #include "frontend/TokenStream.h" |
| #include "vm/ScopeObject.h" |
| |
| namespace js { |
| namespace frontend { |
| |
| // 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-exmaples are: |
| // - upvars of nested functions |
| // - function statement |
| // since the set of assigned name is known dynamically. 'with' could be in |
| // the non-example category, provided the set of all free variables within |
| // the with block was noted. However, we do not optimize 'with' so, for |
| // simplicity, 'with' is treated like eval. |
| // |
| // 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::argIsAlised). |
| // |
| 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; |
| |
| public: |
| AnyContextFlags() |
| : hasExplicitUseStrict(false), |
| bindingsAccessedDynamically(false), |
| hasDebuggerStatement(false) |
| { } |
| }; |
| |
| class FunctionContextFlags |
| { |
| // This class's data is all private and so only visible to these friends. |
| friend class FunctionBox; |
| |
| // We parsed a yield statement in the function. |
| bool isGenerator:1; |
| |
| // The function or a function that encloses it may define new local names |
| // at runtime through means other than calling eval. |
| bool mightAliasLocals:1; |
| |
| // 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; |
| |
| // This function refers directly to its name in a way which requires the |
| // name to be a separate object on the scope chain. |
| bool needsDeclEnvObject: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; |
| |
| public: |
| FunctionContextFlags() |
| : isGenerator(false), |
| mightAliasLocals(false), |
| hasExtensibleScope(false), |
| needsDeclEnvObject(false), |
| argumentsHasLocalBinding(false), |
| definitelyNeedsArgsObj(false) |
| { } |
| }; |
| |
| class GlobalSharedContext; |
| |
| /* |
| * 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. Note however, that this information is not shared |
| * between the two; they simply reuse the same data structure. |
| */ |
| class SharedContext |
| { |
| public: |
| JSContext *const context; |
| AnyContextFlags anyCxFlags; |
| bool strict; |
| |
| // If it's function code, funbox must be non-NULL and scopeChain must be NULL. |
| // If it's global code, funbox must be NULL. |
| inline SharedContext(JSContext *cx, bool strict); |
| |
| virtual ObjectBox *toObjectBox() = 0; |
| inline bool isGlobalSharedContext() { return toObjectBox() == NULL; } |
| inline bool isModuleBox() { return toObjectBox() && toObjectBox()->isModuleBox(); } |
| inline bool isFunctionBox() { return toObjectBox() && toObjectBox()->isFunctionBox(); } |
| inline GlobalSharedContext *asGlobalSharedContext(); |
| inline ModuleBox *asModuleBox(); |
| inline FunctionBox *asFunctionBox(); |
| |
| bool hasExplicitUseStrict() const { return anyCxFlags.hasExplicitUseStrict; } |
| bool bindingsAccessedDynamically() const { return anyCxFlags.bindingsAccessedDynamically; } |
| bool hasDebuggerStatement() const { return anyCxFlags.hasDebuggerStatement; } |
| |
| void setExplicitUseStrict() { anyCxFlags.hasExplicitUseStrict = true; } |
| void setBindingsAccessedDynamically() { anyCxFlags.bindingsAccessedDynamically = true; } |
| void setHasDebuggerStatement() { anyCxFlags.hasDebuggerStatement = true; } |
| |
| // JSOPTION_EXTRA_WARNINGS warnings or strict mode errors. |
| inline bool needStrictChecks(); |
| }; |
| |
| class GlobalSharedContext : public SharedContext |
| { |
| private: |
| const RootedObject scopeChain_; /* scope chain object for the script */ |
| |
| public: |
| inline GlobalSharedContext(JSContext *cx, JSObject *scopeChain, bool strict); |
| |
| ObjectBox *toObjectBox() { return NULL; } |
| JSObject *scopeChain() const { return scopeChain_; } |
| }; |
| |
| class ModuleBox : public ObjectBox, public SharedContext |
| { |
| public: |
| Bindings bindings; |
| |
| ModuleBox(JSContext *cx, ObjectBox *traceListHead, Module *module, |
| ParseContext<FullParseHandler> *pc); |
| ObjectBox *toObjectBox() { return this; } |
| Module *module() const { return &object->as<Module>(); } |
| }; |
| |
| class FunctionBox : public ObjectBox, public SharedContext |
| { |
| public: |
| Bindings bindings; /* bindings for this function */ |
| uint32_t bufStart; |
| uint32_t bufEnd; |
| uint32_t startLine; |
| uint32_t startColumn; |
| uint32_t asmStart; /* offset of the "use asm" directive, if present */ |
| uint16_t ndefaults; |
| bool inWith:1; /* some enclosing scope is a with-statement */ |
| bool inGenexpLambda:1; /* lambda from generator expression */ |
| bool useAsm:1; /* function contains "use asm" directive */ |
| bool insideUseAsm:1; /* nested function of function of "use asm" directive */ |
| |
| // Fields for use in heuristics. |
| bool usesArguments:1; /* contains a free use of 'arguments' */ |
| bool usesApply:1; /* contains an f.apply() call */ |
| |
| FunctionContextFlags funCxFlags; |
| |
| template <typename ParseHandler> |
| FunctionBox(JSContext *cx, ObjectBox* traceListHead, JSFunction *fun, ParseContext<ParseHandler> *pc, |
| bool strict); |
| |
| ObjectBox *toObjectBox() { return this; } |
| JSFunction *function() const { return &object->as<JSFunction>(); } |
| |
| bool isGenerator() const { return funCxFlags.isGenerator; } |
| bool mightAliasLocals() const { return funCxFlags.mightAliasLocals; } |
| bool hasExtensibleScope() const { return funCxFlags.hasExtensibleScope; } |
| bool needsDeclEnvObject() const { return funCxFlags.needsDeclEnvObject; } |
| bool argumentsHasLocalBinding() const { return funCxFlags.argumentsHasLocalBinding; } |
| bool definitelyNeedsArgsObj() const { return funCxFlags.definitelyNeedsArgsObj; } |
| |
| void setIsGenerator() { funCxFlags.isGenerator = true; } |
| void setMightAliasLocals() { funCxFlags.mightAliasLocals = true; } |
| void setHasExtensibleScope() { funCxFlags.hasExtensibleScope = true; } |
| void setNeedsDeclEnvObject() { funCxFlags.needsDeclEnvObject = true; } |
| void setArgumentsHasLocalBinding() { funCxFlags.argumentsHasLocalBinding = true; } |
| void setDefinitelyNeedsArgsObj() { JS_ASSERT(funCxFlags.argumentsHasLocalBinding); |
| funCxFlags.definitelyNeedsArgsObj = true; } |
| |
| // Return whether this function has either specified "use asm" or is |
| // (transitively) nested inside a function that has. |
| bool useAsmOrInsideUseAsm() const { |
| return useAsm || insideUseAsm; |
| } |
| |
| void setStart(const TokenStream &tokenStream) { |
| bufStart = tokenStream.currentToken().pos.begin; |
| startLine = tokenStream.getLineno(); |
| startColumn = tokenStream.getColumn(); |
| } |
| |
| bool isHeavyweight() |
| { |
| // Note: this should be kept in sync with JSFunction::isHeavyweight(). |
| return bindings.hasAnyAliasedBindings() || |
| hasExtensibleScope() || |
| needsDeclEnvObject(); |
| } |
| }; |
| |
| inline FunctionBox * |
| SharedContext::asFunctionBox() |
| { |
| JS_ASSERT(isFunctionBox()); |
| return static_cast<FunctionBox*>(this); |
| } |
| |
| /* |
| * NB: If you add a new type of statement that is a scope, add it between |
| * STMT_WITH and STMT_CATCH, or you will break StmtInfoBase::linksScope. If you |
| * add a non-looping statement type, add it before STMT_DO_LOOP or you will |
| * break StmtInfoBase::isLoop(). |
| * |
| * Also remember to keep the statementName array in BytecodeEmitter.cpp in |
| * sync. |
| */ |
| enum StmtType { |
| STMT_LABEL, /* labeled statement: L: s */ |
| STMT_IF, /* if (then) statement */ |
| STMT_ELSE, /* else clause of if statement */ |
| STMT_SEQ, /* synthetic sequence of statements */ |
| STMT_BLOCK, /* compound statement: { s1[;... sN] } */ |
| STMT_SWITCH, /* switch statement */ |
| STMT_WITH, /* with statement */ |
| STMT_CATCH, /* catch block */ |
| STMT_TRY, /* try block */ |
| STMT_FINALLY, /* finally block */ |
| STMT_SUBROUTINE, /* gosub-target subroutine body */ |
| STMT_DO_LOOP, /* do/while loop statement */ |
| STMT_FOR_LOOP, /* for loop statement */ |
| STMT_FOR_IN_LOOP, /* for/in loop statement */ |
| STMT_WHILE_LOOP, /* while loop statement */ |
| STMT_LIMIT |
| }; |
| |
| /* |
| * A comment on the encoding of the js::StmtType enum and StmtInfoBase |
| * type-testing methods: |
| * |
| * StmtInfoBase::maybeScope() tells whether a statement type is always, or may |
| * become, a lexical scope. It therefore includes block and switch (the two |
| * low-numbered "maybe" scope types) and excludes with (with has dynamic scope |
| * pending the "reformed with" in ES4/JS2). It includes all try-catch-finally |
| * types, which are high-numbered maybe-scope types. |
| * |
| * StmtInfoBase::linksScope() tells whether a js::StmtInfo{PC,BCE} of the given |
| * type eagerly links to other scoping statement info records. It excludes the |
| * two early "maybe" types, block and switch, as well as the try and both |
| * finally types, since try and the other trailing maybe-scope types don't need |
| * block scope unless they contain let declarations. |
| * |
| * We treat WITH as a static scope because it prevents lexical binding from |
| * continuing further up the static scope chain. With the lost "reformed with" |
| * proposal for ES4, we would be able to model it statically, too. |
| */ |
| |
| // StmtInfoPC is used by the Parser. StmtInfoBCE is used by the |
| // BytecodeEmitter. The two types have some overlap, encapsulated by |
| // StmtInfoBase. Several functions below (e.g. PushStatement) are templated to |
| // work with both types. |
| |
| struct StmtInfoBase { |
| uint16_t type; /* statement type */ |
| |
| /* |
| * True if type is STMT_BLOCK, STMT_TRY, STMT_SWITCH, or |
| * STMT_FINALLY and the block contains at least one let-declaration. |
| */ |
| bool isBlockScope:1; |
| |
| /* for (let ...) induced block scope */ |
| bool isForLetBlock:1; |
| |
| RootedAtom label; /* name of LABEL */ |
| Rooted<StaticBlockObject *> blockObj; /* block scope object */ |
| |
| StmtInfoBase(JSContext *cx) |
| : isBlockScope(false), isForLetBlock(false), label(cx), blockObj(cx) |
| {} |
| |
| bool maybeScope() const { |
| return STMT_BLOCK <= type && type <= STMT_SUBROUTINE && type != STMT_WITH; |
| } |
| |
| bool linksScope() const { |
| return (STMT_WITH <= type && type <= STMT_CATCH) || isBlockScope; |
| } |
| |
| bool isLoop() const { |
| return type >= STMT_DO_LOOP; |
| } |
| |
| bool isTrying() const { |
| return STMT_TRY <= type && type <= STMT_SUBROUTINE; |
| } |
| }; |
| |
| // Push the C-stack-allocated struct at stmt onto the StmtInfoPC stack. |
| template <class ContextT> |
| void |
| PushStatement(ContextT *ct, typename ContextT::StmtInfo *stmt, StmtType type); |
| |
| template <class ContextT> |
| void |
| FinishPushBlockScope(ContextT *ct, typename ContextT::StmtInfo *stmt, StaticBlockObject &blockObj); |
| |
| // Pop pc->topStmt. If the top StmtInfoPC struct is not stack-allocated, it |
| // is up to the caller to free it. The dummy argument is just to make the |
| // template matching work. |
| template <class ContextT> |
| void |
| FinishPopStatement(ContextT *ct); |
| |
| /* |
| * Find a lexically scoped variable (one declared by let, catch, or an array |
| * comprehension) named by atom, looking in sc's compile-time scopes. |
| * |
| * If a WITH statement is reached along the scope stack, return its statement |
| * info record, so callers can tell that atom is ambiguous. If slotp is not |
| * null, then if atom is found, set *slotp to its stack slot, otherwise to -1. |
| * This means that if slotp is not null, all the block objects on the lexical |
| * scope chain must have had their depth slots computed by the code generator, |
| * so the caller must be under EmitTree. |
| * |
| * In any event, directly return the statement info record in which atom was |
| * found. Otherwise return null. |
| */ |
| template <class ContextT> |
| typename ContextT::StmtInfo * |
| LexicalLookup(ContextT *ct, HandleAtom atom, int *slotp, typename ContextT::StmtInfo *stmt); |
| |
| } // namespace frontend |
| |
| } // namespace js |
| |
| #endif /* frontend_SharedContext_h */ |