/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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/OperatorNewExtensions.h" // mozilla::KnownNotNull #include "mozilla/ScopeExit.h" #include #include #include "builtin/ModuleObject.h" #include "frontend/CompilationInfo.h" // CompiltionAtomCache, CompilationInput, BaseCompilationStencil, CompilationGCOutput #include "frontend/Parser.h" // Copy*ScopeData #include "frontend/ScriptIndex.h" // ScriptIndex #include "frontend/SharedContext.h" #include "frontend/Stencil.h" #include "gc/Allocator.h" #include "gc/MaybeRooted.h" #include "util/StringBuffer.h" #include "vm/EnvironmentObject.h" #include "vm/JSScript.h" #include "wasm/WasmInstance.h" #include "gc/FreeOp-inl.h" #include "gc/ObjectKind-inl.h" #include "vm/Shape-inl.h" using namespace js; using namespace js::frontend; using mozilla::Maybe; 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::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::FunctionLexical: return "function lexical"; case ScopeKind::ClassBody: return "class body"; 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"; case ScopeKind::WasmInstance: return "wasm instance"; case ScopeKind::WasmFunction: return "wasm function"; } MOZ_CRASH("Bad ScopeKind"); } Shape* js::EmptyEnvironmentShape(JSContext* cx, const JSClass* 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(JSContext* 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 child(cx, StackShape(base, id, slot, attrs)); return cx->zone()->propertyTree().getChild(cx, shape, child); } Shape* js::CreateEnvironmentShape(JSContext* cx, BindingIter& bi, const JSClass* cls, uint32_t numSlots, uint32_t baseShapeFlags) { RootedShape shape(cx, EmptyEnvironmentShape(cx, cls, numSlots, baseShapeFlags)); if (!shape) { return nullptr; } RootedAtom name(cx); StackBaseShape stackBase(cls, baseShapeFlags); for (; bi; bi++) { BindingLocation loc = bi.location(); if (loc.kind() == BindingLocation::Kind::Environment) { name = bi.name(); cx->markAtom(name); shape = NextEnvironmentShape(cx, name, bi.kind(), loc.slot(), stackBase, shape); if (!shape) { return nullptr; } } } return shape; } Shape* js::CreateEnvironmentShape( JSContext* cx, frontend::CompilationAtomCache& atomCache, AbstractBindingIter& bi, const JSClass* cls, uint32_t numSlots, uint32_t baseShapeFlags) { RootedShape shape(cx, EmptyEnvironmentShape(cx, cls, numSlots, baseShapeFlags)); if (!shape) { return nullptr; } RootedAtom name(cx); StackBaseShape stackBase(cls, baseShapeFlags); for (; bi; bi++) { BindingLocation loc = bi.location(); if (loc.kind() == BindingLocation::Kind::Environment) { name = atomCache.getExistingAtomAt(cx, bi.name()); MOZ_ASSERT(name); cx->markAtom(name); shape = NextEnvironmentShape(cx, name, bi.kind(), loc.slot(), stackBase, shape); if (!shape) { return nullptr; } } } return shape; } template inline size_t SizeOfAllocatedData(DataT* data) { return SizeOfScopeData(data->slotInfo.length); } template static UniquePtr CopyScopeData( JSContext* cx, typename ConcreteScope::RuntimeData* data) { using Data = typename ConcreteScope::RuntimeData; // Make sure the binding names are marked in the context's zone, if we are // copying data from another zone. BindingName* names = data->trailingNames.start(); uint32_t length = data->slotInfo.length; for (size_t i = 0; i < length; i++) { if (JSAtom* name = names[i].name()) { cx->markAtom(name); } } size_t size = SizeOfAllocatedData(data); void* bytes = cx->pod_malloc(size); if (!bytes) { return nullptr; } auto* dataCopy = new (bytes) Data(*data); std::uninitialized_copy_n(data->trailingNames.start(), data->slotInfo.length, dataCopy->trailingNames.start()); return UniquePtr(dataCopy); } template static void MarkParserScopeData(JSContext* cx, typename ConcreteScope::ParserData* data, frontend::CompilationState& compilationState) { auto* names = data->trailingNames.start(); uint32_t length = data->slotInfo.length; for (size_t i = 0; i < length; i++) { auto index = names[i].name(); if (!index) { continue; } compilationState.getParserAtomAt(cx, index)->markUsedByStencil(); } } static bool SetEnvironmentShape(JSContext* cx, BindingIter& freshBi, BindingIter& bi, const JSClass* cls, uint32_t firstFrameSlot, uint32_t baseShapeFlags, MutableHandleShape envShape) { envShape.set(CreateEnvironmentShape( cx, freshBi, cls, bi.nextEnvironmentSlot(), baseShapeFlags)); return envShape; } static bool SetEnvironmentShape(JSContext* cx, ParserBindingIter& freshBi, ParserBindingIter& bi, const JSClass* cls, uint32_t firstFrameSlot, uint32_t baseShapeFlags, mozilla::Maybe* envShape) { envShape->emplace(bi.nextEnvironmentSlot()); return true; } template static bool PrepareScopeData( JSContext* cx, AbstractBindingIter& bi, typename MaybeRootedScopeData::HandleType data, uint32_t firstFrameSlot, ShapeT envShape) { const JSClass* cls = &EnvironmentT::class_; uint32_t baseShapeFlags = EnvironmentT::BASESHAPE_FLAGS; // Copy a fresh BindingIter for use below. AbstractBindingIter freshBi(bi); // Iterate through all bindings. This counts the number of environment // slots needed and computes the maximum frame slot. while (bi) { bi++; } data->slotInfo.nextFrameSlot = bi.canHaveFrameSlots() ? bi.nextFrameSlot() : LOCALNO_LIMIT; // Data is not used after this point. Before this point, gc cannot // occur, so `data` is fine as a raw pointer. // Make a new environment shape if any environment slots were used. if (bi.nextEnvironmentSlot() != JSSLOT_FREE(cls)) { if (!SetEnvironmentShape(cx, freshBi, bi, cls, firstFrameSlot, baseShapeFlags, envShape)) { return false; } } return true; } template static typename ConcreteScope::ParserData* NewEmptyParserScopeData( JSContext* cx, LifoAlloc& alloc, uint32_t length = 0) { using Data = typename ConcreteScope::ParserData; size_t dataSize = SizeOfScopeData(length); void* raw = alloc.alloc(dataSize); if (!raw) { js::ReportOutOfMemory(cx); return nullptr; } return new (raw) Data(length); } template static UniquePtr> NewEmptyScopeData( JSContext* cx, uint32_t length = 0) { using Data = AbstractScopeData; size_t dataSize = SizeOfScopeData(length); uint8_t* bytes = cx->pod_malloc(dataSize); auto data = reinterpret_cast(bytes); if (data) { new (data) Data(length); } return UniquePtr(data); } template static UniquePtr LiftParserScopeData( JSContext* cx, frontend::CompilationAtomCache& atomCache, BaseParserScopeData* baseData) { using ConcreteData = typename ConcreteScope::RuntimeData; auto* data = static_cast(baseData); // Convert all scope ParserAtoms to rooted JSAtoms. // Rooting is necessary as conversion can gc. JS::RootedVector jsatoms(cx); if (!jsatoms.reserve(data->slotInfo.length)) { return nullptr; } auto* names = data->trailingNames.start(); uint32_t length = data->slotInfo.length; for (size_t i = 0; i < length; i++) { JSAtom* jsatom = nullptr; if (names[i].name()) { jsatom = atomCache.getExistingAtomAt(cx, names[i].name()); MOZ_ASSERT(jsatom); } jsatoms.infallibleAppend(jsatom); } // Allocate a new scope-data of the right kind. UniquePtr scopeData( NewEmptyScopeData(cx, data->slotInfo.length)); if (!scopeData) { return nullptr; } memcpy(&scopeData.get()->slotInfo, &data->slotInfo, sizeof(typename ConcreteScope::SlotInfo)); // Initialize new scoped names. auto* namesOut = scopeData->trailingNames.start(); for (size_t i = 0; i < length; i++) { namesOut[i] = names[i].copyWithNewAtom(jsatoms[i].get()); } return scopeData; } static constexpr size_t HasAtomMask = 1; static constexpr size_t HasAtomShift = 1; static XDRResult XDRTrailingName(XDRState* xdr, BindingName* bindingName, const uint32_t* length) { JSContext* cx = xdr->cx(); RootedAtom atom(cx, bindingName->name()); bool hasAtom = !!atom; uint8_t flags = bindingName->flagsForXDR(); MOZ_ASSERT(((flags << HasAtomShift) >> HasAtomShift) == flags); uint8_t u8 = (flags << HasAtomShift) | uint8_t(hasAtom); MOZ_TRY(xdr->codeUint8(&u8)); if (hasAtom) { MOZ_TRY(XDRAtom(xdr, &atom)); } return Ok(); } static XDRResult XDRTrailingName(XDRState* xdr, void* bindingName, uint32_t* length) { JSContext* cx = xdr->cx(); uint8_t u8; MOZ_TRY(xdr->codeUint8(&u8)); bool hasAtom = u8 & HasAtomMask; RootedAtom atom(cx); if (hasAtom) { MOZ_TRY(XDRAtom(xdr, &atom)); } uint8_t flags = u8 >> HasAtomShift; new (bindingName) BindingName(BindingName::fromXDR(atom, flags)); ++*length; return Ok(); } template /* static */ XDRResult Scope::XDRSizedBindingNames( XDRState* xdr, Handle scope, MutableHandle data) { MOZ_ASSERT(!data); JSContext* cx = xdr->cx(); uint32_t length; if (mode == XDR_ENCODE) { length = scope->data().slotInfo.length; } MOZ_TRY(xdr->codeUint32(&length)); if (mode == XDR_ENCODE) { data.set(&scope->data()); } else { data.set(NewEmptyScopeData(cx, length).release()); if (!data) { return xdr->fail(JS::TranscodeResult_Throw); } } auto dataGuard = mozilla::MakeScopeExit([&]() { if (mode == XDR_DECODE) { js_delete(data.get()); data.set(nullptr); } }); for (uint32_t i = 0; i < length; i++) { if (mode == XDR_DECODE) { MOZ_ASSERT(i == data->slotInfo.length, "must be decoding at the end"); } MOZ_TRY( XDRTrailingName(xdr, &data->trailingNames[i], &data->slotInfo.length)); } MOZ_ASSERT(data->slotInfo.length == length); dataGuard.release(); return Ok(); } /* static */ Scope* Scope::create(JSContext* cx, ScopeKind kind, HandleScope enclosing, HandleShape envShape) { Scope* scope = Allocate(cx); if (scope) { new (scope) Scope(kind, enclosing, envShape); } return scope; } template /* static */ ConcreteScope* Scope::create( JSContext* cx, ScopeKind kind, HandleScope enclosing, HandleShape envShape, MutableHandle> 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(data); return &scope->as(); } template inline void Scope::initData( MutableHandle> data) { MOZ_ASSERT(!rawData()); AddCellMemory(this, SizeOfAllocatedData(data.get().get()), MemoryUse::ScopeData); setHeaderPtr(data.get().release()); } template bool Scope::updateEnvShapeIfRequired(JSContext* cx, MutableHandleShape envShape, bool needsEnvironment) { if (!envShape && needsEnvironment) { envShape.set(EmptyEnvironmentShape(cx)); if (!envShape) { return false; } } return true; } template bool Scope::updateEnvShapeIfRequired(JSContext* cx, mozilla::Maybe* envShape, bool needsEnvironment) { if (envShape->isNothing() && needsEnvironment) { uint32_t numSlots = 0; envShape->emplace(numSlots); } return true; } uint32_t Scope::firstFrameSlot() const { switch (kind()) { case ScopeKind::Lexical: case ScopeKind::SimpleCatch: case ScopeKind::Catch: case ScopeKind::FunctionLexical: case ScopeKind::ClassBody: // For intra-frame scopes, find the enclosing scope's next frame slot. MOZ_ASSERT(is()); return LexicalScope::nextFrameSlot(enclosing()); case ScopeKind::NamedLambda: case ScopeKind::StrictNamedLambda: // Named lambda scopes cannot have frame slots. return LOCALNO_LIMIT; case ScopeKind::FunctionBodyVar: if (enclosing()->is()) { return enclosing()->as().nextFrameSlot(); } break; default: break; } return 0; } uint32_t Scope::chainLength() const { uint32_t length = 0; for (ScopeIter si(const_cast(this)); si; si++) { length++; } return length; } uint32_t Scope::environmentChainLength() const { uint32_t length = 0; for (ScopeIter si(const_cast(this)); si; si++) { if (si.hasSyntacticEnvironment()) { length++; } } return length; } Shape* Scope::maybeCloneEnvironmentShape(JSContext* cx) { // Clone the environment shape if cloning into a different zone. Shape* shape = environmentShape(); if (shape && shape->zoneFromAnyThread() != cx->zone()) { BindingIter bi(this); return CreateEnvironmentShape(cx, bi, shape->getObjectClass(), shape->slotSpan(), shape->getObjectFlags()); } return shape; } /* 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: { RootedScript script(cx, scope->as().script()); const char* filename = script->filename(); // If the script has an internal URL, include it in the crash reason. If // not, it may be a web URL, and therefore privacy-sensitive. if (!strncmp(filename, "chrome:", 7) || !strncmp(filename, "resource:", 9)) { MOZ_CRASH_UNSAFE_PRINTF("Use FunctionScope::clone (script URL: %s)", filename); } MOZ_CRASH("Use FunctionScope::clone."); break; } case ScopeKind::FunctionBodyVar: { Rooted> dataClone(cx); dataClone = CopyScopeData(cx, &scope->as().data()); if (!dataClone) { return nullptr; } return create(cx, scope->kind_, enclosing, envShape, &dataClone); } case ScopeKind::Lexical: case ScopeKind::SimpleCatch: case ScopeKind::Catch: case ScopeKind::NamedLambda: case ScopeKind::StrictNamedLambda: case ScopeKind::FunctionLexical: case ScopeKind::ClassBody: { Rooted> dataClone(cx); dataClone = CopyScopeData(cx, &scope->as().data()); if (!dataClone) { return nullptr; } return create(cx, scope->kind_, enclosing, envShape, &dataClone); } case ScopeKind::With: return create(cx, scope->kind_, enclosing, envShape); case ScopeKind::Eval: case ScopeKind::StrictEval: { Rooted> dataClone(cx); dataClone = CopyScopeData(cx, &scope->as().data()); if (!dataClone) { return nullptr; } return create(cx, scope->kind_, enclosing, envShape, &dataClone); } case ScopeKind::Global: case ScopeKind::NonSyntactic: MOZ_CRASH("Use GlobalScope::clone."); break; case ScopeKind::WasmFunction: MOZ_CRASH("wasm functions are not nested in JSScript"); break; case ScopeKind::Module: case ScopeKind::WasmInstance: MOZ_CRASH("NYI"); break; } return nullptr; } void Scope::finalize(JSFreeOp* fop) { MOZ_ASSERT(CurrentThreadIsGCFinalizing()); applyScopeDataTyped([this, fop](auto data) { fop->delete_(this, data, SizeOfAllocatedData(data), MemoryUse::ScopeData); }); setHeaderPtr(nullptr); } size_t Scope::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { if (rawData()) { return mallocSizeOf(rawData()); } 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"); } #if defined(DEBUG) || defined(JS_JITSPEW) /* static */ bool Scope::dumpForDisassemble(JSContext* cx, JS::Handle scope, GenericPrinter& out, const char* indent) { if (!out.put(ScopeKindString(scope->kind()))) { return false; } if (!out.put(" {")) { return false; } size_t i = 0; for (Rooted bi(cx, BindingIter(scope)); bi; bi++, i++) { if (i == 0) { if (!out.put("\n")) { return false; } } UniqueChars bytes = AtomToPrintableString(cx, bi.name()); if (!bytes) { return false; } if (!out.put(indent)) { return false; } if (!out.printf(" %2zu: %s %s ", i, BindingKindString(bi.kind()), bytes.get())) { return false; } switch (bi.location().kind()) { case BindingLocation::Kind::Global: if (bi.isTopLevelFunction()) { if (!out.put("(global function)\n")) { return false; } } else { if (!out.put("(global)\n")) { return false; } } break; case BindingLocation::Kind::Argument: if (!out.printf("(arg slot %u)\n", bi.location().argumentSlot())) { return false; } break; case BindingLocation::Kind::Frame: if (!out.printf("(frame slot %u)\n", bi.location().slot())) { return false; } break; case BindingLocation::Kind::Environment: if (!out.printf("(env slot %u)\n", bi.location().slot())) { return false; } break; case BindingLocation::Kind::NamedLambdaCallee: if (!out.put("(named lambda callee)\n")) { return false; } break; case BindingLocation::Kind::Import: if (!out.put("(import)\n")) { return false; } break; } } if (i > 0) { if (!out.put(indent)) { return false; } } if (!out.put("}")) { return false; } ScopeIter si(scope); si++; for (; si; si++) { if (!out.put(" -> ")) { return false; } if (!out.put(ScopeKindString(si.kind()))) { return false; } } return true; } #endif /* defined(DEBUG) || defined(JS_JITSPEW) */ /* static */ uint32_t LexicalScope::nextFrameSlot(Scope* scope) { for (ScopeIter si(scope); si; si++) { switch (si.kind()) { case ScopeKind::With: continue; case ScopeKind::Function: return si.scope()->as().nextFrameSlot(); case ScopeKind::FunctionBodyVar: return si.scope()->as().nextFrameSlot(); case ScopeKind::Lexical: case ScopeKind::SimpleCatch: case ScopeKind::Catch: case ScopeKind::FunctionLexical: case ScopeKind::ClassBody: return si.scope()->as().nextFrameSlot(); case ScopeKind::NamedLambda: case ScopeKind::StrictNamedLambda: // Named lambda scopes cannot have frame slots. return 0; case ScopeKind::Eval: case ScopeKind::StrictEval: return si.scope()->as().nextFrameSlot(); case ScopeKind::Global: case ScopeKind::NonSyntactic: return 0; case ScopeKind::Module: return si.scope()->as().nextFrameSlot(); case ScopeKind::WasmInstance: case ScopeKind::WasmFunction: // Invalid; MOZ_CRASH below. break; } } MOZ_CRASH("Not an enclosing intra-frame Scope"); } template bool LexicalScope::prepareForScopeCreation( JSContext* cx, ScopeKind kind, uint32_t firstFrameSlot, typename MaybeRootedScopeData::MutableHandleType data, ShapeT envShape) { bool isNamedLambda = kind == ScopeKind::NamedLambda || kind == ScopeKind::StrictNamedLambda; MOZ_ASSERT_IF(isNamedLambda, firstFrameSlot == LOCALNO_LIMIT); AbstractBindingIter bi(*data, firstFrameSlot, isNamedLambda); if (!PrepareScopeData( cx, bi, data, firstFrameSlot, envShape)) { return false; } return true; } /* static */ LexicalScope* LexicalScope::createWithData( JSContext* cx, ScopeKind kind, MutableHandle> data, uint32_t firstFrameSlot, HandleScope enclosing) { RootedShape envShape(cx); if (!prepareForScopeCreation(cx, kind, firstFrameSlot, data, &envShape)) { return nullptr; } auto scope = Scope::create(cx, kind, enclosing, envShape, data); if (!scope) { return nullptr; } MOZ_ASSERT(scope->firstFrameSlot() == firstFrameSlot); return scope; } /* static */ Shape* LexicalScope::getEmptyExtensibleEnvironmentShape(JSContext* cx) { const JSClass* cls = &LexicalEnvironmentObject::class_; return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), /* baseShapeFlags = */ 0); } template /* static */ XDRResult LexicalScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope) { JSContext* cx = xdr->cx(); Rooted data(cx); MOZ_TRY( XDRSizedBindingNames(xdr, scope.as(), &data)); { Maybe>> uniqueData; if (mode == XDR_DECODE) { uniqueData.emplace(cx, data); } uint32_t firstFrameSlot; uint32_t nextFrameSlot; if (mode == XDR_ENCODE) { firstFrameSlot = scope->firstFrameSlot(); nextFrameSlot = data->slotInfo.nextFrameSlot; } MOZ_TRY(xdr->codeUint32(&data->slotInfo.constStart)); MOZ_TRY(xdr->codeUint32(&firstFrameSlot)); MOZ_TRY(xdr->codeUint32(&nextFrameSlot)); if (mode == XDR_DECODE) { scope.set(createWithData(cx, kind, &uniqueData.ref(), firstFrameSlot, enclosing)); if (!scope) { return xdr->fail(JS::TranscodeResult_Throw); } // nextFrameSlot is used only for this correctness check. MOZ_ASSERT(nextFrameSlot == scope->as().data().slotInfo.nextFrameSlot); } } return Ok(); } template /* static */ XDRResult LexicalScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope); template /* static */ XDRResult LexicalScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope); static void SetCanonicalFunction(FunctionScope::RuntimeData& data, HandleFunction fun) { data.canonicalFunction.init(fun); } static void SetCanonicalFunction(FunctionScope::ParserData& data, HandleFunction fun) {} template bool FunctionScope::prepareForScopeCreation( JSContext* cx, typename MaybeRootedScopeData::MutableHandleType data, bool hasParameterExprs, bool needsEnvironment, HandleFunction fun, ShapeT envShape) { uint32_t firstFrameSlot = 0; AbstractBindingIter bi(*data, hasParameterExprs); if (!PrepareScopeData( cx, bi, data, firstFrameSlot, envShape)) { return false; } if (hasParameterExprs) { data->slotInfo.setHasParameterExprs(); } SetCanonicalFunction(*data, 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 or async function // Also see |FunctionBox::needsExtraBodyVarEnvironmentRegardlessOfBindings()|. return updateEnvShapeIfRequired(cx, envShape, needsEnvironment); } /* static */ FunctionScope* FunctionScope::createWithData( JSContext* cx, MutableHandle> data, bool hasParameterExprs, bool needsEnvironment, HandleFunction fun, HandleScope enclosing) { MOZ_ASSERT(data); MOZ_ASSERT(fun->isTenured()); RootedShape envShape(cx); if (!prepareForScopeCreation(cx, data, hasParameterExprs, needsEnvironment, fun, &envShape)) { return nullptr; } return Scope::create(cx, ScopeKind::Function, enclosing, envShape, data); } JSScript* FunctionScope::script() const { return canonicalFunction()->nonLazyScript(); } /* static */ bool FunctionScope::isSpecialName(JSContext* cx, JSAtom* name) { return name == cx->names().arguments || name == cx->names().dotThis || name == cx->names().dotGenerator; } /* static */ bool FunctionScope::isSpecialName(JSContext* cx, frontend::TaggedParserAtomIndex name) { return name == frontend::TaggedParserAtomIndex::arguments() || name == frontend::TaggedParserAtomIndex::dotThis() || name == frontend::TaggedParserAtomIndex::dotGenerator(); } /* static */ FunctionScope* FunctionScope::clone(JSContext* cx, Handle scope, HandleFunction fun, HandleScope enclosing) { MOZ_ASSERT(fun != scope->canonicalFunction()); RootedShape envShape(cx); if (scope->environmentShape()) { envShape = scope->maybeCloneEnvironmentShape(cx); if (!envShape) { return nullptr; } } Rooted dataOriginal(cx, &scope->as().data()); Rooted> dataClone( cx, CopyScopeData(cx, dataOriginal)); if (!dataClone) { return nullptr; } dataClone->canonicalFunction = fun; return Scope::create(cx, scope->kind(), enclosing, envShape, &dataClone); } template /* static */ XDRResult FunctionScope::XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosing, MutableHandleScope scope) { JSContext* cx = xdr->cx(); Rooted data(cx); MOZ_TRY(XDRSizedBindingNames(xdr, scope.as(), &data)); { Maybe>> 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->slotInfo.hasParameterExprs(); nextFrameSlot = data->slotInfo.nextFrameSlot; } MOZ_TRY(xdr->codeUint8(&needsEnvironment)); MOZ_TRY(xdr->codeUint8(&hasParameterExprs)); MOZ_TRY(xdr->codeUint16(&data->slotInfo.nonPositionalFormalStart)); MOZ_TRY(xdr->codeUint16(&data->slotInfo.varStart)); MOZ_TRY(xdr->codeUint32(&nextFrameSlot)); if (mode == XDR_DECODE) { if (!data->slotInfo.length) { MOZ_ASSERT(!data->slotInfo.nonPositionalFormalStart); MOZ_ASSERT(!data->slotInfo.varStart); MOZ_ASSERT(!data->slotInfo.nextFrameSlot); } scope.set(createWithData(cx, &uniqueData.ref(), hasParameterExprs, needsEnvironment, fun, enclosing)); if (!scope) { return xdr->fail(JS::TranscodeResult_Throw); } // nextFrameSlot is used only for this correctness check. MOZ_ASSERT(nextFrameSlot == scope->as().data().slotInfo.nextFrameSlot); } } return Ok(); } template /* static */ XDRResult FunctionScope::XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosing, MutableHandleScope scope); template /* static */ XDRResult FunctionScope::XDR(XDRState* xdr, HandleFunction fun, HandleScope enclosing, MutableHandleScope scope); template bool VarScope::prepareForScopeCreation( JSContext* cx, ScopeKind kind, typename MaybeRootedScopeData::MutableHandleType data, uint32_t firstFrameSlot, bool needsEnvironment, ShapeT envShape) { AbstractBindingIter bi(*data, firstFrameSlot); if (!PrepareScopeData( cx, bi, data, firstFrameSlot, envShape)) { return false; } // An environment may be needed regardless of existence of any closed over // bindings: // - Extensible scopes (i.e., due to direct eval) // - Being a generator return updateEnvShapeIfRequired(cx, envShape, needsEnvironment); } /* static */ VarScope* VarScope::createWithData(JSContext* cx, ScopeKind kind, MutableHandle> data, uint32_t firstFrameSlot, bool needsEnvironment, HandleScope enclosing) { MOZ_ASSERT(data); RootedShape envShape(cx); if (!prepareForScopeCreation(cx, kind, data, firstFrameSlot, needsEnvironment, &envShape)) { return nullptr; } return Scope::create(cx, kind, enclosing, envShape, data); } template /* static */ XDRResult VarScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope) { JSContext* cx = xdr->cx(); Rooted data(cx); MOZ_TRY(XDRSizedBindingNames(xdr, scope.as(), &data)); { Maybe>> 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->firstFrameSlot(); nextFrameSlot = data->slotInfo.nextFrameSlot; } MOZ_TRY(xdr->codeUint8(&needsEnvironment)); MOZ_TRY(xdr->codeUint32(&firstFrameSlot)); MOZ_TRY(xdr->codeUint32(&nextFrameSlot)); if (mode == XDR_DECODE) { if (!data->slotInfo.length) { MOZ_ASSERT(!data->slotInfo.nextFrameSlot); } scope.set(createWithData(cx, kind, &uniqueData.ref(), firstFrameSlot, needsEnvironment, enclosing)); if (!scope) { return xdr->fail(JS::TranscodeResult_Throw); } // nextFrameSlot is used only for this correctness check. MOZ_ASSERT(nextFrameSlot == scope->as().data().slotInfo.nextFrameSlot); } } return Ok(); } template /* static */ XDRResult VarScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope); template /* static */ XDRResult VarScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope); /* static */ GlobalScope* GlobalScope::create(JSContext* cx, ScopeKind kind, Handle dataArg) { // The data that's passed in is from the frontend and is LifoAlloc'd. // Copy it now that we're creating a permanent VM scope. Rooted> data( cx, dataArg ? CopyScopeData(cx, dataArg) : NewEmptyScopeData(cx)); if (!data) { return nullptr; } return createWithData(cx, kind, &data); } /* static */ GlobalScope* GlobalScope::createWithData( JSContext* cx, ScopeKind kind, MutableHandle> 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. return Scope::create(cx, kind, nullptr, nullptr, data); } /* static */ GlobalScope* GlobalScope::clone(JSContext* cx, Handle scope, ScopeKind kind) { Rooted dataOriginal(cx, &scope->as().data()); Rooted> dataClone( cx, CopyScopeData(cx, dataOriginal)); if (!dataClone) { return nullptr; } return Scope::create(cx, kind, nullptr, nullptr, &dataClone); } template /* static */ XDRResult GlobalScope::XDR(XDRState* xdr, ScopeKind kind, MutableHandleScope scope) { MOZ_ASSERT((mode == XDR_DECODE) == !scope); JSContext* cx = xdr->cx(); Rooted data(cx); MOZ_TRY( XDRSizedBindingNames(xdr, scope.as(), &data)); { Maybe>> uniqueData; if (mode == XDR_DECODE) { uniqueData.emplace(cx, data); } MOZ_TRY(xdr->codeUint32(&data->slotInfo.letStart)); MOZ_TRY(xdr->codeUint32(&data->slotInfo.constStart)); if (mode == XDR_DECODE) { if (!data->slotInfo.length) { MOZ_ASSERT(!data->slotInfo.letStart); MOZ_ASSERT(!data->slotInfo.constStart); } scope.set(createWithData(cx, kind, &uniqueData.ref())); if (!scope) { return xdr->fail(JS::TranscodeResult_Throw); } } } return Ok(); } template /* static */ XDRResult GlobalScope::XDR(XDRState* xdr, ScopeKind kind, MutableHandleScope scope); template /* static */ XDRResult GlobalScope::XDR(XDRState* xdr, ScopeKind kind, MutableHandleScope scope); /* static */ WithScope* WithScope::create(JSContext* cx, HandleScope enclosing) { Scope* scope = Scope::create(cx, ScopeKind::With, enclosing, nullptr); return static_cast(scope); } template /* static */ XDRResult WithScope::XDR(XDRState* xdr, HandleScope enclosing, MutableHandleScope scope) { JSContext* cx = xdr->cx(); if (mode == XDR_DECODE) { scope.set(create(cx, enclosing)); if (!scope) { return xdr->fail(JS::TranscodeResult_Throw); } } return Ok(); } template /* static */ XDRResult WithScope::XDR(XDRState* xdr, HandleScope enclosing, MutableHandleScope scope); template /* static */ XDRResult WithScope::XDR(XDRState* xdr, HandleScope enclosing, MutableHandleScope scope); template bool EvalScope::prepareForScopeCreation( JSContext* cx, ScopeKind scopeKind, typename MaybeRootedScopeData::MutableHandleType data, ShapeT envShape) { if (scopeKind == ScopeKind::StrictEval) { uint32_t firstFrameSlot = 0; AbstractBindingIter bi(*data, true); if (!PrepareScopeData( cx, bi, data, firstFrameSlot, envShape)) { return false; } } // Strict eval and direct eval in parameter expressions always get their own // var environment even if there are no bindings. bool needsEnvironment = (scopeKind == ScopeKind::StrictEval); return updateEnvShapeIfRequired(cx, envShape, needsEnvironment); } /* static */ EvalScope* EvalScope::createWithData(JSContext* cx, ScopeKind scopeKind, MutableHandle> data, HandleScope enclosing) { MOZ_ASSERT(data); RootedShape envShape(cx); if (!prepareForScopeCreation(cx, scopeKind, data, &envShape)) { return nullptr; } return Scope::create(cx, scopeKind, enclosing, envShape, data); } /* static */ Scope* EvalScope::nearestVarScopeForDirectEval(Scope* scope) { for (ScopeIter si(scope); si; si++) { switch (si.kind()) { case ScopeKind::Function: case ScopeKind::FunctionBodyVar: case ScopeKind::Global: case ScopeKind::NonSyntactic: return scope; default: break; } } return nullptr; } template /* static */ XDRResult EvalScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope) { JSContext* cx = xdr->cx(); Rooted data(cx); { Maybe>> uniqueData; if (mode == XDR_DECODE) { uniqueData.emplace(cx, data); } MOZ_TRY(XDRSizedBindingNames(xdr, scope.as(), &data)); if (mode == XDR_DECODE) { if (!data->slotInfo.length) { MOZ_ASSERT(!data->slotInfo.nextFrameSlot); } scope.set(createWithData(cx, kind, &uniqueData.ref(), enclosing)); if (!scope) { return xdr->fail(JS::TranscodeResult_Throw); } } } return Ok(); } template /* static */ XDRResult EvalScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope); template /* static */ XDRResult EvalScope::XDR(XDRState* xdr, ScopeKind kind, HandleScope enclosing, MutableHandleScope scope); ModuleScope::RuntimeData::RuntimeData(size_t nameCount) : trailingNames(nameCount) {} static void InitModule(ModuleScope::RuntimeData& data, HandleModuleObject module) { data.module.init(module); } static void InitModule(ModuleScope::ParserData& data, HandleModuleObject module) {} /* static */ template bool ModuleScope::prepareForScopeCreation( JSContext* cx, typename MaybeRootedScopeData::MutableHandleType data, HandleModuleObject module, ShapeT envShape) { uint32_t firstFrameSlot = 0; AbstractBindingIter bi(*data); if (!PrepareScopeData( cx, bi, data, firstFrameSlot, envShape)) { return false; } InitModule(*data, module); // Modules always need an environment object for now. bool needsEnvironment = true; return updateEnvShapeIfRequired(cx, envShape, needsEnvironment); } /* static */ ModuleScope* ModuleScope::createWithData( JSContext* cx, MutableHandle> data, HandleModuleObject module, HandleScope enclosing) { MOZ_ASSERT(data); MOZ_ASSERT(enclosing->is()); RootedShape envShape(cx); if (!prepareForScopeCreation(cx, data, module, &envShape)) { return nullptr; } return Scope::create(cx, ScopeKind::Module, enclosing, envShape, data); } template static JSAtom* GenerateWasmName(JSContext* cx, const char (&prefix)[ArrayLength], uint32_t index) { StringBuffer sb(cx); if (!sb.append(prefix)) { return nullptr; } if (!NumberValueToStringBuffer(cx, Int32Value(index), sb)) { return nullptr; } return sb.finishAtom(); } template /* static */ XDRResult ModuleScope::XDR(XDRState* xdr, HandleModuleObject module, HandleScope enclosing, MutableHandleScope scope) { JSContext* cx = xdr->cx(); Rooted data(cx); MOZ_TRY( XDRSizedBindingNames(xdr, scope.as(), &data)); { Maybe>> uniqueData; if (mode == XDR_DECODE) { uniqueData.emplace(cx, data); } uint32_t nextFrameSlot; if (mode == XDR_ENCODE) { nextFrameSlot = data->slotInfo.nextFrameSlot; } MOZ_TRY(xdr->codeUint32(&data->slotInfo.varStart)); MOZ_TRY(xdr->codeUint32(&data->slotInfo.letStart)); MOZ_TRY(xdr->codeUint32(&data->slotInfo.constStart)); MOZ_TRY(xdr->codeUint32(&nextFrameSlot)); if (mode == XDR_DECODE) { if (!data->slotInfo.length) { MOZ_ASSERT(!data->slotInfo.varStart); MOZ_ASSERT(!data->slotInfo.letStart); MOZ_ASSERT(!data->slotInfo.constStart); MOZ_ASSERT(!data->slotInfo.nextFrameSlot); } scope.set(createWithData(cx, &uniqueData.ref(), module, enclosing)); if (!scope) { return xdr->fail(JS::TranscodeResult_Throw); } // nextFrameSlot is used only for this correctness check. MOZ_ASSERT(nextFrameSlot == scope->as().data().slotInfo.nextFrameSlot); } } return Ok(); } template /* static */ XDRResult ModuleScope::XDR(XDRState* xdr, HandleModuleObject module, HandleScope enclosing, MutableHandleScope scope); template /* static */ XDRResult ModuleScope::XDR(XDRState* xdr, HandleModuleObject module, HandleScope enclosing, MutableHandleScope scope); static void InitializeTrailingName( AbstractTrailingNamesArray& trailingNames, size_t i, JSAtom* name) { void* trailingName = &trailingNames[i]; new (trailingName) BindingName(name, false); } template static void InitializeNextTrailingName(const Rooted>& data, JSAtom* name) { InitializeTrailingName(data->trailingNames, data->slotInfo.length, name); data->slotInfo.length++; } WasmInstanceScope::RuntimeData::RuntimeData(size_t nameCount) : trailingNames(nameCount) {} /* static */ WasmInstanceScope* WasmInstanceScope::create(JSContext* cx, WasmInstanceObject* instance) { size_t namesCount = 0; if (instance->instance().memory()) { namesCount++; } size_t globalsStart = namesCount; size_t globalsCount = instance->instance().metadata().globals.length(); namesCount += globalsCount; Rooted> data( cx, NewEmptyScopeData(cx, namesCount)); if (!data) { return nullptr; } if (instance->instance().memory()) { JSAtom* wasmName = GenerateWasmName(cx, "memory", /* index = */ 0); if (!wasmName) { return nullptr; } InitializeNextTrailingName(data, wasmName); } for (size_t i = 0; i < globalsCount; i++) { JSAtom* wasmName = GenerateWasmName(cx, "global", i); if (!wasmName) { return nullptr; } InitializeNextTrailingName(data, wasmName); } MOZ_ASSERT(data->slotInfo.length == namesCount); data->instance.init(instance); data->slotInfo.globalsStart = globalsStart; RootedScope enclosing(cx, &cx->global()->emptyGlobalScope()); return Scope::create(cx, ScopeKind::WasmInstance, enclosing, /* envShape = */ nullptr, &data); } /* static */ WasmFunctionScope* WasmFunctionScope::create(JSContext* cx, HandleScope enclosing, uint32_t funcIndex) { MOZ_ASSERT(enclosing->is()); Rooted wasmFunctionScope(cx); Rooted instance( cx, enclosing->as().instance()); // TODO pull the local variable names from the wasm function definition. wasm::ValTypeVector locals; size_t argsLength; wasm::StackResults unusedStackResults; if (!instance->instance().debug().debugGetLocalTypes( funcIndex, &locals, &argsLength, &unusedStackResults)) { return nullptr; } uint32_t namesCount = locals.length(); Rooted> data( cx, NewEmptyScopeData(cx, namesCount)); if (!data) { return nullptr; } for (size_t i = 0; i < namesCount; i++) { JSAtom* wasmName = GenerateWasmName(cx, "var", i); if (!wasmName) { return nullptr; } InitializeNextTrailingName(data, wasmName); } MOZ_ASSERT(data->slotInfo.length == namesCount); return Scope::create(cx, ScopeKind::WasmFunction, enclosing, /* envShape = */ nullptr, &data); } ScopeIter::ScopeIter(JSScript* script) : scope_(script->bodyScope()) {} bool ScopeIter::hasSyntacticEnvironment() const { return scope()->hasEnvironment() && scope()->kind() != ScopeKind::NonSyntactic; } AbstractBindingIter::AbstractBindingIter(ScopeKind kind, BaseScopeData* data, uint32_t firstFrameSlot) : BaseAbstractBindingIter() { switch (kind) { case ScopeKind::Lexical: case ScopeKind::SimpleCatch: case ScopeKind::Catch: case ScopeKind::FunctionLexical: case ScopeKind::ClassBody: init(*static_cast(data), firstFrameSlot, 0); break; case ScopeKind::NamedLambda: case ScopeKind::StrictNamedLambda: init(*static_cast(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 (static_cast(data) ->slotInfo.hasParameterExprs()) { flags |= HasFormalParameterExprs; } init(*static_cast(data), flags); break; } case ScopeKind::FunctionBodyVar: init(*static_cast(data), firstFrameSlot); break; case ScopeKind::Eval: case ScopeKind::StrictEval: init(*static_cast(data), kind == ScopeKind::StrictEval); break; case ScopeKind::Global: case ScopeKind::NonSyntactic: init(*static_cast(data)); break; case ScopeKind::Module: init(*static_cast(data)); break; case ScopeKind::WasmInstance: init(*static_cast(data)); break; case ScopeKind::WasmFunction: init(*static_cast(data)); break; } } AbstractBindingIter::AbstractBindingIter(Scope* scope) : AbstractBindingIter(scope->kind(), scope->rawData(), scope->firstFrameSlot()) {} AbstractBindingIter::AbstractBindingIter(JSScript* script) : AbstractBindingIter(script->bodyScope()) {} template void BaseAbstractBindingIter::init( LexicalScope::AbstractData& data, uint32_t firstFrameSlot, uint8_t flags) { auto& slotInfo = data.slotInfo; // 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, CanHaveEnvironmentSlots | flags, firstFrameSlot, JSSLOT_FREE(&LexicalEnvironmentObject::class_), data.trailingNames.start(), slotInfo.length); } else { // imports - [0, 0) // positional formals - [0, 0) // other formals - [0, 0) // vars - [0, 0) // lets - [0, slotInfo.constStart) // consts - [slotInfo.constStart, slotInfo.length) init(0, 0, 0, 0, slotInfo.constStart, CanHaveFrameSlots | CanHaveEnvironmentSlots | flags, firstFrameSlot, JSSLOT_FREE(&LexicalEnvironmentObject::class_), data.trailingNames.start(), slotInfo.length); } } template void BaseAbstractBindingIter::init( LexicalScope::AbstractData&, uint32_t, uint8_t); template void BaseAbstractBindingIter::init( LexicalScope::AbstractData&, uint32_t, uint8_t); template void BaseAbstractBindingIter::init( FunctionScope::AbstractData& data, uint8_t flags) { flags = CanHaveFrameSlots | CanHaveEnvironmentSlots | flags; if (!(flags & HasFormalParameterExprs)) { flags |= CanHaveArgumentSlots; } auto& slotInfo = data.slotInfo; // imports - [0, 0) // positional formals - [0, slotInfo.nonPositionalFormalStart) // other formals - [slotInfo.nonPositionalParamStart, slotInfo.varStart) // vars - [slotInfo.varStart, slotInfo.length) // lets - [slotInfo.length, slotInfo.length) // consts - [slotInfo.length, slotInfo.length) init(0, slotInfo.nonPositionalFormalStart, slotInfo.varStart, slotInfo.length, slotInfo.length, flags, 0, JSSLOT_FREE(&CallObject::class_), data.trailingNames.start(), slotInfo.length); } template void BaseAbstractBindingIter::init( FunctionScope::AbstractData&, uint8_t); template void BaseAbstractBindingIter::init( FunctionScope::AbstractData&, uint8_t); template void BaseAbstractBindingIter::init(VarScope::AbstractData& data, uint32_t firstFrameSlot) { auto& slotInfo = data.slotInfo; // imports - [0, 0) // positional formals - [0, 0) // other formals - [0, 0) // vars - [0, slotInfo.length) // lets - [slotInfo.length, slotInfo.length) // consts - [slotInfo.length, slotInfo.length) init(0, 0, 0, slotInfo.length, slotInfo.length, CanHaveFrameSlots | CanHaveEnvironmentSlots, firstFrameSlot, JSSLOT_FREE(&VarEnvironmentObject::class_), data.trailingNames.start(), slotInfo.length); } template void BaseAbstractBindingIter::init( VarScope::AbstractData&, uint32_t); template void BaseAbstractBindingIter::init( VarScope::AbstractData&, uint32_t); template void BaseAbstractBindingIter::init( GlobalScope::AbstractData& data) { auto& slotInfo = data.slotInfo; // imports - [0, 0) // positional formals - [0, 0) // other formals - [0, 0) // vars - [0, slotInfo.letStart) // lets - [slotInfo.letStart, slotInfo.constStart) // consts - [slotInfo.constStart, slotInfo.length) init(0, 0, 0, slotInfo.letStart, slotInfo.constStart, CannotHaveSlots, UINT32_MAX, UINT32_MAX, data.trailingNames.start(), slotInfo.length); } template void BaseAbstractBindingIter::init( GlobalScope::AbstractData&); template void BaseAbstractBindingIter::init( GlobalScope::AbstractData&); template void BaseAbstractBindingIter::init(EvalScope::AbstractData& 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; } auto& slotInfo = data.slotInfo; // imports - [0, 0) // positional formals - [0, 0) // other formals - [0, 0) // vars - [0, slotInfo.length) // lets - [slotInfo.length, slotInfo.length) // consts - [slotInfo.length, slotInfo.length) init(0, 0, 0, slotInfo.length, slotInfo.length, flags, firstFrameSlot, firstEnvironmentSlot, data.trailingNames.start(), slotInfo.length); } template void BaseAbstractBindingIter::init( EvalScope::AbstractData&, bool); template void BaseAbstractBindingIter::init( EvalScope::AbstractData&, bool); template void BaseAbstractBindingIter::init( ModuleScope::AbstractData& data) { auto& slotInfo = data.slotInfo; // imports - [0, slotInfo.varStart) // positional formals - [slotInfo.varStart, slotInfo.varStart) // other formals - [slotInfo.varStart, slotInfo.varStart) // vars - [slotInfo.varStart, slotInfo.letStart) // lets - [slotInfo.letStart, slotInfo.constStart) // consts - [slotInfo.constStart, slotInfo.length) init(slotInfo.varStart, slotInfo.varStart, slotInfo.varStart, slotInfo.letStart, slotInfo.constStart, CanHaveFrameSlots | CanHaveEnvironmentSlots, 0, JSSLOT_FREE(&ModuleEnvironmentObject::class_), data.trailingNames.start(), slotInfo.length); } template void BaseAbstractBindingIter::init( ModuleScope::AbstractData&); template void BaseAbstractBindingIter::init( ModuleScope::AbstractData&); template void BaseAbstractBindingIter::init( WasmInstanceScope::AbstractData& data) { auto& slotInfo = data.slotInfo; // imports - [0, 0) // positional formals - [0, 0) // other formals - [0, 0) // vars - [0, slotInfo.length) // lets - [slotInfo.length, slotInfo.length) // consts - [slotInfo.length, slotInfo.length) init(0, 0, 0, slotInfo.length, slotInfo.length, CanHaveFrameSlots | CanHaveEnvironmentSlots, UINT32_MAX, UINT32_MAX, data.trailingNames.start(), slotInfo.length); } template void BaseAbstractBindingIter::init( WasmInstanceScope::AbstractData&); template void BaseAbstractBindingIter::init( WasmInstanceScope::AbstractData&); template void BaseAbstractBindingIter::init( WasmFunctionScope::AbstractData& data) { auto& slotInfo = data.slotInfo; // imports - [0, 0) // positional formals - [0, 0) // other formals - [0, 0) // vars - [0, slotInfo.length) // lets - [slotInfo.length, slotInfo.length) // consts - [slotInfo.length, slotInfo.length) init(0, 0, 0, slotInfo.length, slotInfo.length, CanHaveFrameSlots | CanHaveEnvironmentSlots, UINT32_MAX, UINT32_MAX, data.trailingNames.start(), slotInfo.length); } template void BaseAbstractBindingIter::init( WasmFunctionScope::AbstractData&); template void BaseAbstractBindingIter::init( WasmFunctionScope::AbstractData&); PositionalFormalParameterIter::PositionalFormalParameterIter(Scope* scope) : BindingIter(scope) { // Reinit with flags = 0, i.e., iterate over all positional parameters. if (scope->is()) { init(scope->as().data(), /* flags = */ 0); } settle(); } PositionalFormalParameterIter::PositionalFormalParameterIter(JSScript* script) : PositionalFormalParameterIter(script->bodyScope()) {} void js::DumpBindings(JSContext* cx, Scope* scopeArg) { RootedScope scope(cx, scopeArg); for (Rooted bi(cx, BindingIter(scope)); bi; bi++) { UniqueChars bytes = AtomToPrintableString(cx, bi.name()); if (!bytes) { return; } fprintf(stderr, "%s %s ", BindingKindString(bi.kind()), bytes.get()); switch (bi.location().kind()) { case BindingLocation::Kind::Global: if (bi.isTopLevelFunction()) { fprintf(stderr, "global function\n"); } else { 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()) { continue; } LexicalScope& lexicalScope = si.scope()->as(); // 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::size( mozilla::MallocSizeOf mallocSizeOf) const { return js::gc::Arena::thingSize(get().asTenured().getAllocKind()) + get().sizeOfExcludingThis(mallocSizeOf); } template /* static */ bool ScopeStencil::appendScopeStencilAndData( JSContext* cx, CompilationState& compilationState, BaseParserScopeData* data, ScopeIndex* indexOut, Args&&... args) { *indexOut = compilationState.scopeData.length(); if (uint32_t(*indexOut) >= TaggedScriptThingIndex::IndexLimit) { ReportAllocationOverflow(cx); return false; } if (!compilationState.scopeData.emplaceBack(std::forward(args)...)) { js::ReportOutOfMemory(cx); return false; } if (!compilationState.scopeNames.append(data)) { js::ReportOutOfMemory(cx); return false; } return true; } /* static */ bool ScopeStencil::createForFunctionScope( JSContext* cx, frontend::CompilationStencil& stencil, frontend::CompilationState& compilationState, FunctionScope::ParserData* data, bool hasParameterExprs, bool needsEnvironment, ScriptIndex functionIndex, bool isArrow, mozilla::Maybe enclosing, ScopeIndex* index) { auto kind = ScopeKind::Function; using ScopeType = FunctionScope; MOZ_ASSERT(matchScopeKind(kind)); if (data) { MarkParserScopeData(cx, data, compilationState); } else { data = NewEmptyParserScopeData(cx, stencil.alloc); if (!data) { return false; } } // We do not initialize the canonical function while the data is owned by the // ScopeStencil. It gets set in ScopeStencil::releaseData. RootedFunction fun(cx, nullptr); uint32_t firstFrameSlot = 0; mozilla::Maybe envShape; if (!FunctionScope::prepareForScopeCreation( cx, &data, hasParameterExprs, needsEnvironment, fun, &envShape)) { return false; } return appendScopeStencilAndData(cx, compilationState, data, index, kind, enclosing, firstFrameSlot, envShape, mozilla::Some(functionIndex), isArrow); } /* static */ bool ScopeStencil::createForLexicalScope( JSContext* cx, frontend::CompilationStencil& stencil, frontend::CompilationState& compilationState, ScopeKind kind, LexicalScope::ParserData* data, uint32_t firstFrameSlot, mozilla::Maybe enclosing, ScopeIndex* index) { using ScopeType = LexicalScope; MOZ_ASSERT(matchScopeKind(kind)); if (data) { MarkParserScopeData(cx, data, compilationState); } else { data = NewEmptyParserScopeData(cx, stencil.alloc); if (!data) { return false; } } mozilla::Maybe envShape; if (!LexicalScope::prepareForScopeCreation( cx, kind, firstFrameSlot, &data, &envShape)) { return false; } return appendScopeStencilAndData(cx, compilationState, data, index, kind, enclosing, firstFrameSlot, envShape); } bool ScopeStencil::createForVarScope( JSContext* cx, frontend::CompilationStencil& stencil, frontend::CompilationState& compilationState, ScopeKind kind, VarScope::ParserData* data, uint32_t firstFrameSlot, bool needsEnvironment, mozilla::Maybe enclosing, ScopeIndex* index) { using ScopeType = VarScope; MOZ_ASSERT(matchScopeKind(kind)); if (data) { MarkParserScopeData(cx, data, compilationState); } else { data = NewEmptyParserScopeData(cx, stencil.alloc); if (!data) { return false; } } mozilla::Maybe envShape; if (!VarScope::prepareForScopeCreation( cx, kind, &data, firstFrameSlot, needsEnvironment, &envShape)) { return false; } return appendScopeStencilAndData(cx, compilationState, data, index, kind, enclosing, firstFrameSlot, envShape); } /* static */ bool ScopeStencil::createForGlobalScope( JSContext* cx, frontend::CompilationStencil& stencil, frontend::CompilationState& compilationState, ScopeKind kind, GlobalScope::ParserData* data, ScopeIndex* index) { using ScopeType = GlobalScope; MOZ_ASSERT(matchScopeKind(kind)); if (data) { MarkParserScopeData(cx, data, compilationState); } else { data = NewEmptyParserScopeData(cx, stencil.alloc); if (!data) { return false; } } // 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. uint32_t firstFrameSlot = 0; mozilla::Maybe envShape; mozilla::Maybe enclosing; return appendScopeStencilAndData(cx, compilationState, data, index, kind, enclosing, firstFrameSlot, envShape); } /* static */ bool ScopeStencil::createForEvalScope( JSContext* cx, frontend::CompilationStencil& stencil, frontend::CompilationState& compilationState, ScopeKind kind, EvalScope::ParserData* data, mozilla::Maybe enclosing, ScopeIndex* index) { using ScopeType = EvalScope; MOZ_ASSERT(matchScopeKind(kind)); if (data) { MarkParserScopeData(cx, data, compilationState); } else { data = NewEmptyParserScopeData(cx, stencil.alloc); if (!data) { return false; } } uint32_t firstFrameSlot = 0; mozilla::Maybe envShape; if (!EvalScope::prepareForScopeCreation( cx, kind, &data, &envShape)) { return false; } return appendScopeStencilAndData(cx, compilationState, data, index, kind, enclosing, firstFrameSlot, envShape); } /* static */ bool ScopeStencil::createForModuleScope( JSContext* cx, frontend::CompilationStencil& stencil, frontend::CompilationState& compilationState, ModuleScope::ParserData* data, mozilla::Maybe enclosing, ScopeIndex* index) { auto kind = ScopeKind::Module; using ScopeType = ModuleScope; MOZ_ASSERT(matchScopeKind(kind)); if (data) { MarkParserScopeData(cx, data, compilationState); } else { data = NewEmptyParserScopeData(cx, stencil.alloc); if (!data) { return false; } } MOZ_ASSERT(enclosing.isNothing()); // We do not initialize the canonical module while the data is owned by the // ScopeStencil. It gets set in ScopeStencil::releaseData. RootedModuleObject module(cx, nullptr); // The data that's passed in is from the frontend and is LifoAlloc'd. // Copy it now that we're creating a permanent VM scope. uint32_t firstFrameSlot = 0; mozilla::Maybe envShape; if (!ModuleScope::prepareForScopeCreation( cx, &data, module, &envShape)) { return false; } return appendScopeStencilAndData(cx, compilationState, data, index, kind, enclosing, firstFrameSlot, envShape); } template bool ScopeStencil::createSpecificShape(JSContext* cx, ScopeKind kind, BaseScopeData* scopeData, MutableHandleShape shape) const { const JSClass* cls = &SpecificEnvironmentT::class_; uint32_t baseShapeFlags = SpecificEnvironmentT::BASESHAPE_FLAGS; if (hasEnvironmentShape()) { if (numEnvironmentSlots() > 0) { BindingIter bi(kind, scopeData, firstFrameSlot_); shape.set(CreateEnvironmentShape(cx, bi, cls, numEnvironmentSlots(), baseShapeFlags)); return shape; } shape.set(EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), baseShapeFlags)); return shape; } return true; } /* static */ bool ScopeStencil::createForWithScope(JSContext* cx, CompilationStencil& stencil, CompilationState& compilationState, mozilla::Maybe enclosing, ScopeIndex* index) { auto kind = ScopeKind::With; MOZ_ASSERT(matchScopeKind(kind)); uint32_t firstFrameSlot = 0; mozilla::Maybe envShape; return appendScopeStencilAndData(cx, compilationState, nullptr, index, kind, enclosing, firstFrameSlot, envShape); } template UniquePtr ScopeStencil::createSpecificScopeData(JSContext* cx, CompilationAtomCache& atomCache, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const { return LiftParserScopeData(cx, atomCache, baseData); } template <> UniquePtr ScopeStencil::createSpecificScopeData( JSContext* cx, CompilationAtomCache& atomCache, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const { // Allocate a new vm function-scope. UniquePtr data = LiftParserScopeData(cx, atomCache, baseData); if (!data) { return nullptr; } // Initialize the HeapPtr in the FunctionScope::RuntimeData. data->canonicalFunction = gcOutput.functions[functionIndex()]; return data; } template <> UniquePtr ScopeStencil::createSpecificScopeData( JSContext* cx, CompilationAtomCache& atomCache, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const { // Allocate a new vm module-scope. UniquePtr data = LiftParserScopeData(cx, atomCache, baseData); if (!data) { return nullptr; } // Initialize the HeapPtr in the ModuleScope::RuntimeData. data->module = gcOutput.module; return data; } // WithScope does not use binding data. template <> Scope* ScopeStencil::createSpecificScope( JSContext* cx, CompilationInput& input, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const { RootedScope enclosingScope(cx, enclosingExistingScope(input, gcOutput)); return Scope::create(cx, ScopeKind::With, enclosingScope, nullptr); } // GlobalScope has bindings but no environment shape. template <> Scope* ScopeStencil::createSpecificScope( JSContext* cx, CompilationInput& input, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const { Rooted> rootedData( cx, createSpecificScopeData(cx, input.atomCache, gcOutput, baseData)); if (!rootedData) { return nullptr; } MOZ_ASSERT(!enclosing_); MOZ_ASSERT(!input.enclosingScope); // Because we already baked the data here, we needn't do it again. return Scope::create(cx, kind(), nullptr, nullptr, &rootedData); } template Scope* ScopeStencil::createSpecificScope(JSContext* cx, CompilationInput& input, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const { Rooted> rootedData( cx, createSpecificScopeData(cx, input.atomCache, gcOutput, baseData)); if (!rootedData) { return nullptr; } RootedShape shape(cx); if (!createSpecificShape( cx, kind(), rootedData.get().get(), &shape)) { return nullptr; } RootedScope enclosingScope(cx, enclosingExistingScope(input, gcOutput)); // Because we already baked the data here, we needn't do it again. return Scope::create(cx, kind(), enclosingScope, shape, &rootedData); } template Scope* ScopeStencil::createSpecificScope( JSContext* cx, CompilationInput& input, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const; template Scope* ScopeStencil::createSpecificScope( JSContext* cx, CompilationInput& input, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const; template Scope* ScopeStencil::createSpecificScope( JSContext* cx, CompilationInput& input, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const; template Scope* ScopeStencil::createSpecificScope( JSContext* cx, CompilationInput& input, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const; template Scope* ScopeStencil::createSpecificScope( JSContext* cx, CompilationInput& input, CompilationGCOutput& gcOutput, BaseParserScopeData* baseData) const;