/* -*- 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/. */ #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_calloc<uint8_t>(ConcreteScope::sizeOfData(length)); if (!bytes) ReportOutOfMemory(cx); auto data = reinterpret_cast<typename ConcreteScope::Data*>(bytes); if (data) new (data) typename ConcreteScope::Data(); return UniquePtr<typename ConcreteScope::Data>(data); } static bool XDRBindingName(XDRState<XDR_ENCODE>* xdr, BindingName* bindingName) { JSContext* 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) { JSContext* 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); JSContext* 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->names[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, ©, 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) { JSContext* 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 */ 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) { JSContext* 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) { JSContext* 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); JSContext* 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) { JSContext* 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.names, 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.names, 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.names, 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.names, 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.names, 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.names, 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.names, 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); }