Dactyloidae/js/src/vm/Scope.cpp

1467 lines
46 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* 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/. */
#include "vm/Scope.h"
#include "mozilla/ScopeExit.h"
#include "jsscript.h"
#include "builtin/ModuleObject.h"
#include "gc/Allocator.h"
#include "vm/EnvironmentObject.h"
#include "vm/Runtime.h"
#include "vm/Shape-inl.h"
using namespace js;
using mozilla::Maybe;
using mozilla::MakeScopeExit;
using mozilla::Move;
using mozilla::Nothing;
using mozilla::Some;
const char*
js::BindingKindString(BindingKind kind)
{
switch (kind) {
case BindingKind::Import:
return "import";
case BindingKind::FormalParameter:
return "formal parameter";
case BindingKind::Var:
return "var";
case BindingKind::Let:
return "let";
case BindingKind::Const:
return "const";
case BindingKind::NamedLambdaCallee:
return "named lambda callee";
}
MOZ_CRASH("Bad BindingKind");
}
const char*
js::ScopeKindString(ScopeKind kind)
{
switch (kind) {
case ScopeKind::Function:
return "function";
case ScopeKind::FunctionBodyVar:
return "function body var";
case ScopeKind::ParameterExpressionVar:
return "parameter expression var";
case ScopeKind::Lexical:
return "lexical";
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
return "catch";
case ScopeKind::NamedLambda:
return "named lambda";
case ScopeKind::StrictNamedLambda:
return "strict named lambda";
case ScopeKind::With:
return "with";
case ScopeKind::Eval:
return "eval";
case ScopeKind::StrictEval:
return "strict eval";
case ScopeKind::Global:
return "global";
case ScopeKind::NonSyntactic:
return "non-syntactic";
case ScopeKind::Module:
return "module";
}
MOZ_CRASH("Bad ScopeKind");
}
static Shape*
EmptyEnvironmentShape(ExclusiveContext* cx, const Class* cls, uint32_t numSlots,
uint32_t baseShapeFlags)
{
// Put as many slots into the object header as possible.
uint32_t numFixed = gc::GetGCKindSlots(gc::GetGCObjectKind(numSlots));
return EmptyShape::getInitialShape(cx, cls, TaggedProto(nullptr), numFixed,
baseShapeFlags);
}
static Shape*
NextEnvironmentShape(ExclusiveContext* cx, HandleAtom name, BindingKind bindKind, uint32_t slot,
StackBaseShape& stackBase, HandleShape shape)
{
UnownedBaseShape* base = BaseShape::getUnowned(cx, stackBase);
if (!base)
return nullptr;
unsigned attrs = JSPROP_PERMANENT | JSPROP_ENUMERATE;
switch (bindKind) {
case BindingKind::Const:
case BindingKind::NamedLambdaCallee:
attrs |= JSPROP_READONLY;
break;
default:
break;
}
jsid id = NameToId(name->asPropertyName());
Rooted<StackShape> child(cx, StackShape(base, id, slot, attrs, 0));
return cx->zone()->propertyTree.getChild(cx, shape, child);
}
static Shape*
CreateEnvironmentShape(ExclusiveContext* cx, BindingIter& bi, const Class* cls,
uint32_t numSlots, uint32_t baseShapeFlags)
{
RootedShape shape(cx, EmptyEnvironmentShape(cx, cls, numSlots, baseShapeFlags));
if (!shape)
return nullptr;
RootedAtom name(cx);
StackBaseShape stackBase(cx, cls, baseShapeFlags);
for (; bi; bi++) {
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Environment) {
name = bi.name();
shape = NextEnvironmentShape(cx, name, bi.kind(), loc.slot(), stackBase, shape);
if (!shape)
return nullptr;
}
}
return shape;
}
template <typename ConcreteScope>
static UniquePtr<typename ConcreteScope::Data>
CopyScopeData(ExclusiveContext* cx, Handle<typename ConcreteScope::Data*> data)
{
size_t dataSize = ConcreteScope::sizeOfData(data->length);
size_t headerSize = sizeof(typename ConcreteScope::Data);
MOZ_ASSERT(dataSize >= headerSize);
size_t extraSize = dataSize - headerSize;
uint8_t* copyBytes = cx->zone()->pod_malloc<uint8_t>(dataSize);
if (!copyBytes) {
ReportOutOfMemory(cx);
return nullptr;
}
auto dataCopy = reinterpret_cast<typename ConcreteScope::Data*>(copyBytes);
new (dataCopy) typename ConcreteScope::Data(*data);
uint8_t* extra = reinterpret_cast<uint8_t*>(data.get()) + headerSize;
uint8_t* extraCopy = copyBytes + headerSize;
mozilla::PodCopy<uint8_t>(extraCopy, extra, extraSize);
return UniquePtr<typename ConcreteScope::Data>(dataCopy);
}
template <typename ConcreteScope>
static bool
PrepareScopeData(ExclusiveContext* cx, BindingIter& bi, Handle<UniquePtr<typename ConcreteScope::Data>> data,
const Class* cls, uint32_t baseShapeFlags, MutableHandleShape envShape)
{
// Copy a fresh BindingIter for use below.
BindingIter freshBi(bi);
// Iterate through all bindings. This counts the number of environment
// slots needed and computes the maximum frame slot.
while (bi)
bi++;
data->nextFrameSlot = bi.canHaveFrameSlots() ? bi.nextFrameSlot() : LOCALNO_LIMIT;
// Make a new environment shape if any environment slots were used.
if (bi.nextEnvironmentSlot() == JSSLOT_FREE(cls)) {
envShape.set(nullptr);
} else {
envShape.set(CreateEnvironmentShape(cx, freshBi, cls, bi.nextEnvironmentSlot(),
baseShapeFlags));
if (!envShape)
return false;
}
return true;
}
template <typename ConcreteScope>
static UniquePtr<typename ConcreteScope::Data>
NewEmptyScopeData(ExclusiveContext* cx, uint32_t length = 0)
{
uint8_t* bytes = cx->zone()->pod_malloc<uint8_t>(ConcreteScope::sizeOfData(length));
if (!bytes)
ReportOutOfMemory(cx);
auto data = reinterpret_cast<typename ConcreteScope::Data*>(bytes);
if (data)
new (data) typename ConcreteScope::Data(length);
return UniquePtr<typename ConcreteScope::Data>(data);
}
static bool
XDRBindingName(XDRState<XDR_ENCODE>* xdr, BindingName* bindingName)
{
ExclusiveContext* cx = xdr->cx();
RootedAtom atom(cx, bindingName->name());
bool hasAtom = !!atom;
uint8_t u8 = uint8_t(hasAtom << 1) | uint8_t(bindingName->closedOver());
if (!xdr->codeUint8(&u8))
return false;
if (atom && !XDRAtom(xdr, &atom))
return false;
return true;
}
static bool
XDRBindingName(XDRState<XDR_DECODE>* xdr, BindingName* bindingName)
{
ExclusiveContext* cx = xdr->cx();
uint8_t u8;
if (!xdr->codeUint8(&u8))
return false;
bool closedOver = u8 & 1;
bool hasAtom = u8 >> 1;
RootedAtom atom(cx);
if (hasAtom && !XDRAtom(xdr, &atom))
return false;
*bindingName = BindingName(atom, closedOver);
return true;
}
template <typename ConcreteScopeData>
static void
DeleteScopeData(ConcreteScopeData* data)
{
// Some scope Data classes have GCManagedDeletePolicy because then contain
// GCPtrs. Dispose of them in the appropriate way.
JS::DeletePolicy<ConcreteScopeData>()(data);
}
template <typename ConcreteScope, XDRMode mode>
/* static */ bool
Scope::XDRSizedBindingNames(XDRState<mode>* xdr, Handle<ConcreteScope*> scope,
MutableHandle<typename ConcreteScope::Data*> data)
{
MOZ_ASSERT(!data);
ExclusiveContext* cx = xdr->cx();
uint32_t length;
if (mode == XDR_ENCODE)
length = scope->data().length;
if (!xdr->codeUint32(&length))
return false;
if (mode == XDR_ENCODE) {
data.set(&scope->data());
} else {
data.set(NewEmptyScopeData<ConcreteScope>(cx, length).release());
if (!data)
return false;
data->length = length;
}
for (uint32_t i = 0; i < length; i++) {
if (!XDRBindingName(xdr, &data->trailingNames[i])) {
if (mode == XDR_DECODE) {
DeleteScopeData(data.get());
data.set(nullptr);
}
return false;
}
}
return true;
}
/* static */ Scope*
Scope::create(ExclusiveContext* cx, ScopeKind kind, HandleScope enclosing, HandleShape envShape)
{
Scope* scope = Allocate<Scope>(cx);
if (scope)
new (scope) Scope(kind, enclosing, envShape);
return scope;
}
template <typename T, typename D>
/* static */ Scope*
Scope::create(ExclusiveContext* cx, ScopeKind kind, HandleScope enclosing,
HandleShape envShape, mozilla::UniquePtr<T, D> data)
{
Scope* scope = create(cx, kind, enclosing, envShape);
if (!scope)
return nullptr;
// It is an invariant that all Scopes that have data (currently, all
// ScopeKinds except With) must have non-null data.
MOZ_ASSERT(data);
scope->initData(Move(data));
return scope;
}
uint32_t
Scope::chainLength() const
{
uint32_t length = 0;
for (ScopeIter si(const_cast<Scope*>(this)); si; si++)
length++;
return length;
}
uint32_t
Scope::environmentChainLength() const
{
uint32_t length = 0;
for (ScopeIter si(const_cast<Scope*>(this)); si; si++) {
if (si.hasSyntacticEnvironment())
length++;
}
return length;
}
Shape*
Scope::maybeCloneEnvironmentShape(JSContext* cx)
{
// Clone the environment shape if cloning into a different zone.
if (environmentShape_ && environmentShape_->zoneFromAnyThread() != cx->zone()) {
BindingIter bi(this);
return CreateEnvironmentShape(cx, bi,
environmentShape_->getObjectClass(),
environmentShape_->slotSpan(),
environmentShape_->getObjectFlags());
}
return environmentShape_;
}
/* static */ Scope*
Scope::clone(JSContext* cx, HandleScope scope, HandleScope enclosing)
{
RootedShape envShape(cx);
if (scope->environmentShape()) {
envShape = scope->maybeCloneEnvironmentShape(cx);
if (!envShape)
return nullptr;
}
switch (scope->kind_) {
case ScopeKind::Function:
MOZ_CRASH("Use FunctionScope::clone.");
break;
case ScopeKind::FunctionBodyVar:
case ScopeKind::ParameterExpressionVar: {
Rooted<VarScope::Data*> original(cx, &scope->as<VarScope>().data());
UniquePtr<VarScope::Data> dataClone = CopyScopeData<VarScope>(cx, original);
if (!dataClone)
return nullptr;
return create(cx, scope->kind_, enclosing, envShape, Move(dataClone));
}
case ScopeKind::Lexical:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda: {
Rooted<LexicalScope::Data*> original(cx, &scope->as<LexicalScope>().data());
UniquePtr<LexicalScope::Data> dataClone = CopyScopeData<LexicalScope>(cx, original);
if (!dataClone)
return nullptr;
return create(cx, scope->kind_, enclosing, envShape, Move(dataClone));
}
case ScopeKind::With:
return create(cx, scope->kind_, enclosing, envShape);
case ScopeKind::Eval:
case ScopeKind::StrictEval: {
Rooted<EvalScope::Data*> original(cx, &scope->as<EvalScope>().data());
UniquePtr<EvalScope::Data> dataClone = CopyScopeData<EvalScope>(cx, original);
if (!dataClone)
return nullptr;
return create(cx, scope->kind_, enclosing, envShape, Move(dataClone));
}
case ScopeKind::Global:
case ScopeKind::NonSyntactic:
MOZ_CRASH("Use GlobalScope::clone.");
break;
case ScopeKind::Module:
MOZ_CRASH("NYI");
break;
}
return nullptr;
}
void
Scope::finalize(FreeOp* fop)
{
MOZ_ASSERT(CurrentThreadIsGCSweeping());
if (data_) {
// We don't need to call the destructors for any GCPtrs in Data because
// this only happens during a GC.
fop->free_(reinterpret_cast<void*>(data_));
data_ = 0;
}
}
size_t
Scope::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
{
if (data_)
return mallocSizeOf(reinterpret_cast<void*>(data_));
return 0;
}
void
Scope::dump()
{
for (ScopeIter si(this); si; si++) {
fprintf(stderr, "%s [%p]", ScopeKindString(si.kind()), si.scope());
if (si.scope()->enclosing())
fprintf(stderr, " -> ");
}
fprintf(stderr, "\n");
}
uint32_t
LexicalScope::firstFrameSlot() const
{
switch (kind()) {
case ScopeKind::Lexical:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
// For intra-frame scopes, find the enclosing scope's next frame slot.
return nextFrameSlot(enclosing());
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda:
// Named lambda scopes cannot have frame slots.
return LOCALNO_LIMIT;
default:
// Otherwise start at 0.
break;
}
return 0;
}
/* static */ uint32_t
LexicalScope::nextFrameSlot(Scope* scope)
{
for (ScopeIter si(scope); si; si++) {
switch (si.kind()) {
case ScopeKind::Function:
return si.scope()->as<FunctionScope>().nextFrameSlot();
case ScopeKind::FunctionBodyVar:
case ScopeKind::ParameterExpressionVar:
return si.scope()->as<VarScope>().nextFrameSlot();
case ScopeKind::Lexical:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
return si.scope()->as<LexicalScope>().nextFrameSlot();
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda:
// Named lambda scopes cannot have frame slots.
return 0;
case ScopeKind::With:
continue;
case ScopeKind::Eval:
case ScopeKind::StrictEval:
return si.scope()->as<EvalScope>().nextFrameSlot();
case ScopeKind::Global:
case ScopeKind::NonSyntactic:
return 0;
case ScopeKind::Module:
return si.scope()->as<ModuleScope>().nextFrameSlot();
}
}
MOZ_CRASH("Not an enclosing intra-frame Scope");
}
/* static */ LexicalScope*
LexicalScope::create(ExclusiveContext* cx, ScopeKind kind, Handle<Data*> data,
uint32_t firstFrameSlot, HandleScope enclosing)
{
MOZ_ASSERT(data, "LexicalScopes should not be created if there are no bindings.");
// The data that's passed in is be from the frontend and is LifoAlloc'd.
// Copy it now that we're creating a permanent VM scope.
Rooted<UniquePtr<Data>> copy(cx, CopyScopeData<LexicalScope>(cx, data));
if (!copy)
return nullptr;
return createWithData(cx, kind, &copy, firstFrameSlot, enclosing);
}
/* static */ LexicalScope*
LexicalScope::createWithData(ExclusiveContext* cx, ScopeKind kind, MutableHandle<UniquePtr<Data>> data,
uint32_t firstFrameSlot, HandleScope enclosing)
{
bool isNamedLambda = kind == ScopeKind::NamedLambda || kind == ScopeKind::StrictNamedLambda;
MOZ_ASSERT_IF(!isNamedLambda && firstFrameSlot != 0,
firstFrameSlot == nextFrameSlot(enclosing));
MOZ_ASSERT_IF(isNamedLambda, firstFrameSlot == LOCALNO_LIMIT);
RootedShape envShape(cx);
BindingIter bi(*data, firstFrameSlot, isNamedLambda);
if (!PrepareScopeData<LexicalScope>(cx, bi, data, &LexicalEnvironmentObject::class_,
BaseShape::NOT_EXTENSIBLE | BaseShape::DELEGATE, &envShape))
{
return nullptr;
}
Scope* scope = Scope::create(cx, kind, enclosing, envShape, Move(data.get()));
if (!scope)
return nullptr;
MOZ_ASSERT(scope->as<LexicalScope>().firstFrameSlot() == firstFrameSlot);
return &scope->as<LexicalScope>();
}
/* static */ Shape*
LexicalScope::getEmptyExtensibleEnvironmentShape(ExclusiveContext* cx)
{
const Class* cls = &LexicalEnvironmentObject::class_;
return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), BaseShape::DELEGATE);
}
template <XDRMode mode>
/* static */ bool
LexicalScope::XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope)
{
ExclusiveContext* cx = xdr->cx();
Rooted<Data*> data(cx);
if (!XDRSizedBindingNames<LexicalScope>(xdr, scope.as<LexicalScope>(), &data))
return false;
{
Maybe<Rooted<UniquePtr<Data>>> uniqueData;
if (mode == XDR_DECODE)
uniqueData.emplace(cx, data);
uint32_t firstFrameSlot;
uint32_t nextFrameSlot;
if (mode == XDR_ENCODE) {
firstFrameSlot = scope->as<LexicalScope>().firstFrameSlot();
nextFrameSlot = data->nextFrameSlot;
}
if (!xdr->codeUint32(&data->constStart))
return false;
if (!xdr->codeUint32(&firstFrameSlot))
return false;
if (!xdr->codeUint32(&nextFrameSlot))
return false;
if (mode == XDR_DECODE) {
scope.set(createWithData(cx, kind, &uniqueData.ref(), firstFrameSlot, enclosing));
if (!scope)
return false;
// nextFrameSlot is used only for this correctness check.
MOZ_ASSERT(nextFrameSlot == scope->as<LexicalScope>().data().nextFrameSlot);
}
}
return true;
}
template
/* static */ bool
LexicalScope::XDR(XDRState<XDR_ENCODE>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope);
template
/* static */ bool
LexicalScope::XDR(XDRState<XDR_DECODE>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope);
static inline uint32_t
FunctionScopeEnvShapeFlags(bool hasParameterExprs)
{
if (hasParameterExprs)
return BaseShape::DELEGATE;
return BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE;
}
/* static */ FunctionScope*
FunctionScope::create(ExclusiveContext* cx, Handle<Data*> dataArg,
bool hasParameterExprs, bool needsEnvironment,
HandleFunction fun, HandleScope enclosing)
{
// The data that's passed in is be from the frontend and is LifoAlloc'd.
// Copy it now that we're creating a permanent VM scope.
Rooted<UniquePtr<Data>> data(cx, dataArg ? CopyScopeData<FunctionScope>(cx, dataArg)
: NewEmptyScopeData<FunctionScope>(cx));
if (!data)
return nullptr;
return createWithData(cx, &data, hasParameterExprs, needsEnvironment, fun, enclosing);
}
/* static */ FunctionScope*
FunctionScope::createWithData(ExclusiveContext* cx, MutableHandle<UniquePtr<Data>> data,
bool hasParameterExprs, bool needsEnvironment,
HandleFunction fun, HandleScope enclosing)
{
MOZ_ASSERT(data);
MOZ_ASSERT(fun->isTenured());
// FunctionScope::Data has GCManagedDeletePolicy because it contains a
// GCPtr. Destruction of |data| below may trigger calls into the GC.
Rooted<FunctionScope*> funScope(cx);
{
RootedShape envShape(cx);
BindingIter bi(*data, hasParameterExprs);
uint32_t shapeFlags = FunctionScopeEnvShapeFlags(hasParameterExprs);
if (!PrepareScopeData<FunctionScope>(cx, bi, data, &CallObject::class_, shapeFlags,
&envShape))
{
return nullptr;
}
data->hasParameterExprs = hasParameterExprs;
data->canonicalFunction.init(fun);
// An environment may be needed regardless of existence of any closed over
// bindings:
// - Extensible scopes (i.e., due to direct eval)
// - Needing a home object
// - Being a derived class constructor
// - Being a generator
if (!envShape && needsEnvironment) {
envShape = getEmptyEnvironmentShape(cx, hasParameterExprs);
if (!envShape)
return nullptr;
}
Scope* scope = Scope::create(cx, ScopeKind::Function, enclosing, envShape);
if (!scope)
return nullptr;
funScope = &scope->as<FunctionScope>();
funScope->initData(Move(data.get()));
}
return funScope;
}
JSScript*
FunctionScope::script() const
{
return canonicalFunction()->nonLazyScript();
}
/* static */ bool
FunctionScope::isSpecialName(ExclusiveContext* cx, JSAtom* name)
{
return name == cx->names().arguments ||
name == cx->names().dotThis ||
name == cx->names().dotGenerator;
}
/* static */ Shape*
FunctionScope::getEmptyEnvironmentShape(ExclusiveContext* cx, bool hasParameterExprs)
{
const Class* cls = &CallObject::class_;
uint32_t shapeFlags = FunctionScopeEnvShapeFlags(hasParameterExprs);
return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), shapeFlags);
}
/* static */ FunctionScope*
FunctionScope::clone(JSContext* cx, Handle<FunctionScope*> scope, HandleFunction fun,
HandleScope enclosing)
{
MOZ_ASSERT(fun != scope->canonicalFunction());
// FunctionScope::Data has GCManagedDeletePolicy because it contains a
// GCPtr. Destruction of |dataClone| below may trigger calls into the GC.
Rooted<FunctionScope*> funScopeClone(cx);
{
RootedShape envShape(cx);
if (scope->environmentShape()) {
envShape = scope->maybeCloneEnvironmentShape(cx);
if (!envShape)
return nullptr;
}
Rooted<Data*> dataOriginal(cx, &scope->as<FunctionScope>().data());
Rooted<UniquePtr<Data>> dataClone(cx, CopyScopeData<FunctionScope>(cx, dataOriginal));
if (!dataClone)
return nullptr;
dataClone->canonicalFunction.init(fun);
Scope* scopeClone = Scope::create(cx, scope->kind(), enclosing, envShape);
if (!scopeClone)
return nullptr;
funScopeClone = &scopeClone->as<FunctionScope>();
funScopeClone->initData(Move(dataClone.get()));
}
return funScopeClone;
}
template <XDRMode mode>
/* static */ bool
FunctionScope::XDR(XDRState<mode>* xdr, HandleFunction fun, HandleScope enclosing,
MutableHandleScope scope)
{
ExclusiveContext* cx = xdr->cx();
Rooted<Data*> data(cx);
if (!XDRSizedBindingNames<FunctionScope>(xdr, scope.as<FunctionScope>(), &data))
return false;
{
Maybe<Rooted<UniquePtr<Data>>> uniqueData;
if (mode == XDR_DECODE)
uniqueData.emplace(cx, data);
uint8_t needsEnvironment;
uint8_t hasParameterExprs;
uint32_t nextFrameSlot;
if (mode == XDR_ENCODE) {
needsEnvironment = scope->hasEnvironment();
hasParameterExprs = data->hasParameterExprs;
nextFrameSlot = data->nextFrameSlot;
}
if (!xdr->codeUint8(&needsEnvironment))
return false;
if (!xdr->codeUint8(&hasParameterExprs))
return false;
if (!xdr->codeUint16(&data->nonPositionalFormalStart))
return false;
if (!xdr->codeUint16(&data->varStart))
return false;
if (!xdr->codeUint32(&nextFrameSlot))
return false;
if (mode == XDR_DECODE) {
if (!data->length) {
MOZ_ASSERT(!data->nonPositionalFormalStart);
MOZ_ASSERT(!data->varStart);
MOZ_ASSERT(!data->nextFrameSlot);
}
scope.set(createWithData(cx, &uniqueData.ref(), hasParameterExprs, needsEnvironment, fun,
enclosing));
if (!scope)
return false;
// nextFrameSlot is used only for this correctness check.
MOZ_ASSERT(nextFrameSlot == scope->as<FunctionScope>().data().nextFrameSlot);
}
}
return true;
}
template
/* static */ bool
FunctionScope::XDR(XDRState<XDR_ENCODE>* xdr, HandleFunction fun, HandleScope enclosing,
MutableHandleScope scope);
template
/* static */ bool
FunctionScope::XDR(XDRState<XDR_DECODE>* xdr, HandleFunction fun, HandleScope enclosing,
MutableHandleScope scope);
static const uint32_t VarScopeEnvShapeFlags =
BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE;
static UniquePtr<VarScope::Data>
NewEmptyVarScopeData(ExclusiveContext* cx, uint32_t firstFrameSlot)
{
UniquePtr<VarScope::Data> data(NewEmptyScopeData<VarScope>(cx));
if (data)
data->nextFrameSlot = firstFrameSlot;
return data;
}
/* static */ VarScope*
VarScope::create(ExclusiveContext* cx, ScopeKind kind, Handle<Data*> dataArg,
uint32_t firstFrameSlot, bool needsEnvironment, HandleScope enclosing)
{
// The data that's passed in is be from the frontend and is LifoAlloc'd.
// Copy it now that we're creating a permanent VM scope.
Rooted<UniquePtr<Data>> data(cx, dataArg ? CopyScopeData<VarScope>(cx, dataArg)
: NewEmptyVarScopeData(cx, firstFrameSlot));
if (!data)
return nullptr;
return createWithData(cx, kind, &data, firstFrameSlot, needsEnvironment, enclosing);
}
/* static */ VarScope*
VarScope::createWithData(ExclusiveContext* cx, ScopeKind kind, MutableHandle<UniquePtr<Data>> data,
uint32_t firstFrameSlot, bool needsEnvironment, HandleScope enclosing)
{
MOZ_ASSERT(data);
RootedShape envShape(cx);
BindingIter bi(*data, firstFrameSlot);
if (!PrepareScopeData<VarScope>(cx, bi, data, &VarEnvironmentObject::class_, VarScopeEnvShapeFlags,
&envShape))
{
return nullptr;
}
// An environment may be needed regardless of existence of any closed over
// bindings:
// - Extensible scopes (i.e., due to direct eval)
// - Being a generator
if (!envShape && needsEnvironment) {
envShape = getEmptyEnvironmentShape(cx);
if (!envShape)
return nullptr;
}
Scope* scope = Scope::create(cx, kind, enclosing, envShape, Move(data.get()));
if (!scope)
return nullptr;
return &scope->as<VarScope>();
}
/* static */ Shape*
VarScope::getEmptyEnvironmentShape(ExclusiveContext* cx)
{
const Class* cls = &VarEnvironmentObject::class_;
return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), VarScopeEnvShapeFlags);
}
uint32_t
VarScope::firstFrameSlot() const
{
if (enclosing()->is<FunctionScope>())
return enclosing()->as<FunctionScope>().nextFrameSlot();
return 0;
}
template <XDRMode mode>
/* static */ bool
VarScope::XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope)
{
ExclusiveContext* cx = xdr->cx();
Rooted<Data*> data(cx);
if (!XDRSizedBindingNames<VarScope>(xdr, scope.as<VarScope>(), &data))
return false;
{
Maybe<Rooted<UniquePtr<Data>>> uniqueData;
if (mode == XDR_DECODE)
uniqueData.emplace(cx, data);
uint8_t needsEnvironment;
uint32_t firstFrameSlot;
uint32_t nextFrameSlot;
if (mode == XDR_ENCODE) {
needsEnvironment = scope->hasEnvironment();
firstFrameSlot = scope->as<VarScope>().firstFrameSlot();
nextFrameSlot = data->nextFrameSlot;
}
if (!xdr->codeUint8(&needsEnvironment))
return false;
if (!xdr->codeUint32(&firstFrameSlot))
return false;
if (!xdr->codeUint32(&nextFrameSlot))
return false;
if (mode == XDR_DECODE) {
if (!data->length) {
MOZ_ASSERT(!data->nextFrameSlot);
}
scope.set(createWithData(cx, kind, &uniqueData.ref(), firstFrameSlot, needsEnvironment,
enclosing));
if (!scope)
return false;
// nextFrameSlot is used only for this correctness check.
MOZ_ASSERT(nextFrameSlot == scope->as<VarScope>().data().nextFrameSlot);
}
}
return true;
}
template
/* static */ bool
VarScope::XDR(XDRState<XDR_ENCODE>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope);
template
/* static */ bool
VarScope::XDR(XDRState<XDR_DECODE>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope);
/* static */ GlobalScope*
GlobalScope::create(ExclusiveContext* cx, ScopeKind kind, Handle<Data*> dataArg)
{
// The data that's passed in is be from the frontend and is LifoAlloc'd.
// Copy it now that we're creating a permanent VM scope.
Rooted<UniquePtr<Data>> data(cx, dataArg ? CopyScopeData<GlobalScope>(cx, dataArg)
: NewEmptyScopeData<GlobalScope>(cx));
if (!data)
return nullptr;
return createWithData(cx, kind, &data);
}
/* static */ GlobalScope*
GlobalScope::createWithData(ExclusiveContext* cx, ScopeKind kind, MutableHandle<UniquePtr<Data>> data)
{
MOZ_ASSERT(data);
// The global scope has no environment shape. Its environment is the
// global lexical scope and the global object or non-syntactic objects
// created by embedding, all of which are not only extensible but may
// have names on them deleted.
Scope* scope = Scope::create(cx, kind, nullptr, nullptr, Move(data.get()));
if (!scope)
return nullptr;
return &scope->as<GlobalScope>();
}
/* static */ GlobalScope*
GlobalScope::clone(JSContext* cx, Handle<GlobalScope*> scope, ScopeKind kind)
{
Rooted<Data*> dataOriginal(cx, &scope->as<GlobalScope>().data());
Rooted<UniquePtr<Data>> dataClone(cx, CopyScopeData<GlobalScope>(cx, dataOriginal));
if (!dataClone)
return nullptr;
Scope* scopeClone = Scope::create(cx, kind, nullptr, nullptr, Move(dataClone.get()));
if (!scopeClone)
return nullptr;
return &scopeClone->as<GlobalScope>();
}
template <XDRMode mode>
/* static */ bool
GlobalScope::XDR(XDRState<mode>* xdr, ScopeKind kind, MutableHandleScope scope)
{
MOZ_ASSERT((mode == XDR_DECODE) == !scope);
ExclusiveContext* cx = xdr->cx();
Rooted<Data*> data(cx);
if (!XDRSizedBindingNames<GlobalScope>(xdr, scope.as<GlobalScope>(), &data))
return false;
{
Maybe<Rooted<UniquePtr<Data>>> uniqueData;
if (mode == XDR_DECODE)
uniqueData.emplace(cx, data);
if (!xdr->codeUint32(&data->letStart))
return false;
if (!xdr->codeUint32(&data->constStart))
return false;
if (mode == XDR_DECODE) {
if (!data->length) {
MOZ_ASSERT(!data->letStart);
MOZ_ASSERT(!data->constStart);
}
scope.set(createWithData(cx, kind, &uniqueData.ref()));
if (!scope)
return false;
}
}
return true;
}
template
/* static */ bool
GlobalScope::XDR(XDRState<XDR_ENCODE>* xdr, ScopeKind kind, MutableHandleScope scope);
template
/* static */ bool
GlobalScope::XDR(XDRState<XDR_DECODE>* xdr, ScopeKind kind, MutableHandleScope scope);
/* static */ WithScope*
WithScope::create(ExclusiveContext* cx, HandleScope enclosing)
{
Scope* scope = Scope::create(cx, ScopeKind::With, enclosing, nullptr);
return static_cast<WithScope*>(scope);
}
static const uint32_t EvalScopeEnvShapeFlags =
BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE;
/* static */ EvalScope*
EvalScope::create(ExclusiveContext* cx, ScopeKind scopeKind, Handle<Data*> dataArg,
HandleScope enclosing)
{
// The data that's passed in is be from the frontend and is LifoAlloc'd.
// Copy it now that we're creating a permanent VM scope.
Rooted<UniquePtr<Data>> data(cx, dataArg ? CopyScopeData<EvalScope>(cx, dataArg)
: NewEmptyScopeData<EvalScope>(cx));
if (!data)
return nullptr;
return createWithData(cx, scopeKind, &data, enclosing);
}
/* static */ EvalScope*
EvalScope::createWithData(ExclusiveContext* cx, ScopeKind scopeKind, MutableHandle<UniquePtr<Data>> data,
HandleScope enclosing)
{
MOZ_ASSERT(data);
RootedShape envShape(cx);
if (scopeKind == ScopeKind::StrictEval) {
BindingIter bi(*data, true);
if (!PrepareScopeData<EvalScope>(cx, bi, data, &VarEnvironmentObject::class_,
EvalScopeEnvShapeFlags, &envShape))
{
return nullptr;
}
}
// Strict eval and direct eval in parameter expressions always get their own
// var environment even if there are no bindings.
if (!envShape && scopeKind == ScopeKind::StrictEval) {
envShape = getEmptyEnvironmentShape(cx);
if (!envShape)
return nullptr;
}
Scope* scope = Scope::create(cx, scopeKind, enclosing, envShape, Move(data.get()));
if (!scope)
return nullptr;
return &scope->as<EvalScope>();
}
/* static */ Scope*
EvalScope::nearestVarScopeForDirectEval(Scope* scope)
{
for (ScopeIter si(scope); si; si++) {
switch (si.kind()) {
case ScopeKind::Function:
case ScopeKind::FunctionBodyVar:
case ScopeKind::ParameterExpressionVar:
case ScopeKind::Global:
case ScopeKind::NonSyntactic:
return scope;
default:
break;
}
}
return nullptr;
}
/* static */ Shape*
EvalScope::getEmptyEnvironmentShape(ExclusiveContext* cx)
{
const Class* cls = &VarEnvironmentObject::class_;
return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), EvalScopeEnvShapeFlags);
}
template <XDRMode mode>
/* static */ bool
EvalScope::XDR(XDRState<mode>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope)
{
ExclusiveContext* cx = xdr->cx();
Rooted<Data*> data(cx);
{
Maybe<Rooted<UniquePtr<Data>>> uniqueData;
if (mode == XDR_DECODE)
uniqueData.emplace(cx, data);
if (!XDRSizedBindingNames<EvalScope>(xdr, scope.as<EvalScope>(), &data))
return false;
if (mode == XDR_DECODE) {
if (!data->length)
MOZ_ASSERT(!data->nextFrameSlot);
scope.set(createWithData(cx, kind, &uniqueData.ref(), enclosing));
if (!scope)
return false;
}
}
return true;
}
template
/* static */ bool
EvalScope::XDR(XDRState<XDR_ENCODE>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope);
template
/* static */ bool
EvalScope::XDR(XDRState<XDR_DECODE>* xdr, ScopeKind kind, HandleScope enclosing,
MutableHandleScope scope);
static const uint32_t ModuleScopeEnvShapeFlags =
BaseShape::NOT_EXTENSIBLE | BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE;
/* static */ ModuleScope*
ModuleScope::create(ExclusiveContext* cx, Handle<Data*> dataArg,
HandleModuleObject module, HandleScope enclosing)
{
Rooted<UniquePtr<Data>> data(cx, dataArg ? CopyScopeData<ModuleScope>(cx, dataArg)
: NewEmptyScopeData<ModuleScope>(cx));
if (!data)
return nullptr;
return createWithData(cx, &data, module, enclosing);
}
/* static */ ModuleScope*
ModuleScope::createWithData(ExclusiveContext* cx, MutableHandle<UniquePtr<Data>> data,
HandleModuleObject module, HandleScope enclosing)
{
MOZ_ASSERT(data);
MOZ_ASSERT(enclosing->is<GlobalScope>());
// ModuleScope::Data has GCManagedDeletePolicy because it contains a
// GCPtr. Destruction of |copy| below may trigger calls into the GC.
Rooted<ModuleScope*> moduleScope(cx);
{
// The data that's passed in is be from the frontend and is LifoAlloc'd.
// Copy it now that we're creating a permanent VM scope.
RootedShape envShape(cx);
BindingIter bi(*data);
if (!PrepareScopeData<ModuleScope>(cx, bi, data, &ModuleEnvironmentObject::class_,
ModuleScopeEnvShapeFlags, &envShape))
{
return nullptr;
}
// Modules always need an environment object for now.
if (!envShape) {
envShape = getEmptyEnvironmentShape(cx);
if (!envShape)
return nullptr;
}
Scope* scope = Scope::create(cx, ScopeKind::Module, enclosing, envShape);
if (!scope)
return nullptr;
data->module.init(module);
moduleScope = &scope->as<ModuleScope>();
moduleScope->initData(Move(data.get()));
}
return moduleScope;
}
/* static */ Shape*
ModuleScope::getEmptyEnvironmentShape(ExclusiveContext* cx)
{
const Class* cls = &ModuleEnvironmentObject::class_;
return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), ModuleScopeEnvShapeFlags);
}
JSScript*
ModuleScope::script() const
{
return module()->script();
}
ScopeIter::ScopeIter(JSScript* script)
: scope_(script->bodyScope())
{ }
bool
ScopeIter::hasSyntacticEnvironment() const
{
return scope()->hasEnvironment() && scope()->kind() != ScopeKind::NonSyntactic;
}
BindingIter::BindingIter(Scope* scope)
{
switch (scope->kind()) {
case ScopeKind::Lexical:
case ScopeKind::SimpleCatch:
case ScopeKind::Catch:
init(scope->as<LexicalScope>().data(),
scope->as<LexicalScope>().firstFrameSlot(), 0);
break;
case ScopeKind::NamedLambda:
case ScopeKind::StrictNamedLambda:
init(scope->as<LexicalScope>().data(), LOCALNO_LIMIT, IsNamedLambda);
break;
case ScopeKind::With:
// With scopes do not have bindings.
index_ = length_ = 0;
MOZ_ASSERT(done());
break;
case ScopeKind::Function: {
uint8_t flags = IgnoreDestructuredFormalParameters;
if (scope->as<FunctionScope>().hasParameterExprs())
flags |= HasFormalParameterExprs;
init(scope->as<FunctionScope>().data(), flags);
break;
}
case ScopeKind::FunctionBodyVar:
case ScopeKind::ParameterExpressionVar:
init(scope->as<VarScope>().data(),
scope->as<VarScope>().firstFrameSlot());
break;
case ScopeKind::Eval:
case ScopeKind::StrictEval:
init(scope->as<EvalScope>().data(), scope->kind() == ScopeKind::StrictEval);
break;
case ScopeKind::Global:
case ScopeKind::NonSyntactic:
init(scope->as<GlobalScope>().data());
break;
case ScopeKind::Module:
init(scope->as<ModuleScope>().data());
break;
}
}
BindingIter::BindingIter(JSScript* script)
: BindingIter(script->bodyScope())
{ }
void
BindingIter::init(LexicalScope::Data& data, uint32_t firstFrameSlot, uint8_t flags)
{
// Named lambda scopes can only have environment slots. If the callee
// isn't closed over, it is accessed via JSOP_CALLEE.
if (flags & IsNamedLambda) {
// Named lambda binding is weird. Normal BindingKind ordering rules
// don't apply.
init(0, 0, 0, 0, 0, 0,
CanHaveEnvironmentSlots | flags,
firstFrameSlot, JSSLOT_FREE(&LexicalEnvironmentObject::class_),
data.trailingNames.start(), data.length);
} else {
// imports - [0, 0)
// positional formals - [0, 0)
// other formals - [0, 0)
// top-level funcs - [0, 0)
// vars - [0, 0)
// lets - [0, data.constStart)
// consts - [data.constStart, data.length)
init(0, 0, 0, 0, 0, data.constStart,
CanHaveFrameSlots | CanHaveEnvironmentSlots | flags,
firstFrameSlot, JSSLOT_FREE(&LexicalEnvironmentObject::class_),
data.trailingNames.start(), data.length);
}
}
void
BindingIter::init(FunctionScope::Data& data, uint8_t flags)
{
flags = CanHaveFrameSlots | CanHaveEnvironmentSlots | flags;
if (!(flags & HasFormalParameterExprs))
flags |= CanHaveArgumentSlots;
// imports - [0, 0)
// positional formals - [0, data.nonPositionalFormalStart)
// other formals - [data.nonPositionalParamStart, data.varStart)
// top-level funcs - [data.varStart, data.varStart)
// vars - [data.varStart, data.length)
// lets - [data.length, data.length)
// consts - [data.length, data.length)
init(0, data.nonPositionalFormalStart, data.varStart, data.varStart, data.length, data.length,
flags,
0, JSSLOT_FREE(&CallObject::class_),
data.trailingNames.start(), data.length);
}
void
BindingIter::init(VarScope::Data& data, uint32_t firstFrameSlot)
{
// imports - [0, 0)
// positional formals - [0, 0)
// other formals - [0, 0)
// top-level funcs - [0, 0)
// vars - [0, data.length)
// lets - [data.length, data.length)
// consts - [data.length, data.length)
init(0, 0, 0, 0, data.length, data.length,
CanHaveFrameSlots | CanHaveEnvironmentSlots,
firstFrameSlot, JSSLOT_FREE(&VarEnvironmentObject::class_),
data.trailingNames.start(), data.length);
}
void
BindingIter::init(GlobalScope::Data& data)
{
// imports - [0, 0)
// positional formals - [0, 0)
// other formals - [0, 0)
// top-level funcs - [0, data.varStart)
// vars - [data.varStart, data.letStart)
// lets - [data.letStart, data.constStart)
// consts - [data.constStart, data.length)
init(0, 0, 0, data.varStart, data.letStart, data.constStart,
CannotHaveSlots,
UINT32_MAX, UINT32_MAX,
data.trailingNames.start(), data.length);
}
void
BindingIter::init(EvalScope::Data& data, bool strict)
{
uint32_t flags;
uint32_t firstFrameSlot;
uint32_t firstEnvironmentSlot;
if (strict) {
flags = CanHaveFrameSlots | CanHaveEnvironmentSlots;
firstFrameSlot = 0;
firstEnvironmentSlot = JSSLOT_FREE(&VarEnvironmentObject::class_);
} else {
flags = CannotHaveSlots;
firstFrameSlot = UINT32_MAX;
firstEnvironmentSlot = UINT32_MAX;
}
// imports - [0, 0)
// positional formals - [0, 0)
// other formals - [0, 0)
// top-level funcs - [0, data.varStart)
// vars - [data.varStart, data.length)
// lets - [data.length, data.length)
// consts - [data.length, data.length)
init(0, 0, 0, data.varStart, data.length, data.length,
flags, firstFrameSlot, firstEnvironmentSlot,
data.trailingNames.start(), data.length);
}
void
BindingIter::init(ModuleScope::Data& data)
{
// imports - [0, data.varStart)
// positional formals - [data.varStart, data.varStart)
// other formals - [data.varStart, data.varStart)
// top-level funcs - [data.varStart, data.varStart)
// vars - [data.varStart, data.letStart)
// lets - [data.letStart, data.constStart)
// consts - [data.constStart, data.length)
init(data.varStart, data.varStart, data.varStart, data.varStart, data.letStart, data.constStart,
CanHaveFrameSlots | CanHaveEnvironmentSlots,
0, JSSLOT_FREE(&ModuleEnvironmentObject::class_),
data.trailingNames.start(), data.length);
}
PositionalFormalParameterIter::PositionalFormalParameterIter(JSScript* script)
: BindingIter(script)
{
// Reinit with flags = 0, i.e., iterate over all positional parameters.
if (script->bodyScope()->is<FunctionScope>())
init(script->bodyScope()->as<FunctionScope>().data(), /* flags = */ 0);
settle();
}
void
js::DumpBindings(JSContext* cx, Scope* scopeArg)
{
RootedScope scope(cx, scopeArg);
for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
JSAutoByteString bytes;
if (!AtomToPrintableString(cx, bi.name(), &bytes))
return;
fprintf(stderr, "%s %s ", BindingKindString(bi.kind()), bytes.ptr());
switch (bi.location().kind()) {
case BindingLocation::Kind::Global:
fprintf(stderr, "global\n");
break;
case BindingLocation::Kind::Argument:
fprintf(stderr, "arg slot %u\n", bi.location().argumentSlot());
break;
case BindingLocation::Kind::Frame:
fprintf(stderr, "frame slot %u\n", bi.location().slot());
break;
case BindingLocation::Kind::Environment:
fprintf(stderr, "env slot %u\n", bi.location().slot());
break;
case BindingLocation::Kind::NamedLambdaCallee:
fprintf(stderr, "named lambda callee\n");
break;
case BindingLocation::Kind::Import:
fprintf(stderr, "import\n");
break;
}
}
}
static JSAtom*
GetFrameSlotNameInScope(Scope* scope, uint32_t slot)
{
for (BindingIter bi(scope); bi; bi++) {
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Frame && loc.slot() == slot)
return bi.name();
}
return nullptr;
}
JSAtom*
js::FrameSlotName(JSScript* script, jsbytecode* pc)
{
MOZ_ASSERT(IsLocalOp(JSOp(*pc)));
uint32_t slot = GET_LOCALNO(pc);
MOZ_ASSERT(slot < script->nfixed());
// Look for it in the body scope first.
if (JSAtom* name = GetFrameSlotNameInScope(script->bodyScope(), slot))
return name;
// If this is a function script and there is an extra var scope, look for
// it there.
if (script->functionHasExtraBodyVarScope()) {
if (JSAtom* name = GetFrameSlotNameInScope(script->functionExtraBodyVarScope(), slot))
return name;
}
// If not found, look for it in a lexical scope.
for (ScopeIter si(script->innermostScope(pc)); si; si++) {
if (!si.scope()->is<LexicalScope>())
continue;
LexicalScope& lexicalScope = si.scope()->as<LexicalScope>();
// Is the slot within bounds of the current lexical scope?
if (slot < lexicalScope.firstFrameSlot())
continue;
if (slot >= lexicalScope.nextFrameSlot())
break;
// If so, get the name.
if (JSAtom* name = GetFrameSlotNameInScope(&lexicalScope, slot))
return name;
}
MOZ_CRASH("Frame slot not found");
}
JS::ubi::Node::Size
JS::ubi::Concrete<Scope>::size(mozilla::MallocSizeOf mallocSizeOf) const
{
return js::gc::Arena::thingSize(get().asTenured().getAllocKind()) +
get().sizeOfExcludingThis(mallocSizeOf);
}