Mypal/js/src/frontend/SharedContext.h

647 lines
23 KiB
C++

/* -*- 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 */