blob: 61f3399897ef863cdabc1e283880de7590dc7679 [file] [log] [blame]
/* -*- 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 */