| /* -*- 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 <algorithm> |
| #include <string.h> |
| |
| #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<Bindings> 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<uint64_t> 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<PropertyName*> 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<StackShape> 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<uint8_t*>(newBindingArray + count()); |
| } |
| |
| /* static */ bool |
| Bindings::clone(JSContext* cx, MutableHandle<Bindings> self, |
| uint8_t* dstScriptData, HandleScript srcScript) |
| { |
| /* The clone has the same bindingArray_ offset as 'src'. */ |
| Handle<Bindings> src = Handle<Bindings>::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<XDRMode mode> |
| static bool |
| XDRScriptBindings(XDRState<mode>* 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<Binding>(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> 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<XDRMode mode> |
| bool |
| js::XDRScriptConst(XDRState<mode>* 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<XDR_ENCODE>*, MutableHandleValue); |
| |
| template bool |
| js::XDRScriptConst(XDRState<XDR_DECODE>*, MutableHandleValue); |
| |
| // Code LazyScript's free variables. |
| template<XDRMode mode> |
| static bool |
| XDRLazyFreeVariables(XDRState<mode>* xdr, MutableHandle<LazyScript*> 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<XDRMode mode> |
| static bool |
| XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun, HandleScript script, |
| HandleObject enclosingScope, MutableHandle<LazyScript*> 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<JSScript*>, SharedScriptData*, uint32_t); |
| |
| enum XDRClassKind { |
| CK_BlockObject = 0, |
| CK_WithObject = 1, |
| CK_JSFunction = 2, |
| CK_JSObject = 3 |
| }; |
| |
| template<XDRMode mode> |
| bool |
| js::XDRScript(XDRState<mode>* 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_<ScriptSource>(); |
| 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<ScriptSourceObject>()); |
| sourceObject = &enclosingScript->sourceObject()->as<ScriptSourceObject>(); |
| } |
| |
| // 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<mode>(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<BlockObject>()) |
| classk = CK_BlockObject; |
| else if (obj->is<StaticWithObject>()) |
| classk = CK_WithObject; |
| else if (obj->is<JSFunction>()) |
| classk = CK_JSFunction; |
| else if (obj->is<PlainObject>() || obj->is<ArrayObject>()) |
| 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<NestedScopeObject>(); |
| 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<JSObject*> 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<StaticBlockObject*> tmp(cx, static_cast<StaticBlockObject*>(objp->get())); |
| if (!XDRStaticBlockObject(xdr, enclosingStaticScope, &tmp)) |
| return false; |
| *objp = tmp; |
| } else { |
| Rooted<StaticWithObject*> tmp(cx, static_cast<StaticWithObject*>(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<JSFunction>()); |
| |
| 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<NoGC> 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<NoGC>::NonSyntactic || |
| ssi.type() == StaticScopeIter<NoGC>::Function) |
| { |
| MOZ_ASSERT_IF(ssi.done() || ssi.type() != StaticScopeIter<NoGC>::Function, !fun); |
| funEnclosingScopeIndex = UINT32_MAX; |
| } else if (ssi.type() == StaticScopeIter<NoGC>::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<JSFunction>(); |
| 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<RegExpObject*> regexp(cx); |
| if (mode == XDR_ENCODE) |
| regexp = &script->regexps()->vector[i]->as<RegExpObject>(); |
| 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<LazyScript*> 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<XDR_ENCODE>*, HandleObject, HandleScript, HandleFunction, |
| MutableHandleScript); |
| |
| template bool |
| js::XDRScript(XDRState<XDR_DECODE>*, HandleObject, HandleScript, HandleFunction, |
| MutableHandleScript); |
| |
| template<XDRMode mode> |
| bool |
| js::XDRLazyScript(XDRState<mode>* xdr, HandleObject enclosingScope, HandleScript enclosingScript, |
| HandleFunction fun, MutableHandle<LazyScript*> 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<XDR_ENCODE>*, HandleObject, HandleScript, |
| HandleFunction, MutableHandle<LazyScript*>); |
| |
| template bool |
| js::XDRLazyScript(XDRState<XDR_DECODE>*, HandleObject, HandleScript, |
| HandleFunction, MutableHandle<LazyScript*>); |
| |
| void |
| JSScript::setSourceObject(JSObject* object) |
| { |
| MOZ_ASSERT(compartment() == object->compartment()); |
| sourceObject_ = object; |
| } |
| |
| js::ScriptSourceObject& |
| JSScript::scriptSourceUnwrap() const { |
| return UncheckedUnwrap(sourceObject())->as<ScriptSourceObject>(); |
| } |
| |
| js::ScriptSource* |
| JSScript::scriptSource() const { |
| return scriptSourceUnwrap().source(); |
| } |
| |
| js::ScriptSource* |
| JSScript::maybeForwardedScriptSource() const { |
| return UncheckedUnwrap(MaybeForwarded(sourceObject()))->as<ScriptSourceObject>().source(); |
| } |
| |
| bool |
| JSScript::initScriptCounts(JSContext* cx) |
| { |
| MOZ_ASSERT(!hasScriptCounts()); |
| |
| // Record all pc which are the first instruction of a basic block. |
| mozilla::Vector<jsbytecode*, 16, SystemAllocPolicy> jumpTargets; |
| 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_<ScriptCountsMap>(); |
| 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<ScriptSourceObject*>(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<ScriptSourceObject>(); |
| |
| // 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<ScriptSourceObject>()); |
| |
| 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) |
| { |
| assertSameCompartment(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<char16_t*>(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<Map>(); |
| 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<char16_t*>(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<char16_t*>(js_malloc(nbytes)); |
| if (!decompressed) { |
| JS_ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| |
| if (!DecompressString((const unsigned char*) compressedData(), compressedBytes(), |
| reinterpret_cast<unsigned char*>(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<CanGC>(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<CanGC>(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<char16_t*>(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<char16_t>(Max<size_t>(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<const unsigned char*>(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<char16_t*>(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<XDRMode mode> |
| bool |
| ScriptSource::performXDR(XDRState<mode>* 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<uint8_t>(Max<size_t>(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<char16_t>(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<char16_t>(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<char>(len); |
| if (!formatted) { |
| ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| mozilla::DebugOnly<size_t> 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() : "<unknown>"; |
| 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<SharedScriptData*>( |
| cx->zone()->pod_malloc<uint8_t>(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<uintptr_t>(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<JSScript*> 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<JSScript>(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<uint8_t>(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<YieldOffsetArray*>(cursor); |
| cursor += sizeof(YieldOffsetArray); |
| } |
| |
| if (nconsts != 0) { |
| MOZ_ASSERT(reinterpret_cast<uintptr_t>(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<JSTryNote*>(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<BlockScopeNote*>(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<uint32_t*>(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<uint8_t*> |
| (JS_ROUNDUP(reinterpret_cast<uintptr_t>(cursor), JS_ALIGNMENT_OF(Binding))); |
| } |
| cursor = script->bindings.switchToScriptStorage(reinterpret_cast<Binding*>(cursor)); |
| |
| MOZ_ASSERT(cursor == script->data + size); |
| return true; |
| } |
| |
| /* static */ bool |
| JSScript::fullyInitTrivial(ExclusiveContext* cx, Handle<JSScript*> 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<JSScript*> 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<JSScript*> 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<jsbytecode>(code, bce->prologue.code.begin(), prologueLength); |
| PodCopy<jsbytecode>(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 <class T> |
| static inline T* |
| Rebase(JSScript* dst, JSScript* src, T* srcp) |
| { |
| size_t off = reinterpret_cast<uint8_t*>(srcp) - src->data; |
| return reinterpret_cast<T*>(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<uint8_t> data(AllocScriptData(cx->zone(), size)); |
| if (size && !data) { |
| ReportOutOfMemory(cx); |
| return false; |
| } |
| |
| /* Bindings */ |
| |
| Rooted<Bindings> 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<NestedScopeObject>()) { |
| Rooted<NestedScopeObject*> innerBlock(cx, &obj->as<NestedScopeObject>()); |
| |
| RootedObject enclosingScope(cx); |
| if (NestedScopeObject* enclosingBlock = innerBlock->enclosingNestedScope()) { |
| if (IsStaticGlobalLexicalScope(enclosingBlock)) { |
| MOZ_ASSERT(IsStaticGlobalLexicalScope(scriptStaticScope) || |
| scriptStaticScope->is<StaticNonSyntacticScopeObjects>()); |
| enclosingScope = scriptStaticScope; |
| } else { |
| enclosingScope = objects[FindScopeObjectIndex(src, *enclosingBlock)]; |
| } |
| } else { |
| enclosingScope = scriptStaticScope; |
| } |
| |
| clone = CloneNestedScopeObject(cx, enclosingScope, innerBlock); |
| } else if (obj->is<JSFunction>()) { |
| RootedFunction innerFun(cx, &obj->as<JSFunction>()); |
| 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<CanGC> ssi(cx, staticScope); |
| RootedObject enclosingScope(cx); |
| if (ssi.done() || ssi.type() == StaticScopeIter<CanGC>::NonSyntactic) { |
| enclosingScope = scriptStaticScope; |
| } else if (ssi.type() == StaticScopeIter<CanGC>::Function) { |
| MOZ_ASSERT(scriptStaticScope->is<JSFunction>()); |
| enclosingScope = scriptStaticScope; |
| } else if (ssi.type() == StaticScopeIter<CanGC>::Block) { |
| if (ssi.block().isGlobal()) { |
| MOZ_ASSERT(IsStaticGlobalLexicalScope(scriptStaticScope) || |
| scriptStaticScope->is<StaticNonSyntacticScopeObjects>()); |
| 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); |
| for (unsigned i = 0; i < nregexps; i++) { |
| HeapPtrObject* vector = src->regexps()->vector; |
| for (unsigned i = 0; i < nregexps; i++) { |
| JSObject* clone = CloneScriptRegExpObject(cx, vector[i]->as<RegExpObject>()); |
| 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<HeapValue>(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<HeapPtrObject>(dst, src, src->objects()->vector); |
| dst->objects()->vector = vector; |
| for (unsigned i = 0; i < nobjects; ++i) |
| vector[i].init(&objects[i]->as<NativeObject>()); |
| } |
| if (nregexps != 0) { |
| HeapPtrObject* vector = Rebase<HeapPtrObject>(dst, src, src->regexps()->vector); |
| dst->regexps()->vector = vector; |
| for (unsigned i = 0; i < nregexps; ++i) |
| vector[i].init(®exps[i]->as<NativeObject>()); |
| } |
| if (ntrynotes != 0) |
| dst->trynotes()->vector = Rebase<JSTryNote>(dst, src, src->trynotes()->vector); |
| if (nblockscopes != 0) |
| dst->blockScopes()->vector = Rebase<BlockScopeNote>(dst, src, src->blockScopes()->vector); |
| if (nyieldoffsets != 0) |
| dst->yieldOffsets().vector_ = Rebase<uint32_t>(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<ScopeObject*> enclosingScope, HandleScript src) |
| { |
| MOZ_ASSERT(IsStaticGlobalLexicalScope(enclosingScope) || |
| enclosingScope->is<StaticNonSyntacticScopeObjects>()); |
| |
| 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<LazyScript*> 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<uint8_t>(nbytes); |
| if (!debug) |
| return false; |
| |
| /* Create compartment's debugScriptMap if necessary. */ |
| DebugScriptMap* map = compartment()->debugScriptMap; |
| if (!map) { |
| map = cx->new_<DebugScriptMap>(); |
| 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_<BreakpointSite>(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<GCMarker*>(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<StaticBlockObject>()) { |
| staticScope = staticScope->enclosingNestedScope(); |
| if (staticScope) |
| staticScope = MaybeForwarded(staticScope); |
| } |
| |
| if (staticScope && !IsStaticGlobalLexicalScope(staticScope)) { |
| StaticBlockObject& blockObj = staticScope->as<StaticBlockObject>(); |
| 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<NestedScopeObject>(); |
| 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<ScopeObject>().aliasedVar(ScopeCoordinate(pc)))) |
| frame.callObj().as<ScopeObject>().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<ScriptSourceObject>() : nullptr; |
| } |
| |
| ScriptSource* |
| LazyScript::maybeForwardedScriptSource() const |
| { |
| return UncheckedUnwrap(MaybeForwarded(sourceObject()))->as<ScriptSourceObject>().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<uint8_t> table(bytes ? fun->zone()->pod_malloc<uint8_t>(bytes) : nullptr); |
| if (bytes && !table) { |
| ReportOutOfMemory(cx); |
| return nullptr; |
| } |
| |
| LazyScript* res = Allocate<LazyScript>(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<JSFunction>()) |
| return false; |
| |
| JSFunction& fun = enclosingScope()->as<JSFunction>(); |
| 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<JSScript>::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<JSScript>::scriptFilename() const |
| { |
| return get().filename(); |
| } |
| |
| JS::ubi::Node::Size |
| JS::ubi::Concrete<js::LazyScript>::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<js::LazyScript>::scriptFilename() const |
| { |
| auto sourceObject = get().sourceObject(); |
| if (!sourceObject) |
| return nullptr; |
| |
| auto source = sourceObject->source(); |
| if (!source) |
| return nullptr; |
| |
| return source->filename(); |
| } |