/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * JS script operations. */ #include "jsscriptinlines.h" #include "mozilla/DebugOnly.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PodOperations.h" #include "mozilla/Vector.h" #include #include #include "jsapi.h" #include "jsatom.h" #include "jscntxt.h" #include "jsfun.h" #include "jsgc.h" #include "jsobj.h" #include "jsopcode.h" #include "jsprf.h" #include "jstypes.h" #include "jsutil.h" #include "jswrapper.h" #include "frontend/BytecodeCompiler.h" #include "frontend/BytecodeEmitter.h" #include "frontend/SharedContext.h" #include "gc/Marking.h" #include "jit/BaselineJIT.h" #include "jit/Ion.h" #include "jit/IonCode.h" #include "js/MemoryMetrics.h" #include "js/Utility.h" #include "vm/ArgumentsObject.h" #include "vm/Compression.h" #include "vm/Debugger.h" #include "vm/Opcodes.h" #include "vm/SelfHosting.h" #include "vm/Shape.h" #include "vm/Xdr.h" #include "jsfuninlines.h" #include "jsobjinlines.h" #include "vm/ScopeObject-inl.h" #include "vm/Stack-inl.h" using namespace js; using namespace js::gc; using namespace js::frontend; using mozilla::PodCopy; using mozilla::PodZero; using mozilla::RotateLeft; static BindingIter GetBinding(HandleScript script, HandlePropertyName name) { BindingIter bi(script); while (bi->name() != name) bi++; return bi; } /* static */ BindingIter Bindings::argumentsBinding(ExclusiveContext* cx, HandleScript script) { return GetBinding(script, cx->names().arguments); } /* static */ BindingIter Bindings::thisBinding(ExclusiveContext* cx, HandleScript script) { return GetBinding(script, cx->names().dotThis); } bool Bindings::initWithTemporaryStorage(ExclusiveContext* cx, MutableHandle self, uint32_t numArgs, uint32_t numVars, uint32_t numBodyLevelLexicals, uint32_t numBlockScoped, uint32_t numUnaliasedVars, uint32_t numUnaliasedBodyLevelLexicals, const Binding* bindingArray, bool isModule /* = false */) { MOZ_ASSERT(!self.callObjShape()); MOZ_ASSERT(self.bindingArrayUsingTemporaryStorage()); MOZ_ASSERT(!self.bindingArray()); MOZ_ASSERT(!(uintptr_t(bindingArray) & TEMPORARY_STORAGE_BIT)); MOZ_ASSERT(numArgs <= ARGC_LIMIT); MOZ_ASSERT(numVars <= LOCALNO_LIMIT); MOZ_ASSERT(numBlockScoped <= LOCALNO_LIMIT); MOZ_ASSERT(numBodyLevelLexicals <= LOCALNO_LIMIT); mozilla::DebugOnly totalSlots = uint64_t(numVars) + uint64_t(numBodyLevelLexicals) + uint64_t(numBlockScoped); MOZ_ASSERT(totalSlots <= LOCALNO_LIMIT); MOZ_ASSERT(UINT32_MAX - numArgs >= totalSlots); MOZ_ASSERT(numUnaliasedVars <= numVars); MOZ_ASSERT(numUnaliasedBodyLevelLexicals <= numBodyLevelLexicals); self.setBindingArray(bindingArray, TEMPORARY_STORAGE_BIT); self.setNumArgs(numArgs); self.setNumVars(numVars); self.setNumBodyLevelLexicals(numBodyLevelLexicals); self.setNumBlockScoped(numBlockScoped); self.setNumUnaliasedVars(numUnaliasedVars); self.setNumUnaliasedBodyLevelLexicals(numUnaliasedBodyLevelLexicals); // Get the initial shape to use when creating CallObjects for this script. // After creation, a CallObject's shape may change completely (via direct eval() or // other operations that mutate the lexical scope). However, since the // lexical bindings added to the initial shape are permanent and the // allocKind/nfixed of a CallObject cannot change, one may assume that the // slot location (whether in the fixed or dynamic slots) of a variable is // the same as in the initial shape. (This is assumed by the interpreter and // JITs when interpreting/compiling aliasedvar ops.) // Since unaliased variables are, by definition, only accessed by local // operations and never through the scope chain, only give shapes to // aliased variables. While the debugger may observe any scope object at // any time, such accesses are mediated by DebugScopeProxy (see // DebugScopeProxy::handleUnaliasedAccess). uint32_t nslots = CallObject::RESERVED_SLOTS; // Unless there are aliased body-level lexical bindings at all, set the // begin index to an impossible slot number. uint32_t aliasedBodyLevelLexicalBegin = LOCALNO_LIMIT; for (BindingIter bi(self); bi; bi++) { if (bi->aliased()) { // Per ES6, lexical bindings cannot be accessed until // initialized. Remember the first aliased slot that is a // body-level lexical, so that they may be initialized to sentinel // magic values. if (numBodyLevelLexicals > 0 && nslots < aliasedBodyLevelLexicalBegin && bi.isBodyLevelLexical() && bi.localIndex() >= numVars) { aliasedBodyLevelLexicalBegin = nslots; } nslots++; } } self.setAliasedBodyLevelLexicalBegin(aliasedBodyLevelLexicalBegin); // Put as many of nslots inline into the object header as possible. uint32_t nfixed = gc::GetGCKindSlots(gc::GetGCObjectKind(nslots)); // Start with the empty shape and then append one shape per aliased binding. const Class* cls = isModule ? &ModuleEnvironmentObject::class_ : &CallObject::class_; uint32_t baseShapeFlags = BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE; if (isModule) baseShapeFlags |= BaseShape::NOT_EXTENSIBLE; // Module code is always strict. RootedShape shape(cx, EmptyShape::getInitialShape(cx, cls, TaggedProto(nullptr), nfixed, baseShapeFlags)); if (!shape) return false; #ifdef DEBUG HashSet added(cx); if (!added.init()) { ReportOutOfMemory(cx); return false; } #endif uint32_t slot = CallObject::RESERVED_SLOTS; for (BindingIter bi(self); bi; bi++) { MOZ_ASSERT_IF(isModule, bi->aliased()); if (!bi->aliased()) continue; #ifdef DEBUG // The caller ensures no duplicate aliased names. MOZ_ASSERT(!added.has(bi->name())); if (!added.put(bi->name())) { ReportOutOfMemory(cx); return false; } #endif StackBaseShape stackBase(cx, cls, baseShapeFlags); UnownedBaseShape* base = BaseShape::getUnowned(cx, stackBase); if (!base) return false; unsigned attrs = JSPROP_PERMANENT | JSPROP_ENUMERATE | (bi->kind() == Binding::CONSTANT ? JSPROP_READONLY : 0); Rooted child(cx, StackShape(base, NameToId(bi->name()), slot, attrs, 0)); shape = cx->compartment()->propertyTree.getChild(cx, shape, child); if (!shape) return false; MOZ_ASSERT(slot < nslots); slot++; } MOZ_ASSERT(slot == nslots); MOZ_ASSERT(!shape->inDictionary()); self.setCallObjShape(shape); return true; } bool Bindings::initTrivial(ExclusiveContext* cx) { Shape* shape = EmptyShape::getInitialShape(cx, &CallObject::class_, TaggedProto(nullptr), CallObject::RESERVED_SLOTS, BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE); if (!shape) return false; callObjShape_.init(shape); return true; } uint8_t* Bindings::switchToScriptStorage(Binding* newBindingArray) { MOZ_ASSERT(bindingArrayUsingTemporaryStorage()); MOZ_ASSERT(!(uintptr_t(newBindingArray) & TEMPORARY_STORAGE_BIT)); if (count() > 0) PodCopy(newBindingArray, bindingArray(), count()); bindingArrayAndFlag_ = uintptr_t(newBindingArray); return reinterpret_cast(newBindingArray + count()); } /* static */ bool Bindings::clone(JSContext* cx, MutableHandle self, uint8_t* dstScriptData, HandleScript srcScript) { /* The clone has the same bindingArray_ offset as 'src'. */ Handle src = Handle::fromMarkedLocation(&srcScript->bindings); ptrdiff_t off = (uint8_t*)src.bindingArray() - srcScript->data; MOZ_ASSERT(off >= 0); MOZ_ASSERT(size_t(off) <= srcScript->dataSize()); Binding* dstPackedBindings = (Binding*)(dstScriptData + off); /* * Since atoms are shareable throughout the runtime, we can simply copy * the source's bindingArray directly. */ if (!initWithTemporaryStorage(cx, self, src.numArgs(), src.numVars(), src.numBodyLevelLexicals(), src.numBlockScoped(), src.numUnaliasedVars(), src.numUnaliasedBodyLevelLexicals(), src.bindingArray())) { return false; } self.switchToScriptStorage(dstPackedBindings); return true; } template static bool XDRScriptBindings(XDRState* xdr, LifoAllocScope& las, uint16_t numArgs, uint32_t numVars, uint16_t numBodyLevelLexicals, uint16_t numBlockScoped, uint32_t numUnaliasedVars, uint16_t numUnaliasedBodyLevelLexicals, HandleScript script) { JSContext* cx = xdr->cx(); if (mode == XDR_ENCODE) { for (BindingIter bi(script); bi; bi++) { RootedAtom atom(cx, bi->name()); if (!XDRAtom(xdr, &atom)) return false; } for (BindingIter bi(script); bi; bi++) { uint8_t u8 = (uint8_t(bi->kind()) << 1) | uint8_t(bi->aliased()); if (!xdr->codeUint8(&u8)) return false; } } else { uint32_t nameCount = numArgs + numVars + numBodyLevelLexicals; AutoValueVector atoms(cx); if (!atoms.resize(nameCount)) return false; for (uint32_t i = 0; i < nameCount; i++) { RootedAtom atom(cx); if (!XDRAtom(xdr, &atom)) return false; atoms[i].setString(atom); } Binding* bindingArray = las.alloc().newArrayUninitialized(nameCount); if (!bindingArray) return false; for (uint32_t i = 0; i < nameCount; i++) { uint8_t u8; if (!xdr->codeUint8(&u8)) return false; PropertyName* name = atoms[i].toString()->asAtom().asPropertyName(); Binding::Kind kind = Binding::Kind(u8 >> 1); bool aliased = bool(u8 & 1); bindingArray[i] = Binding(name, kind, aliased); } Rooted bindings(cx, script->bindings); if (!Bindings::initWithTemporaryStorage(cx, &bindings, numArgs, numVars, numBodyLevelLexicals, numBlockScoped, numUnaliasedVars, numUnaliasedBodyLevelLexicals, bindingArray)) { return false; } script->bindings = bindings; } return true; } bool Bindings::bindingIsAliased(uint32_t bindingIndex) { MOZ_ASSERT(bindingIndex < count()); return bindingArray()[bindingIndex].aliased(); } void Binding::trace(JSTracer* trc) { PropertyName* name = this->name(); TraceManuallyBarrieredEdge(trc, &name, "binding"); } void Bindings::trace(JSTracer* trc) { if (callObjShape_) TraceEdge(trc, &callObjShape_, "callObjShape"); /* * As the comment in Bindings explains, bindingsArray may point into freed * storage when bindingArrayUsingTemporaryStorage so we don't mark it. * Note: during compilation, atoms are already kept alive by gcKeepAtoms. */ if (bindingArrayUsingTemporaryStorage()) return; for (Binding& b : *this) b.trace(trc); } template bool js::XDRScriptConst(XDRState* xdr, MutableHandleValue vp) { JSContext* cx = xdr->cx(); /* * A script constant can be an arbitrary primitive value as they are used * to implement JSOP_LOOKUPSWITCH. But they cannot be objects, see * bug 407186. */ enum ConstTag { SCRIPT_INT = 0, SCRIPT_DOUBLE = 1, SCRIPT_ATOM = 2, SCRIPT_TRUE = 3, SCRIPT_FALSE = 4, SCRIPT_NULL = 5, SCRIPT_OBJECT = 6, SCRIPT_VOID = 7, SCRIPT_HOLE = 8 }; uint32_t 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 { MOZ_ASSERT(vp.isUndefined()); tag = SCRIPT_VOID; } } if (!xdr->codeUint32(&tag)) return false; switch (tag) { case SCRIPT_INT: { uint32_t i; if (mode == XDR_ENCODE) i = uint32_t(vp.toInt32()); if (!xdr->codeUint32(&i)) return false; if (mode == XDR_DECODE) vp.set(Int32Value(int32_t(i))); break; } case SCRIPT_DOUBLE: { double d; if (mode == XDR_ENCODE) d = vp.toDouble(); if (!xdr->codeDouble(&d)) return false; if (mode == XDR_DECODE) vp.set(DoubleValue(d)); break; } case SCRIPT_ATOM: { RootedAtom atom(cx); if (mode == XDR_ENCODE) atom = &vp.toString()->asAtom(); if (!XDRAtom(xdr, &atom)) return false; 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(); if (!XDRObjectLiteral(xdr, &obj)) return false; 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; } return true; } template bool js::XDRScriptConst(XDRState*, MutableHandleValue); template bool js::XDRScriptConst(XDRState*, MutableHandleValue); // Code LazyScript's free variables. template static bool XDRLazyFreeVariables(XDRState* xdr, MutableHandle lazy) { JSContext* cx = xdr->cx(); RootedAtom atom(cx); uint8_t isHoistedUse; LazyScript::FreeVariable* freeVariables = lazy->freeVariables(); size_t numFreeVariables = lazy->numFreeVariables(); for (size_t i = 0; i < numFreeVariables; i++) { if (mode == XDR_ENCODE) { atom = freeVariables[i].atom(); isHoistedUse = freeVariables[i].isHoistedUse(); } if (!XDRAtom(xdr, &atom)) return false; if (!xdr->codeUint8(&isHoistedUse)) return false; if (mode == XDR_DECODE) { freeVariables[i] = LazyScript::FreeVariable(atom); if (isHoistedUse) freeVariables[i].setIsHoistedUse(); } } return true; } // Code the missing part needed to re-create a LazyScript from a JSScript. template static bool XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, HandleScript script, HandleObject enclosingScope, MutableHandle lazy) { MOZ_ASSERT_IF(mode == XDR_ENCODE, script->isRelazifiable() && script->maybeLazyScript()); MOZ_ASSERT_IF(mode == XDR_ENCODE, !lazy->numInnerFunctions()); JSContext* cx = xdr->cx(); uint64_t packedFields; { uint32_t begin = script->sourceStart(); uint32_t end = script->sourceEnd(); uint32_t lineno = script->lineno(); uint32_t column = script->column(); if (mode == XDR_ENCODE) { packedFields = lazy->packedFields(); MOZ_ASSERT(begin == lazy->begin()); MOZ_ASSERT(end == lazy->end()); MOZ_ASSERT(lineno == lazy->lineno()); MOZ_ASSERT(column == lazy->column()); // We can assert we have no inner functions because we don't // relazify scripts with inner functions. See // JSFunction::createScriptForLazilyInterpretedFunction. MOZ_ASSERT(lazy->numInnerFunctions() == 0); } if (!xdr->codeUint64(&packedFields)) return false; if (mode == XDR_DECODE) { lazy.set(LazyScript::Create(cx, fun, script, enclosingScope, script, packedFields, begin, end, lineno, column)); // As opposed to XDRLazyScript, we need to restore the runtime bits // of the script, as we are trying to match the fact this function // has already been parsed and that it would need to be re-lazified. lazy->initRuntimeFields(packedFields); } } // Code free variables. if (!XDRLazyFreeVariables(xdr, lazy)) return false; // No need to do anything with inner functions, since we asserted we don't // have any. return true; } static inline uint32_t FindScopeObjectIndex(JSScript* script, NestedScopeObject& scope) { ObjectArray* objects = script->objects(); HeapPtrObject* vector = objects->vector; unsigned length = objects->length; for (unsigned i = 0; i < length; ++i) { if (vector[i] == &scope) return i; } MOZ_CRASH("Scope not found"); } static bool SaveSharedScriptData(ExclusiveContext*, Handle, SharedScriptData*, uint32_t); enum XDRClassKind { CK_BlockObject = 0, CK_WithObject = 1, CK_JSFunction = 2, CK_JSObject = 3 }; template bool js::XDRScript(XDRState* xdr, HandleObject enclosingScopeArg, HandleScript enclosingScript, HandleFunction fun, MutableHandleScript scriptp) { /* NB: Keep this in sync with CopyScript. */ MOZ_ASSERT(enclosingScopeArg); enum ScriptBits { NoScriptRval, SavedCallerFun, Strict, ContainsDynamicNameAccess, FunHasExtensibleScope, FunNeedsDeclEnvObject, FunHasAnyAliasedFormal, ArgumentsHasVarBinding, NeedsArgsObj, HasMappedArgsObj, FunctionHasThisBinding, IsGeneratorExp, IsLegacyGenerator, IsStarGenerator, OwnSource, ExplicitUseStrict, SelfHosted, HasSingleton, TreatAsRunOnce, HasLazyScript, HasNonSyntacticScope, HasInnerFunctions, NeedsHomeObject, IsDerivedClassConstructor, }; uint32_t length, lineno, column, nslots; uint32_t natoms, nsrcnotes, i; uint32_t nconsts, nobjects, nregexps, ntrynotes, nblockscopes, nyieldoffsets; uint32_t prologueLength, version; uint32_t funLength = 0; uint32_t nTypeSets = 0; uint32_t scriptBits = 0; JSContext* cx = xdr->cx(); RootedScript script(cx); RootedObject enclosingScope(cx, enclosingScopeArg); natoms = nsrcnotes = 0; nconsts = nobjects = nregexps = ntrynotes = nblockscopes = nyieldoffsets = 0; /* XDR arguments and vars. */ uint16_t nargs = 0; uint16_t nblocklocals = 0; uint16_t nbodylevellexicals = 0; uint32_t nvars = 0; uint32_t nunaliasedvars = 0; uint16_t nunaliasedbodylevellexicals = 0; if (mode == XDR_ENCODE) { script = scriptp.get(); MOZ_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment()); MOZ_ASSERT(script->functionNonDelazifying() == fun); if (!fun && script->treatAsRunOnce()) { // This is a toplevel or eval script that's runOnce. We want to // make sure that we're not XDR-saving an object we emitted for // JSOP_OBJECT that then got modified. So throw if we're not // cloning in JSOP_OBJECT or if we ever didn't clone in it in the // past. const JS::CompartmentOptions& opts = JS::CompartmentOptionsRef(cx); if (!opts.cloneSingletons() || !opts.getSingletonsAsTemplates()) { JS_ReportError(cx, "Can't serialize a run-once non-function script " "when we're not doing singleton cloning"); return false; } } nargs = script->bindings.numArgs(); nblocklocals = script->bindings.numBlockScoped(); nbodylevellexicals = script->bindings.numBodyLevelLexicals(); nvars = script->bindings.numVars(); nunaliasedvars = script->bindings.numUnaliasedVars(); nunaliasedbodylevellexicals = script->bindings.numUnaliasedBodyLevelLexicals(); } if (!xdr->codeUint16(&nargs)) return false; if (!xdr->codeUint16(&nblocklocals)) return false; if (!xdr->codeUint16(&nbodylevellexicals)) return false; if (!xdr->codeUint32(&nvars)) return false; if (!xdr->codeUint32(&nunaliasedvars)) return false; if (!xdr->codeUint16(&nunaliasedbodylevellexicals)) return false; if (mode == XDR_ENCODE) length = script->length(); if (!xdr->codeUint32(&length)) return false; if (mode == XDR_ENCODE) { prologueLength = script->mainOffset(); MOZ_ASSERT(script->getVersion() != JSVERSION_UNKNOWN); version = script->getVersion(); lineno = script->lineno(); column = script->column(); nslots = script->nslots(); natoms = script->natoms(); nsrcnotes = script->numNotes(); if (script->hasConsts()) nconsts = script->consts()->length; if (script->hasObjects()) nobjects = script->objects()->length; if (script->hasRegexps()) nregexps = script->regexps()->length; if (script->hasTrynotes()) ntrynotes = script->trynotes()->length; if (script->hasBlockScopes()) nblockscopes = script->blockScopes()->length; if (script->hasYieldOffsets()) nyieldoffsets = script->yieldOffsets().length(); nTypeSets = script->nTypeSets(); funLength = script->funLength(); if (script->noScriptRval()) scriptBits |= (1 << NoScriptRval); if (script->savedCallerFun()) scriptBits |= (1 << SavedCallerFun); if (script->strict()) scriptBits |= (1 << Strict); if (script->explicitUseStrict()) scriptBits |= (1 << ExplicitUseStrict); if (script->selfHosted()) scriptBits |= (1 << SelfHosted); if (script->bindingsAccessedDynamically()) scriptBits |= (1 << ContainsDynamicNameAccess); if (script->funHasExtensibleScope()) scriptBits |= (1 << FunHasExtensibleScope); if (script->funNeedsDeclEnvObject()) scriptBits |= (1 << FunNeedsDeclEnvObject); if (script->funHasAnyAliasedFormal()) scriptBits |= (1 << FunHasAnyAliasedFormal); if (script->argumentsHasVarBinding()) scriptBits |= (1 << ArgumentsHasVarBinding); if (script->analyzedArgsUsage() && script->needsArgsObj()) scriptBits |= (1 << NeedsArgsObj); if (script->hasMappedArgsObj()) scriptBits |= (1 << HasMappedArgsObj); if (script->functionHasThisBinding()) scriptBits |= (1 << FunctionHasThisBinding); if (!enclosingScript || enclosingScript->scriptSource() != script->scriptSource()) scriptBits |= (1 << OwnSource); if (script->isGeneratorExp()) scriptBits |= (1 << IsGeneratorExp); if (script->isLegacyGenerator()) scriptBits |= (1 << IsLegacyGenerator); if (script->isStarGenerator()) scriptBits |= (1 << IsStarGenerator); if (script->hasSingletons()) scriptBits |= (1 << HasSingleton); if (script->treatAsRunOnce()) scriptBits |= (1 << TreatAsRunOnce); if (script->isRelazifiable()) scriptBits |= (1 << HasLazyScript); if (script->hasNonSyntacticScope()) scriptBits |= (1 << HasNonSyntacticScope); if (script->hasInnerFunctions()) scriptBits |= (1 << HasInnerFunctions); if (script->needsHomeObject()) scriptBits |= (1 << NeedsHomeObject); if (script->isDerivedClassConstructor()) scriptBits |= (1 << IsDerivedClassConstructor); } if (!xdr->codeUint32(&prologueLength)) return false; if (!xdr->codeUint32(&version)) return false; // To fuse allocations, we need lengths of all embedded arrays early. if (!xdr->codeUint32(&natoms)) return false; if (!xdr->codeUint32(&nsrcnotes)) return false; if (!xdr->codeUint32(&nconsts)) return false; if (!xdr->codeUint32(&nobjects)) return false; if (!xdr->codeUint32(&nregexps)) return false; if (!xdr->codeUint32(&ntrynotes)) return false; if (!xdr->codeUint32(&nblockscopes)) return false; if (!xdr->codeUint32(&nyieldoffsets)) return false; if (!xdr->codeUint32(&nTypeSets)) return false; if (!xdr->codeUint32(&funLength)) return false; if (!xdr->codeUint32(&scriptBits)) return false; if (mode == XDR_DECODE) { JSVersion version_ = JSVersion(version); MOZ_ASSERT((version_ & VersionFlags::MASK) == unsigned(version_)); CompileOptions options(cx); options.setVersion(version_) .setNoScriptRval(!!(scriptBits & (1 << NoScriptRval))) .setSelfHostingMode(!!(scriptBits & (1 << SelfHosted))); RootedScriptSource sourceObject(cx); if (scriptBits & (1 << OwnSource)) { ScriptSource* ss = cx->new_(); if (!ss) return false; ScriptSourceHolder ssHolder(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. */ CompileOptions options(cx); ss->initFromOptions(cx, options); sourceObject = ScriptSourceObject::create(cx, ss); if (!sourceObject || !ScriptSourceObject::initFromOptions(cx, sourceObject, options)) return false; } else { MOZ_ASSERT(enclosingScript); // When decoding, all the scripts and the script source object // are in the same compartment, so the script's source object // should never be a cross-compartment wrapper. MOZ_ASSERT(enclosingScript->sourceObject()->is()); sourceObject = &enclosingScript->sourceObject()->as(); } // If the outermost script has a non-syntactic scope, reflect that on // the static scope chain. if (scriptBits & (1 << HasNonSyntacticScope) && IsStaticGlobalLexicalScope(enclosingScope)) { enclosingScope = StaticNonSyntacticScopeObjects::create(cx, enclosingScope); if (!enclosingScope) return false; } script = JSScript::Create(cx, enclosingScope, !!(scriptBits & (1 << SavedCallerFun)), options, sourceObject, 0, 0); if (!script) return false; // Set the script in its function now so that inner scripts to be // decoded may iterate the static scope chain. if (fun) { fun->initScript(script); script->setFunction(fun); } } /* JSScript::partiallyInit assumes script->bindings is fully initialized. */ LifoAllocScope las(&cx->tempLifoAlloc()); if (!XDRScriptBindings(xdr, las, nargs, nvars, nbodylevellexicals, nblocklocals, nunaliasedvars, nunaliasedbodylevellexicals, script)) return false; if (mode == XDR_DECODE) { if (!JSScript::partiallyInit(cx, script, nconsts, nobjects, nregexps, ntrynotes, nblockscopes, nyieldoffsets, nTypeSets)) { return false; } MOZ_ASSERT(!script->mainOffset()); script->mainOffset_ = prologueLength; script->setLength(length); script->funLength_ = funLength; scriptp.set(script); if (scriptBits & (1 << Strict)) script->strict_ = true; if (scriptBits & (1 << ExplicitUseStrict)) script->explicitUseStrict_ = true; if (scriptBits & (1 << ContainsDynamicNameAccess)) script->bindingsAccessedDynamically_ = true; if (scriptBits & (1 << FunHasExtensibleScope)) script->funHasExtensibleScope_ = true; if (scriptBits & (1 << FunNeedsDeclEnvObject)) script->funNeedsDeclEnvObject_ = true; if (scriptBits & (1 << FunHasAnyAliasedFormal)) script->funHasAnyAliasedFormal_ = true; if (scriptBits & (1 << ArgumentsHasVarBinding)) script->setArgumentsHasVarBinding(); if (scriptBits & (1 << NeedsArgsObj)) script->setNeedsArgsObj(true); if (scriptBits & (1 << HasMappedArgsObj)) script->hasMappedArgsObj_ = true; if (scriptBits & (1 << FunctionHasThisBinding)) script->functionHasThisBinding_ = true; if (scriptBits & (1 << IsGeneratorExp)) script->isGeneratorExp_ = true; if (scriptBits & (1 << HasSingleton)) script->hasSingletons_ = true; if (scriptBits & (1 << TreatAsRunOnce)) script->treatAsRunOnce_ = true; if (scriptBits & (1 << HasNonSyntacticScope)) script->hasNonSyntacticScope_ = true; if (scriptBits & (1 << HasInnerFunctions)) script->hasInnerFunctions_ = true; if (scriptBits & (1 << NeedsHomeObject)) script->needsHomeObject_ = true; if (scriptBits & (1 << IsDerivedClassConstructor)) script->isDerivedClassConstructor_ = true; if (scriptBits & (1 << IsLegacyGenerator)) { MOZ_ASSERT(!(scriptBits & (1 << IsStarGenerator))); script->setGeneratorKind(LegacyGenerator); } else if (scriptBits & (1 << IsStarGenerator)) script->setGeneratorKind(StarGenerator); } JS_STATIC_ASSERT(sizeof(jsbytecode) == 1); JS_STATIC_ASSERT(sizeof(jssrcnote) == 1); if (scriptBits & (1 << OwnSource)) { if (!script->scriptSource()->performXDR(xdr)) return false; } if (!xdr->codeUint32(&script->sourceStart_)) return false; if (!xdr->codeUint32(&script->sourceEnd_)) return false; if (!xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) || !xdr->codeUint32(&nslots)) { return false; } if (mode == XDR_DECODE) { script->lineno_ = lineno; script->column_ = column; script->nslots_ = nslots; } jsbytecode* code = script->code(); SharedScriptData* ssd; if (mode == XDR_DECODE) { ssd = SharedScriptData::new_(cx, length, nsrcnotes, natoms); if (!ssd) return false; code = ssd->data; if (natoms != 0) { script->natoms_ = natoms; script->atoms = ssd->atoms(); } } if (!xdr->codeBytes(code, length) || !xdr->codeBytes(code + length, nsrcnotes)) { if (mode == XDR_DECODE) js_free(ssd); return false; } for (i = 0; i != natoms; ++i) { if (mode == XDR_DECODE) { RootedAtom tmp(cx); if (!XDRAtom(xdr, &tmp)) return false; script->atoms[i].init(tmp); } else { RootedAtom tmp(cx, script->atoms[i]); if (!XDRAtom(xdr, &tmp)) return false; } } if (mode == XDR_DECODE) { if (!SaveSharedScriptData(cx, script, ssd, nsrcnotes)) return false; } if (nconsts) { HeapValue* vector = script->consts()->vector; RootedValue val(cx); for (i = 0; i != nconsts; ++i) { if (mode == XDR_ENCODE) val = vector[i]; if (!XDRScriptConst(xdr, &val)) return false; if (mode == XDR_DECODE) vector[i].init(val); } } /* * Here looping from 0-to-length to xdr objects is essential to ensure that * all references to enclosing blocks (via FindScopeObjectIndex below) happen * after the enclosing block has been XDR'd. */ for (i = 0; i != nobjects; ++i) { HeapPtrObject* objp = &script->objects()->vector[i]; XDRClassKind classk; if (mode == XDR_ENCODE) { JSObject* obj = *objp; if (obj->is()) classk = CK_BlockObject; else if (obj->is()) classk = CK_WithObject; else if (obj->is()) classk = CK_JSFunction; else if (obj->is() || obj->is()) classk = CK_JSObject; else MOZ_CRASH("Cannot encode this class of object."); } if (!xdr->codeEnum32(&classk)) return false; switch (classk) { case CK_BlockObject: case CK_WithObject: { /* Code the nested block's enclosing scope. */ uint32_t enclosingStaticScopeIndex = 0; if (mode == XDR_ENCODE) { NestedScopeObject& scope = (*objp)->as(); if (NestedScopeObject* enclosing = scope.enclosingNestedScope()) { if (IsStaticGlobalLexicalScope(enclosing)) enclosingStaticScopeIndex = UINT32_MAX; else enclosingStaticScopeIndex = FindScopeObjectIndex(script, *enclosing); } else { enclosingStaticScopeIndex = UINT32_MAX; } } if (!xdr->codeUint32(&enclosingStaticScopeIndex)) return false; Rooted enclosingStaticScope(cx); if (mode == XDR_DECODE) { if (enclosingStaticScopeIndex != UINT32_MAX) { MOZ_ASSERT(enclosingStaticScopeIndex < i); enclosingStaticScope = script->objects()->vector[enclosingStaticScopeIndex]; } else { // This is not ternary because MSVC can't typecheck the // ternary. if (fun) enclosingStaticScope = fun; else enclosingStaticScope = enclosingScope; } } if (classk == CK_BlockObject) { Rooted tmp(cx, static_cast(objp->get())); if (!XDRStaticBlockObject(xdr, enclosingStaticScope, &tmp)) return false; *objp = tmp; } else { Rooted tmp(cx, static_cast(objp->get())); if (!XDRStaticWithObject(xdr, enclosingStaticScope, &tmp)) return false; *objp = tmp; } break; } case CK_JSFunction: { /* Code the nested function's enclosing scope. */ uint32_t funEnclosingScopeIndex = 0; RootedObject funEnclosingScope(cx); if (mode == XDR_ENCODE) { RootedFunction function(cx, &(*objp)->as()); if (function->isInterpretedLazy()) funEnclosingScope = function->lazyScript()->enclosingScope(); else if (function->isInterpreted()) funEnclosingScope = function->nonLazyScript()->enclosingStaticScope(); else { MOZ_ASSERT(function->isAsmJSNative()); JS_ReportError(cx, "AsmJS modules are not yet supported in XDR serialization."); return false; } StaticScopeIter ssi(funEnclosingScope); // Starting from a nested function, hitting a non-syntactic // scope on the static scope chain means that its enclosing // function has a non-syntactic scope. Nested functions // themselves never have non-syntactic scope chains. if (ssi.done() || ssi.type() == StaticScopeIter::NonSyntactic || ssi.type() == StaticScopeIter::Function) { MOZ_ASSERT_IF(ssi.done() || ssi.type() != StaticScopeIter::Function, !fun); funEnclosingScopeIndex = UINT32_MAX; } else if (ssi.type() == StaticScopeIter::Block) { if (ssi.block().isGlobal()) { funEnclosingScopeIndex = UINT32_MAX; } else { funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.block()); MOZ_ASSERT(funEnclosingScopeIndex < i); } } else { funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.staticWith()); MOZ_ASSERT(funEnclosingScopeIndex < i); } } if (!xdr->codeUint32(&funEnclosingScopeIndex)) return false; if (mode == XDR_DECODE) { if (funEnclosingScopeIndex == UINT32_MAX) { // This is not ternary because MSVC can't typecheck the // ternary. if (fun) funEnclosingScope = fun; else funEnclosingScope = enclosingScope; } else { MOZ_ASSERT(funEnclosingScopeIndex < i); funEnclosingScope = script->objects()->vector[funEnclosingScopeIndex]; } } // Code nested function and script. RootedFunction tmp(cx); if (mode == XDR_ENCODE) tmp = &(*objp)->as(); if (!XDRInterpretedFunction(xdr, funEnclosingScope, script, &tmp)) return false; *objp = tmp; break; } case CK_JSObject: { /* Code object literal. */ RootedObject tmp(cx, *objp); if (!XDRObjectLiteral(xdr, &tmp)) return false; *objp = tmp; break; } default: { MOZ_ASSERT(false, "Unknown class kind."); return false; } } } for (i = 0; i != nregexps; ++i) { Rooted regexp(cx); if (mode == XDR_ENCODE) regexp = &script->regexps()->vector[i]->as(); if (!XDRScriptRegExpObject(xdr, ®exp)) return false; if (mode == XDR_DECODE) script->regexps()->vector[i] = regexp; } if (ntrynotes != 0) { JSTryNote* tnfirst = script->trynotes()->vector; MOZ_ASSERT(script->trynotes()->length == ntrynotes); JSTryNote* tn = tnfirst + ntrynotes; do { --tn; if (!xdr->codeUint8(&tn->kind) || !xdr->codeUint32(&tn->stackDepth) || !xdr->codeUint32(&tn->start) || !xdr->codeUint32(&tn->length)) { return false; } } while (tn != tnfirst); } for (i = 0; i < nblockscopes; ++i) { BlockScopeNote* note = &script->blockScopes()->vector[i]; if (!xdr->codeUint32(¬e->index) || !xdr->codeUint32(¬e->start) || !xdr->codeUint32(¬e->length) || !xdr->codeUint32(¬e->parent)) { return false; } } for (i = 0; i < nyieldoffsets; ++i) { uint32_t* offset = &script->yieldOffsets()[i]; if (!xdr->codeUint32(offset)) return false; } if (scriptBits & (1 << HasLazyScript)) { Rooted lazy(cx); if (mode == XDR_ENCODE) lazy = script->maybeLazyScript(); if (!XDRRelazificationInfo(xdr, fun, script, enclosingScope, &lazy)) return false; if (mode == XDR_DECODE) script->setLazyScript(lazy); } if (mode == XDR_DECODE) { scriptp.set(script); /* see BytecodeEmitter::tellDebuggerAboutCompiledScript */ if (!fun) Debugger::onNewScript(cx, script); } return true; } template bool js::XDRScript(XDRState*, HandleObject, HandleScript, HandleFunction, MutableHandleScript); template bool js::XDRScript(XDRState*, HandleObject, HandleScript, HandleFunction, MutableHandleScript); template bool js::XDRLazyScript(XDRState* xdr, HandleObject enclosingScope, HandleScript enclosingScript, HandleFunction fun, MutableHandle lazy) { JSContext* cx = xdr->cx(); { uint32_t begin; uint32_t end; uint32_t lineno; uint32_t column; uint64_t packedFields; if (mode == XDR_ENCODE) { // Note: it's possible the LazyScript has a non-null script_ pointer // to a JSScript. We don't encode it: we can just delazify the // lazy script. MOZ_ASSERT(fun == lazy->functionNonDelazifying()); begin = lazy->begin(); end = lazy->end(); lineno = lazy->lineno(); column = lazy->column(); packedFields = lazy->packedFields(); } if (!xdr->codeUint32(&begin) || !xdr->codeUint32(&end) || !xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) || !xdr->codeUint64(&packedFields)) { return false; } if (mode == XDR_DECODE) { lazy.set(LazyScript::Create(cx, fun, nullptr, enclosingScope, enclosingScript, packedFields, begin, end, lineno, column)); if (!lazy) return false; fun->initLazyScript(lazy); } } // Code free variables. if (!XDRLazyFreeVariables(xdr, lazy)) return false; // Code inner functions. { RootedFunction func(cx); HeapPtrFunction* innerFunctions = lazy->innerFunctions(); size_t numInnerFunctions = lazy->numInnerFunctions(); for (size_t i = 0; i < numInnerFunctions; i++) { if (mode == XDR_ENCODE) func = innerFunctions[i]; if (!XDRInterpretedFunction(xdr, fun, enclosingScript, &func)) return false; if (mode == XDR_DECODE) innerFunctions[i] = func; } } return true; } template bool js::XDRLazyScript(XDRState*, HandleObject, HandleScript, HandleFunction, MutableHandle); template bool js::XDRLazyScript(XDRState*, HandleObject, HandleScript, HandleFunction, MutableHandle); void JSScript::setSourceObject(JSObject* object) { MOZ_ASSERT(compartment() == object->compartment()); sourceObject_ = object; } js::ScriptSourceObject& JSScript::scriptSourceUnwrap() const { return UncheckedUnwrap(sourceObject())->as(); } js::ScriptSource* JSScript::scriptSource() const { return scriptSourceUnwrap().source(); } js::ScriptSource* JSScript::maybeForwardedScriptSource() const { return UncheckedUnwrap(MaybeForwarded(sourceObject()))->as().source(); } bool JSScript::initScriptCounts(JSContext* cx) { MOZ_ASSERT(!hasScriptCounts()); // Record all pc which are the first instruction of a basic block. mozilla::Vector jumpTargets; jsbytecode* end = codeEnd(); jsbytecode* mainEntry = main(); for (jsbytecode* pc = code(); pc != end; pc = GetNextPc(pc)) { if (pc == mainEntry) { if (!jumpTargets.append(pc)) return false; } bool jump = IsJumpOpcode(JSOp(*pc)); if (jump) { jsbytecode* target = pc + GET_JUMP_OFFSET(pc); if (!jumpTargets.append(target)) return false; if (BytecodeFallsThrough(JSOp(*pc))) { jsbytecode* fallthrough = GetNextPc(pc); if (!jumpTargets.append(fallthrough)) return false; } } if (JSOp(*pc) == JSOP_TABLESWITCH) { jsbytecode* pc2 = pc; int32_t len = GET_JUMP_OFFSET(pc2); // Default target. if (!jumpTargets.append(pc + len)) return false; pc2 += JUMP_OFFSET_LEN; int32_t low = GET_JUMP_OFFSET(pc2); pc2 += JUMP_OFFSET_LEN; int32_t high = GET_JUMP_OFFSET(pc2); for (int i = 0; i < high-low+1; i++) { pc2 += JUMP_OFFSET_LEN; int32_t off = (int32_t) GET_JUMP_OFFSET(pc2); if (off) { // Case (i + low) if (!jumpTargets.append(pc + off)) return false; } } } } // Mark catch/finally blocks as being jump targets. if (hasTrynotes()) { JSTryNote* tn = trynotes()->vector; JSTryNote* tnlimit = tn + trynotes()->length; for (; tn < tnlimit; tn++) { jsbytecode* tryStart = mainEntry + tn->start; jsbytecode* tryPc = tryStart - 1; if (JSOp(*tryPc) != JSOP_TRY) continue; jsbytecode* tryTarget = tryStart + tn->length; if (!jumpTargets.append(tryTarget)) return false; } } // Sort all pc, and remove duplicates. std::sort(jumpTargets.begin(), jumpTargets.end()); auto last = std::unique(jumpTargets.begin(), jumpTargets.end()); jumpTargets.erase(last, jumpTargets.end()); // Initialize all PCCounts counters to 0. ScriptCounts::PCCountsVector base; if (!base.reserve(jumpTargets.length())) return false; for (size_t i = 0; i < jumpTargets.length(); i++) base.infallibleEmplaceBack(pcToOffset(jumpTargets[i])); // Create compartment's scriptCountsMap if necessary. ScriptCountsMap* map = compartment()->scriptCountsMap; if (!map) { map = cx->new_(); if (!map) return false; if (!map->init()) { js_delete(map); ReportOutOfMemory(cx); return false; } compartment()->scriptCountsMap = map; } // Register the current ScriptCount in the compartment's map. if (!map->putNew(this, Move(base))) return false; // safe to set this; we can't fail after this point. hasScriptCounts_ = true; // 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->runtime()); !iter.done(); ++iter) { if (iter->isInterpreter()) iter->asInterpreter()->enableInterruptsIfRunning(this); } return true; } static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript* script) { MOZ_ASSERT(script->hasScriptCounts()); ScriptCountsMap* map = script->compartment()->scriptCountsMap; ScriptCountsMap::Ptr p = map->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; } void JSScript::setIonScript(JSContext* maybecx, js::jit::IonScript* ionScript) { MOZ_ASSERT_IF(ionScript != ION_DISABLED_SCRIPT, !baselineScript()->hasPendingIonBuilder()); if (hasIonScript()) js::jit::IonScript::writeBarrierPre(zone(), ion); ion = ionScript; MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript()); updateBaselineOrIonRaw(maybecx); } 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::takeOverScriptCountsMapEntry(ScriptCounts* entryValue) { #ifdef DEBUG ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); MOZ_ASSERT(entryValue == &p->value()); #endif hasScriptCounts_ = false; } void JSScript::releaseScriptCounts(ScriptCounts* counts) { ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); *counts = Move(p->value()); compartment()->scriptCountsMap->remove(p); hasScriptCounts_ = false; } void JSScript::destroyScriptCounts(FreeOp* fop) { if (hasScriptCounts()) { ScriptCounts scriptCounts; releaseScriptCounts(&scriptCounts); } } void ScriptSourceObject::trace(JSTracer* trc, JSObject* obj) { ScriptSourceObject* sso = static_cast(obj); // Don't trip over the poison 'not yet initialized' values. if (!sso->getReservedSlot(INTRODUCTION_SCRIPT_SLOT).isMagic(JS_GENERIC_MAGIC)) { JSScript* script = sso->introductionScript(); if (script) { TraceManuallyBarrieredEdge(trc, &script, "ScriptSourceObject introductionScript"); sso->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, PrivateValue(script)); } } } void ScriptSourceObject::finalize(FreeOp* fop, JSObject* obj) { ScriptSourceObject* sso = &obj->as(); // If code coverage is enabled, record the filename associated with this // source object. if (fop->runtime()->lcovOutput.isEnabled()) sso->compartment()->lcovOutput.collectSourceFile(sso->compartment(), sso); sso->source()->decref(); sso->setReservedSlot(SOURCE_SLOT, PrivateValue(nullptr)); } const Class ScriptSourceObject::class_ = { "ScriptSource", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_IS_ANONYMOUS, nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ finalize, nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ trace }; ScriptSourceObject* ScriptSourceObject::create(ExclusiveContext* cx, ScriptSource* source) { RootedObject object(cx, NewObjectWithGivenProto(cx, &class_, nullptr)); if (!object) return nullptr; RootedScriptSource sourceObject(cx, &object->as()); source->incref(); // The matching decref is in ScriptSourceObject::finalize. sourceObject->initReservedSlot(SOURCE_SLOT, PrivateValue(source)); // The remaining slots should eventually be populated by a call to // initFromOptions. Poison them until that point. sourceObject->initReservedSlot(ELEMENT_SLOT, MagicValue(JS_GENERIC_MAGIC)); sourceObject->initReservedSlot(ELEMENT_PROPERTY_SLOT, MagicValue(JS_GENERIC_MAGIC)); sourceObject->initReservedSlot(INTRODUCTION_SCRIPT_SLOT, MagicValue(JS_GENERIC_MAGIC)); return sourceObject; } /* static */ bool ScriptSourceObject::initFromOptions(JSContext* cx, HandleScriptSource source, const ReadOnlyCompileOptions& options) { releaseAssertSameCompartment(cx, source); MOZ_ASSERT(source->getReservedSlot(ELEMENT_SLOT).isMagic(JS_GENERIC_MAGIC)); MOZ_ASSERT(source->getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic(JS_GENERIC_MAGIC)); MOZ_ASSERT(source->getReservedSlot(INTRODUCTION_SCRIPT_SLOT).isMagic(JS_GENERIC_MAGIC)); RootedValue element(cx, ObjectOrNullValue(options.element())); if (!cx->compartment()->wrap(cx, &element)) return false; source->setReservedSlot(ELEMENT_SLOT, element); RootedValue elementAttributeName(cx); if (options.elementAttributeName()) elementAttributeName = StringValue(options.elementAttributeName()); else elementAttributeName = UndefinedValue(); if (!cx->compartment()->wrap(cx, &elementAttributeName)) return false; source->setReservedSlot(ELEMENT_PROPERTY_SLOT, elementAttributeName); // 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. In that case, simply don't bother to retain the introduction // script. if (options.introductionScript() && options.introductionScript()->compartment() == cx->compartment()) { source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, PrivateValue(options.introductionScript())); } else { source->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, UndefinedValue()); } return true; } /* static */ bool JSScript::loadSource(JSContext* cx, ScriptSource* ss, bool* worked) { MOZ_ASSERT(!ss->hasSourceData()); *worked = false; if (!cx->runtime()->sourceHook || !ss->sourceRetrievable()) return true; char16_t* src = nullptr; size_t length; if (!cx->runtime()->sourceHook->load(cx, ss->filename(), &src, &length)) return false; if (!src) return true; ss->setSource(src, length); *worked = true; return true; } JSFlatString* JSScript::sourceData(JSContext* cx) { MOZ_ASSERT(scriptSource()->hasSourceData()); return scriptSource()->substring(cx, sourceStart(), sourceEnd()); } UncompressedSourceCache::AutoHoldEntry::AutoHoldEntry() : cache_(nullptr), source_(nullptr), charsToFree_(nullptr) { } void UncompressedSourceCache::AutoHoldEntry::holdEntry(UncompressedSourceCache* cache, ScriptSource* source) { // Initialise the holder for a specific cache and script source. This will // hold on to the cached source chars in the event that the cache is purged. MOZ_ASSERT(!cache_ && !source_ && !charsToFree_); cache_ = cache; source_ = source; } void UncompressedSourceCache::AutoHoldEntry::deferDelete(const char16_t* chars) { // Take ownership of source chars now the cache is being purged. Remove our // reference to the ScriptSource which might soon be destroyed. MOZ_ASSERT(cache_ && source_ && !charsToFree_); cache_ = nullptr; source_ = nullptr; charsToFree_ = chars; } UncompressedSourceCache::AutoHoldEntry::~AutoHoldEntry() { // The holder is going out of scope. If it has taken ownership of cached // chars then delete them, otherwise unregister ourself with the cache. if (charsToFree_) { MOZ_ASSERT(!cache_ && !source_); js_free(const_cast(charsToFree_)); } else if (cache_) { MOZ_ASSERT(source_); cache_->releaseEntry(*this); } } void UncompressedSourceCache::holdEntry(AutoHoldEntry& holder, ScriptSource* ss) { MOZ_ASSERT(!holder_); holder.holdEntry(this, ss); holder_ = &holder; } void UncompressedSourceCache::releaseEntry(AutoHoldEntry& holder) { MOZ_ASSERT(holder_ == &holder); holder_ = nullptr; } const char16_t* UncompressedSourceCache::lookup(ScriptSource* ss, AutoHoldEntry& holder) { MOZ_ASSERT(!holder_); if (!map_) return nullptr; if (Map::Ptr p = map_->lookup(ss)) { holdEntry(holder, ss); return p->value(); } return nullptr; } bool UncompressedSourceCache::put(ScriptSource* ss, const char16_t* str, AutoHoldEntry& holder) { MOZ_ASSERT(!holder_); if (!map_) { map_ = js_new(); if (!map_) return false; if (!map_->init()) { js_delete(map_); map_ = nullptr; return false; } } if (!map_->put(ss, str)) return false; holdEntry(holder, ss); return true; } void UncompressedSourceCache::purge() { if (!map_) return; for (Map::Range r = map_->all(); !r.empty(); r.popFront()) { const char16_t* chars = r.front().value(); if (holder_ && r.front().key() == holder_->source()) { holder_->deferDelete(chars); holder_ = nullptr; } else { js_free(const_cast(chars)); } } js_delete(map_); map_ = nullptr; } size_t UncompressedSourceCache::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { size_t n = 0; if (map_ && !map_->empty()) { n += map_->sizeOfIncludingThis(mallocSizeOf); for (Map::Range r = map_->all(); !r.empty(); r.popFront()) { const char16_t* v = r.front().value(); n += mallocSizeOf(v); } } return n; } const char16_t* ScriptSource::chars(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& holder) { switch (dataType) { case DataUncompressed: return uncompressedChars(); case DataCompressed: { if (const char16_t* decompressed = cx->runtime()->uncompressedSourceCache.lookup(this, holder)) return decompressed; const size_t nbytes = sizeof(char16_t) * (length_ + 1); char16_t* decompressed = static_cast(js_malloc(nbytes)); if (!decompressed) { JS_ReportOutOfMemory(cx); return nullptr; } if (!DecompressString((const unsigned char*) compressedData(), compressedBytes(), reinterpret_cast(decompressed), nbytes)) { JS_ReportOutOfMemory(cx); js_free(decompressed); return nullptr; } decompressed[length_] = 0; if (!cx->runtime()->uncompressedSourceCache.put(this, decompressed, holder)) { JS_ReportOutOfMemory(cx); js_free(decompressed); return nullptr; } return decompressed; } case DataParent: return parent()->chars(cx, holder); default: MOZ_CRASH(); } } JSFlatString* ScriptSource::substring(JSContext* cx, uint32_t start, uint32_t stop) { MOZ_ASSERT(start <= stop); UncompressedSourceCache::AutoHoldEntry holder; const char16_t* chars = this->chars(cx, holder); if (!chars) return nullptr; return NewStringCopyN(cx, chars + start, stop - start); } JSFlatString* ScriptSource::substringDontDeflate(JSContext* cx, uint32_t start, uint32_t stop) { MOZ_ASSERT(start <= stop); UncompressedSourceCache::AutoHoldEntry holder; const char16_t* chars = this->chars(cx, holder); if (!chars) return nullptr; return NewStringCopyNDontDeflate(cx, chars + start, stop - start); } void ScriptSource::setSource(const char16_t* chars, size_t length, bool ownsChars /* = true */) { MOZ_ASSERT(dataType == DataMissing); dataType = DataUncompressed; data.uncompressed.chars = chars; data.uncompressed.ownsChars = ownsChars; length_ = length; } void ScriptSource::setCompressedSource(JSRuntime* maybert, void* raw, size_t nbytes, HashNumber hash) { MOZ_ASSERT(dataType == DataMissing || dataType == DataUncompressed); if (dataType == DataUncompressed && ownsUncompressedChars()) js_free(const_cast(uncompressedChars())); dataType = DataCompressed; data.compressed.raw = raw; data.compressed.nbytes = nbytes; data.compressed.hash = hash; if (maybert) updateCompressedSourceSet(maybert); } void ScriptSource::updateCompressedSourceSet(JSRuntime* rt) { MOZ_ASSERT(dataType == DataCompressed); MOZ_ASSERT(!inCompressedSourceSet); CompressedSourceSet::AddPtr p = rt->compressedSourceSet.lookupForAdd(this); if (p) { // There is another ScriptSource with the same compressed data. // Mark that ScriptSource as the parent and use it for all attempts to // get the source for this ScriptSource. ScriptSource* parent = *p; parent->incref(); js_free(compressedData()); dataType = DataParent; data.parent = parent; } else { if (rt->compressedSourceSet.add(p, this)) inCompressedSourceSet = true; } } bool ScriptSource::ensureOwnsSource(ExclusiveContext* cx) { MOZ_ASSERT(dataType == DataUncompressed); if (ownsUncompressedChars()) return true; char16_t* uncompressed = cx->zone()->pod_malloc(Max(length_, 1)); if (!uncompressed) { ReportOutOfMemory(cx); return false; } PodCopy(uncompressed, uncompressedChars(), length_); data.uncompressed.chars = uncompressed; data.uncompressed.ownsChars = true; return true; } bool ScriptSource::setSourceCopy(ExclusiveContext* cx, SourceBufferHolder& srcBuf, bool argumentsNotIncluded, SourceCompressionTask* task) { MOZ_ASSERT(!hasSourceData()); argumentsNotIncluded_ = argumentsNotIncluded; bool owns = srcBuf.ownsChars(); setSource(owns ? srcBuf.take() : srcBuf.get(), srcBuf.length(), owns); // 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 the script is enormous, then decompression can take seconds. With // lazy parsing, decompression is not uncommon, so this can significantly // increase latency. // - If there is only one core, then compression will contend with JS // execution (which hurts benchmarketing). // - If the source contains a giant string, then parsing will finish much // faster than compression which increases latency (this case is handled // in Parser::stringLiteral). // // Lastly, since the parsing thread will eventually perform a blocking wait // on the compression task's thread, require that there are at least 2 // helper threads: // - If we are on a helper thread, there must be another helper thread to // execute our compression task. // - If we are on the main thread, there must be at least two helper // threads since at most one helper thread can be blocking on the main // thread (see HelperThreadState::canStartParseTask) which would cause a // deadlock if there wasn't a second helper thread that could make // progress on our compression task. bool canCompressOffThread = HelperThreadState().cpuCount > 1 && HelperThreadState().threadCount >= 2 && CanUseExtraThreads(); const size_t TINY_SCRIPT = 256; const size_t HUGE_SCRIPT = 5 * 1024 * 1024; if (TINY_SCRIPT <= srcBuf.length() && srcBuf.length() < HUGE_SCRIPT && canCompressOffThread) { task->ss = this; if (!StartOffThreadCompression(cx, task)) return false; } else if (!ensureOwnsSource(cx)) { return false; } return true; } SourceCompressionTask::ResultType SourceCompressionTask::work() { // Try to keep the maximum memory usage down by only allocating half the // size of the string, first. size_t inputBytes = ss->length() * sizeof(char16_t); size_t firstSize = inputBytes / 2; compressed = js_malloc(firstSize); if (!compressed) return OOM; Compressor comp(reinterpret_cast(ss->uncompressedChars()), inputBytes); if (!comp.init()) return OOM; comp.setOutput((unsigned char*) compressed, firstSize); bool cont = true; while (cont) { if (abort_) return Aborted; switch (comp.compressMore()) { case Compressor::CONTINUE: break; case Compressor::MOREOUTPUT: { if (comp.outWritten() == inputBytes) { // The compressed string is longer than the original string. return Aborted; } // The compressed output is greater than half the size of the // original string. Reallocate to the full size. compressed = js_realloc(compressed, inputBytes); if (!compressed) return OOM; comp.setOutput((unsigned char*) compressed, inputBytes); break; } case Compressor::DONE: cont = false; break; case Compressor::OOM: return OOM; } } compressedBytes = comp.outWritten(); compressedHash = CompressedSourceHasher::computeHash(compressed, compressedBytes); // Shrink the buffer to the size of the compressed data. if (void* newCompressed = js_realloc(compressed, compressedBytes)) compressed = newCompressed; return Success; } ScriptSource::~ScriptSource() { MOZ_ASSERT_IF(inCompressedSourceSet, dataType == DataCompressed); switch (dataType) { case DataUncompressed: if (ownsUncompressedChars()) js_free(const_cast(uncompressedChars())); break; case DataCompressed: // Script source references are only manipulated on the main thread, // except during off thread parsing when the source may be created // and used exclusively by the thread doing the parse. In this case the // ScriptSource might be destroyed while off the main thread, but it // will not have been added to the runtime's compressed source set // until the parse is finished on the main thread. if (inCompressedSourceSet) TlsPerThreadData.get()->runtimeFromMainThread()->compressedSourceSet.remove(this); js_free(compressedData()); break; case DataParent: parent()->decref(); break; default: break; } } void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ScriptSourceInfo* info) const { if (dataType == DataUncompressed && ownsUncompressedChars()) info->uncompressed += mallocSizeOf(uncompressedChars()); else if (dataType == DataCompressed) info->compressed += mallocSizeOf(compressedData()); info->misc += mallocSizeOf(this) + mallocSizeOf(filename_.get()) + mallocSizeOf(introducerFilename_.get()); info->numScripts++; } template bool ScriptSource::performXDR(XDRState* xdr) { uint8_t hasSource = hasSourceData(); if (!xdr->codeUint8(&hasSource)) return false; uint8_t retrievable = sourceRetrievable_; if (!xdr->codeUint8(&retrievable)) return false; sourceRetrievable_ = retrievable; if (hasSource && !sourceRetrievable_) { if (!xdr->codeUint32(&length_)) return false; uint32_t compressedLength; if (mode == XDR_ENCODE) { switch (dataType) { case DataUncompressed: compressedLength = 0; break; case DataCompressed: compressedLength = compressedBytes(); break; case DataParent: compressedLength = parent()->compressedBytes(); break; default: MOZ_CRASH(); } } if (!xdr->codeUint32(&compressedLength)) return false; { uint8_t argumentsNotIncluded; if (mode == XDR_ENCODE) argumentsNotIncluded = argumentsNotIncluded_; if (!xdr->codeUint8(&argumentsNotIncluded)) return false; if (mode == XDR_DECODE) argumentsNotIncluded_ = argumentsNotIncluded; } size_t byteLen = compressedLength ? compressedLength : (length_ * sizeof(char16_t)); if (mode == XDR_DECODE) { uint8_t* p = xdr->cx()->template pod_malloc(Max(byteLen, 1)); if (!p || !xdr->codeBytes(p, byteLen)) { js_free(p); return false; } if (compressedLength) setCompressedSource(xdr->cx()->runtime(), p, compressedLength, CompressedSourceHasher::computeHash(p, compressedLength)); else setSource((const char16_t*) p, length_); } else { void* p; switch (dataType) { case DataUncompressed: p = (void*) uncompressedChars(); break; case DataCompressed: p = compressedData(); break; case DataParent: p = parent()->compressedData(); break; default: MOZ_CRASH(); } if (!xdr->codeBytes(p, byteLen)) return false; } } uint8_t haveSourceMap = hasSourceMapURL(); if (!xdr->codeUint8(&haveSourceMap)) return false; if (haveSourceMap) { uint32_t sourceMapURLLen = (mode == XDR_DECODE) ? 0 : js_strlen(sourceMapURL_.get()); if (!xdr->codeUint32(&sourceMapURLLen)) return false; if (mode == XDR_DECODE) { sourceMapURL_ = xdr->cx()->template make_pod_array(sourceMapURLLen + 1); if (!sourceMapURL_) return false; } if (!xdr->codeChars(sourceMapURL_.get(), sourceMapURLLen)) { if (mode == XDR_DECODE) sourceMapURL_ = nullptr; return false; } sourceMapURL_[sourceMapURLLen] = '\0'; } uint8_t haveDisplayURL = hasDisplayURL(); if (!xdr->codeUint8(&haveDisplayURL)) return false; if (haveDisplayURL) { uint32_t displayURLLen = (mode == XDR_DECODE) ? 0 : js_strlen(displayURL_.get()); if (!xdr->codeUint32(&displayURLLen)) return false; if (mode == XDR_DECODE) { displayURL_ = xdr->cx()->template make_pod_array(displayURLLen + 1); if (!displayURL_) return false; } if (!xdr->codeChars(displayURL_.get(), displayURLLen)) { if (mode == XDR_DECODE) displayURL_ = nullptr; return false; } displayURL_[displayURLLen] = '\0'; } uint8_t haveFilename = !!filename_; if (!xdr->codeUint8(&haveFilename)) return false; if (haveFilename) { const char* fn = filename(); if (!xdr->codeCString(&fn)) return false; if (mode == XDR_DECODE && !setFilename(xdr->cx(), fn)) return false; } return true; } // Format and return a cx->zone()->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. static char* FormatIntroducedFilename(ExclusiveContext* 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 = JS_snprintf(linenoBuf, 15, "%u", lineno); size_t introducerLen = strlen(introducer); size_t len = filenameLen + 6 /* == strlen(" line ") */ + linenoLen + 3 /* == strlen(" > ") */ + introducerLen + 1 /* \0 */; char* formatted = cx->zone()->pod_malloc(len); if (!formatted) { ReportOutOfMemory(cx); return nullptr; } mozilla::DebugOnly checkLen = JS_snprintf(formatted, len, "%s line %s > %s", filename, linenoBuf, introducer); MOZ_ASSERT(checkLen == len - 1); return formatted; } bool ScriptSource::initFromOptions(ExclusiveContext* cx, const ReadOnlyCompileOptions& options) { MOZ_ASSERT(!filename_); MOZ_ASSERT(!introducerFilename_); mutedErrors_ = options.mutedErrors(); introductionType_ = options.introductionType; setIntroductionOffset(options.introductionOffset); if (options.hasIntroductionInfo) { MOZ_ASSERT(options.introductionType != nullptr); const char* filename = options.filename() ? options.filename() : ""; char* formatted = FormatIntroducedFilename(cx, filename, options.introductionLineno, options.introductionType); if (!formatted) return false; filename_.reset(formatted); } else if (options.filename()) { if (!setFilename(cx, options.filename())) return false; } if (options.introducerFilename()) { introducerFilename_ = DuplicateString(cx, options.introducerFilename()); if (!introducerFilename_) return false; } return true; } bool ScriptSource::setFilename(ExclusiveContext* cx, const char* filename) { MOZ_ASSERT(!filename_); filename_ = DuplicateString(cx, filename); return filename_ != nullptr; } bool ScriptSource::setDisplayURL(ExclusiveContext* cx, const char16_t* displayURL) { MOZ_ASSERT(displayURL); if (hasDisplayURL()) { if (cx->isJSContext() && !JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING, GetErrorMessage, nullptr, JSMSG_ALREADY_HAS_PRAGMA, filename_.get(), "//# sourceURL")) { return false; } } size_t len = js_strlen(displayURL) + 1; if (len == 1) return true; displayURL_ = DuplicateString(cx, displayURL); return displayURL_ != nullptr; } bool ScriptSource::setSourceMapURL(ExclusiveContext* cx, const char16_t* sourceMapURL) { MOZ_ASSERT(sourceMapURL); size_t len = js_strlen(sourceMapURL) + 1; if (len == 1) return true; sourceMapURL_ = DuplicateString(cx, sourceMapURL); return sourceMapURL_ != nullptr; } size_t ScriptSource::computedSizeOfData() const { if (dataType == DataUncompressed && ownsUncompressedChars()) return sizeof(char16_t) * length_; if (dataType == DataCompressed) return compressedBytes(); return 0; } /* * Shared script data management. */ SharedScriptData* js::SharedScriptData::new_(ExclusiveContext* cx, uint32_t codeLength, uint32_t srcnotesLength, uint32_t natoms) { /* * Ensure the atoms are aligned, as some architectures don't allow unaligned * access. */ const uint32_t pointerSize = sizeof(JSAtom*); const uint32_t pointerMask = pointerSize - 1; const uint32_t dataOffset = offsetof(SharedScriptData, data); uint32_t baseLength = codeLength + srcnotesLength; uint32_t padding = (pointerSize - ((baseLength + dataOffset) & pointerMask)) & pointerMask; uint32_t length = baseLength + padding + pointerSize * natoms; SharedScriptData* entry = reinterpret_cast( cx->zone()->pod_malloc(length + dataOffset)); if (!entry) { ReportOutOfMemory(cx); return nullptr; } entry->length = length; entry->natoms = natoms; entry->marked = false; memset(entry->data + baseLength, 0, padding); /* * Call constructors to initialize the storage that will be accessed as a * HeapPtrAtom array via atoms(). */ HeapPtrAtom* atoms = entry->atoms(); MOZ_ASSERT(reinterpret_cast(atoms) % sizeof(JSAtom*) == 0); for (unsigned i = 0; i < natoms; ++i) new (&atoms[i]) HeapPtrAtom(); return entry; } /* * Takes ownership of its *ssd parameter and either adds it into the runtime's * ScriptDataTable or frees it if a matching entry already exists. * * Sets the |code| and |atoms| fields on the given JSScript. */ static bool SaveSharedScriptData(ExclusiveContext* cx, Handle script, SharedScriptData* ssd, uint32_t nsrcnotes) { MOZ_ASSERT(script != nullptr); MOZ_ASSERT(ssd != nullptr); AutoLockForExclusiveAccess lock(cx); ScriptBytecodeHasher::Lookup l(ssd); ScriptDataTable::AddPtr p = cx->scriptDataTable().lookupForAdd(l); if (p) { js_free(ssd); ssd = *p; } else { if (!cx->scriptDataTable().add(p, ssd)) { script->setCode(nullptr); script->atoms = nullptr; js_free(ssd); ReportOutOfMemory(cx); return false; } } /* * During the IGC we need to ensure that bytecode is marked whenever it is * accessed even if the bytecode was already in the table: at this point * old scripts or exceptions pointing to the bytecode may no longer be * reachable. This is effectively a read barrier. */ if (cx->isJSContext()) { JSRuntime* rt = cx->asJSContext()->runtime(); if (JS::IsIncrementalGCInProgress(rt) && rt->gc.isFullGc()) ssd->marked = true; } script->setCode(ssd->data); script->atoms = ssd->atoms(); return true; } static inline void MarkScriptData(JSRuntime* rt, const jsbytecode* bytecode) { /* * As an invariant, a ScriptBytecodeEntry should not be 'marked' outside of * a GC. Since SweepScriptBytecodes is only called during a full gc, * to preserve this invariant, only mark during a full gc. */ if (rt->gc.isFullGc()) SharedScriptData::fromBytecode(bytecode)->marked = true; } void js::UnmarkScriptData(JSRuntime* rt) { MOZ_ASSERT(rt->gc.isFullGc()); ScriptDataTable& table = rt->scriptDataTable(); for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { SharedScriptData* entry = e.front(); entry->marked = false; } } void js::SweepScriptData(JSRuntime* rt) { MOZ_ASSERT(rt->gc.isFullGc()); ScriptDataTable& table = rt->scriptDataTable(); if (rt->keepAtoms()) return; for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { SharedScriptData* entry = e.front(); if (!entry->marked) { js_free(entry); e.removeFront(); } } } void js::FreeScriptData(JSRuntime* rt) { ScriptDataTable& table = rt->scriptDataTable(); if (!table.initialized()) return; for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) js_free(e.front()); table.clear(); } /* * JSScript::data and SharedScriptData::data have complex, * manually-controlled, memory layouts. * * JSScript::data begins with some optional array headers. They are optional * because they often aren't needed, i.e. the corresponding arrays often have * zero elements. Each header has a bit in JSScript::hasArrayBits that * indicates if it's present within |data|; from this the offset of each * present array header can be computed. Each header has an accessor function * in JSScript that encapsulates this offset computation. * * Array type Array elements Accessor * ---------- -------------- -------- * ConstArray Consts consts() * ObjectArray Objects objects() * ObjectArray Regexps regexps() * TryNoteArray Try notes trynotes() * BlockScopeArray Scope notes blockScopes() * * Then are the elements of several arrays. * - Most of these arrays have headers listed above (if present). For each of * these, the array pointer and the array length is stored in the header. * - The remaining arrays have pointers and lengths that are stored directly in * JSScript. This is because, unlike the others, they are nearly always * non-zero length and so the optional-header space optimization isn't * worthwhile. * * Array elements Pointed to by Length * -------------- ------------- ------ * Consts consts()->vector consts()->length * Objects objects()->vector objects()->length * Regexps regexps()->vector regexps()->length * Try notes trynotes()->vector trynotes()->length * Scope notes blockScopes()->vector blockScopes()->length * * IMPORTANT: This layout has two key properties. * - It ensures that everything has sufficient alignment; in particular, the * consts() elements need Value alignment. * - It ensures there are no gaps between elements, which saves space and makes * manual layout easy. In particular, in the second part, arrays with larger * elements precede arrays with smaller elements. * * SharedScriptData::data contains data that can be shared within a * runtime. These items' layout is manually controlled to make it easier to * manage both during (temporary) allocation and during matching against * existing entries in the runtime. As the jsbytecode has to come first to * enable lookup by bytecode identity, SharedScriptData::data, the atoms part * has to manually be aligned sufficiently by adding padding after the notes * part. * * Array elements Pointed to by Length * -------------- ------------- ------ * jsbytecode code length * jsscrnote notes() numNotes() * Atoms atoms natoms * * The following static assertions check JSScript::data's alignment properties. */ #define KEEPS_JSVAL_ALIGNMENT(T) \ (JS_ALIGNMENT_OF(JS::Value) % JS_ALIGNMENT_OF(T) == 0 && \ sizeof(T) % sizeof(JS::Value) == 0) #define HAS_JSVAL_ALIGNMENT(T) \ (JS_ALIGNMENT_OF(JS::Value) == JS_ALIGNMENT_OF(T) && \ sizeof(T) == sizeof(JS::Value)) #define NO_PADDING_BETWEEN_ENTRIES(T1, T2) \ (JS_ALIGNMENT_OF(T1) % JS_ALIGNMENT_OF(T2) == 0) /* * These assertions ensure that there is no padding between the array headers, * and also that the consts() elements (which follow immediately afterward) are * Value-aligned. (There is an assumption that |data| itself is Value-aligned; * we check this below). */ JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(ConstArray)); JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(ObjectArray)); /* there are two of these */ JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(TryNoteArray)); JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(BlockScopeArray)); /* These assertions ensure there is no padding required between array elements. */ JS_STATIC_ASSERT(HAS_JSVAL_ALIGNMENT(HeapValue)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapValue, HeapPtrObject)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, HeapPtrObject)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, JSTryNote)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(JSTryNote, uint32_t)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(uint32_t, uint32_t)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapValue, BlockScopeNote)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(BlockScopeNote, BlockScopeNote)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(JSTryNote, BlockScopeNote)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, BlockScopeNote)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(BlockScopeNote, uint32_t)); static inline size_t ScriptDataSize(uint32_t nbindings, uint32_t nconsts, uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nblockscopes, uint32_t nyieldoffsets) { size_t size = 0; if (nconsts != 0) size += sizeof(ConstArray) + nconsts * sizeof(Value); if (nobjects != 0) size += sizeof(ObjectArray) + nobjects * sizeof(NativeObject*); if (nregexps != 0) size += sizeof(ObjectArray) + nregexps * sizeof(NativeObject*); if (ntrynotes != 0) size += sizeof(TryNoteArray) + ntrynotes * sizeof(JSTryNote); if (nblockscopes != 0) size += sizeof(BlockScopeArray) + nblockscopes * sizeof(BlockScopeNote); if (nyieldoffsets != 0) size += sizeof(YieldOffsetArray) + nyieldoffsets * sizeof(uint32_t); if (nbindings != 0) { // Make sure bindings are sufficiently aligned. size = JS_ROUNDUP(size, JS_ALIGNMENT_OF(Binding)) + nbindings * sizeof(Binding); } return size; } void JSScript::initCompartment(ExclusiveContext* cx) { compartment_ = cx->compartment_; } /* static */ JSScript* JSScript::Create(ExclusiveContext* cx, HandleObject enclosingScope, bool savedCallerFun, const ReadOnlyCompileOptions& options, HandleObject sourceObject, uint32_t bufStart, uint32_t bufEnd) { MOZ_ASSERT(bufStart <= bufEnd); RootedScript script(cx, Allocate(cx)); if (!script) return nullptr; PodZero(script.get()); new (&script->bindings) Bindings; script->enclosingStaticScope_ = enclosingScope; script->savedCallerFun_ = savedCallerFun; script->initCompartment(cx); script->selfHosted_ = options.selfHostingMode; script->noScriptRval_ = options.noScriptRval; script->treatAsRunOnce_ = options.isRunOnce; // Compute whether this script is under a non-syntactic scope. We don't // need to walk the entire static scope chain if the script is nested in a // function. In that case, we can propagate the cached value from the // outer script. script->hasNonSyntacticScope_ = HasNonSyntacticStaticScopeChain(enclosingScope); script->version = options.version; MOZ_ASSERT(script->getVersion() == options.version); // assert that no overflow occurred script->setSourceObject(sourceObject); script->sourceStart_ = bufStart; script->sourceEnd_ = bufEnd; return script; } static inline uint8_t* AllocScriptData(JS::Zone* zone, size_t size) { if (!size) return nullptr; uint8_t* data = zone->pod_calloc(JS_ROUNDUP(size, sizeof(Value))); if (!data) return nullptr; MOZ_ASSERT(size_t(data) % sizeof(Value) == 0); return data; } /* static */ bool JSScript::partiallyInit(ExclusiveContext* cx, HandleScript script, uint32_t nconsts, uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nblockscopes, uint32_t nyieldoffsets, uint32_t nTypeSets) { size_t size = ScriptDataSize(script->bindings.count(), nconsts, nobjects, nregexps, ntrynotes, nblockscopes, nyieldoffsets); script->data = AllocScriptData(script->zone(), size); if (size && !script->data) { ReportOutOfMemory(cx); return false; } script->dataSize_ = size; MOZ_ASSERT(nTypeSets <= UINT16_MAX); script->nTypeSets_ = uint16_t(nTypeSets); uint8_t* cursor = script->data; if (nconsts != 0) { script->setHasArray(CONSTS); cursor += sizeof(ConstArray); } if (nobjects != 0) { script->setHasArray(OBJECTS); cursor += sizeof(ObjectArray); } if (nregexps != 0) { script->setHasArray(REGEXPS); cursor += sizeof(ObjectArray); } if (ntrynotes != 0) { script->setHasArray(TRYNOTES); cursor += sizeof(TryNoteArray); } if (nblockscopes != 0) { script->setHasArray(BLOCK_SCOPES); cursor += sizeof(BlockScopeArray); } YieldOffsetArray* yieldOffsets = nullptr; if (nyieldoffsets != 0) { yieldOffsets = reinterpret_cast(cursor); cursor += sizeof(YieldOffsetArray); } if (nconsts != 0) { MOZ_ASSERT(reinterpret_cast(cursor) % sizeof(JS::Value) == 0); script->consts()->length = nconsts; script->consts()->vector = (HeapValue*)cursor; cursor += nconsts * sizeof(script->consts()->vector[0]); } if (nobjects != 0) { script->objects()->length = nobjects; script->objects()->vector = (HeapPtrObject*)cursor; cursor += nobjects * sizeof(script->objects()->vector[0]); } if (nregexps != 0) { script->regexps()->length = nregexps; script->regexps()->vector = (HeapPtrObject*)cursor; cursor += nregexps * sizeof(script->regexps()->vector[0]); } if (ntrynotes != 0) { script->trynotes()->length = ntrynotes; script->trynotes()->vector = reinterpret_cast(cursor); size_t vectorSize = ntrynotes * sizeof(script->trynotes()->vector[0]); #ifdef DEBUG memset(cursor, 0, vectorSize); #endif cursor += vectorSize; } if (nblockscopes != 0) { script->blockScopes()->length = nblockscopes; script->blockScopes()->vector = reinterpret_cast(cursor); size_t vectorSize = nblockscopes * sizeof(script->blockScopes()->vector[0]); #ifdef DEBUG memset(cursor, 0, vectorSize); #endif cursor += vectorSize; } if (nyieldoffsets != 0) { yieldOffsets->init(reinterpret_cast(cursor), nyieldoffsets); size_t vectorSize = nyieldoffsets * sizeof(script->yieldOffsets()[0]); #ifdef DEBUG memset(cursor, 0, vectorSize); #endif cursor += vectorSize; } if (script->bindings.count() != 0) { // Make sure bindings are sufficiently aligned. cursor = reinterpret_cast (JS_ROUNDUP(reinterpret_cast(cursor), JS_ALIGNMENT_OF(Binding))); } cursor = script->bindings.switchToScriptStorage(reinterpret_cast(cursor)); MOZ_ASSERT(cursor == script->data + size); return true; } /* static */ bool JSScript::fullyInitTrivial(ExclusiveContext* cx, Handle script) { if (!script->bindings.initTrivial(cx)) return false; if (!partiallyInit(cx, script, 0, 0, 0, 0, 0, 0, 0)) return false; SharedScriptData* ssd = SharedScriptData::new_(cx, 1, 1, 0); if (!ssd) return false; ssd->data[0] = JSOP_RETRVAL; ssd->data[1] = SRC_NULL; script->setLength(1); return SaveSharedScriptData(cx, script, ssd, 1); } /* static */ void JSScript::linkToFunctionFromEmitter(js::ExclusiveContext* cx, JS::Handle script, js::frontend::FunctionBox* funbox) { script->funHasExtensibleScope_ = funbox->hasExtensibleScope(); script->funNeedsDeclEnvObject_ = funbox->needsDeclEnvObject(); script->needsHomeObject_ = funbox->needsHomeObject(); script->isDerivedClassConstructor_ = funbox->isDerivedClassConstructor(); if (funbox->argumentsHasLocalBinding()) { script->setArgumentsHasVarBinding(); if (funbox->definitelyNeedsArgsObj()) script->setNeedsArgsObj(true); } else { MOZ_ASSERT(!funbox->definitelyNeedsArgsObj()); } script->hasMappedArgsObj_ = funbox->hasMappedArgsObj(); script->functionHasThisBinding_ = funbox->hasThisBinding(); script->funLength_ = funbox->length; script->isGeneratorExp_ = funbox->inGenexpLambda; script->setGeneratorKind(funbox->generatorKind()); // Link the function and the script to each other, so that StaticScopeIter // may walk the scope chain of currently compiling scripts. RootedFunction fun(cx, funbox->function()); MOZ_ASSERT(fun->isInterpreted()); script->setFunction(fun); if (fun->isInterpretedLazy()) fun->setUnlazifiedScript(script); else fun->setScript(script); } /* static */ void JSScript::linkToModuleFromEmitter(js::ExclusiveContext* cx, JS::Handle script, js::frontend::ModuleBox* modulebox) { script->funHasExtensibleScope_ = false; script->funNeedsDeclEnvObject_ = false; script->needsHomeObject_ = false; script->isDerivedClassConstructor_ = false; script->funLength_ = 0; script->isGeneratorExp_ = false; script->setGeneratorKind(NotGenerator); // Link the module and the script to each other, so that StaticScopeIter // may walk the scope chain of currently compiling scripts. RootedModuleObject module(cx, modulebox->module()); script->setModule(module); } /* static */ bool JSScript::fullyInitFromEmitter(ExclusiveContext* cx, HandleScript script, BytecodeEmitter* bce) { /* The counts of indexed things must be checked during code generation. */ MOZ_ASSERT(bce->atomIndices->count() <= INDEX_LIMIT); MOZ_ASSERT(bce->objectList.length <= INDEX_LIMIT); MOZ_ASSERT(bce->regexpList.length <= INDEX_LIMIT); uint32_t mainLength = bce->offset(); uint32_t prologueLength = bce->prologueOffset(); uint32_t nsrcnotes; if (!bce->finishTakingSrcNotes(&nsrcnotes)) return false; uint32_t natoms = bce->atomIndices->count(); if (!partiallyInit(cx, script, bce->constList.length(), bce->objectList.length, bce->regexpList.length, bce->tryNoteList.length(), bce->blockScopeList.length(), bce->yieldOffsetList.length(), bce->typesetCount)) { return false; } MOZ_ASSERT(script->mainOffset() == 0); script->mainOffset_ = prologueLength; script->lineno_ = bce->firstLine; script->setLength(prologueLength + mainLength); script->natoms_ = natoms; SharedScriptData* ssd = SharedScriptData::new_(cx, script->length(), nsrcnotes, natoms); if (!ssd) return false; jsbytecode* code = ssd->data; PodCopy(code, bce->prologue.code.begin(), prologueLength); PodCopy(code + prologueLength, bce->code().begin(), mainLength); bce->copySrcNotes((jssrcnote*)(code + script->length()), nsrcnotes); InitAtomMap(bce->atomIndices.getMap(), ssd->atoms()); if (!SaveSharedScriptData(cx, script, ssd, nsrcnotes)) return false; #ifdef DEBUG FunctionBox* funbox = bce->sc->isFunctionBox() ? bce->sc->asFunctionBox() : nullptr; // Assert that the properties set by linkToFunctionFromEmitter are // correct. if (funbox) { MOZ_ASSERT(script->funHasExtensibleScope_ == funbox->hasExtensibleScope()); MOZ_ASSERT(script->funNeedsDeclEnvObject_ == funbox->needsDeclEnvObject()); MOZ_ASSERT(script->needsHomeObject_ == funbox->needsHomeObject()); MOZ_ASSERT(script->isDerivedClassConstructor_ == funbox->isDerivedClassConstructor()); MOZ_ASSERT(script->argumentsHasVarBinding() == funbox->argumentsHasLocalBinding()); MOZ_ASSERT(script->hasMappedArgsObj() == funbox->hasMappedArgsObj()); MOZ_ASSERT(script->functionHasThisBinding() == funbox->hasThisBinding()); MOZ_ASSERT(script->functionNonDelazifying() == funbox->function()); MOZ_ASSERT(script->isGeneratorExp_ == funbox->inGenexpLambda); MOZ_ASSERT(script->generatorKind() == funbox->generatorKind()); } else { MOZ_ASSERT(!script->funHasExtensibleScope_); MOZ_ASSERT(!script->funNeedsDeclEnvObject_); MOZ_ASSERT(!script->needsHomeObject_); MOZ_ASSERT(!script->isDerivedClassConstructor_); MOZ_ASSERT(!script->argumentsHasVarBinding()); MOZ_ASSERT(!script->hasMappedArgsObj()); MOZ_ASSERT(!script->isGeneratorExp_); MOZ_ASSERT(script->generatorKind() == NotGenerator); } #endif if (bce->constList.length() != 0) bce->constList.finish(script->consts()); if (bce->objectList.length != 0) bce->objectList.finish(script->objects()); if (bce->regexpList.length != 0) bce->regexpList.finish(script->regexps()); if (bce->tryNoteList.length() != 0) bce->tryNoteList.finish(script->trynotes()); if (bce->blockScopeList.length() != 0) bce->blockScopeList.finish(script->blockScopes(), prologueLength); script->strict_ = bce->sc->strict(); script->explicitUseStrict_ = bce->sc->hasExplicitUseStrict(); script->bindingsAccessedDynamically_ = bce->sc->bindingsAccessedDynamically(); script->hasSingletons_ = bce->hasSingletons; if (bce->yieldOffsetList.length() != 0) bce->yieldOffsetList.finish(script->yieldOffsets(), prologueLength); // The call to nfixed() depends on the above setFunction() call. if (UINT32_MAX - script->nfixed() < bce->maxStackDepth) { bce->reportError(nullptr, JSMSG_NEED_DIET, "script"); return false; } script->nslots_ = script->nfixed() + bce->maxStackDepth; for (unsigned i = 0, n = script->bindings.numArgs(); i < n; ++i) { if (script->formalIsAliased(i)) { script->funHasAnyAliasedFormal_ = true; break; } } return true; } size_t JSScript::computedSizeOfData() const { return dataSize(); } size_t JSScript::sizeOfData(mozilla::MallocSizeOf mallocSizeOf) const { return mallocSizeOf(data); } size_t JSScript::sizeOfTypeScript(mozilla::MallocSizeOf mallocSizeOf) const { return types_->sizeOfIncludingThis(mallocSizeOf); } /* * Nb: srcnotes are variable-length. This function computes the number of * srcnote *slots*, which may be greater than the number of srcnotes. */ uint32_t JSScript::numNotes() { jssrcnote* sn; jssrcnote* notes_ = notes(); for (sn = notes_; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) continue; return sn - notes_ + 1; /* +1 for the terminator */ } js::GlobalObject& JSScript::uninlinedGlobal() const { return global(); } void JSScript::fixEnclosingStaticGlobalLexicalScope() { MOZ_ASSERT(IsStaticGlobalLexicalScope(enclosingStaticScope_)); enclosingStaticScope_ = &global().lexicalScope().staticBlock(); } void LazyScript::fixEnclosingStaticGlobalLexicalScope() { MOZ_ASSERT(IsStaticGlobalLexicalScope(enclosingScope_)); enclosingScope_ = &function_->global().lexicalScope().staticBlock(); } void JSScript::finalize(FreeOp* fop) { // NOTE: this JSScript may be partially initialized at this point. E.g. we // may have created it and partially initialized it with // JSScript::Create(), but not yet finished initializing it with // fullyInitFromEmitter() or fullyInitTrivial(). // Collect code coverage information for this script and all its inner // scripts, and store the aggregated information on the compartment. if (fop->runtime()->lcovOutput.isEnabled()) compartment()->lcovOutput.collectCodeCoverageInfo(compartment(), sourceObject(), this); fop->runtime()->spsProfiler.onScriptFinalized(this); if (types_) types_->destroy(); jit::DestroyJitScripts(fop, this); destroyScriptCounts(fop); destroyDebugScript(fop); if (data) { JS_POISON(data, 0xdb, computedSizeOfData()); fop->free_(data); } fop->runtime()->lazyScriptCache.remove(this); // In most cases, our LazyScript's script pointer will reference this // script, and thus be nulled out by normal weakref processing. However, if // we unlazified the LazyScript during incremental sweeping, it will have a // completely different JSScript. MOZ_ASSERT_IF(lazyScript && !IsAboutToBeFinalizedUnbarriered(&lazyScript), !lazyScript->hasScript() || lazyScript->maybeScriptUnbarriered() != this); } static const uint32_t GSN_CACHE_THRESHOLD = 100; void GSNCache::purge() { code = nullptr; if (map.initialized()) map.finish(); } jssrcnote* 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()) { MOZ_ASSERT(cache.map.initialized()); GSNCache::Map::Ptr p = cache.map.lookup(pc); return p ? p->value() : nullptr; } size_t offset = 0; jssrcnote* result; for (jssrcnote* sn = script->notes(); ; sn = SN_NEXT(sn)) { if (SN_IS_TERMINATOR(sn)) { result = nullptr; break; } offset += SN_DELTA(sn); if (offset == target && SN_IS_GETTABLE(sn)) { result = sn; break; } } if (cache.code != script->code() && script->length() >= GSN_CACHE_THRESHOLD) { unsigned nsrcnotes = 0; for (jssrcnote* sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { if (SN_IS_GETTABLE(sn)) ++nsrcnotes; } if (cache.code) { MOZ_ASSERT(cache.map.initialized()); cache.map.finish(); cache.code = nullptr; } if (cache.map.init(nsrcnotes)) { pc = script->code(); for (jssrcnote* sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { pc += SN_DELTA(sn); if (SN_IS_GETTABLE(sn)) cache.map.putNewInfallible(pc, sn); } cache.code = script->code(); } } return result; } jssrcnote* js::GetSrcNote(JSContext* cx, JSScript* script, jsbytecode* pc) { return GetSrcNote(cx->runtime()->gsnCache, script, pc); } unsigned js::PCToLineNumber(unsigned startLine, jssrcnote* notes, jsbytecode* code, jsbytecode* pc, unsigned* columnp) { unsigned lineno = startLine; unsigned column = 0; /* * 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 (jssrcnote* sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { offset += SN_DELTA(sn); SrcNoteType type = (SrcNoteType) SN_TYPE(sn); if (type == SRC_SETLINE) { if (offset <= target) lineno = unsigned(GetSrcNoteOffset(sn, 0)); column = 0; } else if (type == SRC_NEWLINE) { if (offset <= target) lineno++; column = 0; } if (offset > target) break; if (type == SRC_COLSPAN) { ptrdiff_t colspan = SN_OFFSET_TO_COLSPAN(GetSrcNoteOffset(sn, 0)); 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->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 = SN_MAX_OFFSET; for (jssrcnote* sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { /* * 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(sn); SrcNoteType type = (SrcNoteType) SN_TYPE(sn); if (type == SRC_SETLINE) { lineno = unsigned(GetSrcNoteOffset(sn, 0)); } else if (type == SRC_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 (jssrcnote* sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { SrcNoteType type = (SrcNoteType) SN_TYPE(sn); if (type == SRC_SETLINE) lineno = unsigned(GetSrcNoteOffset(sn, 0)); else if (type == SRC_NEWLINE) lineno++; if (maxLineNo < lineno) maxLineNo = lineno; } return 1 + maxLineNo - script->lineno(); } void js::DescribeScriptedCallerForCompilation(JSContext* cx, MutableHandleScript maybeScript, const char** file, unsigned* linenop, uint32_t* pcOffset, bool* mutedErrors, LineOption opt) { if (opt == CALLED_FROM_JSOP_EVAL) { jsbytecode* pc = nullptr; maybeScript.set(cx->currentScript(&pc)); static_assert(JSOP_SPREADEVAL_LENGTH == JSOP_STRICTSPREADEVAL_LENGTH, "next op after a spread must be at consistent offset"); static_assert(JSOP_EVAL_LENGTH == JSOP_STRICTEVAL_LENGTH, "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 ? JSOP_SPREADEVAL_LENGTH : JSOP_EVAL_LENGTH); MOZ_ASSERT(*nextpc == JSOP_LINENO); *file = maybeScript->filename(); *linenop = GET_UINT32(nextpc); *pcOffset = pc - maybeScript->code(); *mutedErrors = maybeScript->mutedErrors(); return; } NonBuiltinFrameIter iter(cx); if (iter.done()) { maybeScript.set(nullptr); *file = nullptr; *linenop = 0; *pcOffset = 0; *mutedErrors = false; return; } *file = iter.scriptFilename(); *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 asm.js frames. if (iter.hasScript()) { maybeScript.set(iter.script()); *pcOffset = iter.pc() - maybeScript->code(); } else { maybeScript.set(nullptr); *pcOffset = 0; } } template static inline T* Rebase(JSScript* dst, JSScript* src, T* srcp) { size_t off = reinterpret_cast(srcp) - src->data; return reinterpret_cast(dst->data + off); } static JSObject* CloneInnerInterpretedFunction(JSContext* cx, HandleObject enclosingScope, HandleFunction srcFun) { /* NB: Keep this in sync with XDRInterpretedFunction. */ RootedObject cloneProto(cx); if (srcFun->isStarGenerator()) { cloneProto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, cx->global()); if (!cloneProto) return nullptr; } gc::AllocKind allocKind = srcFun->getAllocKind(); RootedFunction clone(cx, NewFunctionWithProto(cx, nullptr, 0, JSFunction::INTERPRETED, nullptr, nullptr, cloneProto, allocKind, TenuredObject)); if (!clone) return nullptr; JSScript::AutoDelazify srcScript(cx, srcFun); if (!srcScript) return nullptr; JSScript* cloneScript = CloneScriptIntoFunction(cx, enclosingScope, clone, srcScript); if (!cloneScript) return nullptr; clone->setArgCount(srcFun->nargs()); clone->setFlags(srcFun->flags()); clone->initAtom(srcFun->displayAtom()); if (!JSFunction::setTypeForScriptedFunction(cx, clone)) return nullptr; return clone; } bool js::detail::CopyScript(JSContext* cx, HandleObject scriptStaticScope, HandleScript src, HandleScript dst) { if (src->treatAsRunOnce() && !src->functionNonDelazifying()) { // Toplevel run-once scripts may not be cloned. JS_ReportError(cx, "No cloning toplevel run-once scripts"); return false; } /* NB: Keep this in sync with XDRScript. */ /* Some embeddings are not careful to use ExposeObjectToActiveJS as needed. */ MOZ_ASSERT(!src->sourceObject()->asTenured().isMarked(gc::GRAY)); uint32_t nconsts = src->hasConsts() ? src->consts()->length : 0; uint32_t nobjects = src->hasObjects() ? src->objects()->length : 0; uint32_t nregexps = src->hasRegexps() ? src->regexps()->length : 0; uint32_t ntrynotes = src->hasTrynotes() ? src->trynotes()->length : 0; uint32_t nblockscopes = src->hasBlockScopes() ? src->blockScopes()->length : 0; uint32_t nyieldoffsets = src->hasYieldOffsets() ? src->yieldOffsets().length() : 0; /* Script data */ size_t size = src->dataSize(); ScopedJSFreePtr data(AllocScriptData(cx->zone(), size)); if (size && !data) { ReportOutOfMemory(cx); return false; } /* Bindings */ Rooted bindings(cx); if (!Bindings::clone(cx, &bindings, data, src)) return false; /* Objects */ AutoObjectVector objects(cx); if (nobjects != 0) { HeapPtrObject* vector = src->objects()->vector; for (unsigned i = 0; i < nobjects; i++) { RootedObject obj(cx, vector[i]); RootedObject clone(cx); if (obj->is()) { Rooted innerBlock(cx, &obj->as()); RootedObject enclosingScope(cx); if (NestedScopeObject* enclosingBlock = innerBlock->enclosingNestedScope()) { if (IsStaticGlobalLexicalScope(enclosingBlock)) { MOZ_ASSERT(IsStaticGlobalLexicalScope(scriptStaticScope) || scriptStaticScope->is()); enclosingScope = scriptStaticScope; } else { enclosingScope = objects[FindScopeObjectIndex(src, *enclosingBlock)]; } } else { enclosingScope = scriptStaticScope; } clone = CloneNestedScopeObject(cx, enclosingScope, innerBlock); } else if (obj->is()) { RootedFunction innerFun(cx, &obj->as()); if (innerFun->isNative()) { if (cx->compartment() != innerFun->compartment()) { MOZ_ASSERT(innerFun->isAsmJSNative()); JS_ReportError(cx, "AsmJS modules do not yet support cloning."); return false; } clone = innerFun; } else { if (innerFun->isInterpretedLazy()) { AutoCompartment ac(cx, innerFun); if (!innerFun->getOrCreateScript(cx)) return false; } RootedObject staticScope(cx, innerFun->nonLazyScript()->enclosingStaticScope()); StaticScopeIter ssi(cx, staticScope); RootedObject enclosingScope(cx); if (ssi.done() || ssi.type() == StaticScopeIter::NonSyntactic) { enclosingScope = scriptStaticScope; } else if (ssi.type() == StaticScopeIter::Function) { MOZ_ASSERT(scriptStaticScope->is()); enclosingScope = scriptStaticScope; } else if (ssi.type() == StaticScopeIter::Block) { if (ssi.block().isGlobal()) { MOZ_ASSERT(IsStaticGlobalLexicalScope(scriptStaticScope) || scriptStaticScope->is()); enclosingScope = scriptStaticScope; } else { enclosingScope = objects[FindScopeObjectIndex(src, ssi.block())]; } } else { enclosingScope = objects[FindScopeObjectIndex(src, ssi.staticWith())]; } clone = CloneInnerInterpretedFunction(cx, enclosingScope, innerFun); } } else { clone = DeepCloneObjectLiteral(cx, obj, TenuredObject); } if (!clone || !objects.append(clone)) return false; } } /* RegExps */ AutoObjectVector regexps(cx); if (nregexps != 0) { HeapPtrObject* vector = src->regexps()->vector; for (unsigned i = 0; i < nregexps; i++) { JSObject* clone = CloneScriptRegExpObject(cx, vector[i]->as()); if (!clone || !regexps.append(clone)) return false; } } /* Now that all fallible allocation is complete, do the copying. */ dst->bindings = bindings; /* This assignment must occur before all the Rebase calls. */ dst->data = data.forget(); dst->dataSize_ = size; memcpy(dst->data, src->data, size); /* Script filenames, bytecodes and atoms are runtime-wide. */ dst->setCode(src->code()); dst->atoms = src->atoms; dst->setLength(src->length()); dst->lineno_ = src->lineno(); dst->mainOffset_ = src->mainOffset(); dst->natoms_ = src->natoms(); dst->funLength_ = src->funLength(); dst->nTypeSets_ = src->nTypeSets(); dst->nslots_ = src->nslots(); if (src->argumentsHasVarBinding()) { dst->setArgumentsHasVarBinding(); if (src->analyzedArgsUsage()) dst->setNeedsArgsObj(src->needsArgsObj()); } dst->hasMappedArgsObj_ = src->hasMappedArgsObj(); dst->functionHasThisBinding_ = src->functionHasThisBinding(); dst->cloneHasArray(src); dst->strict_ = src->strict(); dst->explicitUseStrict_ = src->explicitUseStrict(); dst->bindingsAccessedDynamically_ = src->bindingsAccessedDynamically(); dst->funHasExtensibleScope_ = src->funHasExtensibleScope(); dst->funNeedsDeclEnvObject_ = src->funNeedsDeclEnvObject(); dst->funHasAnyAliasedFormal_ = src->funHasAnyAliasedFormal(); dst->hasSingletons_ = src->hasSingletons(); dst->treatAsRunOnce_ = src->treatAsRunOnce(); dst->hasInnerFunctions_ = src->hasInnerFunctions(); dst->isGeneratorExp_ = src->isGeneratorExp(); dst->setGeneratorKind(src->generatorKind()); if (nconsts != 0) { HeapValue* vector = Rebase(dst, src, src->consts()->vector); dst->consts()->vector = vector; for (unsigned i = 0; i < nconsts; ++i) MOZ_ASSERT_IF(vector[i].isMarkable(), vector[i].toString()->isAtom()); } if (nobjects != 0) { HeapPtrObject* vector = Rebase(dst, src, src->objects()->vector); dst->objects()->vector = vector; for (unsigned i = 0; i < nobjects; ++i) vector[i].init(&objects[i]->as()); } if (nregexps != 0) { HeapPtrObject* vector = Rebase(dst, src, src->regexps()->vector); dst->regexps()->vector = vector; for (unsigned i = 0; i < nregexps; ++i) vector[i].init(®exps[i]->as()); } if (ntrynotes != 0) dst->trynotes()->vector = Rebase(dst, src, src->trynotes()->vector); if (nblockscopes != 0) dst->blockScopes()->vector = Rebase(dst, src, src->blockScopes()->vector); if (nyieldoffsets != 0) dst->yieldOffsets().vector_ = Rebase(dst, src, src->yieldOffsets().vector_); /* * Function delazification assumes that their script does not have a * non-syntactic global scope. We ensure that as follows: * * 1) Initial parsing only creates lazy functions if * !hasNonSyntacticScope. * 2) Cloning a lazy function into a non-global scope will always require * that its script be cloned. See comments in * CloneFunctionObjectUseSameScript. * 3) Cloning a script never sets a lazyScript on the clone, so the function * cannot be relazified. * * If you decide that lazy functions should be supported with a * non-syntactic global scope, make sure delazification can deal. */ MOZ_ASSERT_IF(dst->hasNonSyntacticScope(), !dst->maybeLazyScript()); MOZ_ASSERT_IF(dst->hasNonSyntacticScope(), !dst->isRelazifiable()); return true; } static JSScript* CreateEmptyScriptForClone(JSContext* cx, HandleObject enclosingScope, HandleScript src) { /* * Wrap the script source object as needed. Self-hosted scripts may be * in another runtime, so lazily create a new script source object to * use for them. */ RootedObject sourceObject(cx); if (cx->runtime()->isSelfHostingCompartment(src->compartment())) { if (!cx->compartment()->selfHostingScriptSource) { CompileOptions options(cx); FillSelfHostingCompileOptions(options); ScriptSourceObject* obj = frontend::CreateScriptSourceObject(cx, options); if (!obj) return nullptr; cx->compartment()->selfHostingScriptSource.set(obj); } sourceObject = cx->compartment()->selfHostingScriptSource; } else { sourceObject = src->sourceObject(); if (!cx->compartment()->wrap(cx, &sourceObject)) return nullptr; } CompileOptions options(cx); options.setMutedErrors(src->mutedErrors()) .setSelfHostingMode(src->selfHosted()) .setNoScriptRval(src->noScriptRval()) .setVersion(src->getVersion()); return JSScript::Create(cx, enclosingScope, src->savedCallerFun(), options, sourceObject, src->sourceStart(), src->sourceEnd()); } JSScript* js::CloneGlobalScript(JSContext* cx, Handle enclosingScope, HandleScript src) { MOZ_ASSERT(IsStaticGlobalLexicalScope(enclosingScope) || enclosingScope->is()); RootedScript dst(cx, CreateEmptyScriptForClone(cx, enclosingScope, src)); if (!dst) return nullptr; if (!detail::CopyScript(cx, enclosingScope, src, dst)) return nullptr; return dst; } JSScript* js::CloneScriptIntoFunction(JSContext* cx, HandleObject enclosingScope, HandleFunction fun, HandleScript src) { MOZ_ASSERT(fun->isInterpreted()); // Allocate the destination script up front and set it as the script of // |fun|, which is to be its container. // // This is so that when cloning nested functions, they can walk the static // scope chain via fun and correctly compute the presence of a // non-syntactic global. RootedScript dst(cx, CreateEmptyScriptForClone(cx, enclosingScope, src)); if (!dst) return nullptr; // Save flags in case we need to undo the early mutations. const int preservedFlags = fun->flags(); dst->setFunction(fun); Rooted lazy(cx); if (fun->isInterpretedLazy()) { lazy = fun->lazyScriptOrNull(); fun->setUnlazifiedScript(dst); } else { fun->initScript(dst); } if (!detail::CopyScript(cx, fun, src, dst)) { if (lazy) fun->initLazyScript(lazy); else fun->setScript(nullptr); fun->setFlags(preservedFlags); return nullptr; } return dst; } DebugScript* JSScript::debugScript() { MOZ_ASSERT(hasDebugScript_); DebugScriptMap* map = compartment()->debugScriptMap; MOZ_ASSERT(map); DebugScriptMap::Ptr p = map->lookup(this); MOZ_ASSERT(p); return p->value(); } DebugScript* JSScript::releaseDebugScript() { MOZ_ASSERT(hasDebugScript_); DebugScriptMap* map = compartment()->debugScriptMap; MOZ_ASSERT(map); DebugScriptMap::Ptr p = map->lookup(this); MOZ_ASSERT(p); DebugScript* debug = p->value(); map->remove(p); hasDebugScript_ = false; return debug; } void JSScript::destroyDebugScript(FreeOp* fop) { if (hasDebugScript_) { #ifdef DEBUG for (jsbytecode* pc = code(); pc < codeEnd(); pc++) { if (BreakpointSite* site = getBreakpointSite(pc)) { /* Breakpoints are swept before finalization. */ MOZ_ASSERT(site->firstBreakpoint() == nullptr); MOZ_ASSERT(getBreakpointSite(pc) == nullptr); } } #endif fop->free_(releaseDebugScript()); } } bool JSScript::ensureHasDebugScript(JSContext* cx) { if (hasDebugScript_) return true; size_t nbytes = offsetof(DebugScript, breakpoints) + length() * sizeof(BreakpointSite*); DebugScript* debug = (DebugScript*) zone()->pod_calloc(nbytes); if (!debug) return false; /* Create compartment's debugScriptMap if necessary. */ DebugScriptMap* map = compartment()->debugScriptMap; if (!map) { map = cx->new_(); if (!map || !map->init()) { js_free(debug); js_delete(map); return false; } compartment()->debugScriptMap = map; } if (!map->putNew(this, debug)) { js_free(debug); return false; } hasDebugScript_ = true; // safe to set this; we can't fail after this point /* * Ensure that any Interpret() instances running on this script have * interrupts enabled. The interrupts must stay enabled until the * debug state is destroyed. */ for (ActivationIterator iter(cx->runtime()); !iter.done(); ++iter) { if (iter->isInterpreter()) iter->asInterpreter()->enableInterruptsIfRunning(this); } return true; } void JSScript::setNewStepMode(FreeOp* fop, uint32_t newValue) { DebugScript* debug = debugScript(); uint32_t prior = debug->stepMode; debug->stepMode = newValue; if (!prior != !newValue) { if (hasBaselineScript()) baseline->toggleDebugTraps(this, nullptr); if (!stepModeEnabled() && !debug->numSites) fop->free_(releaseDebugScript()); } } bool JSScript::incrementStepModeCount(JSContext* cx) { assertSameCompartment(cx, this); MOZ_ASSERT(cx->compartment()->isDebuggee()); if (!ensureHasDebugScript(cx)) return false; DebugScript* debug = debugScript(); uint32_t count = debug->stepMode; setNewStepMode(cx->runtime()->defaultFreeOp(), count + 1); return true; } void JSScript::decrementStepModeCount(FreeOp* fop) { DebugScript* debug = debugScript(); uint32_t count = debug->stepMode; MOZ_ASSERT(count > 0); setNewStepMode(fop, count - 1); } BreakpointSite* JSScript::getOrCreateBreakpointSite(JSContext* cx, jsbytecode* pc) { if (!ensureHasDebugScript(cx)) return nullptr; DebugScript* debug = debugScript(); BreakpointSite*& site = debug->breakpoints[pcToOffset(pc)]; if (!site) { site = cx->runtime()->new_(this, pc); if (!site) { ReportOutOfMemory(cx); return nullptr; } debug->numSites++; } return site; } void JSScript::destroyBreakpointSite(FreeOp* fop, jsbytecode* pc) { DebugScript* debug = debugScript(); BreakpointSite*& site = debug->breakpoints[pcToOffset(pc)]; MOZ_ASSERT(site); fop->delete_(site); site = nullptr; if (--debug->numSites == 0 && !stepModeEnabled()) fop->free_(releaseDebugScript()); } void JSScript::clearBreakpointsIn(FreeOp* fop, js::Debugger* dbg, JSObject* handler) { if (!hasAnyBreakpointsOrStepMode()) return; for (jsbytecode* pc = code(); pc < codeEnd(); pc++) { BreakpointSite* site = getBreakpointSite(pc); if (site) { Breakpoint* nextbp; for (Breakpoint* bp = site->firstBreakpoint(); bp; bp = nextbp) { nextbp = bp->nextInSite(); if ((!dbg || bp->debugger == dbg) && (!handler || bp->getHandler() == handler)) bp->destroy(fop); } } } } bool JSScript::hasBreakpointsAt(jsbytecode* pc) { BreakpointSite* site = getBreakpointSite(pc); if (!site) return false; return site->enabledCount > 0; } void JSScript::traceChildren(JSTracer* trc) { // NOTE: this JSScript may be partially initialized at this point. E.g. we // may have created it and partially initialized it with // JSScript::Create(), but not yet finished initializing it with // fullyInitFromEmitter() or fullyInitTrivial(). MOZ_ASSERT_IF(trc->isMarkingTracer() && static_cast(trc)->shouldCheckCompartments(), zone()->isCollecting()); if (atoms) { for (uint32_t i = 0; i < natoms(); ++i) { if (atoms[i]) TraceEdge(trc, &atoms[i], "atom"); } } if (hasObjects()) { ObjectArray* objarray = objects(); TraceRange(trc, objarray->length, objarray->vector, "objects"); } if (hasRegexps()) { ObjectArray* objarray = regexps(); TraceRange(trc, objarray->length, objarray->vector, "regexps"); } if (hasConsts()) { ConstArray* constarray = consts(); TraceRange(trc, constarray->length, constarray->vector, "consts"); } if (sourceObject()) { MOZ_ASSERT(MaybeForwarded(sourceObject())->compartment() == compartment()); TraceEdge(trc, &sourceObject_, "sourceObject"); } if (functionNonDelazifying()) TraceEdge(trc, &function_, "function"); if (module_) TraceEdge(trc, &module_, "module"); if (enclosingStaticScope_) TraceEdge(trc, &enclosingStaticScope_, "enclosingStaticScope"); if (maybeLazyScript()) TraceManuallyBarrieredEdge(trc, &lazyScript, "lazyScript"); if (trc->isMarkingTracer()) { compartment()->mark(); if (code()) MarkScriptData(trc->runtime(), code()); } bindings.trace(trc); jit::TraceJitScripts(trc, this); } void LazyScript::finalize(FreeOp* fop) { if (table_) fop->free_(table_); } size_t JSScript::calculateLiveFixed(jsbytecode* pc) { size_t nlivefixed = nbodyfixed(); if (nfixed() != nlivefixed) { NestedScopeObject* staticScope = getStaticBlockScope(pc); if (staticScope) staticScope = MaybeForwarded(staticScope); while (staticScope && !staticScope->is()) { staticScope = staticScope->enclosingNestedScope(); if (staticScope) staticScope = MaybeForwarded(staticScope); } if (staticScope && !IsStaticGlobalLexicalScope(staticScope)) { StaticBlockObject& blockObj = staticScope->as(); nlivefixed = blockObj.localOffset() + blockObj.numVariables(); } } MOZ_ASSERT(nlivefixed <= nfixed()); MOZ_ASSERT(nlivefixed >= nbodyfixed()); return nlivefixed; } NestedScopeObject* JSScript::getStaticBlockScope(jsbytecode* pc) { MOZ_ASSERT(containsPC(pc)); if (!hasBlockScopes()) return nullptr; size_t offset = pc - code(); BlockScopeArray* scopes = blockScopes(); NestedScopeObject* blockChain = nullptr; // Find the innermost block chain using a binary search. size_t bottom = 0; size_t top = scopes->length; while (bottom < top) { size_t mid = bottom + (top - bottom) / 2; const BlockScopeNote* note = &scopes->vector[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 BlockScopeNote* checkNote = &scopes->vector[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 == BlockScopeNote::NoBlockScopeIndex) blockChain = nullptr; else blockChain = &getObject(checkNote->index)->as(); break; } if (checkNote->parent == UINT32_MAX) break; check = checkNote->parent; } bottom = mid + 1; } else { top = mid; } } return blockChain; } JSObject* JSScript::innermostStaticScopeInScript(jsbytecode* pc) { if (JSObject* scope = getStaticBlockScope(pc)) return scope; if (module()) return module(); return functionNonDelazifying(); } JSObject* JSScript::innermostStaticScope(jsbytecode* pc) { if (JSObject* scope = innermostStaticScopeInScript(pc)) return scope; return enclosingStaticScope(); } void JSScript::setArgumentsHasVarBinding() { argsHasVarBinding_ = true; needsArgsAnalysis_ = true; } void JSScript::setNeedsArgsObj(bool needsArgsObj) { MOZ_ASSERT_IF(needsArgsObj, argumentsHasVarBinding()); needsArgsAnalysis_ = false; 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. */ BindingIter bi = Bindings::argumentsBinding(cx, script); if (script->bindingIsAliased(bi)) { /* * Scan the script to find the slot in the call object that 'arguments' * is assigned to. */ jsbytecode* pc = script->code(); while (*pc != JSOP_ARGUMENTS) pc += GetBytecodeLength(pc); pc += JSOP_ARGUMENTS_LENGTH; MOZ_ASSERT(*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. if (IsOptimizedPlaceholderMagicValue(frame.callObj().as().aliasedVar(ScopeCoordinate(pc)))) frame.callObj().as().setAliasedVar(cx, ScopeCoordinate(pc), cx->names().arguments, ObjectValue(*argsobj)); } else { if (IsOptimizedPlaceholderMagicValue(frame.unaliasedLocal(bi.frameIndex()))) frame.unaliasedLocal(bi.frameIndex()) = ObjectValue(*argsobj); } } /* static */ bool JSScript::argumentsOptimizationFailed(JSContext* cx, HandleScript script) { MOZ_ASSERT(script->functionNonDelazifying()); MOZ_ASSERT(script->analyzedArgsUsage()); 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 true; MOZ_ASSERT(!script->isGenerator()); script->needsArgsObj_ = true; /* * Since we can't invalidate baseline scripts, set a flag that's checked from * JIT code to indicate the arguments optimization failed and JSOP_ARGUMENTS * should create an arguments object next time. */ if (script->hasBaselineScript()) script->baselineScript()->setNeedsArgsObj(); /* * 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 (AllFramesIter 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); } } return true; } bool JSScript::bindingIsAliased(const BindingIter& bi) { return bindings.bindingIsAliased(bi.i_); } bool JSScript::formalIsAliased(unsigned argSlot) { MOZ_ASSERT(argSlot < bindings.numArgs()); return bindings.bindingIsAliased(argSlot); } bool JSScript::localIsAliased(unsigned localSlot) { return bindings.bindingIsAliased(bindings.numArgs() + localSlot); } bool JSScript::formalLivesInArgumentsObject(unsigned argSlot) { return argsObjAliasesFormals() && !formalIsAliased(argSlot); } LazyScript::LazyScript(JSFunction* fun, void* table, uint64_t packedFields, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column) : script_(nullptr), function_(fun), enclosingScope_(nullptr), sourceObject_(nullptr), table_(table), packedFields_(packedFields), begin_(begin), end_(end), lineno_(lineno), column_(column) { MOZ_ASSERT(begin <= end); } void LazyScript::initScript(JSScript* script) { MOZ_ASSERT(script); MOZ_ASSERT(!script_.unbarrieredGet()); script_.set(script); } void LazyScript::resetScript() { MOZ_ASSERT(script_.unbarrieredGet()); script_.set(nullptr); } void LazyScript::setParent(JSObject* enclosingScope, ScriptSourceObject* sourceObject) { MOZ_ASSERT(!sourceObject_ && !enclosingScope_); MOZ_ASSERT_IF(enclosingScope, function_->compartment() == enclosingScope->compartment()); MOZ_ASSERT(function_->compartment() == sourceObject->compartment()); enclosingScope_ = enclosingScope; sourceObject_ = sourceObject; } ScriptSourceObject* LazyScript::sourceObject() const { return sourceObject_ ? &sourceObject_->as() : nullptr; } ScriptSource* LazyScript::maybeForwardedScriptSource() const { return UncheckedUnwrap(MaybeForwarded(sourceObject()))->as().source(); } /* static */ LazyScript* LazyScript::CreateRaw(ExclusiveContext* cx, HandleFunction fun, uint64_t packedFields, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column) { union { PackedView p; uint64_t packed; }; packed = packedFields; // Reset runtime flags to obtain a fresh LazyScript. p.hasBeenCloned = false; p.treatAsRunOnce = false; size_t bytes = (p.numFreeVariables * sizeof(FreeVariable)) + (p.numInnerFunctions * sizeof(HeapPtrFunction)); ScopedJSFreePtr table(bytes ? fun->zone()->pod_malloc(bytes) : nullptr); if (bytes && !table) { ReportOutOfMemory(cx); return nullptr; } LazyScript* res = Allocate(cx); if (!res) return nullptr; cx->compartment()->scheduleDelazificationForDebugger(); return new (res) LazyScript(fun, table.forget(), packed, begin, end, lineno, column); } /* static */ LazyScript* LazyScript::CreateRaw(ExclusiveContext* cx, HandleFunction fun, uint32_t numFreeVariables, uint32_t numInnerFunctions, JSVersion version, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column) { union { PackedView p; uint64_t packedFields; }; p.version = version; p.numFreeVariables = numFreeVariables; p.numInnerFunctions = numInnerFunctions; p.generatorKindBits = GeneratorKindAsBits(NotGenerator); p.strict = false; p.bindingsAccessedDynamically = false; p.hasDebuggerStatement = false; p.hasDirectEval = false; p.isLikelyConstructorWrapper = false; p.isDerivedClassConstructor = false; p.needsHomeObject = false; LazyScript* res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, lineno, column); MOZ_ASSERT_IF(res, res->version() == version); return res; } /* static */ LazyScript* LazyScript::Create(ExclusiveContext* cx, HandleFunction fun, HandleScript script, HandleObject enclosingScope, HandleScript sourceObjectScript, uint64_t packedFields, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column) { // Dummy atom which is not a valid property name. RootedAtom dummyAtom(cx, cx->names().comma); // Dummy function which is not a valid function as this is the one which is // holding this lazy script. HandleFunction dummyFun = fun; LazyScript* res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, lineno, column); if (!res) return nullptr; // Fill with dummies, to be GC-safe after the initialization of the free // variables and inner functions. size_t i, num; FreeVariable* variables = res->freeVariables(); for (i = 0, num = res->numFreeVariables(); i < num; i++) variables[i] = FreeVariable(dummyAtom); HeapPtrFunction* functions = res->innerFunctions(); for (i = 0, num = res->numInnerFunctions(); i < num; i++) functions[i].init(dummyFun); // Set the enclosing scope of the lazy function, this would later be // used to define the environment when the function would be used. MOZ_ASSERT(!res->sourceObject()); res->setParent(enclosingScope, &sourceObjectScript->scriptSourceUnwrap()); MOZ_ASSERT(!res->hasScript()); if (script) res->initScript(script); return res; } void LazyScript::initRuntimeFields(uint64_t packedFields) { union { PackedView p; uint64_t packed; }; packed = packedFields; p_.hasBeenCloned = p.hasBeenCloned; p_.treatAsRunOnce = p.treatAsRunOnce; } bool LazyScript::hasUncompiledEnclosingScript() const { // It can happen that we created lazy scripts while compiling an enclosing // script, but we errored out while compiling that script. When we iterate // over lazy script in a compartment, we might see lazy scripts that never // escaped to script and should be ignored. // // If the enclosing scope is a function with a null script or has a script // without code, it was not successfully compiled. if (!enclosingScope() || !enclosingScope()->is()) return false; JSFunction& fun = enclosingScope()->as(); return !fun.hasScript() || fun.hasUncompiledScript() || !fun.nonLazyScript()->code(); } void JSScript::updateBaselineOrIonRaw(JSContext* maybecx) { if (hasBaselineScript() && baseline->hasPendingIonBuilder()) { MOZ_ASSERT(maybecx); MOZ_ASSERT(!isIonCompilingOffThread()); baselineOrIonRaw = maybecx->runtime()->jitRuntime()->lazyLinkStub()->raw(); baselineOrIonSkipArgCheck = maybecx->runtime()->jitRuntime()->lazyLinkStub()->raw(); } else if (hasIonScript()) { baselineOrIonRaw = ion->method()->raw(); baselineOrIonSkipArgCheck = ion->method()->raw() + ion->getSkipArgCheckEntryOffset(); } else if (hasBaselineScript()) { baselineOrIonRaw = baseline->method()->raw(); baselineOrIonSkipArgCheck = baseline->method()->raw(); } else { baselineOrIonRaw = nullptr; baselineOrIonSkipArgCheck = nullptr; } } bool JSScript::hasLoops() { if (!hasTrynotes()) return false; JSTryNote* tn = trynotes()->vector; JSTryNote* tnlimit = tn + trynotes()->length; for (; tn < tnlimit; tn++) { if (tn->kind == JSTRY_FOR_IN || tn->kind == JSTRY_LOOP) return true; } return false; } bool JSScript::mayReadFrameArgsDirectly() { return argumentsHasVarBinding() || (function_ && function_->hasRest()); } static inline void LazyScriptHash(uint32_t lineno, uint32_t column, uint32_t begin, uint32_t end, HashNumber hashes[3]) { HashNumber hash = lineno; hash = RotateLeft(hash, 4) ^ column; hash = RotateLeft(hash, 4) ^ begin; hash = RotateLeft(hash, 4) ^ end; hashes[0] = hash; hashes[1] = RotateLeft(hashes[0], 4) ^ begin; hashes[2] = RotateLeft(hashes[1], 4) ^ end; } void LazyScriptHashPolicy::hash(const Lookup& lookup, HashNumber hashes[3]) { LazyScript* lazy = lookup.lazy; LazyScriptHash(lazy->lineno(), lazy->column(), lazy->begin(), lazy->end(), hashes); } void LazyScriptHashPolicy::hash(JSScript* script, HashNumber hashes[3]) { LazyScriptHash(script->lineno(), script->column(), script->sourceStart(), script->sourceEnd(), hashes); } bool LazyScriptHashPolicy::match(JSScript* script, const Lookup& lookup) { JSContext* cx = lookup.cx; LazyScript* lazy = lookup.lazy; // To be a match, the script and lazy script need to have the same line // and column and to be at the same position within their respective // source blobs, and to have the same source contents and version. // // While the surrounding code in the source may differ, this is // sufficient to ensure that compiling the lazy script will yield an // identical result to compiling the original script. // // Note that the filenames and origin principals of the lazy script and // original script can differ. If there is a match, these will be fixed // up in the resulting clone by the caller. if (script->lineno() != lazy->lineno() || script->column() != lazy->column() || script->getVersion() != lazy->version() || script->sourceStart() != lazy->begin() || script->sourceEnd() != lazy->end()) { return false; } UncompressedSourceCache::AutoHoldEntry holder; const char16_t* scriptChars = script->scriptSource()->chars(cx, holder); if (!scriptChars) return false; const char16_t* lazyChars = lazy->scriptSource()->chars(cx, holder); if (!lazyChars) return false; size_t begin = script->sourceStart(); size_t length = script->sourceEnd() - begin; return !memcmp(scriptChars + begin, lazyChars + begin, length); } void JSScript::AutoDelazify::holdScript(JS::HandleFunction fun) { if (fun) { if (fun->compartment()->isSelfHosting) { // The self-hosting compartment is shared across runtimes, so we // can't use JSAutoCompartment: it could cause races. Functions in // the self-hosting compartment will never be lazy, so we can safely // assume we don't have to delazify. script_ = fun->nonLazyScript(); } else { JSAutoCompartment ac(cx_, fun); script_ = fun->getOrCreateScript(cx_); if (script_) { oldDoNotRelazify_ = script_->doNotRelazify_; script_->setDoNotRelazify(true); } } } } void JSScript::AutoDelazify::dropScript() { // Don't touch script_ if it's in the self-hosting compartment, see the // comment in holdScript. if (script_ && !script_->compartment()->isSelfHosting) script_->setDoNotRelazify(oldDoNotRelazify_); script_ = nullptr; } JS::ubi::Node::Size JS::ubi::Concrete::size(mozilla::MallocSizeOf mallocSizeOf) const { Size size = Arena::thingSize(get().asTenured().getAllocKind()); size += get().sizeOfData(mallocSizeOf); size += get().sizeOfTypeScript(mallocSizeOf); size_t baselineSize = 0; size_t baselineStubsSize = 0; jit::AddSizeOfBaselineData(&get(), mallocSizeOf, &baselineSize, &baselineStubsSize); size += baselineSize; size += baselineStubsSize; size += jit::SizeOfIonData(&get(), mallocSizeOf); MOZ_ASSERT(size > 0); return size; } const char* JS::ubi::Concrete::scriptFilename() const { return get().filename(); } JS::ubi::Node::Size JS::ubi::Concrete::size(mozilla::MallocSizeOf mallocSizeOf) const { Size size = js::gc::Arena::thingSize(get().asTenured().getAllocKind()); size += get().sizeOfExcludingThis(mallocSizeOf); return size; } const char* JS::ubi::Concrete::scriptFilename() const { auto sourceObject = get().sourceObject(); if (!sourceObject) return nullptr; auto source = sourceObject->source(); if (!source) return nullptr; return source->filename(); }