diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/vm/JSScript.cpp | 5128 |
1 files changed, 5128 insertions, 0 deletions
diff --git a/js/src/vm/JSScript.cpp b/js/src/vm/JSScript.cpp new file mode 100644 index 0000000000..d887a61440 --- /dev/null +++ b/js/src/vm/JSScript.cpp @@ -0,0 +1,5128 @@ +/* -*- 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/. */ + +/* + * JS script operations. + */ + +#include "vm/JSScript-inl.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/PodOperations.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Span.h" // mozilla::{Span,Span} +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" +#include "mozilla/Utf8.h" +#include "mozilla/Vector.h" + +#include <algorithm> +#include <new> +#include <string.h> +#include <type_traits> +#include <utility> + +#include "jsapi.h" +#include "jstypes.h" + +#include "frontend/BytecodeCompiler.h" +#include "frontend/BytecodeEmitter.h" +#include "frontend/CompilationInfo.h" // frontend::BaseCompilationStencil +#include "frontend/SharedContext.h" +#include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteIterator +#include "frontend/StencilXdr.h" // frontend::StencilXdr::SharedData +#include "gc/FreeOp.h" +#include "jit/BaselineJIT.h" +#include "jit/CacheIRHealth.h" +#include "jit/Invalidation.h" +#include "jit/Ion.h" +#include "jit/IonScript.h" +#include "jit/JitCode.h" +#include "jit/JitOptions.h" +#include "jit/JitRuntime.h" +#include "js/CompileOptions.h" +#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* +#include "js/MemoryMetrics.h" +#include "js/Printf.h" +#include "js/SourceText.h" +#include "js/UniquePtr.h" +#include "js/Utility.h" +#include "js/Wrapper.h" +#include "util/Memory.h" +#include "util/Poison.h" +#include "util/StringBuffer.h" +#include "util/Text.h" +#include "vm/ArgumentsObject.h" +#include "vm/BytecodeIterator.h" +#include "vm/BytecodeLocation.h" +#include "vm/BytecodeUtil.h" +#include "vm/Compression.h" +#include "vm/FunctionFlags.h" // js::FunctionFlags +#include "vm/HelperThreadState.h" // js::RunPendingSourceCompressions +#include "vm/JSAtom.h" +#include "vm/JSContext.h" +#include "vm/JSFunction.h" +#include "vm/JSObject.h" +#include "vm/Opcodes.h" +#include "vm/PlainObject.h" // js::PlainObject +#include "vm/SelfHosting.h" +#include "vm/Shape.h" +#include "vm/SharedImmutableStringsCache.h" +#include "vm/Warnings.h" // js::WarnNumberLatin1 +#include "vm/Xdr.h" +#ifdef MOZ_VTUNE +# include "vtune/VTuneWrapper.h" +#endif + +#include "debugger/DebugAPI-inl.h" +#include "gc/Marking-inl.h" +#include "vm/BytecodeIterator-inl.h" +#include "vm/BytecodeLocation-inl.h" +#include "vm/Compartment-inl.h" +#include "vm/EnvironmentObject-inl.h" +#include "vm/JSFunction-inl.h" +#include "vm/JSObject-inl.h" +#include "vm/NativeObject-inl.h" +#include "vm/SharedImmutableStringsCache-inl.h" +#include "vm/Stack-inl.h" + +using namespace js; + +using mozilla::CheckedInt; +using mozilla::Maybe; +using mozilla::PodCopy; +using mozilla::PointerRangeSize; +using mozilla::Utf8AsUnsignedChars; +using mozilla::Utf8Unit; + +using JS::CompileOptions; +using JS::ReadOnlyCompileOptions; +using JS::SourceText; + +template <XDRMode mode> +XDRResult js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp) { + JSContext* cx = xdr->cx(); + + enum ConstTag { + SCRIPT_INT, + SCRIPT_DOUBLE, + SCRIPT_ATOM, + SCRIPT_TRUE, + SCRIPT_FALSE, + SCRIPT_NULL, + SCRIPT_OBJECT, + SCRIPT_VOID, + SCRIPT_HOLE, + SCRIPT_BIGINT + }; + + ConstTag tag; + if (mode == XDR_ENCODE) { + if (vp.isInt32()) { + tag = SCRIPT_INT; + } else if (vp.isDouble()) { + tag = SCRIPT_DOUBLE; + } else if (vp.isString()) { + tag = SCRIPT_ATOM; + } else if (vp.isTrue()) { + tag = SCRIPT_TRUE; + } else if (vp.isFalse()) { + tag = SCRIPT_FALSE; + } else if (vp.isNull()) { + tag = SCRIPT_NULL; + } else if (vp.isObject()) { + tag = SCRIPT_OBJECT; + } else if (vp.isMagic(JS_ELEMENTS_HOLE)) { + tag = SCRIPT_HOLE; + } else if (vp.isBigInt()) { + tag = SCRIPT_BIGINT; + } else { + MOZ_ASSERT(vp.isUndefined()); + tag = SCRIPT_VOID; + } + } + + MOZ_TRY(xdr->codeEnum32(&tag)); + + switch (tag) { + case SCRIPT_INT: { + uint32_t i; + if (mode == XDR_ENCODE) { + i = uint32_t(vp.toInt32()); + } + MOZ_TRY(xdr->codeUint32(&i)); + if (mode == XDR_DECODE) { + vp.set(Int32Value(int32_t(i))); + } + break; + } + case SCRIPT_DOUBLE: { + double d; + if (mode == XDR_ENCODE) { + d = vp.toDouble(); + } + MOZ_TRY(xdr->codeDouble(&d)); + if (mode == XDR_DECODE) { + vp.set(DoubleValue(d)); + } + break; + } + case SCRIPT_ATOM: { + RootedAtom atom(cx); + if (mode == XDR_ENCODE) { + atom = &vp.toString()->asAtom(); + } + MOZ_TRY(XDRAtom(xdr, &atom)); + if (mode == XDR_DECODE) { + vp.set(StringValue(atom)); + } + break; + } + case SCRIPT_TRUE: + if (mode == XDR_DECODE) { + vp.set(BooleanValue(true)); + } + break; + case SCRIPT_FALSE: + if (mode == XDR_DECODE) { + vp.set(BooleanValue(false)); + } + break; + case SCRIPT_NULL: + if (mode == XDR_DECODE) { + vp.set(NullValue()); + } + break; + case SCRIPT_OBJECT: { + RootedObject obj(cx); + if (mode == XDR_ENCODE) { + obj = &vp.toObject(); + } + + MOZ_TRY(XDRObjectLiteral(xdr, &obj)); + + if (mode == XDR_DECODE) { + vp.setObject(*obj); + } + break; + } + case SCRIPT_VOID: + if (mode == XDR_DECODE) { + vp.set(UndefinedValue()); + } + break; + case SCRIPT_HOLE: + if (mode == XDR_DECODE) { + vp.setMagic(JS_ELEMENTS_HOLE); + } + break; + case SCRIPT_BIGINT: { + RootedBigInt bi(cx); + if (mode == XDR_ENCODE) { + bi = vp.toBigInt(); + } + + MOZ_TRY(XDRBigInt(xdr, &bi)); + + if (mode == XDR_DECODE) { + vp.setBigInt(bi); + } + break; + } + default: + // Fail in debug, but only soft-fail in release + MOZ_ASSERT(false, "Bad XDR value kind"); + return xdr->fail(JS::TranscodeResult_Failure_BadDecode); + } + return Ok(); +} + +template XDRResult js::XDRScriptConst(XDRState<XDR_ENCODE>*, + MutableHandleValue); + +template XDRResult js::XDRScriptConst(XDRState<XDR_DECODE>*, + MutableHandleValue); + +// Code lazy scripts's closed over bindings. +template <XDRMode mode> +/* static */ +XDRResult BaseScript::XDRLazyScriptData(XDRState<mode>* xdr, + HandleScriptSourceObject sourceObject, + Handle<BaseScript*> lazy, + bool hasMemberInitializers) { + JSContext* cx = xdr->cx(); + + RootedAtom atom(cx); + RootedFunction func(cx); + + if (hasMemberInitializers) { + uint32_t numMemberInitializers; + if (mode == XDR_ENCODE) { + numMemberInitializers = + lazy->getMemberInitializers().numMemberInitializers; + } + MOZ_TRY(xdr->codeUint32(&numMemberInitializers)); + if (mode == XDR_DECODE) { + lazy->setMemberInitializers(MemberInitializers(numMemberInitializers)); + } + } + + mozilla::Span<JS::GCCellPtr> gcThings = + lazy->data_ ? lazy->data_->gcthings() : mozilla::Span<JS::GCCellPtr>(); + + for (JS::GCCellPtr& elem : gcThings) { + JS::TraceKind kind = elem.kind(); + + MOZ_TRY(xdr->codeEnum32(&kind)); + + switch (kind) { + case JS::TraceKind::Object: { + if (mode == XDR_ENCODE) { + func = &elem.as<JSObject>().as<JSFunction>(); + } + MOZ_TRY(XDRInterpretedFunction(xdr, nullptr, sourceObject, &func)); + if (mode == XDR_DECODE) { + func->setEnclosingLazyScript(lazy); + + elem = JS::GCCellPtr(func); + } + break; + } + + case JS::TraceKind::String: { + if (mode == XDR_ENCODE) { + gc::Cell* cell = elem.asCell(); + MOZ_ASSERT_IF(cell, cell->as<JSString>()->isAtom()); + atom = static_cast<JSAtom*>(cell); + } + MOZ_TRY(XDRAtom(xdr, &atom)); + if (mode == XDR_DECODE) { + elem = JS::GCCellPtr(static_cast<JSString*>(atom)); + } + break; + } + + case JS::TraceKind::Null: { + // This is default so nothing to do. + MOZ_ASSERT(!elem); + break; + } + + default: { + // Fail in debug, but only soft-fail in release + MOZ_ASSERT(false, "Bad XDR class kind"); + return xdr->fail(JS::TranscodeResult_Failure_BadDecode); + } + } + } + + return Ok(); +} + +static inline uint32_t FindScopeIndex(mozilla::Span<const JS::GCCellPtr> scopes, + Scope& scope) { + unsigned length = scopes.size(); + for (uint32_t i = 0; i < length; ++i) { + if (scopes[i].asCell() == &scope) { + return i; + } + } + + MOZ_CRASH("Scope not found"); +} + +template <XDRMode mode> +static XDRResult XDRInnerObject(XDRState<mode>* xdr, + js::PrivateScriptData* data, + HandleScriptSourceObject sourceObject, + MutableHandleObject inner) { + enum class ClassKind { RegexpObject, JSFunction, JSObject, ArrayObject }; + JSContext* cx = xdr->cx(); + + ClassKind classk; + + if (mode == XDR_ENCODE) { + if (inner->is<RegExpObject>()) { + classk = ClassKind::RegexpObject; + } else if (inner->is<JSFunction>()) { + classk = ClassKind::JSFunction; + } else if (inner->is<PlainObject>()) { + classk = ClassKind::JSObject; + } else if (inner->is<ArrayObject>()) { + classk = ClassKind::ArrayObject; + } else { + MOZ_CRASH("Cannot encode this class of object."); + } + } + + MOZ_TRY(xdr->codeEnum32(&classk)); + + switch (classk) { + case ClassKind::RegexpObject: { + Rooted<RegExpObject*> regexp(cx); + if (mode == XDR_ENCODE) { + regexp = &inner->as<RegExpObject>(); + } + MOZ_TRY(XDRScriptRegExpObject(xdr, ®exp)); + if (mode == XDR_DECODE) { + inner.set(regexp); + } + break; + } + + case ClassKind::JSFunction: { + /* Code the nested function's enclosing scope. */ + uint32_t funEnclosingScopeIndex = 0; + RootedScope funEnclosingScope(cx); + + if (mode == XDR_ENCODE) { + RootedFunction function(cx, &inner->as<JSFunction>()); + + if (function->isAsmJSNative()) { + return xdr->fail(JS::TranscodeResult_Failure_AsmJSNotSupported); + } + + MOZ_ASSERT(function->enclosingScope()); + funEnclosingScope = function->enclosingScope(); + funEnclosingScopeIndex = + FindScopeIndex(data->gcthings(), *funEnclosingScope); + } + + MOZ_TRY(xdr->codeUint32(&funEnclosingScopeIndex)); + + if (mode == XDR_DECODE) { + funEnclosingScope = + &data->gcthings()[funEnclosingScopeIndex].as<Scope>(); + } + + // Code nested function and script. + RootedFunction tmp(cx); + if (mode == XDR_ENCODE) { + tmp = &inner->as<JSFunction>(); + } + MOZ_TRY( + XDRInterpretedFunction(xdr, funEnclosingScope, sourceObject, &tmp)); + if (mode == XDR_DECODE) { + inner.set(tmp); + } + break; + } + + case ClassKind::JSObject: + case ClassKind::ArrayObject: { + /* Code object literal. */ + RootedObject tmp(cx); + if (mode == XDR_ENCODE) { + tmp = inner.get(); + } + MOZ_TRY(XDRObjectLiteral(xdr, &tmp)); + if (mode == XDR_DECODE) { + inner.set(tmp); + } + break; + } + + default: { + // Fail in debug, but only soft-fail in release + MOZ_ASSERT(false, "Bad XDR class kind"); + return xdr->fail(JS::TranscodeResult_Failure_BadDecode); + } + } + + return Ok(); +} + +template <XDRMode mode> +static XDRResult XDRScope(XDRState<mode>* xdr, js::PrivateScriptData* data, + HandleScope scriptEnclosingScope, + HandleObject funOrMod, bool isFirstScope, + MutableHandleScope scope) { + JSContext* cx = xdr->cx(); + + ScopeKind scopeKind; + RootedScope enclosing(cx); + RootedFunction fun(cx); + RootedModuleObject module(cx); + uint32_t enclosingIndex = 0; + + // The enclosingScope is encoded using an integer index into the scope array. + // This means that scopes must be topologically sorted. + if (mode == XDR_ENCODE) { + scopeKind = scope->kind(); + + if (isFirstScope) { + enclosingIndex = UINT32_MAX; + } else { + MOZ_ASSERT(scope->enclosing()); + enclosingIndex = FindScopeIndex(data->gcthings(), *scope->enclosing()); + } + } + + MOZ_TRY(xdr->codeEnum32(&scopeKind)); + MOZ_TRY(xdr->codeUint32(&enclosingIndex)); + + if (mode == XDR_DECODE) { + if (isFirstScope) { + MOZ_ASSERT(enclosingIndex == UINT32_MAX); + enclosing = scriptEnclosingScope; + } else { + enclosing = &data->gcthings()[enclosingIndex].as<Scope>(); + } + + if (funOrMod && funOrMod->is<ModuleObject>()) { + module.set(funOrMod.as<ModuleObject>()); + } else if (funOrMod && funOrMod->is<JSFunction>()) { + fun.set(funOrMod.as<JSFunction>()); + } + } + + switch (scopeKind) { + case ScopeKind::Function: + MOZ_TRY(FunctionScope::XDR(xdr, fun, enclosing, scope)); + break; + case ScopeKind::FunctionBodyVar: + MOZ_TRY(VarScope::XDR(xdr, scopeKind, enclosing, scope)); + break; + case ScopeKind::Lexical: + case ScopeKind::SimpleCatch: + case ScopeKind::Catch: + case ScopeKind::NamedLambda: + case ScopeKind::StrictNamedLambda: + case ScopeKind::FunctionLexical: + case ScopeKind::ClassBody: + MOZ_TRY(LexicalScope::XDR(xdr, scopeKind, enclosing, scope)); + break; + case ScopeKind::With: + MOZ_TRY(WithScope::XDR(xdr, enclosing, scope)); + break; + case ScopeKind::Eval: + case ScopeKind::StrictEval: + MOZ_TRY(EvalScope::XDR(xdr, scopeKind, enclosing, scope)); + break; + case ScopeKind::Global: + case ScopeKind::NonSyntactic: + MOZ_TRY(GlobalScope::XDR(xdr, scopeKind, scope)); + break; + case ScopeKind::Module: + MOZ_TRY(ModuleScope::XDR(xdr, module, enclosing, scope)); + break; + case ScopeKind::WasmInstance: + MOZ_CRASH("NYI"); + break; + case ScopeKind::WasmFunction: + MOZ_CRASH("wasm functions cannot be nested in JSScripts"); + break; + default: + // Fail in debug, but only soft-fail in release + MOZ_ASSERT(false, "Bad XDR scope kind"); + return xdr->fail(JS::TranscodeResult_Failure_BadDecode); + } + + return Ok(); +} + +template <XDRMode mode> +static XDRResult XDRScriptGCThing(XDRState<mode>* xdr, PrivateScriptData* data, + HandleScriptSourceObject sourceObject, + HandleScope scriptEnclosingScope, + HandleObject funOrMod, bool* isFirstScope, + JS::GCCellPtr* thingp) { + JSContext* cx = xdr->cx(); + + JS::TraceKind kind = thingp->kind(); + + MOZ_TRY(xdr->codeEnum32(&kind)); + + switch (kind) { + case JS::TraceKind::String: { + RootedAtom atom(cx); + if (mode == XDR_ENCODE) { + atom = &thingp->as<JSString>().asAtom(); + } + MOZ_TRY(XDRAtom(xdr, &atom)); + if (mode == XDR_DECODE) { + *thingp = JS::GCCellPtr(atom.get()); + } + break; + } + + case JS::TraceKind::Object: { + RootedObject obj(cx); + if (mode == XDR_ENCODE) { + obj = &thingp->as<JSObject>(); + } + MOZ_TRY(XDRInnerObject(xdr, data, sourceObject, &obj)); + if (mode == XDR_DECODE) { + *thingp = JS::GCCellPtr(obj.get()); + } + break; + } + + case JS::TraceKind::Scope: { + RootedScope scope(cx); + if (mode == XDR_ENCODE) { + scope = &thingp->as<Scope>(); + } + MOZ_TRY(XDRScope(xdr, data, scriptEnclosingScope, funOrMod, *isFirstScope, + &scope)); + if (mode == XDR_DECODE) { + *thingp = JS::GCCellPtr(scope.get()); + } + *isFirstScope = false; + break; + } + + case JS::TraceKind::BigInt: { + RootedBigInt bi(cx); + if (mode == XDR_ENCODE) { + bi = &thingp->as<BigInt>(); + } + MOZ_TRY(XDRBigInt(xdr, &bi)); + if (mode == XDR_DECODE) { + *thingp = JS::GCCellPtr(bi.get()); + } + break; + } + + default: + // Fail in debug, but only soft-fail in release. + MOZ_ASSERT(false, "Bad XDR class kind"); + return xdr->fail(JS::TranscodeResult_Failure_BadDecode); + } + return Ok(); +} + +bool js::BaseScript::isUsingInterpreterTrampoline(JSRuntime* rt) const { + return jitCodeRaw() == rt->jitRuntime()->interpreterStub().value; +} + +js::ScriptSource* js::BaseScript::maybeForwardedScriptSource() const { + return MaybeForwarded(sourceObject())->source(); +} + +void js::BaseScript::setEnclosingScript(BaseScript* enclosingScript) { + MOZ_ASSERT(enclosingScript); + warmUpData_.initEnclosingScript(enclosingScript); +} + +void js::BaseScript::setEnclosingScope(Scope* enclosingScope) { + if (warmUpData_.isEnclosingScript()) { + warmUpData_.clearEnclosingScript(); + } + + MOZ_ASSERT(enclosingScope); + warmUpData_.initEnclosingScope(enclosingScope); +} + +void js::BaseScript::finalize(JSFreeOp* fop) { + // Scripts with bytecode may have optional data stored in per-runtime or + // per-zone maps. Note that a failed compilation must not have entries since + // the script itself will not be marked as having bytecode. + if (hasBytecode()) { + JSScript* script = this->asJSScript(); + + if (coverage::IsLCovEnabled()) { + coverage::CollectScriptCoverage(script, true); + } + + script->destroyScriptCounts(); + + DebugAPI::destroyDebugScript(fop, script); + } + + fop->runtime()->geckoProfiler().onScriptFinalized(this); + +#ifdef MOZ_VTUNE + if (zone()->scriptVTuneIdMap) { + // Note: we should only get here if the VTune JIT profiler is running. + zone()->scriptVTuneIdMap->remove(this); + } +#endif + + if (warmUpData_.isJitScript()) { + JSScript* script = this->asJSScript(); +#ifdef JS_CACHEIR_SPEW + maybeUpdateWarmUpCount(script); +#endif + script->releaseJitScriptOnFinalize(fop); + } + +#ifdef JS_CACHEIR_SPEW + if (hasBytecode()) { + maybeSpewScriptFinalWarmUpCount(this->asJSScript()); + } +#endif + + if (data_) { + // We don't need to triger any barriers here, just free the memory. + size_t size = data_->allocationSize(); + AlwaysPoison(data_, JS_POISONED_JSSCRIPT_DATA_PATTERN, size, + MemCheckKind::MakeNoAccess); + fop->free_(this, data_, size, MemoryUse::ScriptPrivateData); + } + + freeSharedData(); +} + +js::Scope* js::BaseScript::releaseEnclosingScope() { + Scope* enclosing = warmUpData_.toEnclosingScope(); + warmUpData_.clearEnclosingScope(); + return enclosing; +} + +void js::BaseScript::swapData(UniquePtr<PrivateScriptData>& other) { + PrivateScriptData* tmp = other.release(); + + if (data_) { + // When disconnecting script data from the BaseScript, we must pre-barrier + // all edges contained in it. Those edges are no longer reachable from + // current location in the graph. + if (zone()->needsIncrementalBarrier()) { + data_->trace(zone()->barrierTracer()); + } + + RemoveCellMemory(this, data_->allocationSize(), + MemoryUse::ScriptPrivateData); + } + + std::swap(tmp, data_); + + if (data_) { + AddCellMemory(this, data_->allocationSize(), MemoryUse::ScriptPrivateData); + } + + other.reset(tmp); +} + +js::Scope* js::BaseScript::enclosingScope() const { + MOZ_ASSERT(!warmUpData_.isEnclosingScript(), + "Enclosing scope is not computed yet"); + + if (warmUpData_.isEnclosingScope()) { + return warmUpData_.toEnclosingScope(); + } + + MOZ_ASSERT(data_, "Script doesn't seem to be compiled"); + + return gcthings()[js::GCThingIndex::outermostScopeIndex()] + .as<Scope>() + .enclosing(); +} + +size_t JSScript::numAlwaysLiveFixedSlots() const { + if (bodyScope()->is<js::FunctionScope>()) { + return bodyScope()->as<js::FunctionScope>().nextFrameSlot(); + } + if (bodyScope()->is<js::ModuleScope>()) { + return bodyScope()->as<js::ModuleScope>().nextFrameSlot(); + } + return 0; +} + +unsigned JSScript::numArgs() const { + if (bodyScope()->is<js::FunctionScope>()) { + return bodyScope()->as<js::FunctionScope>().numPositionalFormalParameters(); + } + return 0; +} + +bool JSScript::functionHasParameterExprs() const { + // Only functions have parameters. + js::Scope* scope = bodyScope(); + if (!scope->is<js::FunctionScope>()) { + return false; + } + return scope->as<js::FunctionScope>().hasParameterExprs(); +} + +js::ModuleObject* JSScript::module() const { + if (bodyScope()->is<js::ModuleScope>()) { + return bodyScope()->as<js::ModuleScope>().module(); + } + return nullptr; +} + +bool JSScript::isGlobalCode() const { + return bodyScope()->is<js::GlobalScope>(); +} + +js::VarScope* JSScript::functionExtraBodyVarScope() const { + MOZ_ASSERT(functionHasExtraBodyVarScope()); + for (JS::GCCellPtr gcThing : gcthings()) { + if (!gcThing.is<js::Scope>()) { + continue; + } + js::Scope* scope = &gcThing.as<js::Scope>(); + if (scope->kind() == js::ScopeKind::FunctionBodyVar) { + return &scope->as<js::VarScope>(); + } + } + MOZ_CRASH("Function extra body var scope not found"); +} + +bool JSScript::needsBodyEnvironment() const { + for (JS::GCCellPtr gcThing : gcthings()) { + if (!gcThing.is<js::Scope>()) { + continue; + } + js::Scope* scope = &gcThing.as<js::Scope>(); + if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment()) { + return true; + } + } + return false; +} + +bool JSScript::isDirectEvalInFunction() const { + if (!isForEval()) { + return false; + } + return bodyScope()->hasOnChain(js::ScopeKind::Function); +} + +template <XDRMode mode> +/* static */ +XDRResult js::PrivateScriptData::XDR(XDRState<mode>* xdr, HandleScript script, + HandleScriptSourceObject sourceObject, + HandleScope scriptEnclosingScope, + HandleObject funOrMod) { + uint32_t ngcthings = 0; + + JSContext* cx = xdr->cx(); + PrivateScriptData* data = nullptr; + + if (mode == XDR_ENCODE) { + data = script->data_; + + ngcthings = data->gcthings().size(); + } + + MOZ_TRY(xdr->codeUint32(&ngcthings)); + + if (mode == XDR_DECODE) { + if (!JSScript::createPrivateScriptData(cx, script, ngcthings)) { + return xdr->fail(JS::TranscodeResult_Throw); + } + + data = script->data_; + } + + // Code the field initilizer data. + if (funOrMod && funOrMod->is<JSFunction>() && + funOrMod->as<JSFunction>().isClassConstructor()) { + MOZ_ASSERT(scriptEnclosingScope); + + uint32_t numMemberInitializers; + if (mode == XDR_ENCODE) { + numMemberInitializers = + data->getMemberInitializers().numMemberInitializers; + } + MOZ_TRY(xdr->codeUint32(&numMemberInitializers)); + if (mode == XDR_DECODE) { + data->setMemberInitializers(MemberInitializers(numMemberInitializers)); + } + } + + bool isFirstScope = true; + for (JS::GCCellPtr& gcThing : data->gcthings()) { + MOZ_TRY(XDRScriptGCThing(xdr, data, sourceObject, scriptEnclosingScope, + funOrMod, &isFirstScope, &gcThing)); + } + + // Verify marker to detect data corruption after decoding GC things. A + // mismatch here indicates we will almost certainly crash in release. + MOZ_TRY(xdr->codeMarker(0xF83B989A)); + + return Ok(); +} + +// Initialize the optional arrays in the trailing allocation. This is a set of +// offsets that delimit each optional array followed by the arrays themselves. +// See comment before 'ImmutableScriptData' for more details. +void ImmutableScriptData::initOptionalArrays(Offset* pcursor, + uint32_t numResumeOffsets, + uint32_t numScopeNotes, + uint32_t numTryNotes) { + Offset cursor = (*pcursor); + + // The byte arrays must have already been padded. + MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor), + "Bytecode and source notes should be padded to keep alignment"); + + // Each non-empty optional array needs will need an offset to its end. + unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) + + unsigned(numScopeNotes > 0) + + unsigned(numTryNotes > 0); + + // Default-initialize the optional-offsets. + initElements<Offset>(cursor, numOptionalArrays); + cursor += numOptionalArrays * sizeof(Offset); + + // Offset between optional-offsets table and the optional arrays. This is + // later used to access the optional-offsets table as well as first optional + // array. + optArrayOffset_ = cursor; + + // Each optional array that follows must store an end-offset in the offset + // table. Assign table entries by using this 'offsetIndex'. The index 0 is + // reserved for implicit value 'optArrayOffset'. + int offsetIndex = 0; + + // Default-initialize optional 'resumeOffsets'. + MOZ_ASSERT(resumeOffsetsOffset() == cursor); + if (numResumeOffsets > 0) { + initElements<uint32_t>(cursor, numResumeOffsets); + cursor += numResumeOffsets * sizeof(uint32_t); + setOptionalOffset(++offsetIndex, cursor); + } + flagsRef().resumeOffsetsEndIndex = offsetIndex; + + // Default-initialize optional 'scopeNotes'. + MOZ_ASSERT(scopeNotesOffset() == cursor); + if (numScopeNotes > 0) { + initElements<ScopeNote>(cursor, numScopeNotes); + cursor += numScopeNotes * sizeof(ScopeNote); + setOptionalOffset(++offsetIndex, cursor); + } + flagsRef().scopeNotesEndIndex = offsetIndex; + + // Default-initialize optional 'tryNotes' + MOZ_ASSERT(tryNotesOffset() == cursor); + if (numTryNotes > 0) { + initElements<TryNote>(cursor, numTryNotes); + cursor += numTryNotes * sizeof(TryNote); + setOptionalOffset(++offsetIndex, cursor); + } + flagsRef().tryNotesEndIndex = offsetIndex; + + MOZ_ASSERT(endOffset() == cursor); + (*pcursor) = cursor; +} + +ImmutableScriptData::ImmutableScriptData(uint32_t codeLength, + uint32_t noteLength, + uint32_t numResumeOffsets, + uint32_t numScopeNotes, + uint32_t numTryNotes) + : codeLength_(codeLength) { + // Variable-length data begins immediately after ImmutableScriptData itself. + Offset cursor = sizeof(ImmutableScriptData); + + // The following arrays are byte-aligned with additional padding to ensure + // that together they maintain uint32_t-alignment. + { + MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor)); + + // Zero-initialize 'flags' + MOZ_ASSERT(isAlignedOffset<Flags>(cursor)); + new (offsetToPointer<void>(cursor)) Flags{}; + cursor += sizeof(Flags); + + initElements<jsbytecode>(cursor, codeLength); + cursor += codeLength * sizeof(jsbytecode); + + initElements<SrcNote>(cursor, noteLength); + cursor += noteLength * sizeof(SrcNote); + + MOZ_ASSERT(isAlignedOffset<CodeNoteAlign>(cursor)); + } + + // Initialization for remaining arrays. + initOptionalArrays(&cursor, numResumeOffsets, numScopeNotes, numTryNotes); + + // Check that we correctly recompute the expected values. + MOZ_ASSERT(this->codeLength() == codeLength); + MOZ_ASSERT(this->noteLength() == noteLength); + + // Sanity check + MOZ_ASSERT(endOffset() == cursor); +} + +template <XDRMode mode> +XDRResult js::XDRImmutableScriptData(XDRState<mode>* xdr, + UniquePtr<ImmutableScriptData>& isd) { + uint32_t codeLength = 0; + uint32_t noteLength = 0; + uint32_t numResumeOffsets = 0; + uint32_t numScopeNotes = 0; + uint32_t numTryNotes = 0; + + JSContext* cx = xdr->cx(); + + if (mode == XDR_ENCODE) { + codeLength = isd->codeLength(); + noteLength = isd->noteLength(); + + numResumeOffsets = isd->resumeOffsets().size(); + numScopeNotes = isd->scopeNotes().size(); + numTryNotes = isd->tryNotes().size(); + } + + MOZ_TRY(xdr->codeUint32(&codeLength)); + MOZ_TRY(xdr->codeUint32(¬eLength)); + MOZ_TRY(xdr->codeUint32(&numResumeOffsets)); + MOZ_TRY(xdr->codeUint32(&numScopeNotes)); + MOZ_TRY(xdr->codeUint32(&numTryNotes)); + + if (mode == XDR_DECODE) { + isd = + ImmutableScriptData::new_(cx, codeLength, noteLength, numResumeOffsets, + numScopeNotes, numTryNotes); + if (!isd) { + return xdr->fail(JS::TranscodeResult_Throw); + } + } + + MOZ_TRY(xdr->codeUint32(&isd->mainOffset)); + MOZ_TRY(xdr->codeUint32(&isd->nfixed)); + MOZ_TRY(xdr->codeUint32(&isd->nslots)); + MOZ_TRY(xdr->codeUint32(&isd->bodyScopeIndex.index)); + MOZ_TRY(xdr->codeUint32(&isd->numICEntries)); + MOZ_TRY(xdr->codeUint16(&isd->funLength)); + + static_assert(sizeof(jsbytecode) == 1); + static_assert(sizeof(SrcNote) == 1); + + jsbytecode* code = isd->code(); + SrcNote* notes = isd->notes(); + MOZ_TRY(xdr->codeBytes(code, codeLength)); + MOZ_TRY(xdr->codeBytes(notes, noteLength)); + + for (uint32_t& elem : isd->resumeOffsets()) { + MOZ_TRY(xdr->codeUint32(&elem)); + } + + for (ScopeNote& elem : isd->scopeNotes()) { + MOZ_TRY(xdr->codeUint32(&elem.index.index)); + MOZ_TRY(xdr->codeUint32(&elem.start)); + MOZ_TRY(xdr->codeUint32(&elem.length)); + MOZ_TRY(xdr->codeUint32(&elem.parent)); + } + + for (TryNote& elem : isd->tryNotes()) { + MOZ_TRY(xdr->codeUint32(&elem.kind_)); + MOZ_TRY(xdr->codeUint32(&elem.stackDepth)); + MOZ_TRY(xdr->codeUint32(&elem.start)); + MOZ_TRY(xdr->codeUint32(&elem.length)); + } + + return Ok(); +} + +template XDRResult js::XDRImmutableScriptData( + XDRState<XDR_ENCODE>* xdr, UniquePtr<ImmutableScriptData>& isd); +template XDRResult js::XDRImmutableScriptData( + XDRState<XDR_DECODE>* xdr, UniquePtr<ImmutableScriptData>& isd); + +template <XDRMode mode> +XDRResult js::XDRSourceExtent(XDRState<mode>* xdr, SourceExtent* extent) { + MOZ_TRY(xdr->codeUint32(&extent->sourceStart)); + MOZ_TRY(xdr->codeUint32(&extent->sourceEnd)); + MOZ_TRY(xdr->codeUint32(&extent->toStringStart)); + MOZ_TRY(xdr->codeUint32(&extent->toStringEnd)); + MOZ_TRY(xdr->codeUint32(&extent->lineno)); + MOZ_TRY(xdr->codeUint32(&extent->column)); + + return Ok(); +} + +template /* static */ + XDRResult + js::XDRSourceExtent(XDRState<XDR_ENCODE>* xdr, SourceExtent* extent); + +template /* static */ + XDRResult + js::XDRSourceExtent(XDRState<XDR_DECODE>* xdr, SourceExtent* extent); + +void js::FillImmutableFlagsFromCompileOptionsForTopLevel( + const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) { + using ImmutableFlags = ImmutableScriptFlagsEnum; + + js::FillImmutableFlagsFromCompileOptionsForFunction(options, flags); + + flags.setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce); + flags.setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval); +} + +void js::FillImmutableFlagsFromCompileOptionsForFunction( + const ReadOnlyCompileOptions& options, ImmutableScriptFlags& flags) { + using ImmutableFlags = ImmutableScriptFlagsEnum; + + flags.setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode); + flags.setFlag(ImmutableFlags::ForceStrict, options.forceStrictMode()); + flags.setFlag(ImmutableFlags::HasNonSyntacticScope, + options.nonSyntacticScope); +} + +// Check if flags matches to compile options for flags set by +// FillImmutableFlagsFromCompileOptionsForTopLevel above. +// +// If isMultiDecode is true, this check minimal set of CompileOptions that is +// shared across multiple scripts in JS::DecodeMultiOffThreadScripts. +// Other options should be checked when getting the decoded script from the +// cache. +bool js::CheckCompileOptionsMatch(const ReadOnlyCompileOptions& options, + ImmutableScriptFlags flags, + bool isMultiDecode) { + using ImmutableFlags = ImmutableScriptFlagsEnum; + + bool selfHosted = !!(flags & uint32_t(ImmutableFlags::SelfHosted)); + bool forceStrict = !!(flags & uint32_t(ImmutableFlags::ForceStrict)); + bool hasNonSyntacticScope = + !!(flags & uint32_t(ImmutableFlags::HasNonSyntacticScope)); + bool noScriptRval = !!(flags & uint32_t(ImmutableFlags::NoScriptRval)); + bool treatAsRunOnce = !!(flags & uint32_t(ImmutableFlags::TreatAsRunOnce)); + + return options.selfHostingMode == selfHosted && + options.noScriptRval == noScriptRval && + options.isRunOnce == treatAsRunOnce && + (isMultiDecode || (options.forceStrictMode() == forceStrict && + options.nonSyntacticScope == hasNonSyntacticScope)); +} + +JS_PUBLIC_API bool JS::CheckCompileOptionsMatch( + const ReadOnlyCompileOptions& options, JSScript* script) { + return js::CheckCompileOptionsMatch(options, script->immutableFlags(), false); +} + +template <XDRMode mode> +XDRResult js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope, + HandleScriptSourceObject sourceObjectArg, + HandleObject funOrMod, MutableHandleScript scriptp) { + /* NB: Keep this in sync with CopyScriptImpl. */ + + enum XDRScriptFlags { + OwnSource = 1 << 0, + HasLazyScript = 1 << 1, + }; + + uint8_t xdrFlags = 0; + + SourceExtent extent; + uint32_t immutableFlags = 0; + + // NOTE: |mutableFlags| are not preserved by XDR. + + JSContext* cx = xdr->cx(); + RootedScript script(cx); + + bool isFunctionScript = funOrMod && funOrMod->is<JSFunction>(); + + // Instrumented scripts cannot be encoded, as they have extra instructions + // which are not normally present. Globals with instrumentation enabled must + // compile scripts via the bytecode emitter, which will insert these + // instructions. + if (xdr->hasOptions() ? !!xdr->options().instrumentationKinds + : !!cx->global()->getInstrumentationHolder()) { + return xdr->fail(JS::TranscodeResult_Failure); + } + + if (mode == XDR_ENCODE) { + script = scriptp.get(); + + MOZ_ASSERT_IF(isFunctionScript, script->function() == funOrMod); + + if (!sourceObjectArg) { + xdrFlags |= OwnSource; + } + // Preserve the MutableFlags::AllowRelazify flag. + if (script->allowRelazify()) { + xdrFlags |= HasLazyScript; + } + } + + MOZ_TRY(xdr->codeUint8(&xdrFlags)); + + if (mode == XDR_ENCODE) { + extent = script->extent(); + immutableFlags = script->immutableFlags(); + } + + MOZ_TRY(XDRSourceExtent(xdr, &extent)); + MOZ_TRY(xdr->codeUint32(&immutableFlags)); + + RootedScriptSourceObject sourceObject(cx, sourceObjectArg); + Maybe<CompileOptions> options; + + if (mode == XDR_DECODE) { + MOZ_ASSERT(xdr->hasOptions()); + + // When loading from the bytecode cache, and if we get the CompileOptions + // from the document, if the ImmutableFlags and options don't agree, we + // should fail. This only applies to the top-level and not its inner + // functions. + // + // Also, JS::DecodeMultiOffThreadScripts uses single CompileOptions for + // multiple scripts with different CompileOptions. + // We should check minimal set of common flags here, and let the consumer + // check the full flags when getting from the cache. + if (xdrFlags & OwnSource) { + options.emplace(xdr->cx(), xdr->options()); + if (!js::CheckCompileOptionsMatch(*options, + ImmutableScriptFlags(immutableFlags), + xdr->isMultiDecode())) { + return xdr->fail(JS::TranscodeResult_Failure_WrongCompileOption); + } + } + } + + if (xdrFlags & OwnSource) { + Rooted<ScriptSourceHolder> ssHolder(cx); + + // We are relying on the script's ScriptSource so the caller should not + // have passed in an explicit one. + MOZ_ASSERT(sourceObjectArg == nullptr); + + if (mode == XDR_ENCODE) { + sourceObject = script->sourceObject(); + ssHolder.get().reset(sourceObject->source()); + } + + MOZ_TRY(ScriptSource::XDR(xdr, options.ptrOr(nullptr), &ssHolder)); + + if (mode == XDR_DECODE) { + sourceObject = ScriptSourceObject::create(cx, ssHolder.get().get()); + if (!sourceObject) { + return xdr->fail(JS::TranscodeResult_Throw); + } + + if (xdr->hasScriptSourceObjectOut()) { + // When the ScriptSourceObjectOut is provided by ParseTask, it + // is stored in a location which is traced by the GC. + *xdr->scriptSourceObjectOut() = sourceObject; + } else if (!ScriptSourceObject::initFromOptions(cx, sourceObject, + *options)) { + return xdr->fail(JS::TranscodeResult_Throw); + } + } + } else { + // While encoding, the ScriptSource passed in must match the ScriptSource + // of the script. + MOZ_ASSERT_IF(mode == XDR_ENCODE, + sourceObjectArg->source() == script->scriptSource()); + } + + if (mode == XDR_DECODE) { + RootedObject functionOrGlobal( + cx, isFunctionScript ? static_cast<JSObject*>(funOrMod) + : static_cast<JSObject*>(cx->global())); + + script = JSScript::Create(cx, functionOrGlobal, sourceObject, extent, + ImmutableScriptFlags(immutableFlags)); + if (!script) { + return xdr->fail(JS::TranscodeResult_Throw); + } + scriptp.set(script); + + // Reset the mutable flags to request arguments analysis as needed. + script->resetArgsUsageAnalysis(); + + // Set the script in its function now so that inner scripts to be + // decoded may iterate the static scope chain. + if (isFunctionScript) { + funOrMod->as<JSFunction>().initScript(script); + } + } + + // If XDR operation fails, we must call BaseScript::freeSharedData in order to + // neuter the script. Various things that iterate raw scripts in a GC arena + // use the presense of this data to detect if initialization is complete. + auto scriptDataGuard = mozilla::MakeScopeExit([&] { + if (mode == XDR_DECODE) { + script->freeSharedData(); + } + }); + + // NOTE: The script data is rooted by the script. + MOZ_TRY(PrivateScriptData::XDR<mode>(xdr, script, sourceObject, + scriptEnclosingScope, funOrMod)); + MOZ_TRY(frontend::StencilXDR::SharedData<mode>(xdr, script->sharedData_)); + + if (mode == XDR_DECODE) { + if (!SharedImmutableScriptData::shareScriptData(cx, script->sharedData_)) { + return xdr->fail(JS::TranscodeResult_Throw); + } + } + + if (xdrFlags & HasLazyScript) { + if (mode == XDR_DECODE) { + script->setAllowRelazify(); + } + } + + if (mode == XDR_DECODE) { + if (coverage::IsLCovEnabled()) { + if (!coverage::InitScriptCoverage(cx, script)) { + return xdr->fail(JS::TranscodeResult_Throw); + } + } + + /* see BytecodeEmitter::tellDebuggerAboutCompiledScript */ + if (!isFunctionScript && !cx->isHelperThreadContext()) { + DebugAPI::onNewScript(cx, script); + } + } + + MOZ_ASSERT(script->code(), "Where's our bytecode?"); + scriptDataGuard.release(); + return Ok(); +} + +template XDRResult js::XDRScript(XDRState<XDR_ENCODE>*, HandleScope, + HandleScriptSourceObject, HandleObject, + MutableHandleScript); + +template XDRResult js::XDRScript(XDRState<XDR_DECODE>*, HandleScope, + HandleScriptSourceObject, HandleObject, + MutableHandleScript); + +template <XDRMode mode> +XDRResult js::XDRLazyScript(XDRState<mode>* xdr, HandleScope enclosingScope, + HandleScriptSourceObject sourceObject, + HandleFunction fun, + MutableHandle<BaseScript*> lazy) { + MOZ_ASSERT_IF(mode == XDR_DECODE, sourceObject); + + JSContext* cx = xdr->cx(); + + { + SourceExtent extent; + uint32_t immutableFlags; + uint32_t ngcthings; + + if (mode == XDR_ENCODE) { + MOZ_ASSERT(fun == lazy->function()); + + extent = lazy->extent(); + immutableFlags = lazy->immutableFlags(); + ngcthings = lazy->gcthings().size(); + } + + MOZ_TRY(XDRSourceExtent(xdr, &extent)); + MOZ_TRY(xdr->codeUint32(&immutableFlags)); + MOZ_TRY(xdr->codeUint32(&ngcthings)); + + if (mode == XDR_DECODE) { + lazy.set(BaseScript::CreateRawLazy(cx, ngcthings, fun, sourceObject, + extent, immutableFlags)); + if (!lazy) { + return xdr->fail(JS::TranscodeResult_Throw); + } + + // Set the enclosing scope of the lazy function. This value should only be + // set if we have a non-lazy enclosing script at this point. + // BaseScript::enclosingScriptHasEverBeenCompiled relies on the enclosing + // scope being non-null if we have ever been nested inside non-lazy + // function. + if (enclosingScope) { + lazy->setEnclosingScope(enclosingScope); + } + + fun->initScript(lazy); + } + } + + // FieldInitializer data is defined for class constructors, but only once + // their enclosing script has been compiled. + bool hasMemberInitializers = fun->isClassConstructor() && enclosingScope; + + MOZ_TRY(BaseScript::XDRLazyScriptData(xdr, sourceObject, lazy, + hasMemberInitializers)); + + return Ok(); +} + +template XDRResult js::XDRLazyScript(XDRState<XDR_ENCODE>*, HandleScope, + HandleScriptSourceObject, HandleFunction, + MutableHandle<BaseScript*>); + +template XDRResult js::XDRLazyScript(XDRState<XDR_DECODE>*, HandleScope, + HandleScriptSourceObject, HandleFunction, + MutableHandle<BaseScript*>); + +bool JSScript::initScriptCounts(JSContext* cx) { + MOZ_ASSERT(!hasScriptCounts()); + + // Record all pc which are the first instruction of a basic block. + mozilla::Vector<jsbytecode*, 16, SystemAllocPolicy> jumpTargets; + + js::BytecodeLocation main = mainLocation(); + AllBytecodesIterable iterable(this); + for (auto& loc : iterable) { + if (loc.isJumpTarget() || loc == main) { + if (!jumpTargets.append(loc.toRawBytecode())) { + ReportOutOfMemory(cx); + return false; + } + } + } + + // Initialize all PCCounts counters to 0. + ScriptCounts::PCCountsVector base; + if (!base.reserve(jumpTargets.length())) { + ReportOutOfMemory(cx); + return false; + } + + for (size_t i = 0; i < jumpTargets.length(); i++) { + base.infallibleEmplaceBack(pcToOffset(jumpTargets[i])); + } + + // Create zone's scriptCountsMap if necessary. + if (!zone()->scriptCountsMap) { + auto map = cx->make_unique<ScriptCountsMap>(); + if (!map) { + return false; + } + + zone()->scriptCountsMap = std::move(map); + } + + // Allocate the ScriptCounts. + UniqueScriptCounts sc = cx->make_unique<ScriptCounts>(std::move(base)); + if (!sc) { + ReportOutOfMemory(cx); + return false; + } + + MOZ_ASSERT(this->hasBytecode()); + + // Register the current ScriptCounts in the zone's map. + if (!zone()->scriptCountsMap->putNew(this, std::move(sc))) { + ReportOutOfMemory(cx); + return false; + } + + // safe to set this; we can't fail after this point. + setHasScriptCounts(); + + // Enable interrupts in any interpreter frames running on this script. This + // is used to let the interpreter increment the PCCounts, if present. + for (ActivationIterator iter(cx); !iter.done(); ++iter) { + if (iter->isInterpreter()) { + iter->asInterpreter()->enableInterruptsIfRunning(this); + } + } + + return true; +} + +static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript* script) { + MOZ_ASSERT(script->hasScriptCounts()); + ScriptCountsMap::Ptr p = script->zone()->scriptCountsMap->lookup(script); + MOZ_ASSERT(p); + return p; +} + +ScriptCounts& JSScript::getScriptCounts() { + ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); + return *p->value(); +} + +js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) { + PCCounts searched = PCCounts(offset); + PCCounts* elem = + std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched); + if (elem == pcCounts_.end() || elem->pcOffset() != offset) { + return nullptr; + } + return elem; +} + +const js::PCCounts* ScriptCounts::maybeGetPCCounts(size_t offset) const { + PCCounts searched = PCCounts(offset); + const PCCounts* elem = + std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched); + if (elem == pcCounts_.end() || elem->pcOffset() != offset) { + return nullptr; + } + return elem; +} + +js::PCCounts* ScriptCounts::getImmediatePrecedingPCCounts(size_t offset) { + PCCounts searched = PCCounts(offset); + PCCounts* elem = + std::lower_bound(pcCounts_.begin(), pcCounts_.end(), searched); + if (elem == pcCounts_.end()) { + return &pcCounts_.back(); + } + if (elem->pcOffset() == offset) { + return elem; + } + if (elem != pcCounts_.begin()) { + return elem - 1; + } + return nullptr; +} + +const js::PCCounts* ScriptCounts::maybeGetThrowCounts(size_t offset) const { + PCCounts searched = PCCounts(offset); + const PCCounts* elem = + std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched); + if (elem == throwCounts_.end() || elem->pcOffset() != offset) { + return nullptr; + } + return elem; +} + +const js::PCCounts* ScriptCounts::getImmediatePrecedingThrowCounts( + size_t offset) const { + PCCounts searched = PCCounts(offset); + const PCCounts* elem = + std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched); + if (elem == throwCounts_.end()) { + if (throwCounts_.begin() == throwCounts_.end()) { + return nullptr; + } + return &throwCounts_.back(); + } + if (elem->pcOffset() == offset) { + return elem; + } + if (elem != throwCounts_.begin()) { + return elem - 1; + } + return nullptr; +} + +js::PCCounts* ScriptCounts::getThrowCounts(size_t offset) { + PCCounts searched = PCCounts(offset); + PCCounts* elem = + std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched); + if (elem == throwCounts_.end() || elem->pcOffset() != offset) { + elem = throwCounts_.insert(elem, searched); + } + return elem; +} + +size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { + size_t size = mallocSizeOf(this); + size += pcCounts_.sizeOfExcludingThis(mallocSizeOf); + size += throwCounts_.sizeOfExcludingThis(mallocSizeOf); + if (ionCounts_) { + size += ionCounts_->sizeOfIncludingThis(mallocSizeOf); + } + return size; +} + +js::PCCounts* JSScript::maybeGetPCCounts(jsbytecode* pc) { + MOZ_ASSERT(containsPC(pc)); + return getScriptCounts().maybeGetPCCounts(pcToOffset(pc)); +} + +const js::PCCounts* JSScript::maybeGetThrowCounts(jsbytecode* pc) { + MOZ_ASSERT(containsPC(pc)); + return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc)); +} + +js::PCCounts* JSScript::getThrowCounts(jsbytecode* pc) { + MOZ_ASSERT(containsPC(pc)); + return getScriptCounts().getThrowCounts(pcToOffset(pc)); +} + +uint64_t JSScript::getHitCount(jsbytecode* pc) { + MOZ_ASSERT(containsPC(pc)); + if (pc < main()) { + pc = main(); + } + + ScriptCounts& sc = getScriptCounts(); + size_t targetOffset = pcToOffset(pc); + const js::PCCounts* baseCount = + sc.getImmediatePrecedingPCCounts(targetOffset); + if (!baseCount) { + return 0; + } + if (baseCount->pcOffset() == targetOffset) { + return baseCount->numExec(); + } + MOZ_ASSERT(baseCount->pcOffset() < targetOffset); + uint64_t count = baseCount->numExec(); + do { + const js::PCCounts* throwCount = + sc.getImmediatePrecedingThrowCounts(targetOffset); + if (!throwCount) { + return count; + } + if (throwCount->pcOffset() <= baseCount->pcOffset()) { + return count; + } + count -= throwCount->numExec(); + targetOffset = throwCount->pcOffset() - 1; + } while (true); +} + +void JSScript::incHitCount(jsbytecode* pc) { + MOZ_ASSERT(containsPC(pc)); + if (pc < main()) { + pc = main(); + } + + ScriptCounts& sc = getScriptCounts(); + js::PCCounts* baseCount = sc.getImmediatePrecedingPCCounts(pcToOffset(pc)); + if (!baseCount) { + return; + } + baseCount->numExec()++; +} + +void JSScript::addIonCounts(jit::IonScriptCounts* ionCounts) { + ScriptCounts& sc = getScriptCounts(); + if (sc.ionCounts_) { + ionCounts->setPrevious(sc.ionCounts_); + } + sc.ionCounts_ = ionCounts; +} + +jit::IonScriptCounts* JSScript::getIonCounts() { + return getScriptCounts().ionCounts_; +} + +void JSScript::releaseScriptCounts(ScriptCounts* counts) { + ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); + *counts = std::move(*p->value().get()); + zone()->scriptCountsMap->remove(p); + clearHasScriptCounts(); +} + +void JSScript::destroyScriptCounts() { + if (hasScriptCounts()) { + ScriptCounts scriptCounts; + releaseScriptCounts(&scriptCounts); + } +} + +void JSScript::resetScriptCounts() { + if (!hasScriptCounts()) { + return; + } + + ScriptCounts& sc = getScriptCounts(); + + for (PCCounts& elem : sc.pcCounts_) { + elem.numExec() = 0; + } + + for (PCCounts& elem : sc.throwCounts_) { + elem.numExec() = 0; + } +} + +void ScriptSourceObject::finalize(JSFreeOp* fop, JSObject* obj) { + MOZ_ASSERT(fop->onMainThread()); + ScriptSourceObject* sso = &obj->as<ScriptSourceObject>(); + if (sso->isCanonical()) { + sso->source()->finalizeGCData(); + } + sso->source()->decref(); + + // Clear the private value, calling the release hook if necessary. + sso->setPrivate(fop->runtime(), UndefinedValue()); +} + +void ScriptSourceObject::trace(JSTracer* trc, JSObject* obj) { + // This can be invoked during allocation of the SSO itself, before we've had a + // chance to initialize things properly. In that case, there's nothing to + // trace. + if (obj->as<ScriptSourceObject>().hasSource()) { + obj->as<ScriptSourceObject>().source()->trace(trc); + } +} + +static const JSClassOps ScriptSourceObjectClassOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + ScriptSourceObject::finalize, // finalize + nullptr, // call + nullptr, // hasInstance + nullptr, // construct + ScriptSourceObject::trace, // trace +}; + +const JSClass ScriptSourceObject::class_ = { + "ScriptSource", + JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_FOREGROUND_FINALIZE, + &ScriptSourceObjectClassOps}; + +ScriptSourceObject* ScriptSourceObject::createInternal(JSContext* cx, + ScriptSource* source, + HandleObject canonical) { + ScriptSourceObject* obj = + NewObjectWithGivenProto<ScriptSourceObject>(cx, nullptr); + if (!obj) { + return nullptr; + } + + source->incref(); // The matching decref is in ScriptSourceObject::finalize. + + obj->initReservedSlot(SOURCE_SLOT, PrivateValue(source)); + + if (canonical) { + obj->initReservedSlot(CANONICAL_SLOT, ObjectValue(*canonical)); + } else { + obj->initReservedSlot(CANONICAL_SLOT, ObjectValue(*obj)); + } + + // The slots below should either be populated by a call to initFromOptions or, + // if this is a non-canonical ScriptSourceObject, they are unused. Poison + // them. + obj->initReservedSlot(ELEMENT_PROPERTY_SLOT, MagicValue(JS_GENERIC_MAGIC)); + obj->initReservedSlot(INTRODUCTION_SCRIPT_SLOT, MagicValue(JS_GENERIC_MAGIC)); + + return obj; +} + +ScriptSourceObject* ScriptSourceObject::create(JSContext* cx, + ScriptSource* source) { + return createInternal(cx, source, nullptr); +} + +ScriptSourceObject* ScriptSourceObject::clone(JSContext* cx, + HandleScriptSourceObject sso) { + MOZ_ASSERT(cx->compartment() != sso->compartment()); + + RootedObject wrapped(cx, sso); + if (!cx->compartment()->wrap(cx, &wrapped)) { + return nullptr; + } + + return createInternal(cx, sso->source(), wrapped); +} + +ScriptSourceObject* ScriptSourceObject::unwrappedCanonical() const { + MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtimeFromAnyThread())); + + JSObject* obj = &getReservedSlot(CANONICAL_SLOT).toObject(); + return &UncheckedUnwrap(obj)->as<ScriptSourceObject>(); +} + +static MOZ_MUST_USE bool MaybeValidateFilename( + JSContext* cx, HandleScriptSourceObject sso, + const ReadOnlyCompileOptions& options) { + // When parsing off-thread we want to do filename validation on the main + // thread. This makes off-thread parsing more pure and is simpler because we + // can't easily throw exceptions off-thread. + MOZ_ASSERT(!cx->isHelperThreadContext()); + + if (!gFilenameValidationCallback) { + return true; + } + + const char* filename = sso->source()->filename(); + if (!filename || options.skipFilenameValidation()) { + return true; + } + + if (gFilenameValidationCallback(filename, cx->realm()->isSystem())) { + return true; + } + + const char* utf8Filename; + if (mozilla::IsUtf8(mozilla::MakeStringSpan(filename))) { + utf8Filename = filename; + } else { + utf8Filename = "(invalid UTF-8 filename)"; + } + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNSAFE_FILENAME, + utf8Filename); + return false; +} + +/* static */ +bool ScriptSourceObject::initFromOptions( + JSContext* cx, HandleScriptSourceObject source, + const ReadOnlyCompileOptions& options) { + cx->releaseCheck(source); + MOZ_ASSERT(source->isCanonical()); + MOZ_ASSERT( + source->getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic(JS_GENERIC_MAGIC)); + MOZ_ASSERT(source->getReservedSlot(INTRODUCTION_SCRIPT_SLOT) + .isMagic(JS_GENERIC_MAGIC)); + + if (!MaybeValidateFilename(cx, source, options)) { + return false; + } + + RootedString elementAttributeName(cx, options.elementAttributeName()); + if (!initElementProperties(cx, source, elementAttributeName)) { + return false; + } + + // There is no equivalent of cross-compartment wrappers for scripts. If the + // introduction script and ScriptSourceObject are in different compartments, + // we would be creating a cross-compartment script reference, which is + // forbidden. We can still store a CCW to the script source object though. + RootedValue introductionScript(cx); + if (JSScript* script = options.introductionScript()) { + if (script->compartment() == cx->compartment()) { + introductionScript.setPrivateGCThing(options.introductionScript()); + } + } + source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript); + + RootedValue privateValue(cx, UndefinedValue()); + if (options.privateValue().isUndefined()) { + // Set the private value to that of the script or module that this source is + // part of, if any. + if (JSScript* script = options.scriptOrModule()) { + privateValue = script->sourceObject()->canonicalPrivate(); + } + } else { + privateValue = options.privateValue(); + } + + if (!privateValue.isUndefined()) { + if (!JS_WrapValue(cx, &privateValue)) { + return false; + } + } + source->setPrivate(cx->runtime(), privateValue); + + return true; +} + +/* static */ +bool ScriptSourceObject::initElementProperties(JSContext* cx, + HandleScriptSourceObject source, + HandleString elementAttrName) { + MOZ_ASSERT(source->isCanonical()); + + RootedValue nameValue(cx); + if (elementAttrName) { + nameValue = StringValue(elementAttrName); + } + if (!cx->compartment()->wrap(cx, &nameValue)) { + return false; + } + + source->setReservedSlot(ELEMENT_PROPERTY_SLOT, nameValue); + + return true; +} + +void ScriptSourceObject::setPrivate(JSRuntime* rt, const Value& value) { + // Update the private value, calling addRef/release hooks if necessary + // to allow the embedding to maintain a reference count for the + // private data. + JS::AutoSuppressGCAnalysis nogc; + Value prevValue = getReservedSlot(PRIVATE_SLOT); + rt->releaseScriptPrivate(prevValue); + setReservedSlot(PRIVATE_SLOT, value); + rt->addRefScriptPrivate(value); +} + +JSObject* ScriptSourceObject::unwrappedElement(JSContext* cx) const { + JS::RootedValue privateValue(cx, unwrappedCanonical()->canonicalPrivate()); + if (privateValue.isUndefined()) { + return nullptr; + } + + MOZ_ASSERT(cx->runtime()->getElementCallback); + return (*cx->runtime()->getElementCallback)(cx, privateValue); +} + +class ScriptSource::LoadSourceMatcher { + JSContext* const cx_; + ScriptSource* const ss_; + bool* const loaded_; + + public: + explicit LoadSourceMatcher(JSContext* cx, ScriptSource* ss, bool* loaded) + : cx_(cx), ss_(ss), loaded_(loaded) {} + + template <typename Unit, SourceRetrievable CanRetrieve> + bool operator()(const Compressed<Unit, CanRetrieve>&) const { + *loaded_ = true; + return true; + } + + template <typename Unit, SourceRetrievable CanRetrieve> + bool operator()(const Uncompressed<Unit, CanRetrieve>&) const { + *loaded_ = true; + return true; + } + + template <typename Unit> + bool operator()(const Retrievable<Unit>&) { + if (!cx_->runtime()->sourceHook.ref()) { + *loaded_ = false; + return true; + } + + size_t length; + + // The first argument is just for overloading -- its value doesn't matter. + if (!tryLoadAndSetSource(Unit('0'), &length)) { + return false; + } + + return true; + } + + bool operator()(const Missing&) const { + *loaded_ = false; + return true; + } + + private: + bool tryLoadAndSetSource(const Utf8Unit&, size_t* length) const { + char* utf8Source; + if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), nullptr, + &utf8Source, length)) { + return false; + } + + if (!utf8Source) { + *loaded_ = false; + return true; + } + + if (!ss_->setRetrievedSource( + cx_, EntryUnits<Utf8Unit>(reinterpret_cast<Utf8Unit*>(utf8Source)), + *length)) { + return false; + } + + *loaded_ = true; + return true; + } + + bool tryLoadAndSetSource(const char16_t&, size_t* length) const { + char16_t* utf16Source; + if (!cx_->runtime()->sourceHook->load(cx_, ss_->filename(), &utf16Source, + nullptr, length)) { + return false; + } + + if (!utf16Source) { + *loaded_ = false; + return true; + } + + if (!ss_->setRetrievedSource(cx_, EntryUnits<char16_t>(utf16Source), + *length)) { + return false; + } + + *loaded_ = true; + return true; + } +}; + +/* static */ +bool ScriptSource::loadSource(JSContext* cx, ScriptSource* ss, bool* loaded) { + return ss->data.match(LoadSourceMatcher(cx, ss, loaded)); +} + +/* static */ +JSLinearString* JSScript::sourceData(JSContext* cx, HandleScript script) { + MOZ_ASSERT(script->scriptSource()->hasSourceText()); + return script->scriptSource()->substring(cx, script->sourceStart(), + script->sourceEnd()); +} + +bool BaseScript::appendSourceDataForToString(JSContext* cx, StringBuffer& buf) { + MOZ_ASSERT(scriptSource()->hasSourceText()); + return scriptSource()->appendSubstring(cx, buf, toStringStart(), + toStringEnd()); +} + +void UncompressedSourceCache::holdEntry(AutoHoldEntry& holder, + const ScriptSourceChunk& ssc) { + MOZ_ASSERT(!holder_); + holder.holdEntry(this, ssc); + holder_ = &holder; +} + +void UncompressedSourceCache::releaseEntry(AutoHoldEntry& holder) { + MOZ_ASSERT(holder_ == &holder); + holder_ = nullptr; +} + +template <typename Unit> +const Unit* UncompressedSourceCache::lookup(const ScriptSourceChunk& ssc, + AutoHoldEntry& holder) { + MOZ_ASSERT(!holder_); + MOZ_ASSERT(ssc.ss->isCompressed<Unit>()); + + if (!map_) { + return nullptr; + } + + if (Map::Ptr p = map_->lookup(ssc)) { + holdEntry(holder, ssc); + return static_cast<const Unit*>(p->value().get()); + } + + return nullptr; +} + +bool UncompressedSourceCache::put(const ScriptSourceChunk& ssc, SourceData data, + AutoHoldEntry& holder) { + MOZ_ASSERT(!holder_); + + if (!map_) { + map_ = MakeUnique<Map>(); + if (!map_) { + return false; + } + } + + if (!map_->put(ssc, std::move(data))) { + return false; + } + + holdEntry(holder, ssc); + return true; +} + +void UncompressedSourceCache::purge() { + if (!map_) { + return; + } + + for (Map::Range r = map_->all(); !r.empty(); r.popFront()) { + if (holder_ && r.front().key() == holder_->sourceChunk()) { + holder_->deferDelete(std::move(r.front().value())); + holder_ = nullptr; + } + } + + map_ = nullptr; +} + +size_t UncompressedSourceCache::sizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf) { + size_t n = 0; + if (map_ && !map_->empty()) { + n += map_->shallowSizeOfIncludingThis(mallocSizeOf); + for (Map::Range r = map_->all(); !r.empty(); r.popFront()) { + n += mallocSizeOf(r.front().value().get()); + } + } + return n; +} + +template <typename Unit> +const Unit* ScriptSource::chunkUnits( + JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder, + size_t chunk) { + const CompressedData<Unit>& c = *compressedData<Unit>(); + + ScriptSourceChunk ssc(this, chunk); + if (const Unit* decompressed = + cx->caches().uncompressedSourceCache.lookup<Unit>(ssc, holder)) { + return decompressed; + } + + size_t totalLengthInBytes = length() * sizeof(Unit); + size_t chunkBytes = Compressor::chunkSize(totalLengthInBytes, chunk); + + MOZ_ASSERT((chunkBytes % sizeof(Unit)) == 0); + const size_t chunkLength = chunkBytes / sizeof(Unit); + EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(chunkLength)); + if (!decompressed) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + + // Compression treats input and output memory as plain ol' bytes. These + // reinterpret_cast<>s accord exactly with that. + if (!DecompressStringChunk( + reinterpret_cast<const unsigned char*>(c.raw.chars()), chunk, + reinterpret_cast<unsigned char*>(decompressed.get()), chunkBytes)) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + + const Unit* ret = decompressed.get(); + if (!cx->caches().uncompressedSourceCache.put( + ssc, ToSourceData(std::move(decompressed)), holder)) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + return ret; +} + +template <typename Unit> +void ScriptSource::convertToCompressedSource(SharedImmutableString compressed, + size_t uncompressedLength) { + MOZ_ASSERT(isUncompressed<Unit>()); + MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength); + + if (data.is<Uncompressed<Unit, SourceRetrievable::Yes>>()) { + data = SourceType(Compressed<Unit, SourceRetrievable::Yes>( + std::move(compressed), uncompressedLength)); + } else { + data = SourceType(Compressed<Unit, SourceRetrievable::No>( + std::move(compressed), uncompressedLength)); + } +} + +template <typename Unit> +void ScriptSource::performDelayedConvertToCompressedSource() { + // There might not be a conversion to compressed source happening at all. + if (pendingCompressed_.empty()) { + return; + } + + CompressedData<Unit>& pending = + pendingCompressed_.ref<CompressedData<Unit>>(); + + convertToCompressedSource<Unit>(std::move(pending.raw), + pending.uncompressedLength); + + pendingCompressed_.destroy(); +} + +template <typename Unit> +ScriptSource::PinnedUnits<Unit>::~PinnedUnits() { + if (units_) { + MOZ_ASSERT(*stack_ == this); + *stack_ = prev_; + if (!prev_) { + source_->performDelayedConvertToCompressedSource<Unit>(); + } + } +} + +template <typename Unit> +const Unit* ScriptSource::units(JSContext* cx, + UncompressedSourceCache::AutoHoldEntry& holder, + size_t begin, size_t len) { + MOZ_ASSERT(begin <= length()); + MOZ_ASSERT(begin + len <= length()); + + if (isUncompressed<Unit>()) { + const Unit* units = uncompressedData<Unit>()->units(); + if (!units) { + return nullptr; + } + return units + begin; + } + + if (data.is<Missing>()) { + MOZ_CRASH("ScriptSource::units() on ScriptSource with missing source"); + } + + if (data.is<Retrievable<Unit>>()) { + MOZ_CRASH("ScriptSource::units() on ScriptSource with retrievable source"); + } + + MOZ_ASSERT(isCompressed<Unit>()); + + // Determine first/last chunks, the offset (in bytes) into the first chunk + // of the requested units, and the number of bytes in the last chunk. + // + // Note that first and last chunk sizes are miscomputed and *must not be + // used* when the first chunk is the last chunk. + size_t firstChunk, firstChunkOffset, firstChunkSize; + size_t lastChunk, lastChunkSize; + Compressor::rangeToChunkAndOffset( + begin * sizeof(Unit), (begin + len) * sizeof(Unit), &firstChunk, + &firstChunkOffset, &firstChunkSize, &lastChunk, &lastChunkSize); + MOZ_ASSERT(firstChunk <= lastChunk); + MOZ_ASSERT(firstChunkOffset % sizeof(Unit) == 0); + MOZ_ASSERT(firstChunkSize % sizeof(Unit) == 0); + + size_t firstUnit = firstChunkOffset / sizeof(Unit); + + // Directly return units within a single chunk. UncompressedSourceCache + // and |holder| will hold the units alive past function return. + if (firstChunk == lastChunk) { + const Unit* units = chunkUnits<Unit>(cx, holder, firstChunk); + if (!units) { + return nullptr; + } + + return units + firstUnit; + } + + // Otherwise the units span multiple chunks. Copy successive chunks' + // decompressed units into freshly-allocated memory to return. + EntryUnits<Unit> decompressed(js_pod_malloc<Unit>(len)); + if (!decompressed) { + JS_ReportOutOfMemory(cx); + return nullptr; + } + + Unit* cursor; + + { + // |AutoHoldEntry| is single-shot, and a holder successfully filled in + // by |chunkUnits| must be destroyed before another can be used. Thus + // we can't use |holder| with |chunkUnits| when |chunkUnits| is used + // with multiple chunks, and we must use and destroy distinct, fresh + // holders for each chunk. + UncompressedSourceCache::AutoHoldEntry firstHolder; + const Unit* units = chunkUnits<Unit>(cx, firstHolder, firstChunk); + if (!units) { + return nullptr; + } + + cursor = std::copy_n(units + firstUnit, firstChunkSize / sizeof(Unit), + decompressed.get()); + } + + for (size_t i = firstChunk + 1; i < lastChunk; i++) { + UncompressedSourceCache::AutoHoldEntry chunkHolder; + const Unit* units = chunkUnits<Unit>(cx, chunkHolder, i); + if (!units) { + return nullptr; + } + + cursor = std::copy_n(units, Compressor::CHUNK_SIZE / sizeof(Unit), cursor); + } + + { + UncompressedSourceCache::AutoHoldEntry lastHolder; + const Unit* units = chunkUnits<Unit>(cx, lastHolder, lastChunk); + if (!units) { + return nullptr; + } + + cursor = std::copy_n(units, lastChunkSize / sizeof(Unit), cursor); + } + + MOZ_ASSERT(PointerRangeSize(decompressed.get(), cursor) == len); + + // Transfer ownership to |holder|. + const Unit* ret = decompressed.get(); + holder.holdUnits(std::move(decompressed)); + return ret; +} + +template <typename Unit> +ScriptSource::PinnedUnits<Unit>::PinnedUnits( + JSContext* cx, ScriptSource* source, + UncompressedSourceCache::AutoHoldEntry& holder, size_t begin, size_t len) + : PinnedUnitsBase(source) { + MOZ_ASSERT(source->hasSourceType<Unit>(), "must pin units of source's type"); + + units_ = source->units<Unit>(cx, holder, begin, len); + if (units_) { + stack_ = &source->pinnedUnitsStack_; + prev_ = *stack_; + *stack_ = this; + } +} + +template class ScriptSource::PinnedUnits<Utf8Unit>; +template class ScriptSource::PinnedUnits<char16_t>; + +JSLinearString* ScriptSource::substring(JSContext* cx, size_t start, + size_t stop) { + MOZ_ASSERT(start <= stop); + + size_t len = stop - start; + if (!len) { + return cx->emptyString(); + } + UncompressedSourceCache::AutoHoldEntry holder; + + // UTF-8 source text. + if (hasSourceType<Utf8Unit>()) { + PinnedUnits<Utf8Unit> units(cx, this, holder, start, len); + if (!units.asChars()) { + return nullptr; + } + + const char* str = units.asChars(); + return NewStringCopyUTF8N<CanGC>(cx, JS::UTF8Chars(str, len)); + } + + // UTF-16 source text. + PinnedUnits<char16_t> units(cx, this, holder, start, len); + if (!units.asChars()) { + return nullptr; + } + + return NewStringCopyN<CanGC>(cx, units.asChars(), len); +} + +JSLinearString* ScriptSource::substringDontDeflate(JSContext* cx, size_t start, + size_t stop) { + MOZ_ASSERT(start <= stop); + + size_t len = stop - start; + if (!len) { + return cx->emptyString(); + } + UncompressedSourceCache::AutoHoldEntry holder; + + // UTF-8 source text. + if (hasSourceType<Utf8Unit>()) { + PinnedUnits<Utf8Unit> units(cx, this, holder, start, len); + if (!units.asChars()) { + return nullptr; + } + + const char* str = units.asChars(); + + // There doesn't appear to be a non-deflating UTF-8 string creation + // function -- but then again, it's not entirely clear how current + // callers benefit from non-deflation. + return NewStringCopyUTF8N<CanGC>(cx, JS::UTF8Chars(str, len)); + } + + // UTF-16 source text. + PinnedUnits<char16_t> units(cx, this, holder, start, len); + if (!units.asChars()) { + return nullptr; + } + + return NewStringCopyNDontDeflate<CanGC>(cx, units.asChars(), len); +} + +bool ScriptSource::appendSubstring(JSContext* cx, StringBuffer& buf, + size_t start, size_t stop) { + MOZ_ASSERT(start <= stop); + + size_t len = stop - start; + UncompressedSourceCache::AutoHoldEntry holder; + + if (hasSourceType<Utf8Unit>()) { + PinnedUnits<Utf8Unit> pinned(cx, this, holder, start, len); + if (!pinned.get()) { + return false; + } + if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) { + return false; + } + + const Utf8Unit* units = pinned.get(); + return buf.append(units, len); + } else { + PinnedUnits<char16_t> pinned(cx, this, holder, start, len); + if (!pinned.get()) { + return false; + } + if (len > SourceDeflateLimit && !buf.ensureTwoByteChars()) { + return false; + } + + const char16_t* units = pinned.get(); + return buf.append(units, len); + } +} + +JSLinearString* ScriptSource::functionBodyString(JSContext* cx) { + MOZ_ASSERT(isFunctionBody()); + + size_t start = + parameterListEnd_ + (sizeof(FunctionConstructorMedialSigils) - 1); + size_t stop = length() - (sizeof(FunctionConstructorFinalBrace) - 1); + return substring(cx, start, stop); +} + +template <typename Unit> +MOZ_MUST_USE bool ScriptSource::setUncompressedSourceHelper( + JSContext* cx, EntryUnits<Unit>&& source, size_t length, + SourceRetrievable retrievable) { + auto& cache = cx->runtime()->sharedImmutableStrings(); + + auto uniqueChars = SourceTypeTraits<Unit>::toCacheable(std::move(source)); + auto deduped = cache.getOrCreate(std::move(uniqueChars), length); + if (!deduped) { + ReportOutOfMemory(cx); + return false; + } + + if (retrievable == SourceRetrievable::Yes) { + data = SourceType( + Uncompressed<Unit, SourceRetrievable::Yes>(std::move(*deduped))); + } else { + data = SourceType( + Uncompressed<Unit, SourceRetrievable::No>(std::move(*deduped))); + } + return true; +} + +template <typename Unit> +MOZ_MUST_USE bool ScriptSource::setRetrievedSource(JSContext* cx, + EntryUnits<Unit>&& source, + size_t length) { + MOZ_ASSERT(data.is<Retrievable<Unit>>(), + "retrieved source can only overwrite the corresponding " + "retrievable source"); + return setUncompressedSourceHelper(cx, std::move(source), length, + SourceRetrievable::Yes); +} + +bool js::IsOffThreadSourceCompressionEnabled() { + // If we don't have concurrent execution compression will contend with + // main-thread execution, in which case we disable. Similarly we don't want to + // block the thread pool if it is too small. + return HelperThreadState().cpuCount > 1 && + HelperThreadState().threadCount > 1 && CanUseExtraThreads(); +} + +bool ScriptSource::tryCompressOffThread(JSContext* cx) { + // Beware: |js::SynchronouslyCompressSource| assumes that this function is + // only called once, just after a script has been compiled, and it's never + // called at some random time after that. If multiple calls of this can ever + // occur, that function may require changes. + + // The SourceCompressionTask needs to record the major GC number for + // scheduling. This cannot be accessed off-thread and must be handle in + // ParseTask::finish instead. + MOZ_ASSERT(!cx->isHelperThreadContext()); + MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); + + if (!hasUncompressedSource()) { + // This excludes compressed, missing, and retrievable source. + return true; + } + + // There are several cases where source compression is not a good idea: + // - If the script is tiny, then compression will save little or no space. + // - If there is only one core, then compression will contend with JS + // execution (which hurts benchmarketing). + // + // Otherwise, enqueue a compression task to be processed when a major + // GC is requested. + + if (length() < ScriptSource::MinimumCompressibleLength || + !IsOffThreadSourceCompressionEnabled()) { + return true; + } + + // Heap allocate the task. It will be freed upon compression + // completing in AttachFinishedCompressedSources. + auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), this); + if (!task) { + ReportOutOfMemory(cx); + return false; + } + return EnqueueOffThreadCompression(cx, std::move(task)); +} + +template <typename Unit> +void ScriptSource::triggerConvertToCompressedSource( + SharedImmutableString compressed, size_t uncompressedLength) { + MOZ_ASSERT(isUncompressed<Unit>(), + "should only be triggering compressed source installation to " + "overwrite identically-encoded uncompressed source"); + MOZ_ASSERT(uncompressedData<Unit>()->length() == uncompressedLength); + + // If units aren't pinned -- and they probably won't be, we'd have to have a + // GC in the small window of time where a |PinnedUnits| was live -- then we + // can immediately convert. + if (MOZ_LIKELY(!pinnedUnitsStack_)) { + convertToCompressedSource<Unit>(std::move(compressed), uncompressedLength); + return; + } + + // Otherwise, set aside the compressed-data info. The conversion is performed + // when the last |PinnedUnits| dies. + MOZ_ASSERT(pendingCompressed_.empty(), + "shouldn't be multiple conversions happening"); + pendingCompressed_.construct<CompressedData<Unit>>(std::move(compressed), + uncompressedLength); +} + +template <typename Unit> +MOZ_MUST_USE bool ScriptSource::initializeWithUnretrievableCompressedSource( + JSContext* cx, UniqueChars&& compressed, size_t rawLength, + size_t sourceLength) { + MOZ_ASSERT(data.is<Missing>(), "shouldn't be double-initializing"); + MOZ_ASSERT(compressed != nullptr); + + auto& cache = cx->runtime()->sharedImmutableStrings(); + auto deduped = cache.getOrCreate(std::move(compressed), rawLength); + if (!deduped) { + ReportOutOfMemory(cx); + return false; + } + + MOZ_ASSERT(pinnedUnitsStack_ == nullptr, + "shouldn't be initializing a ScriptSource while its characters " + "are pinned -- that only makes sense with a ScriptSource actively " + "being inspected"); + + data = SourceType(Compressed<Unit, SourceRetrievable::No>(std::move(*deduped), + sourceLength)); + + return true; +} + +template <typename Unit> +bool ScriptSource::assignSource(JSContext* cx, + const ReadOnlyCompileOptions& options, + SourceText<Unit>& srcBuf) { + MOZ_ASSERT(data.is<Missing>(), + "source assignment should only occur on fresh ScriptSources"); + + if (options.discardSource) { + return true; + } + + if (options.sourceIsLazy) { + data = SourceType(Retrievable<Unit>()); + return true; + } + + JSRuntime* runtime = cx->runtime(); + auto& cache = runtime->sharedImmutableStrings(); + auto deduped = cache.getOrCreate(srcBuf.get(), srcBuf.length(), [&srcBuf]() { + using CharT = typename SourceTypeTraits<Unit>::CharT; + return srcBuf.ownsUnits() + ? UniquePtr<CharT[], JS::FreePolicy>(srcBuf.takeChars()) + : DuplicateString(srcBuf.get(), srcBuf.length()); + }); + if (!deduped) { + ReportOutOfMemory(cx); + return false; + } + + data = SourceType( + Uncompressed<Unit, SourceRetrievable::No>(std::move(*deduped))); + return true; +} + +template bool ScriptSource::assignSource(JSContext* cx, + const ReadOnlyCompileOptions& options, + SourceText<char16_t>& srcBuf); +template bool ScriptSource::assignSource(JSContext* cx, + const ReadOnlyCompileOptions& options, + SourceText<Utf8Unit>& srcBuf); + +void ScriptSource::trace(JSTracer* trc) { + // This should be kept in sync with ScriptSource::finalizeGCData below. + if (xdrEncoder_) { + xdrEncoder_->trace(trc); + } +} + +void ScriptSource::finalizeGCData() { + // This should be kept in sync with ScriptSource::trace above. + + // When the canonical ScriptSourceObject's finalizer runs, this + // ScriptSource can no longer be accessed from the main + // thread. However, an offthread source compression task may still + // hold a reference. We must clean up any GC pointers owned by this + // ScriptSource now, because trying to run those prebarriers + // offthread later will fail. + MOZ_ASSERT(TlsContext.get() && TlsContext.get()->isMainThreadContext()); + + if (xdrEncoder_) { + xdrEncoder_.reset(); + } +} + +ScriptSource::~ScriptSource() { + MOZ_ASSERT(refs == 0); + + // GC pointers must have been cleared earlier, because this destructor could + // be called off-thread by SweepCompressionTasks. See above. + MOZ_ASSERT(!xdrEncoder_); +} + +static MOZ_MUST_USE bool reallocUniquePtr(UniqueChars& unique, size_t size) { + auto newPtr = static_cast<char*>(js_realloc(unique.get(), size)); + if (!newPtr) { + return false; + } + + // Since the realloc succeeded, unique is now holding a freed pointer. + mozilla::Unused << unique.release(); + unique.reset(newPtr); + return true; +} + +template <typename Unit> +void SourceCompressionTask::workEncodingSpecific() { + ScriptSource* source = sourceHolder_.get(); + MOZ_ASSERT(source->isUncompressed<Unit>()); + + // Try to keep the maximum memory usage down by only allocating half the + // size of the string, first. + size_t inputBytes = source->length() * sizeof(Unit); + size_t firstSize = inputBytes / 2; + UniqueChars compressed(js_pod_malloc<char>(firstSize)); + if (!compressed) { + return; + } + + const Unit* chars = source->uncompressedData<Unit>()->units(); + Compressor comp(reinterpret_cast<const unsigned char*>(chars), inputBytes); + if (!comp.init()) { + return; + } + + comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), firstSize); + bool cont = true; + bool reallocated = false; + while (cont) { + if (shouldCancel()) { + return; + } + + switch (comp.compressMore()) { + case Compressor::CONTINUE: + break; + case Compressor::MOREOUTPUT: { + if (reallocated) { + // The compressed string is longer than the original string. + return; + } + + // The compressed output is greater than half the size of the + // original string. Reallocate to the full size. + if (!reallocUniquePtr(compressed, inputBytes)) { + return; + } + + comp.setOutput(reinterpret_cast<unsigned char*>(compressed.get()), + inputBytes); + reallocated = true; + break; + } + case Compressor::DONE: + cont = false; + break; + case Compressor::OOM: + return; + } + } + + size_t totalBytes = comp.totalBytesNeeded(); + + // Shrink the buffer to the size of the compressed data. + if (!reallocUniquePtr(compressed, totalBytes)) { + return; + } + + comp.finish(compressed.get(), totalBytes); + + if (shouldCancel()) { + return; + } + + auto& strings = runtime_->sharedImmutableStrings(); + resultString_ = strings.getOrCreate(std::move(compressed), totalBytes); +} + +struct SourceCompressionTask::PerformTaskWork { + SourceCompressionTask* const task_; + + explicit PerformTaskWork(SourceCompressionTask* task) : task_(task) {} + + template <typename Unit, SourceRetrievable CanRetrieve> + void operator()(const ScriptSource::Uncompressed<Unit, CanRetrieve>&) { + task_->workEncodingSpecific<Unit>(); + } + + template <typename T> + void operator()(const T&) { + MOZ_CRASH( + "why are we compressing missing, missing-but-retrievable, " + "or already-compressed source?"); + } +}; + +void ScriptSource::performTaskWork(SourceCompressionTask* task) { + MOZ_ASSERT(hasUncompressedSource()); + data.match(SourceCompressionTask::PerformTaskWork(task)); +} + +void SourceCompressionTask::runTask() { + if (shouldCancel()) { + return; + } + + TraceLoggerThread* logger = TraceLoggerForCurrentThread(); + AutoTraceLog logCompile(logger, TraceLogger_CompressSource); + + ScriptSource* source = sourceHolder_.get(); + MOZ_ASSERT(source->hasUncompressedSource()); + + source->performTaskWork(this); +} + +void SourceCompressionTask::runHelperThreadTask( + AutoLockHelperThreadState& locked) { + { + AutoUnlockHelperThreadState unlock(locked); + this->runTask(); + } + + { + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!HelperThreadState().compressionFinishedList(locked).append(this)) { + oomUnsafe.crash("SourceCompressionTask::runHelperThreadTask"); + } + } +} + +void ScriptSource::triggerConvertToCompressedSourceFromTask( + SharedImmutableString compressed) { + data.match(TriggerConvertToCompressedSourceFromTask(this, compressed)); +} + +void SourceCompressionTask::complete() { + if (!shouldCancel() && resultString_.isSome()) { + ScriptSource* source = sourceHolder_.get(); + source->triggerConvertToCompressedSourceFromTask(std::move(*resultString_)); + } +} + +bool js::SynchronouslyCompressSource(JSContext* cx, + JS::Handle<BaseScript*> script) { + MOZ_ASSERT(!cx->isHelperThreadContext(), + "should only sync-compress on the main thread"); + + // Finish all pending source compressions, including the single compression + // task that may have been created (by |ScriptSource::tryCompressOffThread|) + // just after the script was compiled. Because we have flushed this queue, + // no code below needs to synchronize with an off-thread parse task that + // assumes the immutability of a |ScriptSource|'s data. + // + // This *may* end up compressing |script|'s source. If it does -- we test + // this below -- that takes care of things. But if it doesn't, we will + // synchronously compress ourselves (and as noted above, this won't race + // anything). + RunPendingSourceCompressions(cx->runtime()); + + ScriptSource* ss = script->scriptSource(); + MOZ_ASSERT(!ss->pinnedUnitsStack_, + "can't synchronously compress while source units are in use"); + + // In principle a previously-triggered compression on a helper thread could + // have already completed. If that happens, there's nothing more to do. + if (ss->hasCompressedSource()) { + return true; + } + + MOZ_ASSERT(ss->hasUncompressedSource(), + "shouldn't be compressing uncompressible source"); + + // Use an explicit scope to delineate the lifetime of |task|, for simplicity. + { +#ifdef DEBUG + uint32_t sourceRefs = ss->refs; +#endif + MOZ_ASSERT(sourceRefs > 0, "at least |script| here should have a ref"); + + // |SourceCompressionTask::shouldCancel| can periodically result in source + // compression being canceled if we're not careful. Guarantee that two refs + // to |ss| are always live in this function (at least one preexisting and + // one held by the task) so that compression is never canceled. + auto task = MakeUnique<SourceCompressionTask>(cx->runtime(), ss); + if (!task) { + ReportOutOfMemory(cx); + return false; + } + + MOZ_ASSERT(ss->refs > sourceRefs, "must have at least two refs now"); + + // Attempt to compress. This may not succeed if OOM happens, but (because + // it ordinarily happens on a helper thread) no error will ever be set here. + MOZ_ASSERT(!cx->isExceptionPending()); + ss->performTaskWork(task.get()); + MOZ_ASSERT(!cx->isExceptionPending()); + + // Convert |ss| from uncompressed to compressed data. + task->complete(); + + MOZ_ASSERT(!cx->isExceptionPending()); + } + + // The only way source won't be compressed here is if OOM happened. + return ss->hasCompressedSource(); +} + +void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, + JS::ScriptSourceInfo* info) const { + info->misc += mallocSizeOf(this); + info->numScripts++; +} + +bool ScriptSource::xdrEncodeTopLevel(JSContext* cx, HandleScript script) { + // Encoding failures are reported by the xdrFinalizeEncoder function. + if (containsAsmJS()) { + return true; + } + + xdrEncoder_ = js::MakeUnique<XDRIncrementalEncoder>(cx); + if (!xdrEncoder_) { + ReportOutOfMemory(cx); + return false; + } + + MOZ_ASSERT(hasEncoder()); + AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime); + + auto failureCase = + mozilla::MakeScopeExit([&] { xdrEncoder_.reset(nullptr); }); + + RootedScript s(cx, script); + XDRResult res = xdrEncoder_->codeScript(&s); + if (res.isErr()) { + // On encoding failure, let failureCase destroy encoder and return true + // to avoid failing any currently executing script. + if (res.unwrapErr() & JS::TranscodeResult_Failure) { + return true; + } + + return false; + } + + failureCase.release(); + return true; +} + +bool ScriptSource::xdrEncodeInitialStencil( + JSContext* cx, frontend::CompilationStencil& stencil, + UniquePtr<XDRIncrementalEncoderBase>& xdrEncoder) { + // Encoding failures are reported by the xdrFinalizeEncoder function. + if (containsAsmJS()) { + return true; + } + + xdrEncoder = js::MakeUnique<XDRIncrementalStencilEncoder>(cx); + if (!xdrEncoder) { + ReportOutOfMemory(cx); + return false; + } + + AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime); + auto failureCase = mozilla::MakeScopeExit([&] { xdrEncoder.reset(nullptr); }); + + XDRResult res = xdrEncoder->codeStencil(stencil); + if (res.isErr()) { + // On encoding failure, let failureCase destroy encoder and return true + // to avoid failing any currently executing script. + if (res.unwrapErr() & JS::TranscodeResult_Failure) { + return true; + } + + return false; + } + + failureCase.release(); + return true; +} + +bool ScriptSource::xdrEncodeStencils( + JSContext* cx, frontend::CompilationStencilSet& stencilSet, + UniquePtr<XDRIncrementalEncoderBase>& xdrEncoder) { + if (!xdrEncodeInitialStencil(cx, stencilSet, xdrEncoder)) { + return false; + } + + for (auto& delazification : stencilSet.delazifications) { + if (!xdrEncodeFunctionStencilWith(cx, delazification, xdrEncoder)) { + return false; + } + } + + return true; +} + +void ScriptSource::setIncrementalEncoder( + XDRIncrementalEncoderBase* xdrEncoder) { + xdrEncoder_.reset(xdrEncoder); +} + +bool ScriptSource::xdrEncodeFunction(JSContext* cx, HandleFunction fun, + HandleScriptSourceObject sourceObject) { + MOZ_ASSERT(sourceObject->source() == this); + MOZ_ASSERT(hasEncoder()); + AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime); + + auto failureCase = + mozilla::MakeScopeExit([&] { xdrEncoder_.reset(nullptr); }); + + RootedFunction f(cx, fun); + XDRResult res = xdrEncoder_->codeFunction(&f, sourceObject); + if (res.isErr()) { + // On encoding failure, let failureCase destroy encoder and return true + // to avoid failing any currently executing script. + if (res.unwrapErr() & JS::TranscodeResult_Failure) { + return true; + } + return false; + } + + failureCase.release(); + return true; +} + +bool ScriptSource::xdrEncodeFunctionStencil( + JSContext* cx, frontend::BaseCompilationStencil& stencil) { + MOZ_ASSERT(hasEncoder()); + AutoIncrementalTimer timer(cx->realm()->timers.xdrEncodingTime); + return xdrEncodeFunctionStencilWith(cx, stencil, xdrEncoder_); +} + +bool ScriptSource::xdrEncodeFunctionStencilWith( + JSContext* cx, frontend::BaseCompilationStencil& stencil, + UniquePtr<XDRIncrementalEncoderBase>& xdrEncoder) { + auto failureCase = mozilla::MakeScopeExit([&] { xdrEncoder.reset(nullptr); }); + + XDRResult res = xdrEncoder->codeFunctionStencil(stencil); + if (res.isErr()) { + // On encoding failure, let failureCase destroy encoder and return true + // to avoid failing any currently executing script. + if (res.unwrapErr() & JS::TranscodeResult_Failure) { + return true; + } + return false; + } + + failureCase.release(); + return true; +} + +bool ScriptSource::xdrFinalizeEncoder(JSContext* cx, + JS::TranscodeBuffer& buffer) { + if (!hasEncoder()) { + JS_ReportErrorASCII(cx, "XDR encoding failure"); + return false; + } + + auto cleanup = mozilla::MakeScopeExit([&] { xdrEncoder_.reset(nullptr); }); + + XDRResult res = xdrEncoder_->linearize(buffer, this); + return res.isOk(); +} + +template <typename Unit> +MOZ_MUST_USE bool ScriptSource::initializeUnretrievableUncompressedSource( + JSContext* cx, EntryUnits<Unit>&& source, size_t length) { + MOZ_ASSERT(data.is<Missing>(), "must be initializing a fresh ScriptSource"); + return setUncompressedSourceHelper(cx, std::move(source), length, + SourceRetrievable::No); +} + +template <typename Unit> +struct UnretrievableSourceDecoder { + XDRState<XDR_DECODE>* const xdr_; + ScriptSource* const scriptSource_; + const uint32_t uncompressedLength_; + + public: + UnretrievableSourceDecoder(XDRState<XDR_DECODE>* xdr, + ScriptSource* scriptSource, + uint32_t uncompressedLength) + : xdr_(xdr), + scriptSource_(scriptSource), + uncompressedLength_(uncompressedLength) {} + + XDRResult decode() { + auto sourceUnits = xdr_->cx()->make_pod_array<Unit>( + std::max<size_t>(uncompressedLength_, 1)); + if (!sourceUnits) { + return xdr_->fail(JS::TranscodeResult_Throw); + } + + MOZ_TRY(xdr_->codeChars(sourceUnits.get(), uncompressedLength_)); + + if (!scriptSource_->initializeUnretrievableUncompressedSource( + xdr_->cx(), std::move(sourceUnits), uncompressedLength_)) { + return xdr_->fail(JS::TranscodeResult_Throw); + } + + return Ok(); + } +}; + +namespace js { + +template <> +XDRResult ScriptSource::xdrUnretrievableUncompressedSource<XDR_DECODE>( + XDRState<XDR_DECODE>* xdr, uint8_t sourceCharSize, + uint32_t uncompressedLength) { + MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2); + + if (sourceCharSize == 1) { + UnretrievableSourceDecoder<Utf8Unit> decoder(xdr, this, uncompressedLength); + return decoder.decode(); + } + + UnretrievableSourceDecoder<char16_t> decoder(xdr, this, uncompressedLength); + return decoder.decode(); +} + +} // namespace js + +template <typename Unit> +struct UnretrievableSourceEncoder { + XDRState<XDR_ENCODE>* const xdr_; + ScriptSource* const source_; + const uint32_t uncompressedLength_; + + UnretrievableSourceEncoder(XDRState<XDR_ENCODE>* xdr, ScriptSource* source, + uint32_t uncompressedLength) + : xdr_(xdr), source_(source), uncompressedLength_(uncompressedLength) {} + + XDRResult encode() { + Unit* sourceUnits = + const_cast<Unit*>(source_->uncompressedData<Unit>()->units()); + + return xdr_->codeChars(sourceUnits, uncompressedLength_); + } +}; + +namespace js { + +template <> +XDRResult ScriptSource::xdrUnretrievableUncompressedSource<XDR_ENCODE>( + XDRState<XDR_ENCODE>* xdr, uint8_t sourceCharSize, + uint32_t uncompressedLength) { + MOZ_ASSERT(sourceCharSize == 1 || sourceCharSize == 2); + + if (sourceCharSize == 1) { + UnretrievableSourceEncoder<Utf8Unit> encoder(xdr, this, uncompressedLength); + return encoder.encode(); + } + + UnretrievableSourceEncoder<char16_t> encoder(xdr, this, uncompressedLength); + return encoder.encode(); +} + +} // namespace js + +template <typename Unit, XDRMode mode> +/* static */ +XDRResult ScriptSource::codeUncompressedData(XDRState<mode>* const xdr, + ScriptSource* const ss) { + static_assert( + std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>, + "should handle UTF-8 and UTF-16"); + + if (mode == XDR_ENCODE) { + MOZ_ASSERT(ss->isUncompressed<Unit>()); + } else { + MOZ_ASSERT(ss->data.is<Missing>()); + } + + uint32_t uncompressedLength; + if (mode == XDR_ENCODE) { + uncompressedLength = ss->uncompressedData<Unit>()->length(); + } + MOZ_TRY(xdr->codeUint32(&uncompressedLength)); + + return ss->xdrUnretrievableUncompressedSource(xdr, sizeof(Unit), + uncompressedLength); +} + +template <typename Unit, XDRMode mode> +/* static */ +XDRResult ScriptSource::codeCompressedData(XDRState<mode>* const xdr, + ScriptSource* const ss) { + static_assert( + std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>, + "should handle UTF-8 and UTF-16"); + + if (mode == XDR_ENCODE) { + MOZ_ASSERT(ss->isCompressed<Unit>()); + } else { + MOZ_ASSERT(ss->data.is<Missing>()); + } + + uint32_t uncompressedLength; + if (mode == XDR_ENCODE) { + uncompressedLength = ss->data.as<Compressed<Unit, SourceRetrievable::No>>() + .uncompressedLength; + } + MOZ_TRY(xdr->codeUint32(&uncompressedLength)); + + uint32_t compressedLength; + if (mode == XDR_ENCODE) { + compressedLength = + ss->data.as<Compressed<Unit, SourceRetrievable::No>>().raw.length(); + } + MOZ_TRY(xdr->codeUint32(&compressedLength)); + + if (mode == XDR_DECODE) { + // Compressed data is always single-byte chars. + auto bytes = xdr->cx()->template make_pod_array<char>(compressedLength); + if (!bytes) { + return xdr->fail(JS::TranscodeResult_Throw); + } + MOZ_TRY(xdr->codeBytes(bytes.get(), compressedLength)); + + if (!ss->initializeWithUnretrievableCompressedSource<Unit>( + xdr->cx(), std::move(bytes), compressedLength, + uncompressedLength)) { + return xdr->fail(JS::TranscodeResult_Throw); + } + } else { + void* bytes = const_cast<char*>(ss->compressedData<Unit>()->raw.chars()); + MOZ_TRY(xdr->codeBytes(bytes, compressedLength)); + } + + return Ok(); +} + +template <typename Unit, + template <typename U, SourceRetrievable CanRetrieve> class Data, + XDRMode mode> +/* static */ +void ScriptSource::codeRetrievable(ScriptSource* const ss) { + static_assert( + std::is_same_v<Unit, Utf8Unit> || std::is_same_v<Unit, char16_t>, + "should handle UTF-8 and UTF-16"); + + if (mode == XDR_ENCODE) { + MOZ_ASSERT((ss->data.is<Data<Unit, SourceRetrievable::Yes>>())); + } else { + MOZ_ASSERT(ss->data.is<Missing>()); + ss->data = SourceType(Retrievable<Unit>()); + } +} + +template <typename Unit, XDRMode mode> +/* static */ +void ScriptSource::codeRetrievableData(ScriptSource* ss) { + // There's nothing to code for retrievable data. Just be sure to set + // retrievable data when decoding. + if (mode == XDR_ENCODE) { + MOZ_ASSERT(ss->data.is<Retrievable<Unit>>()); + } else { + MOZ_ASSERT(ss->data.is<Missing>()); + ss->data = SourceType(Retrievable<Unit>()); + } +} + +template <XDRMode mode> +/* static */ +XDRResult ScriptSource::xdrData(XDRState<mode>* const xdr, + ScriptSource* const ss) { + // The order here corresponds to the type order in |ScriptSource::SourceType| + // so number->internal Variant tag is a no-op. + enum class DataType { + CompressedUtf8Retrievable, + UncompressedUtf8Retrievable, + CompressedUtf8NotRetrievable, + UncompressedUtf8NotRetrievable, + CompressedUtf16Retrievable, + UncompressedUtf16Retrievable, + CompressedUtf16NotRetrievable, + UncompressedUtf16NotRetrievable, + RetrievableUtf8, + RetrievableUtf16, + Missing, + }; + + DataType tag; + { + // This is terrible, but we can't do better. When |mode == XDR_DECODE| we + // don't have a |ScriptSource::data| |Variant| to match -- the entire XDR + // idiom for tagged unions depends on coding a tag-number, then the + // corresponding tagged data. So we must manually define a tag-enum, code + // it, then switch on it (and ignore the |Variant::match| API). + class XDRDataTag { + public: + DataType operator()(const Compressed<Utf8Unit, SourceRetrievable::Yes>&) { + return DataType::CompressedUtf8Retrievable; + } + DataType operator()( + const Uncompressed<Utf8Unit, SourceRetrievable::Yes>&) { + return DataType::UncompressedUtf8Retrievable; + } + DataType operator()(const Compressed<Utf8Unit, SourceRetrievable::No>&) { + return DataType::CompressedUtf8NotRetrievable; + } + DataType operator()( + const Uncompressed<Utf8Unit, SourceRetrievable::No>&) { + return DataType::UncompressedUtf8NotRetrievable; + } + DataType operator()(const Compressed<char16_t, SourceRetrievable::Yes>&) { + return DataType::CompressedUtf16Retrievable; + } + DataType operator()( + const Uncompressed<char16_t, SourceRetrievable::Yes>&) { + return DataType::UncompressedUtf16Retrievable; + } + DataType operator()(const Compressed<char16_t, SourceRetrievable::No>&) { + return DataType::CompressedUtf16NotRetrievable; + } + DataType operator()( + const Uncompressed<char16_t, SourceRetrievable::No>&) { + return DataType::UncompressedUtf16NotRetrievable; + } + DataType operator()(const Retrievable<Utf8Unit>&) { + return DataType::RetrievableUtf8; + } + DataType operator()(const Retrievable<char16_t>&) { + return DataType::RetrievableUtf16; + } + DataType operator()(const Missing&) { return DataType::Missing; } + }; + + uint8_t type; + if (mode == XDR_ENCODE) { + type = static_cast<uint8_t>(ss->data.match(XDRDataTag())); + } + MOZ_TRY(xdr->codeUint8(&type)); + + if (type > static_cast<uint8_t>(DataType::Missing)) { + // Fail in debug, but only soft-fail in release, if the type is invalid. + MOZ_ASSERT_UNREACHABLE("bad tag"); + return xdr->fail(JS::TranscodeResult_Failure_BadDecode); + } + + tag = static_cast<DataType>(type); + } + + switch (tag) { + case DataType::CompressedUtf8Retrievable: + ScriptSource::codeRetrievable<Utf8Unit, Compressed, mode>(ss); + return Ok(); + + case DataType::CompressedUtf8NotRetrievable: + return ScriptSource::codeCompressedData<Utf8Unit>(xdr, ss); + + case DataType::UncompressedUtf8Retrievable: + ScriptSource::codeRetrievable<Utf8Unit, Uncompressed, mode>(ss); + return Ok(); + + case DataType::UncompressedUtf8NotRetrievable: + return ScriptSource::codeUncompressedData<Utf8Unit>(xdr, ss); + + case DataType::CompressedUtf16Retrievable: + ScriptSource::codeRetrievable<char16_t, Compressed, mode>(ss); + return Ok(); + + case DataType::CompressedUtf16NotRetrievable: + return ScriptSource::codeCompressedData<char16_t>(xdr, ss); + + case DataType::UncompressedUtf16Retrievable: + ScriptSource::codeRetrievable<char16_t, Uncompressed, mode>(ss); + return Ok(); + + case DataType::UncompressedUtf16NotRetrievable: + return ScriptSource::codeUncompressedData<char16_t>(xdr, ss); + + case DataType::Missing: { + MOZ_ASSERT(ss->data.is<Missing>(), + "ScriptSource::data is initialized as missing, so neither " + "encoding nor decoding has to change anything"); + + // There's no data to XDR for missing source. + break; + } + + case DataType::RetrievableUtf8: + ScriptSource::codeRetrievableData<Utf8Unit, mode>(ss); + return Ok(); + + case DataType::RetrievableUtf16: + ScriptSource::codeRetrievableData<char16_t, mode>(ss); + return Ok(); + } + + // The range-check on |type| far above ought ensure the above |switch| is + // exhaustive and all cases will return, but not all compilers understand + // this. Make the Missing case break to here so control obviously never flows + // off the end. + MOZ_ASSERT(tag == DataType::Missing); + return Ok(); +} + +template <XDRMode mode> +/* static */ +XDRResult ScriptSource::XDR(XDRState<mode>* xdr, + const ReadOnlyCompileOptions* maybeOptions, + MutableHandle<ScriptSourceHolder> holder) { + JSContext* cx = xdr->cx(); + ScriptSource* ss = nullptr; + + if (mode == XDR_ENCODE) { + ss = holder.get().get(); + } else { + // Allocate a new ScriptSource and root it with the holder. + ss = cx->new_<ScriptSource>(); + if (!ss) { + return xdr->fail(JS::TranscodeResult_Throw); + } + holder.get().reset(ss); + + // We use this CompileOptions only to initialize the ScriptSourceObject. + // Most CompileOptions fields aren't used by ScriptSourceObject, and those + // that are (element; elementAttributeName) aren't preserved by XDR. So + // this can be simple. + if (!ss->initFromOptions(cx, *maybeOptions)) { + return xdr->fail(JS::TranscodeResult_Throw); + } + } + + MOZ_TRY(xdrData(xdr, ss)); + + uint8_t haveSourceMap = ss->hasSourceMapURL(); + MOZ_TRY(xdr->codeUint8(&haveSourceMap)); + + if (haveSourceMap) { + XDRTranscodeString<char16_t> chars; + + if (mode == XDR_ENCODE) { + chars.construct<const char16_t*>(ss->sourceMapURL()); + } + MOZ_TRY(xdr->codeCharsZ(chars)); + if (mode == XDR_DECODE) { + if (!ss->setSourceMapURL(cx, + std::move(chars.ref<UniqueTwoByteChars>()))) { + return xdr->fail(JS::TranscodeResult_Throw); + } + } + } + + uint8_t haveDisplayURL = ss->hasDisplayURL(); + MOZ_TRY(xdr->codeUint8(&haveDisplayURL)); + + if (haveDisplayURL) { + XDRTranscodeString<char16_t> chars; + + if (mode == XDR_ENCODE) { + chars.construct<const char16_t*>(ss->displayURL()); + } + MOZ_TRY(xdr->codeCharsZ(chars)); + if (mode == XDR_DECODE) { + if (!ss->setDisplayURL(cx, std::move(chars.ref<UniqueTwoByteChars>()))) { + return xdr->fail(JS::TranscodeResult_Throw); + } + } + } + + uint8_t haveFilename = !!ss->filename_; + MOZ_TRY(xdr->codeUint8(&haveFilename)); + + if (haveFilename) { + XDRTranscodeString<char> chars; + + if (mode == XDR_ENCODE) { + chars.construct<const char*>(ss->filename()); + } + MOZ_TRY(xdr->codeCharsZ(chars)); + if (mode == XDR_DECODE) { + if (!ss->filename()) { + if (!ss->setFilename(cx, std::move(chars.ref<UniqueChars>()))) { + return xdr->fail(JS::TranscodeResult_Throw); + } + } + MOZ_ASSERT(ss->filename()); + } + } + + return Ok(); +} + +template /* static */ + XDRResult + ScriptSource::XDR(XDRState<XDR_ENCODE>* xdr, + const ReadOnlyCompileOptions* maybeOptions, + MutableHandle<ScriptSourceHolder> holder); +template /* static */ + XDRResult + ScriptSource::XDR(XDRState<XDR_DECODE>* xdr, + const ReadOnlyCompileOptions* maybeOptions, + MutableHandle<ScriptSourceHolder> holder); + +// Format and return a cx->pod_malloc'ed URL for a generated script like: +// {filename} line {lineno} > {introducer} +// For example: +// foo.js line 7 > eval +// indicating code compiled by the call to 'eval' on line 7 of foo.js. +UniqueChars js::FormatIntroducedFilename(JSContext* cx, const char* filename, + unsigned lineno, + const char* introducer) { + // Compute the length of the string in advance, so we can allocate a + // buffer of the right size on the first shot. + // + // (JS_smprintf would be perfect, as that allocates the result + // dynamically as it formats the string, but it won't allocate from cx, + // and wants us to use a special free function.) + char linenoBuf[15]; + size_t filenameLen = strlen(filename); + size_t linenoLen = SprintfLiteral(linenoBuf, "%u", lineno); + size_t introducerLen = strlen(introducer); + size_t len = filenameLen + 6 /* == strlen(" line ") */ + linenoLen + + 3 /* == strlen(" > ") */ + introducerLen + 1 /* \0 */; + UniqueChars formatted(cx->pod_malloc<char>(len)); + if (!formatted) { + return nullptr; + } + + mozilla::DebugOnly<size_t> checkLen = snprintf( + formatted.get(), len, "%s line %s > %s", filename, linenoBuf, introducer); + MOZ_ASSERT(checkLen == len - 1); + + return formatted; +} + +bool ScriptSource::initFromOptions(JSContext* cx, + const ReadOnlyCompileOptions& options) { + MOZ_ASSERT(!filename_); + MOZ_ASSERT(!introducerFilename_); + + mutedErrors_ = options.mutedErrors(); + + startLine_ = options.lineno; + introductionType_ = options.introductionType; + setIntroductionOffset(options.introductionOffset); + // The parameterListEnd_ is initialized later by setParameterListEnd, before + // we expose any scripts that use this ScriptSource to the debugger. + + if (options.hasIntroductionInfo) { + MOZ_ASSERT(options.introductionType != nullptr); + const char* filename = + options.filename() ? options.filename() : "<unknown>"; + UniqueChars formatted = FormatIntroducedFilename( + cx, filename, options.introductionLineno, options.introductionType); + if (!formatted) { + return false; + } + if (!setFilename(cx, std::move(formatted))) { + return false; + } + } else if (options.filename()) { + if (!setFilename(cx, options.filename())) { + return false; + } + } + + if (options.introducerFilename()) { + if (!setIntroducerFilename(cx, options.introducerFilename())) { + return false; + } + } + + return true; +} + +// Use the SharedImmutableString map to deduplicate input string. The input +// string must be null-terminated. +template <typename SharedT, typename CharT> +static Maybe<SharedT> GetOrCreateStringZ( + JSContext* cx, UniquePtr<CharT[], JS::FreePolicy>&& str) { + JSRuntime* rt = cx->runtime(); + size_t lengthWithNull = std::char_traits<CharT>::length(str.get()) + 1; + auto res = + rt->sharedImmutableStrings().getOrCreate(std::move(str), lengthWithNull); + if (!res) { + ReportOutOfMemory(cx); + } + return res; +} + +Maybe<SharedImmutableString> ScriptSource::getOrCreateStringZ( + JSContext* cx, UniqueChars&& str) { + return GetOrCreateStringZ<SharedImmutableString>(cx, std::move(str)); +} + +Maybe<SharedImmutableTwoByteString> ScriptSource::getOrCreateStringZ( + JSContext* cx, UniqueTwoByteChars&& str) { + return GetOrCreateStringZ<SharedImmutableTwoByteString>(cx, std::move(str)); +} + +bool ScriptSource::setFilename(JSContext* cx, const char* filename) { + UniqueChars owned = DuplicateString(cx, filename); + if (!owned) { + return false; + } + return setFilename(cx, std::move(owned)); +} + +bool ScriptSource::setFilename(JSContext* cx, UniqueChars&& filename) { + MOZ_ASSERT(!filename_); + filename_ = getOrCreateStringZ(cx, std::move(filename)); + return filename_.isSome(); +} + +bool ScriptSource::setIntroducerFilename(JSContext* cx, const char* filename) { + UniqueChars owned = DuplicateString(cx, filename); + if (!owned) { + return false; + } + return setIntroducerFilename(cx, std::move(owned)); +} + +bool ScriptSource::setIntroducerFilename(JSContext* cx, + UniqueChars&& filename) { + MOZ_ASSERT(!introducerFilename_); + introducerFilename_ = getOrCreateStringZ(cx, std::move(filename)); + return introducerFilename_.isSome(); +} + +bool ScriptSource::setDisplayURL(JSContext* cx, const char16_t* url) { + UniqueTwoByteChars owned = DuplicateString(cx, url); + if (!owned) { + return false; + } + return setDisplayURL(cx, std::move(owned)); +} + +bool ScriptSource::setDisplayURL(JSContext* cx, UniqueTwoByteChars&& url) { + if (hasDisplayURL()) { + // FIXME: filename() should be UTF-8 (bug 987069). + if (!cx->isHelperThreadContext() && + !WarnNumberLatin1(cx, JSMSG_ALREADY_HAS_PRAGMA, filename(), + "//# sourceURL")) { + return false; + } + } + + MOZ_ASSERT(url); + if (url[0] == '\0') { + return true; + } + + displayURL_ = getOrCreateStringZ(cx, std::move(url)); + return displayURL_.isSome(); +} + +bool ScriptSource::setSourceMapURL(JSContext* cx, const char16_t* url) { + UniqueTwoByteChars owned = DuplicateString(cx, url); + if (!owned) { + return false; + } + return setSourceMapURL(cx, std::move(owned)); +} + +bool ScriptSource::setSourceMapURL(JSContext* cx, UniqueTwoByteChars&& url) { + MOZ_ASSERT(url); + if (url[0] == '\0') { + return true; + } + + sourceMapURL_ = getOrCreateStringZ(cx, std::move(url)); + return sourceMapURL_.isSome(); +} + +/* static */ mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent> + ScriptSource::idCount_; + +/* + * [SMDOC] JSScript data layout (immutable) + * + * Script data that shareable across processes. There are no pointers (GC or + * otherwise) and the data is relocatable. + * + * Array elements Pointed to by Length + * -------------- ------------- ------ + * jsbytecode code() codeLength() + * jsscrnote notes() noteLength() + * uint32_t resumeOffsets() + * ScopeNote scopeNotes() + * TryNote tryNotes() + */ + +js::UniquePtr<ImmutableScriptData> js::ImmutableScriptData::new_( + JSContext* cx, uint32_t codeLength, uint32_t noteLength, + uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes) { + // Take a count of which optional arrays will be used and need offset info. + unsigned numOptionalArrays = unsigned(numResumeOffsets > 0) + + unsigned(numScopeNotes > 0) + + unsigned(numTryNotes > 0); + + // Compute size including trailing arrays. + CheckedInt<Offset> size = sizeof(ImmutableScriptData); + size += sizeof(Flags); + size += CheckedInt<Offset>(codeLength) * sizeof(jsbytecode); + size += CheckedInt<Offset>(noteLength) * sizeof(SrcNote); + size += CheckedInt<Offset>(numOptionalArrays) * sizeof(Offset); + size += CheckedInt<Offset>(numResumeOffsets) * sizeof(uint32_t); + size += CheckedInt<Offset>(numScopeNotes) * sizeof(ScopeNote); + size += CheckedInt<Offset>(numTryNotes) * sizeof(TryNote); + if (!size.isValid()) { + ReportAllocationOverflow(cx); + return nullptr; + } + + // Allocate contiguous raw buffer. + void* raw = cx->pod_malloc<uint8_t>(size.value()); + MOZ_ASSERT(uintptr_t(raw) % alignof(ImmutableScriptData) == 0); + if (!raw) { + return nullptr; + } + + // Constuct the ImmutableScriptData. Trailing arrays are uninitialized but + // GCPtrs are put into a safe state. + UniquePtr<ImmutableScriptData> result(new (raw) ImmutableScriptData( + codeLength, noteLength, numResumeOffsets, numScopeNotes, numTryNotes)); + if (!result) { + return nullptr; + } + + // Sanity check + MOZ_ASSERT(result->endOffset() == size.value()); + + return result; +} + +/* static */ +SharedImmutableScriptData* SharedImmutableScriptData::create(JSContext* cx) { + return cx->new_<SharedImmutableScriptData>(); +} + +/* static */ +SharedImmutableScriptData* SharedImmutableScriptData::createWith( + JSContext* cx, js::UniquePtr<ImmutableScriptData>&& isd) { + MOZ_ASSERT(isd.get()); + SharedImmutableScriptData* sisd = create(cx); + if (!sisd) { + return nullptr; + } + + sisd->isd_ = std::move(isd); + return sisd; +} + +void JSScript::relazify(JSRuntime* rt) { + js::Scope* scope = enclosingScope(); + UniquePtr<PrivateScriptData> scriptData; + +#ifndef JS_CODEGEN_NONE + // Any JIT compiles should have been released, so we already point to the + // interpreter trampoline which supports lazy scripts. + MOZ_ASSERT(isUsingInterpreterTrampoline(rt)); +#endif + + // Without bytecode, the script counts are invalid so destroy them if they + // still exist. + destroyScriptCounts(); + + // Release the bytecode and gcthings list. + // NOTE: We clear the PrivateScriptData to nullptr. This is fine because we + // only allowed relazification (via AllowRelazify) if the original lazy + // script we compiled from had a nullptr PrivateScriptData. + swapData(scriptData); + freeSharedData(); + + // We should not still be in any side-tables for the debugger or + // code-coverage. The finalizer will not be able to clean them up once + // bytecode is released. We check in JSFunction::maybeRelazify() for these + // conditions before requesting relazification. + MOZ_ASSERT(!coverage::IsLCovEnabled()); + MOZ_ASSERT(!hasScriptCounts()); + MOZ_ASSERT(!hasDebugScript()); + + // Rollback warmUpData_ to have enclosingScope. + MOZ_ASSERT(warmUpData_.isWarmUpCount(), + "JitScript should already be released"); + warmUpData_.resetWarmUpCount(0); + warmUpData_.initEnclosingScope(scope); + + MOZ_ASSERT(isReadyForDelazification()); +} + +// Takes ownership of the passed SharedImmutableScriptData and either adds it +// into the runtime's SharedImmutableScriptDataTable, or frees it if a matching +// entry already exists and replaces the passed RefPtr with the existing entry. +/* static */ +bool SharedImmutableScriptData::shareScriptData( + JSContext* cx, RefPtr<SharedImmutableScriptData>& sisd) { + MOZ_ASSERT(sisd); + MOZ_ASSERT(sisd->refCount() == 1); + + SharedImmutableScriptData* data = sisd.get(); + + // Calculate the hash before taking the lock. Because the data is reference + // counted, it also will be freed after releasing the lock if necessary. + SharedImmutableScriptData::Hasher::Lookup lookup(data); + + AutoLockScriptData lock(cx->runtime()); + + SharedImmutableScriptDataTable::AddPtr p = + cx->scriptDataTable(lock).lookupForAdd(lookup); + if (p) { + MOZ_ASSERT(data != *p); + sisd = *p; + } else { + if (!cx->scriptDataTable(lock).add(p, data)) { + ReportOutOfMemory(cx); + return false; + } + + // Being in the table counts as a reference on the script data. + data->AddRef(); + } + + // Refs: sisd argument, SharedImmutableScriptDataTable + MOZ_ASSERT(sisd->refCount() >= 2); + + return true; +} + +void js::SweepScriptData(JSRuntime* rt) { + // Entries are removed from the table when their reference count is one, + // i.e. when the only reference to them is from the table entry. + + AutoLockScriptData lock(rt); + SharedImmutableScriptDataTable& table = rt->scriptDataTable(lock); + + for (SharedImmutableScriptDataTable::Enum e(table); !e.empty(); + e.popFront()) { + SharedImmutableScriptData* sharedData = e.front(); + if (sharedData->refCount() == 1) { + sharedData->Release(); + e.removeFront(); + } + } +} + +inline size_t PrivateScriptData::allocationSize() const { return endOffset(); } + +// Initialize and placement-new the trailing arrays. +PrivateScriptData::PrivateScriptData(uint32_t ngcthings) + : ngcthings(ngcthings) { + // Variable-length data begins immediately after PrivateScriptData itself. + // NOTE: Alignment is computed using cursor/offset so the alignment of + // PrivateScriptData must be stricter than any trailing array type. + Offset cursor = sizeof(PrivateScriptData); + + // Layout and initialize the gcthings array. + { + initElements<JS::GCCellPtr>(cursor, ngcthings); + cursor += ngcthings * sizeof(JS::GCCellPtr); + } + + // Sanity check. + MOZ_ASSERT(endOffset() == cursor); +} + +/* static */ +PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t ngcthings) { + // Compute size including trailing arrays. + CheckedInt<Offset> size = sizeof(PrivateScriptData); + size += CheckedInt<Offset>(ngcthings) * sizeof(JS::GCCellPtr); + if (!size.isValid()) { + ReportAllocationOverflow(cx); + return nullptr; + } + + // Allocate contiguous raw buffer for the trailing arrays. + void* raw = cx->pod_malloc<uint8_t>(size.value()); + MOZ_ASSERT(uintptr_t(raw) % alignof(PrivateScriptData) == 0); + if (!raw) { + return nullptr; + } + + // Constuct the PrivateScriptData. Trailing arrays are uninitialized but + // GCPtrs are put into a safe state. + PrivateScriptData* result = new (raw) PrivateScriptData(ngcthings); + if (!result) { + return nullptr; + } + + // Sanity check. + MOZ_ASSERT(result->endOffset() == size.value()); + + return result; +} + +/* static */ +bool PrivateScriptData::InitFromStencil( + JSContext* cx, js::HandleScript script, + js::frontend::CompilationInput& input, + js::frontend::BaseCompilationStencil& stencil, + js::frontend::CompilationGCOutput& gcOutput, + const js::frontend::ScriptIndex scriptIndex) { + js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex]; + uint32_t ngcthings = scriptStencil.gcThingsLength; + + MOZ_ASSERT(ngcthings <= INDEX_LIMIT); + + // Create and initialize PrivateScriptData + if (!JSScript::createPrivateScriptData(cx, script, ngcthings)) { + return false; + } + + js::PrivateScriptData* data = script->data_; + if (ngcthings) { + if (!EmitScriptThingsVector(cx, input, stencil, gcOutput, + scriptStencil.gcthings(stencil), + data->gcthings())) { + return false; + } + } + + if (scriptStencil.hasMemberInitializers()) { + script->setMemberInitializers(scriptStencil.memberInitializers()); + } + + return true; +} + +void PrivateScriptData::trace(JSTracer* trc) { + for (JS::GCCellPtr& elem : gcthings()) { + gc::Cell* thing = elem.asCell(); + TraceManuallyBarrieredGenericPointerEdge(trc, &thing, "script-gcthing"); + if (MOZ_UNLIKELY(!thing)) { + // NOTE: If we are clearing edges, also erase the type. This can happen + // due to OOM triggering the ClearEdgesTracer. + elem = JS::GCCellPtr(); + } else if (thing != elem.asCell()) { + elem = JS::GCCellPtr(thing, elem.kind()); + } + } +} + +/*static*/ +JSScript* JSScript::Create(JSContext* cx, js::HandleObject functionOrGlobal, + js::HandleScriptSourceObject sourceObject, + const SourceExtent& extent, + js::ImmutableScriptFlags flags) { + return static_cast<JSScript*>( + BaseScript::New(cx, functionOrGlobal, sourceObject, extent, flags)); +} + +#ifdef MOZ_VTUNE +uint32_t JSScript::vtuneMethodID() { + if (!zone()->scriptVTuneIdMap) { + auto map = MakeUnique<ScriptVTuneIdMap>(); + if (!map) { + MOZ_CRASH("Failed to allocate ScriptVTuneIdMap"); + } + + zone()->scriptVTuneIdMap = std::move(map); + } + + ScriptVTuneIdMap::AddPtr p = zone()->scriptVTuneIdMap->lookupForAdd(this); + if (p) { + return p->value(); + } + + MOZ_ASSERT(this->hasBytecode()); + + uint32_t id = vtune::GenerateUniqueMethodID(); + if (!zone()->scriptVTuneIdMap->add(p, this, id)) { + MOZ_CRASH("Failed to add vtune method id"); + } + + return id; +} +#endif + +/* static */ +bool JSScript::createPrivateScriptData(JSContext* cx, HandleScript script, + uint32_t ngcthings) { + cx->check(script); + + UniquePtr<PrivateScriptData> data(PrivateScriptData::new_(cx, ngcthings)); + if (!data) { + return false; + } + + script->swapData(data); + MOZ_ASSERT(!data); + + return true; +} + +/* static */ +bool JSScript::fullyInitFromStencil( + JSContext* cx, js::frontend::CompilationInput& input, + js::frontend::BaseCompilationStencil& stencil, + frontend::CompilationGCOutput& gcOutput, HandleScript script, + const js::frontend::ScriptIndex scriptIndex) { + MutableScriptFlags lazyMutableFlags; + RootedScope lazyEnclosingScope(cx); + + // A holder for the lazy PrivateScriptData that we must keep around in case + // this process fails and we must return the script to its original state. + // + // This is initialized by BaseScript::swapData() which will run incremental + // pre-barriers for us. On successful conversion to non-lazy script, the old + // script data here will be released by the UniquePtr. + // + // TODO: This will trigger the ClearEdgesTraces on cleanup. We should + // investigate if this is still necessary if swapData() already ran + // pre-barriers for us. + Rooted<UniquePtr<PrivateScriptData>> lazyData(cx); + +#ifndef JS_CODEGEN_NONE + // Whether we are a newborn script or an existing lazy script, we should + // already be pointing to the interpreter trampoline. + MOZ_ASSERT(script->isUsingInterpreterTrampoline(cx->runtime())); +#endif + + // If we are using an existing lazy script, record enough info to be able to + // rollback on failure. + if (script->isReadyForDelazification()) { + lazyMutableFlags = script->mutableFlags_; + lazyEnclosingScope = script->releaseEnclosingScope(); + script->swapData(lazyData.get()); + MOZ_ASSERT(script->sharedData_ == nullptr); + } + + // Restore the script to lazy state on failure. If this was a fresh script, we + // just need to clear bytecode to mark script as incomplete. + auto rollbackGuard = mozilla::MakeScopeExit([&] { + if (lazyEnclosingScope) { + script->mutableFlags_ = lazyMutableFlags; + script->warmUpData_.initEnclosingScope(lazyEnclosingScope); + script->swapData(lazyData.get()); + script->sharedData_ = nullptr; + + MOZ_ASSERT(script->isReadyForDelazification()); + } else { + script->sharedData_ = nullptr; + } + }); + + /* The counts of indexed things must be checked during code generation. */ + js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex]; + MOZ_ASSERT(scriptStencil.gcThingsLength <= INDEX_LIMIT); + + // Note: These flags should already be correct when the BaseScript was + // allocated. + MOZ_ASSERT_IF(stencil.isInitialStencil(), + script->immutableFlags() == stencil.asCompilationStencil() + .scriptExtra[scriptIndex] + .immutableFlags); + + // Derive initial mutable flags + script->resetArgsUsageAnalysis(); + + // Create and initialize PrivateScriptData + if (!PrivateScriptData::InitFromStencil(cx, script, input, stencil, gcOutput, + scriptIndex)) { + return false; + } + + script->initSharedData(stencil.sharedData.get(scriptIndex)); + + // NOTE: JSScript is now constructed and should be linked in. + rollbackGuard.release(); + + // Link JSFunction to this JSScript. + if (scriptStencil.isFunction()) { + JSFunction* fun = gcOutput.functions[scriptIndex]; + if (fun->isIncomplete()) { + fun->initScript(script); + } else { + // We are delazifying in-place. + MOZ_ASSERT(fun->baseScript() == script); + } + } + + // NOTE: The caller is responsible for linking ModuleObjects if this is a + // module script. + +#ifdef JS_STRUCTURED_SPEW + // We want this to happen after line number initialization to allow filtering + // to work. + script->setSpewEnabled(cx->spewer().enabled(script)); +#endif + +#ifdef DEBUG + script->assertValidJumpTargets(); +#endif + + if (coverage::IsLCovEnabled()) { + if (!coverage::InitScriptCoverage(cx, script)) { + return false; + } + } + + return true; +} + +JSScript* JSScript::fromStencil(JSContext* cx, + js::frontend::CompilationInput& input, + js::frontend::CompilationStencil& stencil, + frontend::CompilationGCOutput& gcOutput, + const js::frontend::ScriptIndex scriptIndex) { + js::frontend::ScriptStencil& scriptStencil = stencil.scriptData[scriptIndex]; + js::frontend::ScriptStencilExtra& scriptExtra = + stencil.scriptExtra[scriptIndex]; + MOZ_ASSERT(scriptStencil.hasSharedData(), + "Need generated bytecode to use JSScript::fromStencil"); + + RootedObject functionOrGlobal(cx, cx->global()); + if (scriptStencil.isFunction()) { + functionOrGlobal = gcOutput.functions[scriptIndex]; + } + + Rooted<ScriptSourceObject*> sourceObject(cx, gcOutput.sourceObject); + RootedScript script( + cx, Create(cx, functionOrGlobal, sourceObject, scriptExtra.extent, + scriptExtra.immutableFlags)); + if (!script) { + return nullptr; + } + + if (!fullyInitFromStencil(cx, input, stencil, gcOutput, script, + scriptIndex)) { + return nullptr; + } + + return script; +} + +void JSScript::resetArgsUsageAnalysis() { + MOZ_ASSERT_IF(alwaysNeedsArgsObj(), argumentsHasVarBinding()); + if (argumentsHasVarBinding()) { + setFlag(MutableFlags::NeedsArgsObj, alwaysNeedsArgsObj()); + setFlag(MutableFlags::NeedsArgsAnalysis, !alwaysNeedsArgsObj()); + } +} + +#ifdef DEBUG +void JSScript::assertValidJumpTargets() const { + BytecodeLocation mainLoc = mainLocation(); + BytecodeLocation endLoc = endLocation(); + AllBytecodesIterable iter(this); + for (BytecodeLocation loc : iter) { + // Check jump instructions' target. + if (loc.isJump()) { + BytecodeLocation target = loc.getJumpTarget(); + MOZ_ASSERT(mainLoc <= target && target < endLoc); + MOZ_ASSERT(target.isJumpTarget()); + + // All backward jumps must be to a JSOp::LoopHead op. This is an invariant + // we want to maintain to simplify JIT compilation and bytecode analysis. + MOZ_ASSERT_IF(target < loc, target.is(JSOp::LoopHead)); + MOZ_ASSERT_IF(target < loc, IsBackedgePC(loc.toRawBytecode())); + + // All forward jumps must be to a JSOp::JumpTarget op. + MOZ_ASSERT_IF(target > loc, target.is(JSOp::JumpTarget)); + + // Jumps must not cross scope boundaries. + MOZ_ASSERT(loc.innermostScope(this) == target.innermostScope(this)); + + // Check fallthrough of conditional jump instructions. + if (loc.fallsThrough()) { + BytecodeLocation fallthrough = loc.next(); + MOZ_ASSERT(mainLoc <= fallthrough && fallthrough < endLoc); + MOZ_ASSERT(fallthrough.isJumpTarget()); + } + } + + // Check table switch case labels. + if (loc.is(JSOp::TableSwitch)) { + BytecodeLocation target = loc.getTableSwitchDefaultTarget(); + + // Default target. + MOZ_ASSERT(mainLoc <= target && target < endLoc); + MOZ_ASSERT(target.is(JSOp::JumpTarget)); + + int32_t low = loc.getTableSwitchLow(); + int32_t high = loc.getTableSwitchHigh(); + + for (int i = 0; i < high - low + 1; i++) { + BytecodeLocation switchCase = loc.getTableSwitchCaseTarget(this, i); + MOZ_ASSERT(mainLoc <= switchCase && switchCase < endLoc); + MOZ_ASSERT(switchCase.is(JSOp::JumpTarget)); + } + } + } + + // Check catch/finally blocks as jump targets. + for (const TryNote& tn : trynotes()) { + if (tn.kind() != TryNoteKind::Catch && tn.kind() != TryNoteKind::Finally) { + continue; + } + + jsbytecode* tryStart = offsetToPC(tn.start); + jsbytecode* tryPc = tryStart - JSOpLength_Try; + MOZ_ASSERT(JSOp(*tryPc) == JSOp::Try); + + jsbytecode* tryTarget = tryStart + tn.length; + MOZ_ASSERT(main() <= tryTarget && tryTarget < codeEnd()); + MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*tryTarget))); + } +} +#endif + +void JSScript::addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf, + size_t* sizeOfJitScript, + size_t* sizeOfBaselineFallbackStubs) const { + if (!hasJitScript()) { + return; + } + + jitScript()->addSizeOfIncludingThis(mallocSizeOf, sizeOfJitScript, + sizeOfBaselineFallbackStubs); +} + +js::GlobalObject& JSScript::uninlinedGlobal() const { return global(); } + +static const uint32_t GSN_CACHE_THRESHOLD = 100; + +void GSNCache::purge() { + code = nullptr; + map.clearAndCompact(); +} + +const js::SrcNote* js::GetSrcNote(GSNCache& cache, JSScript* script, + jsbytecode* pc) { + size_t target = pc - script->code(); + if (target >= script->length()) { + return nullptr; + } + + if (cache.code == script->code()) { + GSNCache::Map::Ptr p = cache.map.lookup(pc); + return p ? p->value() : nullptr; + } + + size_t offset = 0; + const js::SrcNote* result; + for (SrcNoteIterator iter(script->notes());; ++iter) { + auto sn = *iter; + if (sn->isTerminator()) { + result = nullptr; + break; + } + offset += sn->delta(); + if (offset == target && sn->isGettable()) { + result = sn; + break; + } + } + + if (cache.code != script->code() && script->length() >= GSN_CACHE_THRESHOLD) { + unsigned nsrcnotes = 0; + for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) { + auto sn = *iter; + if (sn->isGettable()) { + ++nsrcnotes; + } + } + if (cache.code) { + cache.map.clear(); + cache.code = nullptr; + } + if (cache.map.reserve(nsrcnotes)) { + pc = script->code(); + for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) { + auto sn = *iter; + pc += sn->delta(); + if (sn->isGettable()) { + cache.map.putNewInfallible(pc, sn); + } + } + cache.code = script->code(); + } + } + + return result; +} + +const js::SrcNote* js::GetSrcNote(JSContext* cx, JSScript* script, + jsbytecode* pc) { + return GetSrcNote(cx->caches().gsnCache, script, pc); +} + +unsigned js::PCToLineNumber(unsigned startLine, unsigned startCol, + SrcNote* notes, jsbytecode* code, jsbytecode* pc, + unsigned* columnp) { + unsigned lineno = startLine; + unsigned column = startCol; + + /* + * Walk through source notes accumulating their deltas, keeping track of + * line-number notes, until we pass the note for pc's offset within + * script->code. + */ + ptrdiff_t offset = 0; + ptrdiff_t target = pc - code; + for (SrcNoteIterator iter(notes); !iter.atEnd(); ++iter) { + auto sn = *iter; + offset += sn->delta(); + if (offset > target) { + break; + } + + SrcNoteType type = sn->type(); + if (type == SrcNoteType::SetLine) { + lineno = SrcNote::SetLine::getLine(sn, startLine); + column = 0; + } else if (type == SrcNoteType::NewLine) { + lineno++; + column = 0; + } else if (type == SrcNoteType::ColSpan) { + ptrdiff_t colspan = SrcNote::ColSpan::getSpan(sn); + MOZ_ASSERT(ptrdiff_t(column) + colspan >= 0); + column += colspan; + } + } + + if (columnp) { + *columnp = column; + } + + return lineno; +} + +unsigned js::PCToLineNumber(JSScript* script, jsbytecode* pc, + unsigned* columnp) { + /* Cope with InterpreterFrame.pc value prior to entering Interpret. */ + if (!pc) { + return 0; + } + + return PCToLineNumber(script->lineno(), script->column(), script->notes(), + script->code(), pc, columnp); +} + +jsbytecode* js::LineNumberToPC(JSScript* script, unsigned target) { + ptrdiff_t offset = 0; + ptrdiff_t best = -1; + unsigned lineno = script->lineno(); + unsigned bestdiff = SrcNote::MaxOperand; + for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) { + auto sn = *iter; + /* + * Exact-match only if offset is not in the prologue; otherwise use + * nearest greater-or-equal line number match. + */ + if (lineno == target && offset >= ptrdiff_t(script->mainOffset())) { + goto out; + } + if (lineno >= target) { + unsigned diff = lineno - target; + if (diff < bestdiff) { + bestdiff = diff; + best = offset; + } + } + offset += sn->delta(); + SrcNoteType type = sn->type(); + if (type == SrcNoteType::SetLine) { + lineno = SrcNote::SetLine::getLine(sn, script->lineno()); + } else if (type == SrcNoteType::NewLine) { + lineno++; + } + } + if (best >= 0) { + offset = best; + } +out: + return script->offsetToPC(offset); +} + +JS_FRIEND_API unsigned js::GetScriptLineExtent(JSScript* script) { + unsigned lineno = script->lineno(); + unsigned maxLineNo = lineno; + for (SrcNoteIterator iter(script->notes()); !iter.atEnd(); ++iter) { + auto sn = *iter; + SrcNoteType type = sn->type(); + if (type == SrcNoteType::SetLine) { + lineno = SrcNote::SetLine::getLine(sn, script->lineno()); + } else if (type == SrcNoteType::NewLine) { + lineno++; + } + + if (maxLineNo < lineno) { + maxLineNo = lineno; + } + } + + return 1 + maxLineNo - script->lineno(); +} + +#ifdef JS_CACHEIR_SPEW +void js::maybeUpdateWarmUpCount(JSScript* script) { + if (script->needsFinalWarmUpCount()) { + ScriptFinalWarmUpCountMap* map = + script->zone()->scriptFinalWarmUpCountMap.get(); + // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have + // already been created and thus must be asserted. + MOZ_ASSERT(map); + ScriptFinalWarmUpCountMap::Ptr p = map->lookup(script); + MOZ_ASSERT(p); + + mozilla::Get<0>(p->value()) += script->jitScript()->warmUpCount(); + } +} + +void js::maybeSpewScriptFinalWarmUpCount(JSScript* script) { + if (script->needsFinalWarmUpCount()) { + ScriptFinalWarmUpCountMap* map = + script->zone()->scriptFinalWarmUpCountMap.get(); + // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have + // already been created and thus must be asserted. + MOZ_ASSERT(map); + ScriptFinalWarmUpCountMap::Ptr p = map->lookup(script); + MOZ_ASSERT(p); + uint32_t warmUpCount; + const char* scriptName; + mozilla::Tie(warmUpCount, scriptName) = p->value(); + + JSContext* cx = TlsContext.get(); + cx->spewer().enableSpewing(); + + // In the case that we care about a script's final warmup count but the + // spewer is not enabled, AutoSpewChannel automatically sets and unsets + // the proper channel for the duration of spewing a health report's warm + // up count. + AutoSpewChannel channel(cx, SpewChannel::RateMyCacheIR, script); + jit::CacheIRHealth cih; + cih.spewScriptFinalWarmUpCount(cx, scriptName, script, warmUpCount); + + script->zone()->scriptFinalWarmUpCountMap->remove(script); + script->setNeedsFinalWarmUpCount(false); + } +} +#endif + +void js::DescribeScriptedCallerForDirectEval(JSContext* cx, HandleScript script, + jsbytecode* pc, const char** file, + unsigned* linenop, + uint32_t* pcOffset, + bool* mutedErrors) { + MOZ_ASSERT(script->containsPC(pc)); + + static_assert(JSOpLength_SpreadEval == JSOpLength_StrictSpreadEval, + "next op after a spread must be at consistent offset"); + static_assert(JSOpLength_Eval == JSOpLength_StrictEval, + "next op after a direct eval must be at consistent offset"); + + MOZ_ASSERT(JSOp(*pc) == JSOp::Eval || JSOp(*pc) == JSOp::StrictEval || + JSOp(*pc) == JSOp::SpreadEval || + JSOp(*pc) == JSOp::StrictSpreadEval); + + bool isSpread = + (JSOp(*pc) == JSOp::SpreadEval || JSOp(*pc) == JSOp::StrictSpreadEval); + jsbytecode* nextpc = + pc + (isSpread ? JSOpLength_SpreadEval : JSOpLength_Eval); + MOZ_ASSERT(JSOp(*nextpc) == JSOp::Lineno); + + *file = script->filename(); + *linenop = GET_UINT32(nextpc); + *pcOffset = script->pcToOffset(pc); + *mutedErrors = script->mutedErrors(); +} + +void js::DescribeScriptedCallerForCompilation( + JSContext* cx, MutableHandleScript maybeScript, const char** file, + unsigned* linenop, uint32_t* pcOffset, bool* mutedErrors) { + NonBuiltinFrameIter iter(cx, cx->realm()->principals()); + + if (iter.done()) { + maybeScript.set(nullptr); + *file = nullptr; + *linenop = 0; + *pcOffset = 0; + *mutedErrors = false; + return; + } + + *file = iter.filename(); + *linenop = iter.computeLine(); + *mutedErrors = iter.mutedErrors(); + + // These values are only used for introducer fields which are debugging + // information and can be safely left null for wasm frames. + if (iter.hasScript()) { + maybeScript.set(iter.script()); + *pcOffset = iter.pc() - maybeScript->code(); + } else { + maybeScript.set(nullptr); + *pcOffset = 0; + } +} + +static JSObject* CloneInnerInterpretedFunction( + JSContext* cx, HandleScope enclosingScope, HandleFunction srcFun, + Handle<ScriptSourceObject*> sourceObject) { + /* NB: Keep this in sync with XDRInterpretedFunction. */ + RootedObject cloneProto(cx); + if (!GetFunctionPrototype(cx, srcFun->generatorKind(), srcFun->asyncKind(), + &cloneProto)) { + return nullptr; + } + + gc::AllocKind allocKind = srcFun->getAllocKind(); + FunctionFlags flags = srcFun->flags(); + if (srcFun->isSelfHostedBuiltin()) { + // Functions in the self-hosting compartment are only extended in + // debug mode. For top-level functions, FUNCTION_EXTENDED gets used by + // the cloning algorithm. Do the same for inner functions here. + allocKind = gc::AllocKind::FUNCTION_EXTENDED; + flags.setIsExtended(); + } + RootedAtom atom(cx, srcFun->displayAtom()); + if (atom) { + cx->markAtom(atom); + } + RootedFunction clone( + cx, NewFunctionWithProto(cx, nullptr, srcFun->nargs(), flags, nullptr, + atom, cloneProto, allocKind, TenuredObject)); + if (!clone) { + return nullptr; + } + + JSScript::AutoDelazify srcScript(cx, srcFun); + if (!srcScript) { + return nullptr; + } + JSScript* cloneScript = CloneScriptIntoFunction(cx, enclosingScope, clone, + srcScript, sourceObject); + if (!cloneScript) { + return nullptr; + } + + MOZ_ASSERT(cloneScript->hasBytecode()); + + return clone; +} + +static JSObject* CloneScriptObject(JSContext* cx, PrivateScriptData* srcData, + HandleObject obj, + Handle<ScriptSourceObject*> sourceObject, + JS::HandleVector<JS::GCCellPtr> gcThings) { + if (obj->is<RegExpObject>()) { + return CloneScriptRegExpObject(cx, obj->as<RegExpObject>()); + } + + if (obj->is<JSFunction>()) { + HandleFunction innerFun = obj.as<JSFunction>(); + if (innerFun->isNative()) { + if (cx->realm() != innerFun->realm()) { + MOZ_ASSERT(innerFun->isAsmJSNative()); + JS_ReportErrorASCII(cx, "AsmJS modules do not yet support cloning."); + return nullptr; + } + return innerFun; + } + + if (!innerFun->hasBytecode()) { + MOZ_ASSERT(!innerFun->isSelfHostedOrIntrinsic(), + "Cannot enter realm of self-hosted functions"); + AutoRealm ar(cx, innerFun); + if (!JSFunction::getOrCreateScript(cx, innerFun)) { + return nullptr; + } + } + + Scope* enclosing = innerFun->enclosingScope(); + uint32_t scopeIndex = FindScopeIndex(srcData->gcthings(), *enclosing); + RootedScope enclosingClone(cx, &gcThings[scopeIndex].get().as<Scope>()); + return CloneInnerInterpretedFunction(cx, enclosingClone, innerFun, + sourceObject); + } + + return DeepCloneObjectLiteral(cx, obj); +} + +/* static */ +bool PrivateScriptData::Clone(JSContext* cx, HandleScript src, HandleScript dst, + MutableHandle<GCVector<Scope*>> scopes) { + PrivateScriptData* srcData = src->data_; + uint32_t ngcthings = srcData->gcthings().size(); + + // Clone GC things. + JS::RootedVector<JS::GCCellPtr> gcThings(cx); + size_t scopeIndex = 0; + Rooted<ScriptSourceObject*> sourceObject(cx, dst->sourceObject()); + RootedObject obj(cx); + RootedScope scope(cx); + RootedScope enclosingScope(cx); + RootedBigInt bigint(cx); + for (JS::GCCellPtr gcThing : srcData->gcthings()) { + if (gcThing.is<JSObject>()) { + obj = &gcThing.as<JSObject>(); + JSObject* clone = + CloneScriptObject(cx, srcData, obj, sourceObject, gcThings); + if (!clone || !gcThings.append(JS::GCCellPtr(clone))) { + return false; + } + } else if (gcThing.is<Scope>()) { + // The passed in scopes vector contains body scopes that needed to be + // cloned especially, depending on whether the script is a function or + // global scope. Clone all other scopes. + if (scopeIndex < scopes.length()) { + if (!gcThings.append(JS::GCCellPtr(scopes[scopeIndex].get()))) { + return false; + } + } else { + scope = &gcThing.as<Scope>(); + uint32_t enclosingScopeIndex = + FindScopeIndex(srcData->gcthings(), *scope->enclosing()); + enclosingScope = &gcThings[enclosingScopeIndex].get().as<Scope>(); + Scope* clone = Scope::clone(cx, scope, enclosingScope); + if (!clone || !gcThings.append(JS::GCCellPtr(clone))) { + return false; + } + } + scopeIndex++; + } else if (gcThing.is<JSString>()) { + JSAtom* atom = &gcThing.as<JSString>().asAtom(); + if (cx->zone() != atom->zone()) { + cx->markAtom(atom); + } + if (!gcThings.append(JS::GCCellPtr(atom))) { + return false; + } + } else { + bigint = &gcThing.as<BigInt>(); + BigInt* clone = bigint; + if (cx->zone() != bigint->zone()) { + clone = BigInt::copy(cx, bigint, gc::TenuredHeap); + if (!clone) { + return false; + } + } + if (!gcThings.append(JS::GCCellPtr(clone))) { + return false; + } + } + } + + // Create the new PrivateScriptData on |dst| and fill it in. + if (!JSScript::createPrivateScriptData(cx, dst, ngcthings)) { + return false; + } + PrivateScriptData* dstData = dst->data_; + + dstData->memberInitializers_ = srcData->memberInitializers_; + + { + auto array = dstData->gcthings(); + for (uint32_t i = 0; i < ngcthings; ++i) { + array[i] = gcThings[i].get(); + } + } + + return true; +} + +static JSScript* CopyScriptImpl(JSContext* cx, HandleScript src, + HandleObject functionOrGlobal, + HandleScriptSourceObject sourceObject, + MutableHandle<GCVector<Scope*>> scopes, + SourceExtent* maybeClassExtent = nullptr) { + if (src->treatAsRunOnce()) { + MOZ_ASSERT(!src->isFunction()); + JS_ReportErrorASCII(cx, "No cloning toplevel run-once scripts"); + return nullptr; + } + + /* NB: Keep this in sync with XDRScript. */ + + // Some embeddings are not careful to use ExposeObjectToActiveJS as needed. + JS::AssertObjectIsNotGray(sourceObject); + + // When cloning is for `MakeDefaultConstructor`, the SourceExtent will be + // provided by caller instead of copying from `src`. + SourceExtent extent = maybeClassExtent ? *maybeClassExtent : src->extent(); + + ImmutableScriptFlags flags = src->immutableFlags(); + flags.setFlag(JSScript::ImmutableFlags::HasNonSyntacticScope, + scopes[0]->hasOnChain(ScopeKind::NonSyntactic)); + + // When this clone is for `MakeDefaultConstructor` we also want to clear the + // SelfHosted flag. This is a hack to do it here, but ensures that the flags + // are not modified after the JSScript is created. + if (maybeClassExtent) { + flags.clearFlag(JSScript::ImmutableFlags::SelfHosted); + } + + // FunctionFlags and ImmutableScriptFlags should agree on self-hosting status. + MOZ_ASSERT_IF(functionOrGlobal->is<JSFunction>(), + functionOrGlobal->as<JSFunction>().isSelfHostedBuiltin() == + flags.hasFlag(JSScript::ImmutableFlags::SelfHosted)); + + // Create a new JSScript to fill in. + RootedScript dst( + cx, JSScript::Create(cx, functionOrGlobal, sourceObject, extent, flags)); + if (!dst) { + return nullptr; + } + + // Reset the mutable flags to request arguments analysis as needed. + dst->resetArgsUsageAnalysis(); + + // Maintain this flag when cloning self-hosted functions. + if (src->isInlinableLargeFunction()) { + dst->setIsInlinableLargeFunction(); + } + + // Clone the PrivateScriptData into dst + if (!PrivateScriptData::Clone(cx, src, dst, scopes)) { + return nullptr; + } + + // The SharedImmutableScriptData can be reused by any zone in the Runtime. + dst->initSharedData(src->sharedData()); + + return dst; +} + +JSScript* js::CloneGlobalScript(JSContext* cx, ScopeKind scopeKind, + HandleScript src) { + MOZ_ASSERT(scopeKind == ScopeKind::Global || + scopeKind == ScopeKind::NonSyntactic); + + Rooted<ScriptSourceObject*> sourceObject(cx, src->sourceObject()); + if (cx->compartment() != sourceObject->compartment()) { + sourceObject = ScriptSourceObject::clone(cx, sourceObject); + if (!sourceObject) { + return nullptr; + } + } + + MOZ_ASSERT(src->bodyScopeIndex() == GCThingIndex::outermostScopeIndex()); + Rooted<GCVector<Scope*>> scopes(cx, GCVector<Scope*>(cx)); + Rooted<GlobalScope*> original(cx, &src->bodyScope()->as<GlobalScope>()); + GlobalScope* clone = GlobalScope::clone(cx, original, scopeKind); + if (!clone || !scopes.append(clone)) { + return nullptr; + } + + RootedObject global(cx, cx->global()); + RootedScript dst(cx, CopyScriptImpl(cx, src, global, sourceObject, &scopes)); + if (!dst) { + return nullptr; + } + + if (coverage::IsLCovEnabled()) { + if (!coverage::InitScriptCoverage(cx, dst)) { + return nullptr; + } + } + + DebugAPI::onNewScript(cx, dst); + + return dst; +} + +JSScript* js::CloneScriptIntoFunction(JSContext* cx, HandleScope enclosingScope, + HandleFunction fun, HandleScript src, + Handle<ScriptSourceObject*> sourceObject, + SourceExtent* maybeClassExtent) { + // We are either delazifying a self-hosted lazy function or the function + // should be in an inactive state. + MOZ_ASSERT(fun->isIncomplete() || fun->hasSelfHostedLazyScript()); + + // Clone the non-intra-body scopes. + Rooted<GCVector<Scope*>> scopes(cx, GCVector<Scope*>(cx)); + RootedScope original(cx); + RootedScope enclosingClone(cx); + for (uint32_t i = 0; i <= src->bodyScopeIndex().index; i++) { + original = src->getScope(GCThingIndex(i)); + + if (i == 0) { + enclosingClone = enclosingScope; + } else { + MOZ_ASSERT(src->getScope(GCThingIndex(i - 1)) == original->enclosing()); + enclosingClone = scopes[i - 1]; + } + + Scope* clone; + if (original->is<FunctionScope>()) { + clone = FunctionScope::clone(cx, original.as<FunctionScope>(), fun, + enclosingClone); + } else { + clone = Scope::clone(cx, original, enclosingClone); + } + + if (!clone || !scopes.append(clone)) { + return nullptr; + } + } + + // Save flags in case we need to undo the early mutations. + const FunctionFlags preservedFlags = fun->flags(); + RootedScript dst(cx, CopyScriptImpl(cx, src, fun, sourceObject, &scopes, + maybeClassExtent)); + if (!dst) { + fun->setFlags(preservedFlags); + return nullptr; + } + + // Finally set the script after all the fallible operations. + if (fun->isIncomplete()) { + fun->initScript(dst); + } else { + MOZ_ASSERT(fun->hasSelfHostedLazyScript()); + fun->clearSelfHostedLazyScript(); + fun->initScript(dst); + } + + if (coverage::IsLCovEnabled()) { + if (!coverage::InitScriptCoverage(cx, dst)) { + return nullptr; + } + } + + return dst; +} + +template <typename SourceSpan, typename TargetSpan> +void CopySpan(const SourceSpan& source, TargetSpan target) { + MOZ_ASSERT(source.size() == target.size()); + std::copy(source.cbegin(), source.cend(), target.begin()); +} + +/* static */ +js::UniquePtr<ImmutableScriptData> ImmutableScriptData::new_( + JSContext* cx, uint32_t mainOffset, uint32_t nfixed, uint32_t nslots, + GCThingIndex bodyScopeIndex, uint32_t numICEntries, bool isFunction, + uint16_t funLength, mozilla::Span<const jsbytecode> code, + mozilla::Span<const SrcNote> notes, + mozilla::Span<const uint32_t> resumeOffsets, + mozilla::Span<const ScopeNote> scopeNotes, + mozilla::Span<const TryNote> tryNotes) { + MOZ_RELEASE_ASSERT(code.Length() <= frontend::MaxBytecodeLength); + + // There are 1-4 copies of SrcNoteType::Null appended after the source + // notes. These are a combination of sentinel and padding values. + static_assert(frontend::MaxSrcNotesLength <= UINT32_MAX - CodeNoteAlign, + "Length + CodeNoteAlign shouldn't overflow UINT32_MAX"); + size_t noteLength = notes.Length(); + MOZ_RELEASE_ASSERT(noteLength <= frontend::MaxSrcNotesLength); + + size_t nullLength = ComputeNotePadding(code.Length(), noteLength); + + // Allocate ImmutableScriptData + js::UniquePtr<ImmutableScriptData> data(ImmutableScriptData::new_( + cx, code.Length(), noteLength + nullLength, resumeOffsets.Length(), + scopeNotes.Length(), tryNotes.Length())); + if (!data) { + return data; + } + + // Initialize POD fields + data->mainOffset = mainOffset; + data->nfixed = nfixed; + data->nslots = nslots; + data->bodyScopeIndex = bodyScopeIndex; + data->numICEntries = numICEntries; + + if (isFunction) { + data->funLength = funLength; + } + + // Initialize trailing arrays + CopySpan(code, data->codeSpan()); + CopySpan(notes, data->notesSpan().To(noteLength)); + std::fill_n(data->notes() + noteLength, nullLength, SrcNote::terminator()); + CopySpan(resumeOffsets, data->resumeOffsets()); + CopySpan(scopeNotes, data->scopeNotes()); + CopySpan(tryNotes, data->tryNotes()); + + return data; +} + +void ScriptWarmUpData::trace(JSTracer* trc) { + uintptr_t tag = data_ & TagMask; + switch (tag) { + case EnclosingScriptTag: { + BaseScript* enclosingScript = toEnclosingScript(); + TraceManuallyBarrieredEdge(trc, &enclosingScript, "enclosingScript"); + setTaggedPtr<EnclosingScriptTag>(enclosingScript); + break; + } + + case EnclosingScopeTag: { + Scope* enclosingScope = toEnclosingScope(); + TraceManuallyBarrieredEdge(trc, &enclosingScope, "enclosingScope"); + setTaggedPtr<EnclosingScopeTag>(enclosingScope); + break; + } + + case JitScriptTag: { + toJitScript()->trace(trc); + break; + } + + default: { + MOZ_ASSERT(isWarmUpCount()); + break; + } + } +} + +size_t JSScript::calculateLiveFixed(jsbytecode* pc) { + size_t nlivefixed = numAlwaysLiveFixedSlots(); + + if (nfixed() != nlivefixed) { + Scope* scope = lookupScope(pc); + if (scope) { + scope = MaybeForwarded(scope); + } + + // Find the nearest LexicalScope in the same script. + while (scope && scope->is<WithScope>()) { + scope = scope->enclosing(); + if (scope) { + scope = MaybeForwarded(scope); + } + } + + if (scope) { + if (scope->is<LexicalScope>()) { + nlivefixed = scope->as<LexicalScope>().nextFrameSlot(); + } else if (scope->is<VarScope>()) { + nlivefixed = scope->as<VarScope>().nextFrameSlot(); + } + } + } + + MOZ_ASSERT(nlivefixed <= nfixed()); + MOZ_ASSERT(nlivefixed >= numAlwaysLiveFixedSlots()); + + return nlivefixed; +} + +Scope* JSScript::lookupScope(jsbytecode* pc) const { + MOZ_ASSERT(containsPC(pc)); + + size_t offset = pc - code(); + + auto notes = scopeNotes(); + Scope* scope = nullptr; + + // Find the innermost block chain using a binary search. + size_t bottom = 0; + size_t top = notes.size(); + + while (bottom < top) { + size_t mid = bottom + (top - bottom) / 2; + const ScopeNote* note = ¬es[mid]; + if (note->start <= offset) { + // Block scopes are ordered in the list by their starting offset, and + // since blocks form a tree ones earlier in the list may cover the pc even + // if later blocks end before the pc. This only happens when the earlier + // block is a parent of the later block, so we need to check parents of + // |mid| in the searched range for coverage. + size_t check = mid; + while (check >= bottom) { + const ScopeNote* checkNote = ¬es[check]; + MOZ_ASSERT(checkNote->start <= offset); + if (offset < checkNote->start + checkNote->length) { + // We found a matching block chain but there may be inner ones + // at a higher block chain index than mid. Continue the binary search. + if (checkNote->index == ScopeNote::NoScopeIndex) { + scope = nullptr; + } else { + scope = getScope(checkNote->index); + } + break; + } + if (checkNote->parent == UINT32_MAX) { + break; + } + check = checkNote->parent; + } + bottom = mid + 1; + } else { + top = mid; + } + } + + return scope; +} + +Scope* JSScript::innermostScope(jsbytecode* pc) const { + if (Scope* scope = lookupScope(pc)) { + return scope; + } + return bodyScope(); +} + +void JSScript::setNeedsArgsObj(bool needsArgsObj) { + MOZ_ASSERT_IF(needsArgsObj, argumentsHasVarBinding()); + clearFlag(MutableFlags::NeedsArgsAnalysis); + setFlag(MutableFlags::NeedsArgsObj, needsArgsObj); +} + +void js::SetFrameArgumentsObject(JSContext* cx, AbstractFramePtr frame, + HandleScript script, JSObject* argsobj) { + /* + * Replace any optimized arguments in the frame with an explicit arguments + * object. Note that 'arguments' may have already been overwritten. + */ + + Rooted<BindingIter> bi(cx, BindingIter(script)); + while (bi && bi.name() != cx->names().arguments) { + bi++; + } + if (!bi) { + return; + } + + if (bi.location().kind() == BindingLocation::Kind::Environment) { + /* + * Scan the script to find the slot in the call object that 'arguments' + * is assigned to. + */ + jsbytecode* pc = script->code(); + while (JSOp(*pc) != JSOp::Arguments) { + pc += GetBytecodeLength(pc); + } + pc += JSOpLength_Arguments; + MOZ_ASSERT(JSOp(*pc) == JSOp::SetAliasedVar); + + // Note that here and below, it is insufficient to only check for + // JS_OPTIMIZED_ARGUMENTS, as Ion could have optimized out the + // arguments slot. + EnvironmentObject& env = frame.callObj().as<EnvironmentObject>(); + if (IsOptimizedPlaceholderMagicValue(env.aliasedBinding(bi))) { + env.setAliasedBinding(cx, bi, ObjectValue(*argsobj)); + } + } else { + MOZ_ASSERT(bi.location().kind() == BindingLocation::Kind::Frame); + uint32_t frameSlot = bi.location().slot(); + if (IsOptimizedPlaceholderMagicValue(frame.unaliasedLocal(frameSlot))) { + frame.unaliasedLocal(frameSlot) = ObjectValue(*argsobj); + } + } + + // JS_OPTIMIZED_ARGUMENTS may also have been stored to a local slot + // during bailout. Update those local slots. + for (uint32_t i = 0; i < script->nfixed(); i++) { + Value& value = frame.unaliasedLocal(i); + if (value.isMagic() && value.whyMagic() == JS_OPTIMIZED_ARGUMENTS) { + frame.unaliasedLocal(i) = ObjectValue(*argsobj); + } + } +} + +/* static */ +void JSScript::argumentsOptimizationFailed(JSContext* cx, HandleScript script) { + MOZ_ASSERT(script->isFunction()); + MOZ_ASSERT(!script->needsArgsAnalysis()); + MOZ_ASSERT(script->argumentsHasVarBinding()); + + /* + * It is possible that the arguments optimization has already failed, + * everything has been fixed up, but there was an outstanding magic value + * on the stack that has just now flowed into an apply. In this case, there + * is nothing to do; GuardFunApplySpeculation will patch in the real + * argsobj. + */ + if (script->needsArgsObj()) { + return; + } + + MOZ_ASSERT(!script->isGenerator()); + MOZ_ASSERT(!script->isAsync()); + + script->setFlag(MutableFlags::NeedsArgsObj); + + // Warp code depends on the NeedsArgsObj flag so invalidate the script + // (including compilations inlining the script). + { + jit::RecompileInfoVector invalid; + AddPendingInvalidation(invalid, script); + Invalidate(cx, invalid); + } + + /* + * By design, the arguments optimization is only made when there are no + * outstanding cases of MagicValue(JS_OPTIMIZED_ARGUMENTS) at any points + * where the optimization could fail, other than an active invocation of + * 'f.apply(x, arguments)'. Thus, there are no outstanding values of + * MagicValue(JS_OPTIMIZED_ARGUMENTS) on the stack. However, there are + * three things that need fixup: + * - there may be any number of activations of this script that don't have + * an argsObj that now need one. + * - jit code compiled (and possible active on the stack) with the static + * assumption of !script->needsArgsObj(); + * - type inference data for the script assuming script->needsArgsObj + */ + for (AllScriptFramesIter i(cx); !i.done(); ++i) { + /* + * We cannot reliably create an arguments object for Ion activations of + * this script. To maintain the invariant that "script->needsArgsObj + * implies fp->hasArgsObj", the Ion bail mechanism will create an + * arguments object right after restoring the BaselineFrame and before + * entering Baseline code (in jit::FinishBailoutToBaseline). + */ + if (i.isIon()) { + continue; + } + AbstractFramePtr frame = i.abstractFramePtr(); + if (frame.isFunctionFrame() && frame.script() == script) { + /* We crash on OOM since cleaning up here would be complicated. */ + AutoEnterOOMUnsafeRegion oomUnsafe; + ArgumentsObject* argsobj = ArgumentsObject::createExpected(cx, frame); + if (!argsobj) { + oomUnsafe.crash("JSScript::argumentsOptimizationFailed"); + } + SetFrameArgumentsObject(cx, frame, script, argsobj); + } + } +} + +bool JSScript::formalIsAliased(unsigned argSlot) { + if (functionHasParameterExprs()) { + return false; + } + + for (PositionalFormalParameterIter fi(this); fi; fi++) { + if (fi.argumentSlot() == argSlot) { + return fi.closedOver(); + } + } + MOZ_CRASH("Argument slot not found"); +} + +bool JSScript::formalLivesInArgumentsObject(unsigned argSlot) { + return argsObjAliasesFormals() && !formalIsAliased(argSlot); +} + +/* static */ +BaseScript* BaseScript::New(JSContext* cx, HandleObject functionOrGlobal, + HandleScriptSourceObject sourceObject, + const SourceExtent& extent, + uint32_t immutableFlags) { + void* script = Allocate<BaseScript>(cx); + if (!script) { + return nullptr; + } + +#ifndef JS_CODEGEN_NONE + uint8_t* stubEntry = cx->runtime()->jitRuntime()->interpreterStub().value; +#else + uint8_t* stubEntry = nullptr; +#endif + + return new (script) BaseScript(stubEntry, functionOrGlobal, sourceObject, + extent, immutableFlags); +} + +/* static */ +BaseScript* BaseScript::CreateRawLazy(JSContext* cx, uint32_t ngcthings, + HandleFunction fun, + HandleScriptSourceObject sourceObject, + const SourceExtent& extent, + uint32_t immutableFlags) { + cx->check(fun); + + BaseScript* lazy = New(cx, fun, sourceObject, extent, immutableFlags); + if (!lazy) { + return nullptr; + } + + // Allocate a PrivateScriptData if it will not be empty. Lazy class + // constructors also need PrivateScriptData for field lists. + if (ngcthings || fun->isClassConstructor()) { + UniquePtr<PrivateScriptData> data(PrivateScriptData::new_(cx, ngcthings)); + if (!data) { + return nullptr; + } + lazy->swapData(data); + MOZ_ASSERT(!data); + } + + return lazy; +} + +void JSScript::updateJitCodeRaw(JSRuntime* rt) { + MOZ_ASSERT(rt); + if (hasBaselineScript() && baselineScript()->hasPendingIonCompileTask()) { + MOZ_ASSERT(!isIonCompilingOffThread()); + setJitCodeRaw(rt->jitRuntime()->lazyLinkStub().value); + } else if (hasIonScript()) { + jit::IonScript* ion = ionScript(); + setJitCodeRaw(ion->method()->raw()); + } else if (hasBaselineScript()) { + setJitCodeRaw(baselineScript()->method()->raw()); + } else if (hasJitScript() && js::jit::IsBaselineInterpreterEnabled()) { + setJitCodeRaw(rt->jitRuntime()->baselineInterpreter().codeRaw()); + } else { + setJitCodeRaw(rt->jitRuntime()->interpreterStub().value); + } + MOZ_ASSERT(jitCodeRaw()); +} + +bool JSScript::hasLoops() { + for (const TryNote& tn : trynotes()) { + if (tn.isLoop()) { + return true; + } + } + return false; +} + +bool JSScript::mayReadFrameArgsDirectly() { + return argumentsHasVarBinding() || hasRest(); +} + +void JSScript::resetWarmUpCounterToDelayIonCompilation() { + // Reset the warm-up count only if it's greater than the BaselineCompiler + // threshold. We do this to ensure this has no effect on Baseline compilation + // because we don't want scripts to get stuck in the (Baseline) interpreter in + // pathological cases. + + if (getWarmUpCount() > jit::JitOptions.baselineJitWarmUpThreshold) { + incWarmUpResetCounter(); + uint32_t newCount = jit::JitOptions.baselineJitWarmUpThreshold; + if (warmUpData_.isWarmUpCount()) { + warmUpData_.resetWarmUpCount(newCount); + } else { + warmUpData_.toJitScript()->resetWarmUpCount(newCount); + } + } +} + +void JSScript::AutoDelazify::holdScript(JS::HandleFunction fun) { + if (fun) { + if (fun->realm()->isSelfHostingRealm()) { + // The self-hosting realm is shared across runtimes, so we can't use + // JSAutoRealm: it could cause races. Functions in the self-hosting + // realm will never be lazy, so we can safely assume we don't have + // to delazify. + script_ = fun->nonLazyScript(); + } else { + JSAutoRealm ar(cx_, fun); + script_ = JSFunction::getOrCreateScript(cx_, fun); + if (script_) { + oldAllowRelazify_ = script_->allowRelazify(); + script_->clearAllowRelazify(); + } + } + } +} + +void JSScript::AutoDelazify::dropScript() { + // Don't touch script_ if it's in the self-hosting realm, see the comment + // in holdScript. + if (script_ && !script_->realm()->isSelfHostingRealm()) { + script_->setAllowRelazify(oldAllowRelazify_); + } + script_ = nullptr; +} + +JS::ubi::Base::Size JS::ubi::Concrete<BaseScript>::size( + mozilla::MallocSizeOf mallocSizeOf) const { + BaseScript* base = &get(); + + Size size = gc::Arena::thingSize(base->getAllocKind()); + size += base->sizeOfExcludingThis(mallocSizeOf); + + // Include any JIT data if it exists. + if (base->hasJitScript()) { + JSScript* script = base->asJSScript(); + + size_t jitScriptSize = 0; + size_t fallbackStubSize = 0; + script->addSizeOfJitScript(mallocSizeOf, &jitScriptSize, &fallbackStubSize); + size += jitScriptSize; + size += fallbackStubSize; + + size_t baselineSize = 0; + jit::AddSizeOfBaselineData(script, mallocSizeOf, &baselineSize); + size += baselineSize; + + size += jit::SizeOfIonData(script, mallocSizeOf); + } + + MOZ_ASSERT(size > 0); + return size; +} + +const char* JS::ubi::Concrete<BaseScript>::scriptFilename() const { + return get().filename(); +} |