michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * JS script operations. michael@0: */ michael@0: michael@0: #include "jsscriptinlines.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jsfun.h" michael@0: #include "jsgc.h" michael@0: #include "jsobj.h" michael@0: #include "jsopcode.h" michael@0: #include "jsprf.h" michael@0: #include "jstypes.h" michael@0: #include "jsutil.h" michael@0: #include "jswrapper.h" michael@0: michael@0: #include "frontend/BytecodeCompiler.h" michael@0: #include "frontend/BytecodeEmitter.h" michael@0: #include "frontend/SharedContext.h" michael@0: #include "gc/Marking.h" michael@0: #include "jit/BaselineJIT.h" michael@0: #include "jit/IonCode.h" michael@0: #include "js/MemoryMetrics.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "js/Utility.h" michael@0: #include "vm/ArgumentsObject.h" michael@0: #include "vm/Compression.h" michael@0: #include "vm/Debugger.h" michael@0: #include "vm/Opcodes.h" michael@0: #include "vm/SelfHosting.h" michael@0: #include "vm/Shape.h" michael@0: #include "vm/Xdr.h" michael@0: michael@0: #include "jsfuninlines.h" michael@0: #include "jsinferinlines.h" michael@0: #include "jsobjinlines.h" michael@0: michael@0: #include "vm/ScopeObject-inl.h" michael@0: #include "vm/Stack-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using namespace js::frontend; michael@0: michael@0: using mozilla::PodCopy; michael@0: using mozilla::PodZero; michael@0: using mozilla::RotateLeft; michael@0: michael@0: typedef Rooted RootedGlobalObject; michael@0: michael@0: /* static */ uint32_t michael@0: Bindings::argumentsVarIndex(ExclusiveContext *cx, InternalBindingsHandle bindings) michael@0: { michael@0: HandlePropertyName arguments = cx->names().arguments; michael@0: BindingIter bi(bindings); michael@0: while (bi->name() != arguments) michael@0: bi++; michael@0: return bi.frameIndex(); michael@0: } michael@0: michael@0: bool michael@0: Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self, michael@0: unsigned numArgs, uint32_t numVars, michael@0: Binding *bindingArray, uint32_t numBlockScoped) michael@0: { michael@0: JS_ASSERT(!self->callObjShape_); michael@0: JS_ASSERT(self->bindingArrayAndFlag_ == TEMPORARY_STORAGE_BIT); michael@0: JS_ASSERT(!(uintptr_t(bindingArray) & TEMPORARY_STORAGE_BIT)); michael@0: JS_ASSERT(numArgs <= ARGC_LIMIT); michael@0: JS_ASSERT(numVars <= LOCALNO_LIMIT); michael@0: JS_ASSERT(numBlockScoped <= LOCALNO_LIMIT); michael@0: JS_ASSERT(numVars <= LOCALNO_LIMIT - numBlockScoped); michael@0: JS_ASSERT(UINT32_MAX - numArgs >= numVars + numBlockScoped); michael@0: michael@0: self->bindingArrayAndFlag_ = uintptr_t(bindingArray) | TEMPORARY_STORAGE_BIT; michael@0: self->numArgs_ = numArgs; michael@0: self->numVars_ = numVars; michael@0: self->numBlockScoped_ = numBlockScoped; michael@0: michael@0: // Get the initial shape to use when creating CallObjects for this script. michael@0: // After creation, a CallObject's shape may change completely (via direct eval() or michael@0: // other operations that mutate the lexical scope). However, since the michael@0: // lexical bindings added to the initial shape are permanent and the michael@0: // allocKind/nfixed of a CallObject cannot change, one may assume that the michael@0: // slot location (whether in the fixed or dynamic slots) of a variable is michael@0: // the same as in the initial shape. (This is assumed by the interpreter and michael@0: // JITs when interpreting/compiling aliasedvar ops.) michael@0: michael@0: // Since unaliased variables are, by definition, only accessed by local michael@0: // operations and never through the scope chain, only give shapes to michael@0: // aliased variables. While the debugger may observe any scope object at michael@0: // any time, such accesses are mediated by DebugScopeProxy (see michael@0: // DebugScopeProxy::handleUnaliasedAccess). michael@0: uint32_t nslots = CallObject::RESERVED_SLOTS; michael@0: for (BindingIter bi(self); bi; bi++) { michael@0: if (bi->aliased()) michael@0: nslots++; michael@0: } michael@0: michael@0: // Put as many of nslots inline into the object header as possible. michael@0: uint32_t nfixed = gc::GetGCKindSlots(gc::GetGCObjectKind(nslots)); michael@0: michael@0: // Start with the empty shape and then append one shape per aliased binding. michael@0: RootedShape shape(cx, michael@0: EmptyShape::getInitialShape(cx, &CallObject::class_, nullptr, nullptr, nullptr, michael@0: nfixed, BaseShape::VAROBJ | BaseShape::DELEGATE)); michael@0: if (!shape) michael@0: return false; michael@0: michael@0: #ifdef DEBUG michael@0: HashSet added(cx); michael@0: if (!added.init()) michael@0: return false; michael@0: #endif michael@0: michael@0: uint32_t slot = CallObject::RESERVED_SLOTS; michael@0: for (BindingIter bi(self); bi; bi++) { michael@0: if (!bi->aliased()) michael@0: continue; michael@0: michael@0: #ifdef DEBUG michael@0: // The caller ensures no duplicate aliased names. michael@0: JS_ASSERT(!added.has(bi->name())); michael@0: if (!added.put(bi->name())) michael@0: return false; michael@0: #endif michael@0: michael@0: StackBaseShape stackBase(cx, &CallObject::class_, nullptr, nullptr, michael@0: BaseShape::VAROBJ | BaseShape::DELEGATE); michael@0: michael@0: UnownedBaseShape *base = BaseShape::getUnowned(cx, stackBase); michael@0: if (!base) michael@0: return false; michael@0: michael@0: unsigned attrs = JSPROP_PERMANENT | michael@0: JSPROP_ENUMERATE | michael@0: (bi->kind() == Binding::CONSTANT ? JSPROP_READONLY : 0); michael@0: StackShape child(base, NameToId(bi->name()), slot, attrs, 0); michael@0: michael@0: shape = cx->compartment()->propertyTree.getChild(cx, shape, child); michael@0: if (!shape) michael@0: return false; michael@0: michael@0: JS_ASSERT(slot < nslots); michael@0: slot++; michael@0: } michael@0: JS_ASSERT(slot == nslots); michael@0: michael@0: JS_ASSERT(!shape->inDictionary()); michael@0: self->callObjShape_.init(shape); michael@0: return true; michael@0: } michael@0: michael@0: uint8_t * michael@0: Bindings::switchToScriptStorage(Binding *newBindingArray) michael@0: { michael@0: JS_ASSERT(bindingArrayUsingTemporaryStorage()); michael@0: JS_ASSERT(!(uintptr_t(newBindingArray) & TEMPORARY_STORAGE_BIT)); michael@0: michael@0: if (count() > 0) michael@0: PodCopy(newBindingArray, bindingArray(), count()); michael@0: bindingArrayAndFlag_ = uintptr_t(newBindingArray); michael@0: return reinterpret_cast(newBindingArray + count()); michael@0: } michael@0: michael@0: bool michael@0: Bindings::clone(JSContext *cx, InternalBindingsHandle self, michael@0: uint8_t *dstScriptData, HandleScript srcScript) michael@0: { michael@0: /* The clone has the same bindingArray_ offset as 'src'. */ michael@0: Bindings &src = srcScript->bindings; michael@0: ptrdiff_t off = (uint8_t *)src.bindingArray() - srcScript->data; michael@0: JS_ASSERT(off >= 0); michael@0: JS_ASSERT(size_t(off) <= srcScript->dataSize()); michael@0: Binding *dstPackedBindings = (Binding *)(dstScriptData + off); michael@0: michael@0: /* michael@0: * Since atoms are shareable throughout the runtime, we can simply copy michael@0: * the source's bindingArray directly. michael@0: */ michael@0: if (!initWithTemporaryStorage(cx, self, src.numArgs(), src.numVars(), src.bindingArray(), michael@0: src.numBlockScoped())) michael@0: return false; michael@0: self->switchToScriptStorage(dstPackedBindings); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ Bindings michael@0: GCMethods::initial() michael@0: { michael@0: return Bindings(); michael@0: } michael@0: michael@0: template michael@0: static bool michael@0: XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, uint32_t numVars, michael@0: HandleScript script, unsigned numBlockScoped) michael@0: { michael@0: JSContext *cx = xdr->cx(); michael@0: michael@0: if (mode == XDR_ENCODE) { michael@0: for (BindingIter bi(script); bi; bi++) { michael@0: RootedAtom atom(cx, bi->name()); michael@0: if (!XDRAtom(xdr, &atom)) michael@0: return false; michael@0: } michael@0: michael@0: for (BindingIter bi(script); bi; bi++) { michael@0: uint8_t u8 = (uint8_t(bi->kind()) << 1) | uint8_t(bi->aliased()); michael@0: if (!xdr->codeUint8(&u8)) michael@0: return false; michael@0: } michael@0: } else { michael@0: uint32_t nameCount = numArgs + numVars; michael@0: michael@0: AutoValueVector atoms(cx); michael@0: if (!atoms.resize(nameCount)) michael@0: return false; michael@0: for (uint32_t i = 0; i < nameCount; i++) { michael@0: RootedAtom atom(cx); michael@0: if (!XDRAtom(xdr, &atom)) michael@0: return false; michael@0: atoms[i] = StringValue(atom); michael@0: } michael@0: michael@0: Binding *bindingArray = las.alloc().newArrayUninitialized(nameCount); michael@0: if (!bindingArray) michael@0: return false; michael@0: for (uint32_t i = 0; i < nameCount; i++) { michael@0: uint8_t u8; michael@0: if (!xdr->codeUint8(&u8)) michael@0: return false; michael@0: michael@0: PropertyName *name = atoms[i].toString()->asAtom().asPropertyName(); michael@0: Binding::Kind kind = Binding::Kind(u8 >> 1); michael@0: bool aliased = bool(u8 & 1); michael@0: michael@0: bindingArray[i] = Binding(name, kind, aliased); michael@0: } michael@0: michael@0: InternalBindingsHandle bindings(script, &script->bindings); michael@0: if (!Bindings::initWithTemporaryStorage(cx, bindings, numArgs, numVars, bindingArray, michael@0: numBlockScoped)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: Bindings::bindingIsAliased(uint32_t bindingIndex) michael@0: { michael@0: JS_ASSERT(bindingIndex < count()); michael@0: return bindingArray()[bindingIndex].aliased(); michael@0: } michael@0: michael@0: void michael@0: Bindings::trace(JSTracer *trc) michael@0: { michael@0: if (callObjShape_) michael@0: MarkShape(trc, &callObjShape_, "callObjShape"); michael@0: michael@0: /* michael@0: * As the comment in Bindings explains, bindingsArray may point into freed michael@0: * storage when bindingArrayUsingTemporaryStorage so we don't mark it. michael@0: * Note: during compilation, atoms are already kept alive by gcKeepAtoms. michael@0: */ michael@0: if (bindingArrayUsingTemporaryStorage()) michael@0: return; michael@0: michael@0: for (Binding *b = bindingArray(), *end = b + count(); b != end; b++) { michael@0: PropertyName *name = b->name(); michael@0: MarkStringUnbarriered(trc, &name, "bindingArray"); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: js::FillBindingVector(HandleScript fromScript, BindingVector *vec) michael@0: { michael@0: for (BindingIter bi(fromScript); bi; bi++) { michael@0: if (!vec->append(*bi)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: js::XDRScriptConst(XDRState *xdr, MutableHandleValue vp) michael@0: { michael@0: JSContext *cx = xdr->cx(); michael@0: michael@0: /* michael@0: * A script constant can be an arbitrary primitive value as they are used michael@0: * to implement JSOP_LOOKUPSWITCH. But they cannot be objects, see michael@0: * bug 407186. michael@0: */ michael@0: enum ConstTag { michael@0: SCRIPT_INT = 0, michael@0: SCRIPT_DOUBLE = 1, michael@0: SCRIPT_ATOM = 2, michael@0: SCRIPT_TRUE = 3, michael@0: SCRIPT_FALSE = 4, michael@0: SCRIPT_NULL = 5, michael@0: SCRIPT_OBJECT = 6, michael@0: SCRIPT_VOID = 7, michael@0: SCRIPT_HOLE = 8 michael@0: }; michael@0: michael@0: uint32_t tag; michael@0: if (mode == XDR_ENCODE) { michael@0: if (vp.isInt32()) { michael@0: tag = SCRIPT_INT; michael@0: } else if (vp.isDouble()) { michael@0: tag = SCRIPT_DOUBLE; michael@0: } else if (vp.isString()) { michael@0: tag = SCRIPT_ATOM; michael@0: } else if (vp.isTrue()) { michael@0: tag = SCRIPT_TRUE; michael@0: } else if (vp.isFalse()) { michael@0: tag = SCRIPT_FALSE; michael@0: } else if (vp.isNull()) { michael@0: tag = SCRIPT_NULL; michael@0: } else if (vp.isObject()) { michael@0: tag = SCRIPT_OBJECT; michael@0: } else if (vp.isMagic(JS_ELEMENTS_HOLE)) { michael@0: tag = SCRIPT_HOLE; michael@0: } else { michael@0: JS_ASSERT(vp.isUndefined()); michael@0: tag = SCRIPT_VOID; michael@0: } michael@0: } michael@0: michael@0: if (!xdr->codeUint32(&tag)) michael@0: return false; michael@0: michael@0: switch (tag) { michael@0: case SCRIPT_INT: { michael@0: uint32_t i; michael@0: if (mode == XDR_ENCODE) michael@0: i = uint32_t(vp.toInt32()); michael@0: if (!xdr->codeUint32(&i)) michael@0: return false; michael@0: if (mode == XDR_DECODE) michael@0: vp.set(Int32Value(int32_t(i))); michael@0: break; michael@0: } michael@0: case SCRIPT_DOUBLE: { michael@0: double d; michael@0: if (mode == XDR_ENCODE) michael@0: d = vp.toDouble(); michael@0: if (!xdr->codeDouble(&d)) michael@0: return false; michael@0: if (mode == XDR_DECODE) michael@0: vp.set(DoubleValue(d)); michael@0: break; michael@0: } michael@0: case SCRIPT_ATOM: { michael@0: RootedAtom atom(cx); michael@0: if (mode == XDR_ENCODE) michael@0: atom = &vp.toString()->asAtom(); michael@0: if (!XDRAtom(xdr, &atom)) michael@0: return false; michael@0: if (mode == XDR_DECODE) michael@0: vp.set(StringValue(atom)); michael@0: break; michael@0: } michael@0: case SCRIPT_TRUE: michael@0: if (mode == XDR_DECODE) michael@0: vp.set(BooleanValue(true)); michael@0: break; michael@0: case SCRIPT_FALSE: michael@0: if (mode == XDR_DECODE) michael@0: vp.set(BooleanValue(false)); michael@0: break; michael@0: case SCRIPT_NULL: michael@0: if (mode == XDR_DECODE) michael@0: vp.set(NullValue()); michael@0: break; michael@0: case SCRIPT_OBJECT: { michael@0: RootedObject obj(cx); michael@0: if (mode == XDR_ENCODE) michael@0: obj = &vp.toObject(); michael@0: michael@0: if (!XDRObjectLiteral(xdr, &obj)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) michael@0: vp.setObject(*obj); michael@0: break; michael@0: } michael@0: case SCRIPT_VOID: michael@0: if (mode == XDR_DECODE) michael@0: vp.set(UndefinedValue()); michael@0: break; michael@0: case SCRIPT_HOLE: michael@0: if (mode == XDR_DECODE) michael@0: vp.setMagic(JS_ELEMENTS_HOLE); michael@0: break; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template bool michael@0: js::XDRScriptConst(XDRState *, MutableHandleValue); michael@0: michael@0: template bool michael@0: js::XDRScriptConst(XDRState *, MutableHandleValue); michael@0: michael@0: // Code LazyScript's free variables. michael@0: template michael@0: static bool michael@0: XDRLazyFreeVariables(XDRState *xdr, MutableHandle lazy) michael@0: { michael@0: JSContext *cx = xdr->cx(); michael@0: RootedAtom atom(cx); michael@0: HeapPtrAtom *freeVariables = lazy->freeVariables(); michael@0: size_t numFreeVariables = lazy->numFreeVariables(); michael@0: for (size_t i = 0; i < numFreeVariables; i++) { michael@0: if (mode == XDR_ENCODE) michael@0: atom = freeVariables[i]; michael@0: michael@0: if (!XDRAtom(xdr, &atom)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) michael@0: freeVariables[i] = atom; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Code the missing part needed to re-create a LazyScript from a JSScript. michael@0: template michael@0: static bool michael@0: XDRRelazificationInfo(XDRState *xdr, HandleFunction fun, HandleScript script, michael@0: MutableHandle lazy) michael@0: { michael@0: MOZ_ASSERT_IF(mode == XDR_ENCODE, script->isRelazifiable() && script->maybeLazyScript()); michael@0: MOZ_ASSERT_IF(mode == XDR_ENCODE, !lazy->numInnerFunctions()); michael@0: michael@0: JSContext *cx = xdr->cx(); michael@0: michael@0: uint64_t packedFields; michael@0: { michael@0: uint32_t begin = script->sourceStart(); michael@0: uint32_t end = script->sourceEnd(); michael@0: uint32_t lineno = script->lineno(); michael@0: uint32_t column = script->column(); michael@0: michael@0: if (mode == XDR_ENCODE) { michael@0: packedFields = lazy->packedFields(); michael@0: MOZ_ASSERT(begin == lazy->begin()); michael@0: MOZ_ASSERT(end == lazy->end()); michael@0: MOZ_ASSERT(lineno == lazy->lineno()); michael@0: MOZ_ASSERT(column == lazy->column()); michael@0: } michael@0: michael@0: if (!xdr->codeUint64(&packedFields)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: lazy.set(LazyScript::Create(cx, fun, packedFields, begin, end, lineno, column)); michael@0: michael@0: // As opposed to XDRLazyScript, we need to restore the runtime bits michael@0: // of the script, as we are trying to match the fact this function michael@0: // has already been parsed and that it would need to be re-lazified. michael@0: lazy->initRuntimeFields(packedFields); michael@0: } michael@0: } michael@0: michael@0: // Code free variables. michael@0: if (!XDRLazyFreeVariables(xdr, lazy)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static inline uint32_t michael@0: FindScopeObjectIndex(JSScript *script, NestedScopeObject &scope) michael@0: { michael@0: ObjectArray *objects = script->objects(); michael@0: HeapPtrObject *vector = objects->vector; michael@0: unsigned length = objects->length; michael@0: for (unsigned i = 0; i < length; ++i) { michael@0: if (vector[i] == &scope) michael@0: return i; michael@0: } michael@0: michael@0: MOZ_ASSUME_UNREACHABLE("Scope not found"); michael@0: } michael@0: michael@0: static bool michael@0: SaveSharedScriptData(ExclusiveContext *, Handle, SharedScriptData *, uint32_t); michael@0: michael@0: enum XDRClassKind { michael@0: CK_BlockObject = 0, michael@0: CK_WithObject = 1, michael@0: CK_JSFunction = 2, michael@0: CK_JSObject = 3 michael@0: }; michael@0: michael@0: template michael@0: bool michael@0: js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enclosingScript, michael@0: HandleFunction fun, MutableHandleScript scriptp) michael@0: { michael@0: /* NB: Keep this in sync with CloneScript. */ michael@0: michael@0: enum ScriptBits { michael@0: NoScriptRval, michael@0: SavedCallerFun, michael@0: Strict, michael@0: ContainsDynamicNameAccess, michael@0: FunHasExtensibleScope, michael@0: FunNeedsDeclEnvObject, michael@0: FunHasAnyAliasedFormal, michael@0: ArgumentsHasVarBinding, michael@0: NeedsArgsObj, michael@0: IsGeneratorExp, michael@0: IsLegacyGenerator, michael@0: IsStarGenerator, michael@0: OwnSource, michael@0: ExplicitUseStrict, michael@0: SelfHosted, michael@0: IsCompileAndGo, michael@0: HasSingleton, michael@0: TreatAsRunOnce, michael@0: HasLazyScript michael@0: }; michael@0: michael@0: uint32_t length, lineno, column, nslots, staticLevel; michael@0: uint32_t natoms, nsrcnotes, i; michael@0: uint32_t nconsts, nobjects, nregexps, ntrynotes, nblockscopes; michael@0: uint32_t prologLength, version; michael@0: uint32_t funLength = 0; michael@0: uint32_t nTypeSets = 0; michael@0: uint32_t scriptBits = 0; michael@0: michael@0: JSContext *cx = xdr->cx(); michael@0: RootedScript script(cx); michael@0: natoms = nsrcnotes = 0; michael@0: nconsts = nobjects = nregexps = ntrynotes = nblockscopes = 0; michael@0: michael@0: /* XDR arguments and vars. */ michael@0: uint16_t nargs = 0; michael@0: uint16_t nblocklocals = 0; michael@0: uint32_t nvars = 0; michael@0: if (mode == XDR_ENCODE) { michael@0: script = scriptp.get(); michael@0: JS_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment()); michael@0: michael@0: nargs = script->bindings.numArgs(); michael@0: nblocklocals = script->bindings.numBlockScoped(); michael@0: nvars = script->bindings.numVars(); michael@0: } michael@0: if (!xdr->codeUint16(&nargs)) michael@0: return false; michael@0: if (!xdr->codeUint16(&nblocklocals)) michael@0: return false; michael@0: if (!xdr->codeUint32(&nvars)) michael@0: return false; michael@0: michael@0: if (mode == XDR_ENCODE) michael@0: length = script->length(); michael@0: if (!xdr->codeUint32(&length)) michael@0: return false; michael@0: michael@0: if (mode == XDR_ENCODE) { michael@0: prologLength = script->mainOffset(); michael@0: JS_ASSERT(script->getVersion() != JSVERSION_UNKNOWN); michael@0: version = script->getVersion(); michael@0: lineno = script->lineno(); michael@0: column = script->column(); michael@0: nslots = script->nslots(); michael@0: staticLevel = script->staticLevel(); michael@0: natoms = script->natoms(); michael@0: michael@0: nsrcnotes = script->numNotes(); michael@0: michael@0: if (script->hasConsts()) michael@0: nconsts = script->consts()->length; michael@0: if (script->hasObjects()) michael@0: nobjects = script->objects()->length; michael@0: if (script->hasRegexps()) michael@0: nregexps = script->regexps()->length; michael@0: if (script->hasTrynotes()) michael@0: ntrynotes = script->trynotes()->length; michael@0: if (script->hasBlockScopes()) michael@0: nblockscopes = script->blockScopes()->length; michael@0: michael@0: nTypeSets = script->nTypeSets(); michael@0: funLength = script->funLength(); michael@0: michael@0: if (script->noScriptRval()) michael@0: scriptBits |= (1 << NoScriptRval); michael@0: if (script->savedCallerFun()) michael@0: scriptBits |= (1 << SavedCallerFun); michael@0: if (script->strict()) michael@0: scriptBits |= (1 << Strict); michael@0: if (script->explicitUseStrict()) michael@0: scriptBits |= (1 << ExplicitUseStrict); michael@0: if (script->selfHosted()) michael@0: scriptBits |= (1 << SelfHosted); michael@0: if (script->bindingsAccessedDynamically()) michael@0: scriptBits |= (1 << ContainsDynamicNameAccess); michael@0: if (script->funHasExtensibleScope()) michael@0: scriptBits |= (1 << FunHasExtensibleScope); michael@0: if (script->funNeedsDeclEnvObject()) michael@0: scriptBits |= (1 << FunNeedsDeclEnvObject); michael@0: if (script->funHasAnyAliasedFormal()) michael@0: scriptBits |= (1 << FunHasAnyAliasedFormal); michael@0: if (script->argumentsHasVarBinding()) michael@0: scriptBits |= (1 << ArgumentsHasVarBinding); michael@0: if (script->analyzedArgsUsage() && script->needsArgsObj()) michael@0: scriptBits |= (1 << NeedsArgsObj); michael@0: if (!enclosingScript || enclosingScript->scriptSource() != script->scriptSource()) michael@0: scriptBits |= (1 << OwnSource); michael@0: if (script->isGeneratorExp()) michael@0: scriptBits |= (1 << IsGeneratorExp); michael@0: if (script->isLegacyGenerator()) michael@0: scriptBits |= (1 << IsLegacyGenerator); michael@0: if (script->isStarGenerator()) michael@0: scriptBits |= (1 << IsStarGenerator); michael@0: if (script->compileAndGo()) michael@0: scriptBits |= (1 << IsCompileAndGo); michael@0: if (script->hasSingletons()) michael@0: scriptBits |= (1 << HasSingleton); michael@0: if (script->treatAsRunOnce()) michael@0: scriptBits |= (1 << TreatAsRunOnce); michael@0: if (script->isRelazifiable()) michael@0: scriptBits |= (1 << HasLazyScript); michael@0: } michael@0: michael@0: if (!xdr->codeUint32(&prologLength)) michael@0: return false; michael@0: if (!xdr->codeUint32(&version)) michael@0: return false; michael@0: michael@0: // To fuse allocations, we need lengths of all embedded arrays early. michael@0: if (!xdr->codeUint32(&natoms)) michael@0: return false; michael@0: if (!xdr->codeUint32(&nsrcnotes)) michael@0: return false; michael@0: if (!xdr->codeUint32(&nconsts)) michael@0: return false; michael@0: if (!xdr->codeUint32(&nobjects)) michael@0: return false; michael@0: if (!xdr->codeUint32(&nregexps)) michael@0: return false; michael@0: if (!xdr->codeUint32(&ntrynotes)) michael@0: return false; michael@0: if (!xdr->codeUint32(&nblockscopes)) michael@0: return false; michael@0: if (!xdr->codeUint32(&nTypeSets)) michael@0: return false; michael@0: if (!xdr->codeUint32(&funLength)) michael@0: return false; michael@0: if (!xdr->codeUint32(&scriptBits)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: JSVersion version_ = JSVersion(version); michael@0: JS_ASSERT((version_ & VersionFlags::MASK) == unsigned(version_)); michael@0: michael@0: // staticLevel is set below. michael@0: CompileOptions options(cx); michael@0: options.setVersion(version_) michael@0: .setNoScriptRval(!!(scriptBits & (1 << NoScriptRval))) michael@0: .setSelfHostingMode(!!(scriptBits & (1 << SelfHosted))); michael@0: RootedScriptSource sourceObject(cx); michael@0: if (scriptBits & (1 << OwnSource)) { michael@0: ScriptSource *ss = cx->new_(); michael@0: if (!ss) michael@0: return false; michael@0: ScriptSourceHolder ssHolder(ss); michael@0: michael@0: /* michael@0: * We use this CompileOptions only to initialize the michael@0: * ScriptSourceObject. Most CompileOptions fields aren't used by michael@0: * ScriptSourceObject, and those that are (element; elementAttributeName) michael@0: * aren't preserved by XDR. So this can be simple. michael@0: */ michael@0: CompileOptions options(cx); michael@0: options.setOriginPrincipals(xdr->originPrincipals()); michael@0: ss->initFromOptions(cx, options); michael@0: sourceObject = ScriptSourceObject::create(cx, ss, options); michael@0: if (!sourceObject) michael@0: return false; michael@0: } else { michael@0: JS_ASSERT(enclosingScript); michael@0: // When decoding, all the scripts and the script source object michael@0: // are in the same compartment, so the script's source object michael@0: // should never be a cross-compartment wrapper. michael@0: JS_ASSERT(enclosingScript->sourceObject()->is()); michael@0: sourceObject = &enclosingScript->sourceObject()->as(); michael@0: } michael@0: script = JSScript::Create(cx, enclosingScope, !!(scriptBits & (1 << SavedCallerFun)), michael@0: options, /* staticLevel = */ 0, sourceObject, 0, 0); michael@0: if (!script) michael@0: return false; michael@0: } michael@0: michael@0: /* JSScript::partiallyInit assumes script->bindings is fully initialized. */ michael@0: LifoAllocScope las(&cx->tempLifoAlloc()); michael@0: if (!XDRScriptBindings(xdr, las, nargs, nvars, script, nblocklocals)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: if (!JSScript::partiallyInit(cx, script, nconsts, nobjects, nregexps, ntrynotes, michael@0: nblockscopes, nTypeSets)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: JS_ASSERT(!script->mainOffset()); michael@0: script->mainOffset_ = prologLength; michael@0: script->setLength(length); michael@0: script->funLength_ = funLength; michael@0: michael@0: scriptp.set(script); michael@0: michael@0: if (scriptBits & (1 << Strict)) michael@0: script->strict_ = true; michael@0: if (scriptBits & (1 << ExplicitUseStrict)) michael@0: script->explicitUseStrict_ = true; michael@0: if (scriptBits & (1 << ContainsDynamicNameAccess)) michael@0: script->bindingsAccessedDynamically_ = true; michael@0: if (scriptBits & (1 << FunHasExtensibleScope)) michael@0: script->funHasExtensibleScope_ = true; michael@0: if (scriptBits & (1 << FunNeedsDeclEnvObject)) michael@0: script->funNeedsDeclEnvObject_ = true; michael@0: if (scriptBits & (1 << FunHasAnyAliasedFormal)) michael@0: script->funHasAnyAliasedFormal_ = true; michael@0: if (scriptBits & (1 << ArgumentsHasVarBinding)) michael@0: script->setArgumentsHasVarBinding(); michael@0: if (scriptBits & (1 << NeedsArgsObj)) michael@0: script->setNeedsArgsObj(true); michael@0: if (scriptBits & (1 << IsGeneratorExp)) michael@0: script->isGeneratorExp_ = true; michael@0: if (scriptBits & (1 << IsCompileAndGo)) michael@0: script->compileAndGo_ = true; michael@0: if (scriptBits & (1 << HasSingleton)) michael@0: script->hasSingletons_ = true; michael@0: if (scriptBits & (1 << TreatAsRunOnce)) michael@0: script->treatAsRunOnce_ = true; michael@0: michael@0: if (scriptBits & (1 << IsLegacyGenerator)) { michael@0: JS_ASSERT(!(scriptBits & (1 << IsStarGenerator))); michael@0: script->setGeneratorKind(LegacyGenerator); michael@0: } else if (scriptBits & (1 << IsStarGenerator)) michael@0: script->setGeneratorKind(StarGenerator); michael@0: } michael@0: michael@0: JS_STATIC_ASSERT(sizeof(jsbytecode) == 1); michael@0: JS_STATIC_ASSERT(sizeof(jssrcnote) == 1); michael@0: michael@0: if (scriptBits & (1 << OwnSource)) { michael@0: if (!script->scriptSource()->performXDR(xdr)) michael@0: return false; michael@0: } michael@0: if (!xdr->codeUint32(&script->sourceStart_)) michael@0: return false; michael@0: if (!xdr->codeUint32(&script->sourceEnd_)) michael@0: return false; michael@0: michael@0: if (!xdr->codeUint32(&lineno) || michael@0: !xdr->codeUint32(&column) || michael@0: !xdr->codeUint32(&nslots) || michael@0: !xdr->codeUint32(&staticLevel)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: script->lineno_ = lineno; michael@0: script->column_ = column; michael@0: script->nslots_ = nslots; michael@0: script->staticLevel_ = staticLevel; michael@0: } michael@0: michael@0: jsbytecode *code = script->code(); michael@0: SharedScriptData *ssd; michael@0: if (mode == XDR_DECODE) { michael@0: ssd = SharedScriptData::new_(cx, length, nsrcnotes, natoms); michael@0: if (!ssd) michael@0: return false; michael@0: code = ssd->data; michael@0: if (natoms != 0) { michael@0: script->natoms_ = natoms; michael@0: script->atoms = ssd->atoms(); michael@0: } michael@0: } michael@0: michael@0: if (!xdr->codeBytes(code, length) || !xdr->codeBytes(code + length, nsrcnotes)) { michael@0: if (mode == XDR_DECODE) michael@0: js_free(ssd); michael@0: return false; michael@0: } michael@0: michael@0: for (i = 0; i != natoms; ++i) { michael@0: if (mode == XDR_DECODE) { michael@0: RootedAtom tmp(cx); michael@0: if (!XDRAtom(xdr, &tmp)) michael@0: return false; michael@0: script->atoms[i].init(tmp); michael@0: } else { michael@0: RootedAtom tmp(cx, script->atoms[i]); michael@0: if (!XDRAtom(xdr, &tmp)) michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: if (!SaveSharedScriptData(cx, script, ssd, nsrcnotes)) michael@0: return false; michael@0: } michael@0: michael@0: if (nconsts) { michael@0: HeapValue *vector = script->consts()->vector; michael@0: RootedValue val(cx); michael@0: for (i = 0; i != nconsts; ++i) { michael@0: if (mode == XDR_ENCODE) michael@0: val = vector[i]; michael@0: if (!XDRScriptConst(xdr, &val)) michael@0: return false; michael@0: if (mode == XDR_DECODE) michael@0: vector[i].init(val); michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Here looping from 0-to-length to xdr objects is essential to ensure that michael@0: * all references to enclosing blocks (via FindScopeObjectIndex below) happen michael@0: * after the enclosing block has been XDR'd. michael@0: */ michael@0: for (i = 0; i != nobjects; ++i) { michael@0: HeapPtr *objp = &script->objects()->vector[i]; michael@0: XDRClassKind classk; michael@0: michael@0: if (mode == XDR_ENCODE) { michael@0: JSObject *obj = *objp; michael@0: if (obj->is()) michael@0: classk = CK_BlockObject; michael@0: else if (obj->is()) michael@0: classk = CK_WithObject; michael@0: else if (obj->is()) michael@0: classk = CK_JSFunction; michael@0: else if (obj->is() || obj->is()) michael@0: classk = CK_JSObject; michael@0: else michael@0: MOZ_ASSUME_UNREACHABLE("Cannot encode this class of object."); michael@0: } michael@0: michael@0: if (!xdr->codeEnum32(&classk)) michael@0: return false; michael@0: michael@0: switch (classk) { michael@0: case CK_BlockObject: michael@0: case CK_WithObject: { michael@0: /* Code the nested block's enclosing scope. */ michael@0: uint32_t enclosingStaticScopeIndex = 0; michael@0: if (mode == XDR_ENCODE) { michael@0: NestedScopeObject &scope = (*objp)->as(); michael@0: if (NestedScopeObject *enclosing = scope.enclosingNestedScope()) michael@0: enclosingStaticScopeIndex = FindScopeObjectIndex(script, *enclosing); michael@0: else michael@0: enclosingStaticScopeIndex = UINT32_MAX; michael@0: } michael@0: if (!xdr->codeUint32(&enclosingStaticScopeIndex)) michael@0: return false; michael@0: Rooted enclosingStaticScope(cx); michael@0: if (mode == XDR_DECODE) { michael@0: if (enclosingStaticScopeIndex != UINT32_MAX) { michael@0: JS_ASSERT(enclosingStaticScopeIndex < i); michael@0: enclosingStaticScope = script->objects()->vector[enclosingStaticScopeIndex]; michael@0: } else { michael@0: enclosingStaticScope = fun; michael@0: } michael@0: } michael@0: michael@0: if (classk == CK_BlockObject) { michael@0: Rooted tmp(cx, static_cast(objp->get())); michael@0: if (!XDRStaticBlockObject(xdr, enclosingStaticScope, tmp.address())) michael@0: return false; michael@0: *objp = tmp; michael@0: } else { michael@0: Rooted tmp(cx, static_cast(objp->get())); michael@0: if (!XDRStaticWithObject(xdr, enclosingStaticScope, tmp.address())) michael@0: return false; michael@0: *objp = tmp; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case CK_JSFunction: { michael@0: /* Code the nested function's enclosing scope. */ michael@0: uint32_t funEnclosingScopeIndex = 0; michael@0: RootedObject funEnclosingScope(cx); michael@0: if (mode == XDR_ENCODE) { michael@0: RootedFunction function(cx, &(*objp)->as()); michael@0: michael@0: if (function->isInterpretedLazy()) michael@0: funEnclosingScope = function->lazyScript()->enclosingScope(); michael@0: else michael@0: funEnclosingScope = function->nonLazyScript()->enclosingStaticScope(); michael@0: michael@0: StaticScopeIter ssi(funEnclosingScope); michael@0: if (ssi.done() || ssi.type() == StaticScopeIter::FUNCTION) { michael@0: JS_ASSERT(ssi.done() == !fun); michael@0: funEnclosingScopeIndex = UINT32_MAX; michael@0: } else if (ssi.type() == StaticScopeIter::BLOCK) { michael@0: funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.block()); michael@0: JS_ASSERT(funEnclosingScopeIndex < i); michael@0: } else { michael@0: funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.staticWith()); michael@0: JS_ASSERT(funEnclosingScopeIndex < i); michael@0: } michael@0: } michael@0: michael@0: if (!xdr->codeUint32(&funEnclosingScopeIndex)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: if (funEnclosingScopeIndex == UINT32_MAX) { michael@0: funEnclosingScope = fun; michael@0: } else { michael@0: JS_ASSERT(funEnclosingScopeIndex < i); michael@0: funEnclosingScope = script->objects()->vector[funEnclosingScopeIndex]; michael@0: } michael@0: } michael@0: michael@0: // Code nested function and script. michael@0: RootedObject tmp(cx, *objp); michael@0: if (!XDRInterpretedFunction(xdr, funEnclosingScope, script, &tmp)) michael@0: return false; michael@0: *objp = tmp; michael@0: break; michael@0: } michael@0: michael@0: case CK_JSObject: { michael@0: /* Code object literal. */ michael@0: RootedObject tmp(cx, *objp); michael@0: if (!XDRObjectLiteral(xdr, &tmp)) michael@0: return false; michael@0: *objp = tmp; michael@0: break; michael@0: } michael@0: michael@0: default: { michael@0: MOZ_ASSERT(false, "Unknown class kind."); michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: for (i = 0; i != nregexps; ++i) { michael@0: if (!XDRScriptRegExpObject(xdr, &script->regexps()->vector[i])) michael@0: return false; michael@0: } michael@0: michael@0: if (ntrynotes != 0) { michael@0: JSTryNote *tnfirst = script->trynotes()->vector; michael@0: JS_ASSERT(script->trynotes()->length == ntrynotes); michael@0: JSTryNote *tn = tnfirst + ntrynotes; michael@0: do { michael@0: --tn; michael@0: if (!xdr->codeUint8(&tn->kind) || michael@0: !xdr->codeUint32(&tn->stackDepth) || michael@0: !xdr->codeUint32(&tn->start) || michael@0: !xdr->codeUint32(&tn->length)) { michael@0: return false; michael@0: } michael@0: } while (tn != tnfirst); michael@0: } michael@0: michael@0: for (i = 0; i < nblockscopes; ++i) { michael@0: BlockScopeNote *note = &script->blockScopes()->vector[i]; michael@0: if (!xdr->codeUint32(¬e->index) || michael@0: !xdr->codeUint32(¬e->start) || michael@0: !xdr->codeUint32(¬e->length) || michael@0: !xdr->codeUint32(¬e->parent)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (scriptBits & (1 << HasLazyScript)) { michael@0: Rooted lazy(cx); michael@0: if (mode == XDR_ENCODE) michael@0: lazy = script->maybeLazyScript(); michael@0: michael@0: if (!XDRRelazificationInfo(xdr, fun, script, &lazy)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) michael@0: script->setLazyScript(lazy); michael@0: } michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: scriptp.set(script); michael@0: michael@0: /* see BytecodeEmitter::tellDebuggerAboutCompiledScript */ michael@0: CallNewScriptHook(cx, script, fun); michael@0: if (!fun) { michael@0: RootedGlobalObject global(cx, script->compileAndGo() ? &script->global() : nullptr); michael@0: Debugger::onNewScript(cx, script, global); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template bool michael@0: js::XDRScript(XDRState *, HandleObject, HandleScript, HandleFunction, michael@0: MutableHandleScript); michael@0: michael@0: template bool michael@0: js::XDRScript(XDRState *, HandleObject, HandleScript, HandleFunction, michael@0: MutableHandleScript); michael@0: michael@0: template michael@0: bool michael@0: js::XDRLazyScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enclosingScript, michael@0: HandleFunction fun, MutableHandle lazy) michael@0: { michael@0: JSContext *cx = xdr->cx(); michael@0: michael@0: { michael@0: uint32_t begin; michael@0: uint32_t end; michael@0: uint32_t lineno; michael@0: uint32_t column; michael@0: uint64_t packedFields; michael@0: michael@0: if (mode == XDR_ENCODE) { michael@0: MOZ_ASSERT(!lazy->maybeScript()); michael@0: MOZ_ASSERT(fun == lazy->functionNonDelazifying()); michael@0: michael@0: begin = lazy->begin(); michael@0: end = lazy->end(); michael@0: lineno = lazy->lineno(); michael@0: column = lazy->column(); michael@0: packedFields = lazy->packedFields(); michael@0: } michael@0: michael@0: if (!xdr->codeUint32(&begin) || !xdr->codeUint32(&end) || michael@0: !xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) || michael@0: !xdr->codeUint64(&packedFields)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: if (mode == XDR_DECODE) michael@0: lazy.set(LazyScript::Create(cx, fun, packedFields, begin, end, lineno, column)); michael@0: } michael@0: michael@0: // Code free variables. michael@0: if (!XDRLazyFreeVariables(xdr, lazy)) michael@0: return false; michael@0: michael@0: // Code inner functions. michael@0: { michael@0: RootedObject func(cx); michael@0: HeapPtrFunction *innerFunctions = lazy->innerFunctions(); michael@0: size_t numInnerFunctions = lazy->numInnerFunctions(); michael@0: for (size_t i = 0; i < numInnerFunctions; i++) { michael@0: if (mode == XDR_ENCODE) michael@0: func = innerFunctions[i]; michael@0: michael@0: if (!XDRInterpretedFunction(xdr, fun, enclosingScript, &func)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) michael@0: innerFunctions[i] = &func->as(); michael@0: } michael@0: } michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: JS_ASSERT(!lazy->sourceObject()); michael@0: ScriptSourceObject *sourceObject = &enclosingScript->scriptSourceUnwrap(); michael@0: michael@0: // Set the enclosing scope of the lazy function, this would later be michael@0: // used to define the environment when the function would be used. michael@0: lazy->setParent(enclosingScope, sourceObject); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: template bool michael@0: js::XDRLazyScript(XDRState *, HandleObject, HandleScript, michael@0: HandleFunction, MutableHandle); michael@0: michael@0: template bool michael@0: js::XDRLazyScript(XDRState *, HandleObject, HandleScript, michael@0: HandleFunction, MutableHandle); michael@0: michael@0: void michael@0: JSScript::setSourceObject(JSObject *object) michael@0: { michael@0: JS_ASSERT(compartment() == object->compartment()); michael@0: sourceObject_ = object; michael@0: } michael@0: michael@0: js::ScriptSourceObject & michael@0: JSScript::scriptSourceUnwrap() const { michael@0: return UncheckedUnwrap(sourceObject())->as(); michael@0: } michael@0: michael@0: js::ScriptSource * michael@0: JSScript::scriptSource() const { michael@0: return scriptSourceUnwrap().source(); michael@0: } michael@0: michael@0: bool michael@0: JSScript::initScriptCounts(JSContext *cx) michael@0: { michael@0: JS_ASSERT(!hasScriptCounts()); michael@0: michael@0: size_t n = 0; michael@0: michael@0: for (jsbytecode *pc = code(); pc < codeEnd(); pc += GetBytecodeLength(pc)) michael@0: n += PCCounts::numCounts(JSOp(*pc)); michael@0: michael@0: size_t bytes = (length() * sizeof(PCCounts)) + (n * sizeof(double)); michael@0: char *base = (char *) cx->calloc_(bytes); michael@0: if (!base) michael@0: return false; michael@0: michael@0: /* Create compartment's scriptCountsMap if necessary. */ michael@0: ScriptCountsMap *map = compartment()->scriptCountsMap; michael@0: if (!map) { michael@0: map = cx->new_(); michael@0: if (!map || !map->init()) { michael@0: js_free(base); michael@0: js_delete(map); michael@0: return false; michael@0: } michael@0: compartment()->scriptCountsMap = map; michael@0: } michael@0: michael@0: char *cursor = base; michael@0: michael@0: ScriptCounts scriptCounts; michael@0: scriptCounts.pcCountsVector = (PCCounts *) cursor; michael@0: cursor += length() * sizeof(PCCounts); michael@0: michael@0: for (jsbytecode *pc = code(); pc < codeEnd(); pc += GetBytecodeLength(pc)) { michael@0: JS_ASSERT(uintptr_t(cursor) % sizeof(double) == 0); michael@0: scriptCounts.pcCountsVector[pcToOffset(pc)].counts = (double *) cursor; michael@0: size_t capacity = PCCounts::numCounts(JSOp(*pc)); michael@0: #ifdef DEBUG michael@0: scriptCounts.pcCountsVector[pcToOffset(pc)].capacity = capacity; michael@0: #endif michael@0: cursor += capacity * sizeof(double); michael@0: } michael@0: michael@0: if (!map->putNew(this, scriptCounts)) { michael@0: js_free(base); michael@0: return false; michael@0: } michael@0: hasScriptCounts_ = true; // safe to set this; we can't fail after this point michael@0: michael@0: JS_ASSERT(size_t(cursor - base) == bytes); michael@0: michael@0: /* Enable interrupts in any interpreter frames running on this script. */ michael@0: for (ActivationIterator iter(cx->runtime()); !iter.done(); ++iter) { michael@0: if (iter->isInterpreter()) michael@0: iter->asInterpreter()->enableInterruptsIfRunning(this); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript *script) michael@0: { michael@0: JS_ASSERT(script->hasScriptCounts()); michael@0: ScriptCountsMap *map = script->compartment()->scriptCountsMap; michael@0: ScriptCountsMap::Ptr p = map->lookup(script); michael@0: JS_ASSERT(p); michael@0: return p; michael@0: } michael@0: michael@0: js::PCCounts michael@0: JSScript::getPCCounts(jsbytecode *pc) { michael@0: JS_ASSERT(containsPC(pc)); michael@0: ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); michael@0: return p->value().pcCountsVector[pcToOffset(pc)]; michael@0: } michael@0: michael@0: void michael@0: JSScript::addIonCounts(jit::IonScriptCounts *ionCounts) michael@0: { michael@0: ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); michael@0: if (p->value().ionCounts) michael@0: ionCounts->setPrevious(p->value().ionCounts); michael@0: p->value().ionCounts = ionCounts; michael@0: } michael@0: michael@0: jit::IonScriptCounts * michael@0: JSScript::getIonCounts() michael@0: { michael@0: ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); michael@0: return p->value().ionCounts; michael@0: } michael@0: michael@0: ScriptCounts michael@0: JSScript::releaseScriptCounts() michael@0: { michael@0: ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); michael@0: ScriptCounts counts = p->value(); michael@0: compartment()->scriptCountsMap->remove(p); michael@0: hasScriptCounts_ = false; michael@0: return counts; michael@0: } michael@0: michael@0: void michael@0: JSScript::destroyScriptCounts(FreeOp *fop) michael@0: { michael@0: if (hasScriptCounts()) { michael@0: ScriptCounts scriptCounts = releaseScriptCounts(); michael@0: scriptCounts.destroy(fop); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScriptSourceObject::setSource(ScriptSource *source) michael@0: { michael@0: if (source) michael@0: source->incref(); michael@0: if (this->source()) michael@0: this->source()->decref(); michael@0: setReservedSlot(SOURCE_SLOT, PrivateValue(source)); michael@0: } michael@0: michael@0: JSObject * michael@0: ScriptSourceObject::element() const michael@0: { michael@0: return getReservedSlot(ELEMENT_SLOT).toObjectOrNull(); michael@0: } michael@0: michael@0: void michael@0: ScriptSourceObject::initElement(HandleObject element) michael@0: { michael@0: JS_ASSERT(getReservedSlot(ELEMENT_SLOT).isNull()); michael@0: setReservedSlot(ELEMENT_SLOT, ObjectOrNullValue(element)); michael@0: } michael@0: michael@0: const Value & michael@0: ScriptSourceObject::elementAttributeName() const michael@0: { michael@0: const Value &prop = getReservedSlot(ELEMENT_PROPERTY_SLOT); michael@0: JS_ASSERT(prop.isUndefined() || prop.isString()); michael@0: return prop; michael@0: } michael@0: michael@0: void michael@0: ScriptSourceObject::initIntroductionScript(JSScript *script) michael@0: { michael@0: JS_ASSERT(!getReservedSlot(INTRODUCTION_SCRIPT_SLOT).toPrivate()); michael@0: michael@0: // There is no equivalent of cross-compartment wrappers for scripts. If michael@0: // the introduction script would be in a different compartment from the michael@0: // compiled code, we would be creating a cross-compartment script michael@0: // reference, which would be bogus. In that case, just don't bother to michael@0: // retain the introduction script. michael@0: if (script && script->compartment() == compartment()) michael@0: setReservedSlot(INTRODUCTION_SCRIPT_SLOT, PrivateValue(script)); michael@0: } michael@0: michael@0: void michael@0: ScriptSourceObject::trace(JSTracer *trc, JSObject *obj) michael@0: { michael@0: ScriptSourceObject *sso = static_cast(obj); michael@0: michael@0: if (JSScript *script = sso->introductionScript()) { michael@0: MarkScriptUnbarriered(trc, &script, "ScriptSourceObject introductionScript"); michael@0: sso->setReservedSlot(INTRODUCTION_SCRIPT_SLOT, PrivateValue(script)); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ScriptSourceObject::finalize(FreeOp *fop, JSObject *obj) michael@0: { michael@0: // ScriptSource::setSource automatically takes care of the refcount michael@0: obj->as().setSource(nullptr); michael@0: } michael@0: michael@0: const Class ScriptSourceObject::class_ = { michael@0: "ScriptSource", michael@0: JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | michael@0: JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_IS_ANONYMOUS, michael@0: JS_PropertyStub, /* addProperty */ michael@0: JS_DeletePropertyStub, /* delProperty */ michael@0: JS_PropertyStub, /* getProperty */ michael@0: JS_StrictPropertyStub, /* setProperty */ michael@0: JS_EnumerateStub, michael@0: JS_ResolveStub, michael@0: JS_ConvertStub, michael@0: finalize, michael@0: nullptr, /* call */ michael@0: nullptr, /* hasInstance */ michael@0: nullptr, /* construct */ michael@0: trace michael@0: }; michael@0: michael@0: ScriptSourceObject * michael@0: ScriptSourceObject::create(ExclusiveContext *cx, ScriptSource *source, michael@0: const ReadOnlyCompileOptions &options) michael@0: { michael@0: RootedObject object(cx, NewObjectWithGivenProto(cx, &class_, nullptr, cx->global())); michael@0: if (!object) michael@0: return nullptr; michael@0: RootedScriptSource sourceObject(cx, &object->as()); michael@0: michael@0: source->incref(); michael@0: sourceObject->initSlot(SOURCE_SLOT, PrivateValue(source)); michael@0: sourceObject->initSlot(ELEMENT_SLOT, ObjectOrNullValue(options.element())); michael@0: if (options.elementAttributeName()) michael@0: sourceObject->initSlot(ELEMENT_PROPERTY_SLOT, StringValue(options.elementAttributeName())); michael@0: else michael@0: sourceObject->initSlot(ELEMENT_PROPERTY_SLOT, UndefinedValue()); michael@0: michael@0: sourceObject->initSlot(INTRODUCTION_SCRIPT_SLOT, PrivateValue(nullptr)); michael@0: sourceObject->initIntroductionScript(options.introductionScript()); michael@0: michael@0: return sourceObject; michael@0: } michael@0: michael@0: static const unsigned char emptySource[] = ""; michael@0: michael@0: /* Adjust the amount of memory this script source uses for source data, michael@0: reallocating if needed. */ michael@0: bool michael@0: ScriptSource::adjustDataSize(size_t nbytes) michael@0: { michael@0: // Allocating 0 bytes has undefined behavior, so special-case it. michael@0: if (nbytes == 0) { michael@0: if (data.compressed != emptySource) michael@0: js_free(data.compressed); michael@0: data.compressed = const_cast(emptySource); michael@0: return true; michael@0: } michael@0: michael@0: // |data.compressed| can be nullptr. michael@0: void *buf = js_realloc(data.compressed, nbytes); michael@0: if (!buf && data.compressed != emptySource) michael@0: js_free(data.compressed); michael@0: data.compressed = static_cast(buf); michael@0: return !!data.compressed; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSScript::loadSource(JSContext *cx, ScriptSource *ss, bool *worked) michael@0: { michael@0: JS_ASSERT(!ss->hasSourceData()); michael@0: *worked = false; michael@0: if (!cx->runtime()->sourceHook || !ss->sourceRetrievable()) michael@0: return true; michael@0: jschar *src = nullptr; michael@0: size_t length; michael@0: if (!cx->runtime()->sourceHook->load(cx, ss->filename(), &src, &length)) michael@0: return false; michael@0: if (!src) michael@0: return true; michael@0: ss->setSource(src, length); michael@0: *worked = true; michael@0: return true; michael@0: } michael@0: michael@0: JSFlatString * michael@0: JSScript::sourceData(JSContext *cx) michael@0: { michael@0: JS_ASSERT(scriptSource()->hasSourceData()); michael@0: return scriptSource()->substring(cx, sourceStart(), sourceEnd()); michael@0: } michael@0: michael@0: SourceDataCache::AutoHoldEntry::AutoHoldEntry() michael@0: : cache_(nullptr), source_(nullptr), charsToFree_(nullptr) michael@0: { michael@0: } michael@0: michael@0: void michael@0: SourceDataCache::AutoHoldEntry::holdEntry(SourceDataCache *cache, ScriptSource *source) michael@0: { michael@0: // Initialise the holder for a specific cache and script source. This will michael@0: // hold on to the cached source chars in the event that the cache is purged. michael@0: JS_ASSERT(!cache_ && !source_ && !charsToFree_); michael@0: cache_ = cache; michael@0: source_ = source; michael@0: } michael@0: michael@0: void michael@0: SourceDataCache::AutoHoldEntry::deferDelete(const jschar *chars) michael@0: { michael@0: // Take ownership of source chars now the cache is being purged. Remove our michael@0: // reference to the ScriptSource which might soon be destroyed. michael@0: JS_ASSERT(cache_ && source_ && !charsToFree_); michael@0: cache_ = nullptr; michael@0: source_ = nullptr; michael@0: charsToFree_ = chars; michael@0: } michael@0: michael@0: SourceDataCache::AutoHoldEntry::~AutoHoldEntry() michael@0: { michael@0: // The holder is going out of scope. If it has taken ownership of cached michael@0: // chars then delete them, otherwise unregister ourself with the cache. michael@0: if (charsToFree_) { michael@0: JS_ASSERT(!cache_ && !source_); michael@0: js_free(const_cast(charsToFree_)); michael@0: } else if (cache_) { michael@0: JS_ASSERT(source_); michael@0: cache_->releaseEntry(*this); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SourceDataCache::holdEntry(AutoHoldEntry &holder, ScriptSource *ss) michael@0: { michael@0: JS_ASSERT(!holder_); michael@0: holder.holdEntry(this, ss); michael@0: holder_ = &holder; michael@0: } michael@0: michael@0: void michael@0: SourceDataCache::releaseEntry(AutoHoldEntry &holder) michael@0: { michael@0: JS_ASSERT(holder_ == &holder); michael@0: holder_ = nullptr; michael@0: } michael@0: michael@0: const jschar * michael@0: SourceDataCache::lookup(ScriptSource *ss, AutoHoldEntry &holder) michael@0: { michael@0: JS_ASSERT(!holder_); michael@0: if (!map_) michael@0: return nullptr; michael@0: if (Map::Ptr p = map_->lookup(ss)) { michael@0: holdEntry(holder, ss); michael@0: return p->value(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: bool michael@0: SourceDataCache::put(ScriptSource *ss, const jschar *str, AutoHoldEntry &holder) michael@0: { michael@0: JS_ASSERT(!holder_); michael@0: michael@0: if (!map_) { michael@0: map_ = js_new(); michael@0: if (!map_) michael@0: return false; michael@0: michael@0: if (!map_->init()) { michael@0: js_delete(map_); michael@0: map_ = nullptr; michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (!map_->put(ss, str)) michael@0: return false; michael@0: michael@0: holdEntry(holder, ss); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: SourceDataCache::purge() michael@0: { michael@0: if (!map_) michael@0: return; michael@0: michael@0: for (Map::Range r = map_->all(); !r.empty(); r.popFront()) { michael@0: const jschar *chars = r.front().value(); michael@0: if (holder_ && r.front().key() == holder_->source()) { michael@0: holder_->deferDelete(chars); michael@0: holder_ = nullptr; michael@0: } else { michael@0: js_free(const_cast(chars)); michael@0: } michael@0: } michael@0: michael@0: js_delete(map_); michael@0: map_ = nullptr; michael@0: } michael@0: michael@0: size_t michael@0: SourceDataCache::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) michael@0: { michael@0: size_t n = 0; michael@0: if (map_ && !map_->empty()) { michael@0: n += map_->sizeOfIncludingThis(mallocSizeOf); michael@0: for (Map::Range r = map_->all(); !r.empty(); r.popFront()) { michael@0: const jschar *v = r.front().value(); michael@0: n += mallocSizeOf(v); michael@0: } michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: const jschar * michael@0: ScriptSource::chars(JSContext *cx, SourceDataCache::AutoHoldEntry &holder) michael@0: { michael@0: if (const jschar *chars = getOffThreadCompressionChars(cx)) michael@0: return chars; michael@0: JS_ASSERT(ready()); michael@0: michael@0: #ifdef USE_ZLIB michael@0: if (compressed()) { michael@0: if (const jschar *decompressed = cx->runtime()->sourceDataCache.lookup(this, holder)) michael@0: return decompressed; michael@0: michael@0: const size_t nbytes = sizeof(jschar) * (length_ + 1); michael@0: jschar *decompressed = static_cast(js_malloc(nbytes)); michael@0: if (!decompressed) michael@0: return nullptr; michael@0: michael@0: if (!DecompressString(data.compressed, compressedLength_, michael@0: reinterpret_cast(decompressed), nbytes)) { michael@0: JS_ReportOutOfMemory(cx); michael@0: js_free(decompressed); michael@0: return nullptr; michael@0: } michael@0: michael@0: decompressed[length_] = 0; michael@0: michael@0: if (!cx->runtime()->sourceDataCache.put(this, decompressed, holder)) { michael@0: JS_ReportOutOfMemory(cx); michael@0: js_free(decompressed); michael@0: return nullptr; michael@0: } michael@0: michael@0: return decompressed; michael@0: } michael@0: #endif michael@0: return data.source; michael@0: } michael@0: michael@0: JSFlatString * michael@0: ScriptSource::substring(JSContext *cx, uint32_t start, uint32_t stop) michael@0: { michael@0: JS_ASSERT(start <= stop); michael@0: SourceDataCache::AutoHoldEntry holder; michael@0: const jschar *chars = this->chars(cx, holder); michael@0: if (!chars) michael@0: return nullptr; michael@0: return js_NewStringCopyN(cx, chars + start, stop - start); michael@0: } michael@0: michael@0: bool michael@0: ScriptSource::setSourceCopy(ExclusiveContext *cx, SourceBufferHolder &srcBuf, michael@0: bool argumentsNotIncluded, SourceCompressionTask *task) michael@0: { michael@0: JS_ASSERT(!hasSourceData()); michael@0: length_ = srcBuf.length(); michael@0: argumentsNotIncluded_ = argumentsNotIncluded; michael@0: michael@0: // There are several cases where source compression is not a good idea: michael@0: // - If the script is tiny, then compression will save little or no space. michael@0: // - If the script is enormous, then decompression can take seconds. With michael@0: // lazy parsing, decompression is not uncommon, so this can significantly michael@0: // increase latency. michael@0: // - If there is only one core, then compression will contend with JS michael@0: // execution (which hurts benchmarketing). michael@0: // - If the source contains a giant string, then parsing will finish much michael@0: // faster than compression which increases latency (this case is handled michael@0: // in Parser::stringLiteral). michael@0: // michael@0: // Lastly, since the parsing thread will eventually perform a blocking wait michael@0: // on the compresion task's worker thread, require that there are at least 2 michael@0: // worker threads: michael@0: // - If we are on a worker thread, there must be another worker thread to michael@0: // execute our compression task. michael@0: // - If we are on the main thread, there must be at least two worker michael@0: // threads since at most one worker thread can be blocking on the main michael@0: // thread (see WorkerThreadState::canStartParseTask) which would cause a michael@0: // deadlock if there wasn't a second worker thread that could make michael@0: // progress on our compression task. michael@0: #ifdef JS_THREADSAFE michael@0: bool canCompressOffThread = michael@0: WorkerThreadState().cpuCount > 1 && michael@0: WorkerThreadState().threadCount >= 2; michael@0: #else michael@0: bool canCompressOffThread = false; michael@0: #endif michael@0: const size_t TINY_SCRIPT = 256; michael@0: const size_t HUGE_SCRIPT = 5 * 1024 * 1024; michael@0: if (TINY_SCRIPT <= srcBuf.length() && srcBuf.length() < HUGE_SCRIPT && canCompressOffThread) { michael@0: task->ss = this; michael@0: task->chars = srcBuf.get(); michael@0: ready_ = false; michael@0: if (!StartOffThreadCompression(cx, task)) michael@0: return false; michael@0: } else if (srcBuf.ownsChars()) { michael@0: data.source = srcBuf.take(); michael@0: } else { michael@0: if (!adjustDataSize(sizeof(jschar) * srcBuf.length())) michael@0: return false; michael@0: PodCopy(data.source, srcBuf.get(), length_); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: ScriptSource::setSource(const jschar *src, size_t length) michael@0: { michael@0: JS_ASSERT(!hasSourceData()); michael@0: length_ = length; michael@0: JS_ASSERT(!argumentsNotIncluded_); michael@0: data.source = const_cast(src); michael@0: } michael@0: michael@0: bool michael@0: SourceCompressionTask::work() michael@0: { michael@0: // A given compression token can be compressed on any thread, and the ss michael@0: // not being ready indicates to other threads that its fields might change michael@0: // with no lock held. michael@0: JS_ASSERT(!ss->ready()); michael@0: michael@0: size_t compressedLength = 0; michael@0: size_t nbytes = sizeof(jschar) * ss->length_; michael@0: michael@0: // Memory allocation functions on JSRuntime and JSContext are not michael@0: // threadsafe. We have to use the js_* variants. michael@0: michael@0: #ifdef USE_ZLIB michael@0: // Try to keep the maximum memory usage down by only allocating half the michael@0: // size of the string, first. michael@0: size_t firstSize = nbytes / 2; michael@0: if (!ss->adjustDataSize(firstSize)) michael@0: return false; michael@0: Compressor comp(reinterpret_cast(chars), nbytes); michael@0: if (!comp.init()) michael@0: return false; michael@0: comp.setOutput(ss->data.compressed, firstSize); michael@0: bool cont = !abort_; michael@0: while (cont) { michael@0: switch (comp.compressMore()) { michael@0: case Compressor::CONTINUE: michael@0: break; michael@0: case Compressor::MOREOUTPUT: { michael@0: if (comp.outWritten() == nbytes) { michael@0: cont = false; michael@0: break; michael@0: } michael@0: michael@0: // The compressed output is greater than half the size of the michael@0: // original string. Reallocate to the full size. michael@0: if (!ss->adjustDataSize(nbytes)) michael@0: return false; michael@0: comp.setOutput(ss->data.compressed, nbytes); michael@0: break; michael@0: } michael@0: case Compressor::DONE: michael@0: cont = false; michael@0: break; michael@0: case Compressor::OOM: michael@0: return false; michael@0: } michael@0: cont = cont && !abort_; michael@0: } michael@0: compressedLength = comp.outWritten(); michael@0: if (abort_ || compressedLength == nbytes) michael@0: compressedLength = 0; michael@0: #endif michael@0: michael@0: if (compressedLength == 0) { michael@0: if (!ss->adjustDataSize(nbytes)) michael@0: return false; michael@0: PodCopy(ss->data.source, chars, ss->length()); michael@0: } else { michael@0: // Shrink the buffer to the size of the compressed data. Shouldn't fail. michael@0: JS_ALWAYS_TRUE(ss->adjustDataSize(compressedLength)); michael@0: } michael@0: ss->compressedLength_ = compressedLength; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: ScriptSource::destroy() michael@0: { michael@0: JS_ASSERT(ready()); michael@0: adjustDataSize(0); michael@0: if (introducerFilename_ != filename_) michael@0: js_free(introducerFilename_); michael@0: js_free(filename_); michael@0: js_free(displayURL_); michael@0: js_free(sourceMapURL_); michael@0: if (originPrincipals_) michael@0: JS_DropPrincipals(TlsPerThreadData.get()->runtimeFromMainThread(), originPrincipals_); michael@0: ready_ = false; michael@0: js_free(this); michael@0: } michael@0: michael@0: void michael@0: ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, michael@0: JS::ScriptSourceInfo *info) const michael@0: { michael@0: if (ready() && data.compressed != emptySource) { michael@0: if (compressed()) michael@0: info->compressed += mallocSizeOf(data.compressed); michael@0: else michael@0: info->uncompressed += mallocSizeOf(data.source); michael@0: } michael@0: info->misc += mallocSizeOf(this) + mallocSizeOf(filename_); michael@0: info->numScripts++; michael@0: } michael@0: michael@0: template michael@0: bool michael@0: ScriptSource::performXDR(XDRState *xdr) michael@0: { michael@0: uint8_t hasSource = hasSourceData(); michael@0: if (!xdr->codeUint8(&hasSource)) michael@0: return false; michael@0: michael@0: uint8_t retrievable = sourceRetrievable_; michael@0: if (!xdr->codeUint8(&retrievable)) michael@0: return false; michael@0: sourceRetrievable_ = retrievable; michael@0: michael@0: if (hasSource && !sourceRetrievable_) { michael@0: // Only set members when we know decoding cannot fail. This prevents the michael@0: // script source from being partially initialized. michael@0: uint32_t length = length_; michael@0: if (!xdr->codeUint32(&length)) michael@0: return false; michael@0: michael@0: uint32_t compressedLength = compressedLength_; michael@0: if (!xdr->codeUint32(&compressedLength)) michael@0: return false; michael@0: michael@0: uint8_t argumentsNotIncluded = argumentsNotIncluded_; michael@0: if (!xdr->codeUint8(&argumentsNotIncluded)) michael@0: return false; michael@0: michael@0: size_t byteLen = compressedLength ? compressedLength : (length * sizeof(jschar)); michael@0: if (mode == XDR_DECODE) { michael@0: if (!adjustDataSize(byteLen)) michael@0: return false; michael@0: } michael@0: if (!xdr->codeBytes(data.compressed, byteLen)) { michael@0: if (mode == XDR_DECODE) { michael@0: js_free(data.compressed); michael@0: data.compressed = nullptr; michael@0: } michael@0: return false; michael@0: } michael@0: length_ = length; michael@0: compressedLength_ = compressedLength; michael@0: argumentsNotIncluded_ = argumentsNotIncluded; michael@0: } michael@0: michael@0: uint8_t haveSourceMap = hasSourceMapURL(); michael@0: if (!xdr->codeUint8(&haveSourceMap)) michael@0: return false; michael@0: michael@0: if (haveSourceMap) { michael@0: uint32_t sourceMapURLLen = (mode == XDR_DECODE) ? 0 : js_strlen(sourceMapURL_); michael@0: if (!xdr->codeUint32(&sourceMapURLLen)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: size_t byteLen = (sourceMapURLLen + 1) * sizeof(jschar); michael@0: sourceMapURL_ = static_cast(xdr->cx()->malloc_(byteLen)); michael@0: if (!sourceMapURL_) michael@0: return false; michael@0: } michael@0: if (!xdr->codeChars(sourceMapURL_, sourceMapURLLen)) { michael@0: if (mode == XDR_DECODE) { michael@0: js_free(sourceMapURL_); michael@0: sourceMapURL_ = nullptr; michael@0: } michael@0: return false; michael@0: } michael@0: sourceMapURL_[sourceMapURLLen] = '\0'; michael@0: } michael@0: michael@0: uint8_t haveDisplayURL = hasDisplayURL(); michael@0: if (!xdr->codeUint8(&haveDisplayURL)) michael@0: return false; michael@0: michael@0: if (haveDisplayURL) { michael@0: uint32_t displayURLLen = (mode == XDR_DECODE) ? 0 : js_strlen(displayURL_); michael@0: if (!xdr->codeUint32(&displayURLLen)) michael@0: return false; michael@0: michael@0: if (mode == XDR_DECODE) { michael@0: size_t byteLen = (displayURLLen + 1) * sizeof(jschar); michael@0: displayURL_ = static_cast(xdr->cx()->malloc_(byteLen)); michael@0: if (!displayURL_) michael@0: return false; michael@0: } michael@0: if (!xdr->codeChars(displayURL_, displayURLLen)) { michael@0: if (mode == XDR_DECODE) { michael@0: js_free(displayURL_); michael@0: displayURL_ = nullptr; michael@0: } michael@0: return false; michael@0: } michael@0: displayURL_[displayURLLen] = '\0'; michael@0: } michael@0: michael@0: uint8_t haveFilename = !!filename_; michael@0: if (!xdr->codeUint8(&haveFilename)) michael@0: return false; michael@0: michael@0: if (haveFilename) { michael@0: const char *fn = filename(); michael@0: if (!xdr->codeCString(&fn)) michael@0: return false; michael@0: if (mode == XDR_DECODE && !setFilename(xdr->cx(), fn)) michael@0: return false; michael@0: } michael@0: michael@0: if (mode == XDR_DECODE) michael@0: ready_ = true; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Format and return a cx->malloc_'ed URL for a generated script like: michael@0: // {filename} line {lineno} > {introducer} michael@0: // For example: michael@0: // foo.js line 7 > eval michael@0: // indicating code compiled by the call to 'eval' on line 7 of foo.js. michael@0: static char * michael@0: FormatIntroducedFilename(ExclusiveContext *cx, const char *filename, unsigned lineno, michael@0: const char *introducer) michael@0: { michael@0: // Compute the length of the string in advance, so we can allocate a michael@0: // buffer of the right size on the first shot. michael@0: // michael@0: // (JS_smprintf would be perfect, as that allocates the result michael@0: // dynamically as it formats the string, but it won't allocate from cx, michael@0: // and wants us to use a special free function.) michael@0: char linenoBuf[15]; michael@0: size_t filenameLen = strlen(filename); michael@0: size_t linenoLen = JS_snprintf(linenoBuf, 15, "%u", lineno); michael@0: size_t introducerLen = strlen(introducer); michael@0: size_t len = filenameLen + michael@0: 6 /* == strlen(" line ") */ + michael@0: linenoLen + michael@0: 3 /* == strlen(" > ") */ + michael@0: introducerLen + michael@0: 1 /* \0 */; michael@0: char *formatted = cx->pod_malloc(len); michael@0: if (!formatted) michael@0: return nullptr; michael@0: mozilla::DebugOnly checkLen = JS_snprintf(formatted, len, "%s line %s > %s", michael@0: filename, linenoBuf, introducer); michael@0: JS_ASSERT(checkLen == len - 1); michael@0: michael@0: return formatted; michael@0: } michael@0: michael@0: bool michael@0: ScriptSource::initFromOptions(ExclusiveContext *cx, const ReadOnlyCompileOptions &options) michael@0: { michael@0: JS_ASSERT(!filename_); michael@0: JS_ASSERT(!introducerFilename_); michael@0: michael@0: originPrincipals_ = options.originPrincipals(cx); michael@0: if (originPrincipals_) michael@0: JS_HoldPrincipals(originPrincipals_); michael@0: michael@0: introductionType_ = options.introductionType; michael@0: setIntroductionOffset(options.introductionOffset); michael@0: michael@0: if (options.hasIntroductionInfo) { michael@0: JS_ASSERT(options.introductionType != nullptr); michael@0: const char *filename = options.filename() ? options.filename() : ""; michael@0: char *formatted = FormatIntroducedFilename(cx, filename, options.introductionLineno, michael@0: options.introductionType); michael@0: if (!formatted) michael@0: return false; michael@0: filename_ = formatted; michael@0: } else if (options.filename()) { michael@0: if (!setFilename(cx, options.filename())) michael@0: return false; michael@0: } michael@0: michael@0: if (options.introducerFilename()) { michael@0: introducerFilename_ = js_strdup(cx, options.introducerFilename()); michael@0: if (!introducerFilename_) michael@0: return false; michael@0: } else { michael@0: introducerFilename_ = filename_; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ScriptSource::setFilename(ExclusiveContext *cx, const char *filename) michael@0: { michael@0: JS_ASSERT(!filename_); michael@0: filename_ = js_strdup(cx, filename); michael@0: if (!filename_) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: ScriptSource::setDisplayURL(ExclusiveContext *cx, const jschar *displayURL) michael@0: { michael@0: JS_ASSERT(displayURL); michael@0: if (hasDisplayURL()) { michael@0: if (cx->isJSContext() && michael@0: !JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING, michael@0: js_GetErrorMessage, nullptr, michael@0: JSMSG_ALREADY_HAS_PRAGMA, filename_, michael@0: "//# sourceURL")) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: size_t len = js_strlen(displayURL) + 1; michael@0: if (len == 1) michael@0: return true; michael@0: displayURL_ = js_strdup(cx, displayURL); michael@0: if (!displayURL_) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: const jschar * michael@0: ScriptSource::displayURL() michael@0: { michael@0: JS_ASSERT(hasDisplayURL()); michael@0: return displayURL_; michael@0: } michael@0: michael@0: bool michael@0: ScriptSource::setSourceMapURL(ExclusiveContext *cx, const jschar *sourceMapURL) michael@0: { michael@0: JS_ASSERT(sourceMapURL); michael@0: if (hasSourceMapURL()) { michael@0: if (cx->isJSContext() && michael@0: !JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING, michael@0: js_GetErrorMessage, nullptr, michael@0: JSMSG_ALREADY_HAS_PRAGMA, filename_, michael@0: "//# sourceMappingURL")) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: size_t len = js_strlen(sourceMapURL) + 1; michael@0: if (len == 1) michael@0: return true; michael@0: sourceMapURL_ = js_strdup(cx, sourceMapURL); michael@0: if (!sourceMapURL_) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: const jschar * michael@0: ScriptSource::sourceMapURL() michael@0: { michael@0: JS_ASSERT(hasSourceMapURL()); michael@0: return sourceMapURL_; michael@0: } michael@0: michael@0: /* michael@0: * Shared script data management. michael@0: */ michael@0: michael@0: SharedScriptData * michael@0: js::SharedScriptData::new_(ExclusiveContext *cx, uint32_t codeLength, michael@0: uint32_t srcnotesLength, uint32_t natoms) michael@0: { michael@0: /* michael@0: * Ensure the atoms are aligned, as some architectures don't allow unaligned michael@0: * access. michael@0: */ michael@0: const uint32_t pointerSize = sizeof(JSAtom *); michael@0: const uint32_t pointerMask = pointerSize - 1; michael@0: const uint32_t dataOffset = offsetof(SharedScriptData, data); michael@0: uint32_t baseLength = codeLength + srcnotesLength; michael@0: uint32_t padding = (pointerSize - ((baseLength + dataOffset) & pointerMask)) & pointerMask; michael@0: uint32_t length = baseLength + padding + pointerSize * natoms; michael@0: michael@0: SharedScriptData *entry = (SharedScriptData *)cx->malloc_(length + dataOffset); michael@0: if (!entry) michael@0: return nullptr; michael@0: michael@0: entry->length = length; michael@0: entry->natoms = natoms; michael@0: entry->marked = false; michael@0: memset(entry->data + baseLength, 0, padding); michael@0: michael@0: /* michael@0: * Call constructors to initialize the storage that will be accessed as a michael@0: * HeapPtrAtom array via atoms(). michael@0: */ michael@0: HeapPtrAtom *atoms = entry->atoms(); michael@0: JS_ASSERT(reinterpret_cast(atoms) % sizeof(JSAtom *) == 0); michael@0: for (unsigned i = 0; i < natoms; ++i) michael@0: new (&atoms[i]) HeapPtrAtom(); michael@0: michael@0: return entry; michael@0: } michael@0: michael@0: /* michael@0: * Takes ownership of its *ssd parameter and either adds it into the runtime's michael@0: * ScriptDataTable or frees it if a matching entry already exists. michael@0: * michael@0: * Sets the |code| and |atoms| fields on the given JSScript. michael@0: */ michael@0: static bool michael@0: SaveSharedScriptData(ExclusiveContext *cx, Handle script, SharedScriptData *ssd, michael@0: uint32_t nsrcnotes) michael@0: { michael@0: ASSERT(script != nullptr); michael@0: ASSERT(ssd != nullptr); michael@0: michael@0: AutoLockForExclusiveAccess lock(cx); michael@0: michael@0: ScriptBytecodeHasher::Lookup l(ssd); michael@0: michael@0: ScriptDataTable::AddPtr p = cx->scriptDataTable().lookupForAdd(l); michael@0: if (p) { michael@0: js_free(ssd); michael@0: ssd = *p; michael@0: } else { michael@0: if (!cx->scriptDataTable().add(p, ssd)) { michael@0: script->setCode(nullptr); michael@0: script->atoms = nullptr; michael@0: js_free(ssd); michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: #ifdef JSGC_INCREMENTAL michael@0: /* michael@0: * During the IGC we need to ensure that bytecode is marked whenever it is michael@0: * accessed even if the bytecode was already in the table: at this point michael@0: * old scripts or exceptions pointing to the bytecode may no longer be michael@0: * reachable. This is effectively a read barrier. michael@0: */ michael@0: if (cx->isJSContext()) { michael@0: JSRuntime *rt = cx->asJSContext()->runtime(); michael@0: if (JS::IsIncrementalGCInProgress(rt) && rt->gcIsFull) michael@0: ssd->marked = true; michael@0: } michael@0: #endif michael@0: michael@0: script->setCode(ssd->data); michael@0: script->atoms = ssd->atoms(); michael@0: return true; michael@0: } michael@0: michael@0: static inline void michael@0: MarkScriptData(JSRuntime *rt, const jsbytecode *bytecode) michael@0: { michael@0: /* michael@0: * As an invariant, a ScriptBytecodeEntry should not be 'marked' outside of michael@0: * a GC. Since SweepScriptBytecodes is only called during a full gc, michael@0: * to preserve this invariant, only mark during a full gc. michael@0: */ michael@0: if (rt->gcIsFull) michael@0: SharedScriptData::fromBytecode(bytecode)->marked = true; michael@0: } michael@0: michael@0: void michael@0: js::UnmarkScriptData(JSRuntime *rt) michael@0: { michael@0: JS_ASSERT(rt->gcIsFull); michael@0: ScriptDataTable &table = rt->scriptDataTable(); michael@0: for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { michael@0: SharedScriptData *entry = e.front(); michael@0: entry->marked = false; michael@0: } michael@0: } michael@0: michael@0: void michael@0: js::SweepScriptData(JSRuntime *rt) michael@0: { michael@0: JS_ASSERT(rt->gcIsFull); michael@0: ScriptDataTable &table = rt->scriptDataTable(); michael@0: michael@0: if (rt->keepAtoms()) michael@0: return; michael@0: michael@0: for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { michael@0: SharedScriptData *entry = e.front(); michael@0: if (!entry->marked) { michael@0: js_free(entry); michael@0: e.removeFront(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: js::FreeScriptData(JSRuntime *rt) michael@0: { michael@0: ScriptDataTable &table = rt->scriptDataTable(); michael@0: if (!table.initialized()) michael@0: return; michael@0: michael@0: for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) michael@0: js_free(e.front()); michael@0: michael@0: table.clear(); michael@0: } michael@0: michael@0: /* michael@0: * JSScript::data and SharedScriptData::data have complex, michael@0: * manually-controlled, memory layouts. michael@0: * michael@0: * JSScript::data begins with some optional array headers. They are optional michael@0: * because they often aren't needed, i.e. the corresponding arrays often have michael@0: * zero elements. Each header has a bit in JSScript::hasArrayBits that michael@0: * indicates if it's present within |data|; from this the offset of each michael@0: * present array header can be computed. Each header has an accessor function michael@0: * in JSScript that encapsulates this offset computation. michael@0: * michael@0: * Array type Array elements Accessor michael@0: * ---------- -------------- -------- michael@0: * ConstArray Consts consts() michael@0: * ObjectArray Objects objects() michael@0: * ObjectArray Regexps regexps() michael@0: * TryNoteArray Try notes trynotes() michael@0: * BlockScopeArray Scope notes blockScopes() michael@0: * michael@0: * Then are the elements of several arrays. michael@0: * - Most of these arrays have headers listed above (if present). For each of michael@0: * these, the array pointer and the array length is stored in the header. michael@0: * - The remaining arrays have pointers and lengths that are stored directly in michael@0: * JSScript. This is because, unlike the others, they are nearly always michael@0: * non-zero length and so the optional-header space optimization isn't michael@0: * worthwhile. michael@0: * michael@0: * Array elements Pointed to by Length michael@0: * -------------- ------------- ------ michael@0: * Consts consts()->vector consts()->length michael@0: * Objects objects()->vector objects()->length michael@0: * Regexps regexps()->vector regexps()->length michael@0: * Try notes trynotes()->vector trynotes()->length michael@0: * Scope notes blockScopes()->vector blockScopes()->length michael@0: * michael@0: * IMPORTANT: This layout has two key properties. michael@0: * - It ensures that everything has sufficient alignment; in particular, the michael@0: * consts() elements need jsval alignment. michael@0: * - It ensures there are no gaps between elements, which saves space and makes michael@0: * manual layout easy. In particular, in the second part, arrays with larger michael@0: * elements precede arrays with smaller elements. michael@0: * michael@0: * SharedScriptData::data contains data that can be shared within a michael@0: * runtime. These items' layout is manually controlled to make it easier to michael@0: * manage both during (temporary) allocation and during matching against michael@0: * existing entries in the runtime. As the jsbytecode has to come first to michael@0: * enable lookup by bytecode identity, SharedScriptData::data, the atoms part michael@0: * has to manually be aligned sufficiently by adding padding after the notes michael@0: * part. michael@0: * michael@0: * Array elements Pointed to by Length michael@0: * -------------- ------------- ------ michael@0: * jsbytecode code length michael@0: * jsscrnote notes() numNotes() michael@0: * Atoms atoms natoms michael@0: * michael@0: * The following static assertions check JSScript::data's alignment properties. michael@0: */ michael@0: michael@0: #define KEEPS_JSVAL_ALIGNMENT(T) \ michael@0: (JS_ALIGNMENT_OF(jsval) % JS_ALIGNMENT_OF(T) == 0 && \ michael@0: sizeof(T) % sizeof(jsval) == 0) michael@0: michael@0: #define HAS_JSVAL_ALIGNMENT(T) \ michael@0: (JS_ALIGNMENT_OF(jsval) == JS_ALIGNMENT_OF(T) && \ michael@0: sizeof(T) == sizeof(jsval)) michael@0: michael@0: #define NO_PADDING_BETWEEN_ENTRIES(T1, T2) \ michael@0: (JS_ALIGNMENT_OF(T1) % JS_ALIGNMENT_OF(T2) == 0) michael@0: michael@0: /* michael@0: * These assertions ensure that there is no padding between the array headers, michael@0: * and also that the consts() elements (which follow immediately afterward) are michael@0: * jsval-aligned. (There is an assumption that |data| itself is jsval-aligned; michael@0: * we check this below). michael@0: */ michael@0: JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(ConstArray)); michael@0: JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(ObjectArray)); /* there are two of these */ michael@0: JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(TryNoteArray)); michael@0: JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(BlockScopeArray)); michael@0: michael@0: /* These assertions ensure there is no padding required between array elements. */ michael@0: JS_STATIC_ASSERT(HAS_JSVAL_ALIGNMENT(HeapValue)); michael@0: JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapValue, HeapPtrObject)); michael@0: JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, HeapPtrObject)); michael@0: JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, JSTryNote)); michael@0: JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(JSTryNote, uint32_t)); michael@0: JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(uint32_t, uint32_t)); michael@0: michael@0: JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapValue, BlockScopeNote)); michael@0: JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(BlockScopeNote, BlockScopeNote)); michael@0: JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(JSTryNote, BlockScopeNote)); michael@0: JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, BlockScopeNote)); michael@0: JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(BlockScopeNote, uint32_t)); michael@0: michael@0: static inline size_t michael@0: ScriptDataSize(uint32_t nbindings, uint32_t nconsts, uint32_t nobjects, uint32_t nregexps, michael@0: uint32_t ntrynotes, uint32_t nblockscopes) michael@0: { michael@0: size_t size = 0; michael@0: michael@0: if (nconsts != 0) michael@0: size += sizeof(ConstArray) + nconsts * sizeof(Value); michael@0: if (nobjects != 0) michael@0: size += sizeof(ObjectArray) + nobjects * sizeof(JSObject *); michael@0: if (nregexps != 0) michael@0: size += sizeof(ObjectArray) + nregexps * sizeof(JSObject *); michael@0: if (ntrynotes != 0) michael@0: size += sizeof(TryNoteArray) + ntrynotes * sizeof(JSTryNote); michael@0: if (nblockscopes != 0) michael@0: size += sizeof(BlockScopeArray) + nblockscopes * sizeof(BlockScopeNote); michael@0: michael@0: if (nbindings != 0) { michael@0: // Make sure bindings are sufficiently aligned. michael@0: size = JS_ROUNDUP(size, JS_ALIGNMENT_OF(Binding)) + nbindings * sizeof(Binding); michael@0: } michael@0: michael@0: return size; michael@0: } michael@0: michael@0: void michael@0: JSScript::initCompartment(ExclusiveContext *cx) michael@0: { michael@0: compartment_ = cx->compartment_; michael@0: } michael@0: michael@0: JSScript * michael@0: JSScript::Create(ExclusiveContext *cx, HandleObject enclosingScope, bool savedCallerFun, michael@0: const ReadOnlyCompileOptions &options, unsigned staticLevel, michael@0: HandleObject sourceObject, uint32_t bufStart, uint32_t bufEnd) michael@0: { michael@0: JS_ASSERT(bufStart <= bufEnd); michael@0: michael@0: RootedScript script(cx, js_NewGCScript(cx)); michael@0: if (!script) michael@0: return nullptr; michael@0: michael@0: PodZero(script.get()); michael@0: new (&script->bindings) Bindings; michael@0: michael@0: script->enclosingScopeOrOriginalFunction_ = enclosingScope; michael@0: script->savedCallerFun_ = savedCallerFun; michael@0: script->initCompartment(cx); michael@0: michael@0: script->compileAndGo_ = options.compileAndGo; michael@0: script->selfHosted_ = options.selfHostingMode; michael@0: script->noScriptRval_ = options.noScriptRval; michael@0: michael@0: script->version = options.version; michael@0: JS_ASSERT(script->getVersion() == options.version); // assert that no overflow occurred michael@0: michael@0: // This is an unsigned-to-uint16_t conversion, test for too-high values. michael@0: // In practice, recursion in Parser and/or BytecodeEmitter will blow the michael@0: // stack if we nest functions more than a few hundred deep, so this will michael@0: // never trigger. Oh well. michael@0: if (staticLevel > UINT16_MAX) { michael@0: if (cx->isJSContext()) { michael@0: JS_ReportErrorNumber(cx->asJSContext(), michael@0: js_GetErrorMessage, nullptr, JSMSG_TOO_DEEP, js_function_str); michael@0: } michael@0: return nullptr; michael@0: } michael@0: script->staticLevel_ = uint16_t(staticLevel); michael@0: michael@0: script->setSourceObject(sourceObject); michael@0: script->sourceStart_ = bufStart; michael@0: script->sourceEnd_ = bufEnd; michael@0: michael@0: return script; michael@0: } michael@0: michael@0: static inline uint8_t * michael@0: AllocScriptData(ExclusiveContext *cx, size_t size) michael@0: { michael@0: uint8_t *data = static_cast(cx->calloc_(JS_ROUNDUP(size, sizeof(Value)))); michael@0: if (!data) michael@0: return nullptr; michael@0: michael@0: // All script data is optional, so size might be 0. In that case, we don't care about alignment. michael@0: JS_ASSERT(size == 0 || size_t(data) % sizeof(Value) == 0); michael@0: return data; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSScript::partiallyInit(ExclusiveContext *cx, HandleScript script, uint32_t nconsts, michael@0: uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, michael@0: uint32_t nblockscopes, uint32_t nTypeSets) michael@0: { michael@0: size_t size = ScriptDataSize(script->bindings.count(), nconsts, nobjects, nregexps, ntrynotes, michael@0: nblockscopes); michael@0: if (size > 0) { michael@0: script->data = AllocScriptData(cx, size); michael@0: if (!script->data) michael@0: return false; michael@0: } else { michael@0: script->data = nullptr; michael@0: } michael@0: script->dataSize_ = size; michael@0: michael@0: JS_ASSERT(nTypeSets <= UINT16_MAX); michael@0: script->nTypeSets_ = uint16_t(nTypeSets); michael@0: michael@0: uint8_t *cursor = script->data; michael@0: if (nconsts != 0) { michael@0: script->setHasArray(CONSTS); michael@0: cursor += sizeof(ConstArray); michael@0: } michael@0: if (nobjects != 0) { michael@0: script->setHasArray(OBJECTS); michael@0: cursor += sizeof(ObjectArray); michael@0: } michael@0: if (nregexps != 0) { michael@0: script->setHasArray(REGEXPS); michael@0: cursor += sizeof(ObjectArray); michael@0: } michael@0: if (ntrynotes != 0) { michael@0: script->setHasArray(TRYNOTES); michael@0: cursor += sizeof(TryNoteArray); michael@0: } michael@0: if (nblockscopes != 0) { michael@0: script->setHasArray(BLOCK_SCOPES); michael@0: cursor += sizeof(BlockScopeArray); michael@0: } michael@0: michael@0: if (nconsts != 0) { michael@0: JS_ASSERT(reinterpret_cast(cursor) % sizeof(jsval) == 0); michael@0: script->consts()->length = nconsts; michael@0: script->consts()->vector = (HeapValue *)cursor; michael@0: cursor += nconsts * sizeof(script->consts()->vector[0]); michael@0: } michael@0: michael@0: if (nobjects != 0) { michael@0: script->objects()->length = nobjects; michael@0: script->objects()->vector = (HeapPtr *)cursor; michael@0: cursor += nobjects * sizeof(script->objects()->vector[0]); michael@0: } michael@0: michael@0: if (nregexps != 0) { michael@0: script->regexps()->length = nregexps; michael@0: script->regexps()->vector = (HeapPtr *)cursor; michael@0: cursor += nregexps * sizeof(script->regexps()->vector[0]); michael@0: } michael@0: michael@0: if (ntrynotes != 0) { michael@0: script->trynotes()->length = ntrynotes; michael@0: script->trynotes()->vector = reinterpret_cast(cursor); michael@0: size_t vectorSize = ntrynotes * sizeof(script->trynotes()->vector[0]); michael@0: #ifdef DEBUG michael@0: memset(cursor, 0, vectorSize); michael@0: #endif michael@0: cursor += vectorSize; michael@0: } michael@0: michael@0: if (nblockscopes != 0) { michael@0: script->blockScopes()->length = nblockscopes; michael@0: script->blockScopes()->vector = reinterpret_cast(cursor); michael@0: size_t vectorSize = nblockscopes * sizeof(script->blockScopes()->vector[0]); michael@0: #ifdef DEBUG michael@0: memset(cursor, 0, vectorSize); michael@0: #endif michael@0: cursor += vectorSize; michael@0: } michael@0: michael@0: if (script->bindings.count() != 0) { michael@0: // Make sure bindings are sufficiently aligned. michael@0: cursor = reinterpret_cast michael@0: (JS_ROUNDUP(reinterpret_cast(cursor), JS_ALIGNMENT_OF(Binding))); michael@0: } michael@0: cursor = script->bindings.switchToScriptStorage(reinterpret_cast(cursor)); michael@0: michael@0: JS_ASSERT(cursor == script->data + size); michael@0: return true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSScript::fullyInitTrivial(ExclusiveContext *cx, Handle script) michael@0: { michael@0: if (!partiallyInit(cx, script, 0, 0, 0, 0, 0, 0)) michael@0: return false; michael@0: michael@0: SharedScriptData *ssd = SharedScriptData::new_(cx, 1, 1, 0); michael@0: if (!ssd) michael@0: return false; michael@0: michael@0: ssd->data[0] = JSOP_RETRVAL; michael@0: ssd->data[1] = SRC_NULL; michael@0: script->setLength(1); michael@0: return SaveSharedScriptData(cx, script, ssd, 1); michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSScript::fullyInitFromEmitter(ExclusiveContext *cx, HandleScript script, BytecodeEmitter *bce) michael@0: { michael@0: /* The counts of indexed things must be checked during code generation. */ michael@0: JS_ASSERT(bce->atomIndices->count() <= INDEX_LIMIT); michael@0: JS_ASSERT(bce->objectList.length <= INDEX_LIMIT); michael@0: JS_ASSERT(bce->regexpList.length <= INDEX_LIMIT); michael@0: michael@0: uint32_t mainLength = bce->offset(); michael@0: uint32_t prologLength = bce->prologOffset(); michael@0: uint32_t nsrcnotes; michael@0: if (!FinishTakingSrcNotes(cx, bce, &nsrcnotes)) michael@0: return false; michael@0: uint32_t natoms = bce->atomIndices->count(); michael@0: if (!partiallyInit(cx, script, michael@0: bce->constList.length(), bce->objectList.length, bce->regexpList.length, michael@0: bce->tryNoteList.length(), bce->blockScopeList.length(), bce->typesetCount)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: JS_ASSERT(script->mainOffset() == 0); michael@0: script->mainOffset_ = prologLength; michael@0: michael@0: script->lineno_ = bce->firstLine; michael@0: michael@0: script->setLength(prologLength + mainLength); michael@0: script->natoms_ = natoms; michael@0: SharedScriptData *ssd = SharedScriptData::new_(cx, script->length(), nsrcnotes, natoms); michael@0: if (!ssd) michael@0: return false; michael@0: michael@0: jsbytecode *code = ssd->data; michael@0: PodCopy(code, bce->prolog.code.begin(), prologLength); michael@0: PodCopy(code + prologLength, bce->code().begin(), mainLength); michael@0: CopySrcNotes(bce, (jssrcnote *)(code + script->length()), nsrcnotes); michael@0: InitAtomMap(bce->atomIndices.getMap(), ssd->atoms()); michael@0: michael@0: if (!SaveSharedScriptData(cx, script, ssd, nsrcnotes)) michael@0: return false; michael@0: michael@0: FunctionBox *funbox = bce->sc->isFunctionBox() ? bce->sc->asFunctionBox() : nullptr; michael@0: michael@0: if (bce->constList.length() != 0) michael@0: bce->constList.finish(script->consts()); michael@0: if (bce->objectList.length != 0) michael@0: bce->objectList.finish(script->objects()); michael@0: if (bce->regexpList.length != 0) michael@0: bce->regexpList.finish(script->regexps()); michael@0: if (bce->tryNoteList.length() != 0) michael@0: bce->tryNoteList.finish(script->trynotes()); michael@0: if (bce->blockScopeList.length() != 0) michael@0: bce->blockScopeList.finish(script->blockScopes()); michael@0: script->strict_ = bce->sc->strict; michael@0: script->explicitUseStrict_ = bce->sc->hasExplicitUseStrict(); michael@0: script->bindingsAccessedDynamically_ = bce->sc->bindingsAccessedDynamically(); michael@0: script->funHasExtensibleScope_ = funbox ? funbox->hasExtensibleScope() : false; michael@0: script->funNeedsDeclEnvObject_ = funbox ? funbox->needsDeclEnvObject() : false; michael@0: script->hasSingletons_ = bce->hasSingletons; michael@0: michael@0: if (funbox) { michael@0: if (funbox->argumentsHasLocalBinding()) { michael@0: // This must precede the script->bindings.transfer() call below michael@0: script->setArgumentsHasVarBinding(); michael@0: if (funbox->definitelyNeedsArgsObj()) michael@0: script->setNeedsArgsObj(true); michael@0: } else { michael@0: JS_ASSERT(!funbox->definitelyNeedsArgsObj()); michael@0: } michael@0: michael@0: script->funLength_ = funbox->length; michael@0: } michael@0: michael@0: RootedFunction fun(cx, nullptr); michael@0: if (funbox) { michael@0: JS_ASSERT(!bce->script->noScriptRval()); michael@0: script->isGeneratorExp_ = funbox->inGenexpLambda; michael@0: script->setGeneratorKind(funbox->generatorKind()); michael@0: script->setFunction(funbox->function()); michael@0: } michael@0: michael@0: // The call to nfixed() depends on the above setFunction() call. michael@0: if (UINT32_MAX - script->nfixed() < bce->maxStackDepth) { michael@0: bce->reportError(nullptr, JSMSG_NEED_DIET, "script"); michael@0: return false; michael@0: } michael@0: script->nslots_ = script->nfixed() + bce->maxStackDepth; michael@0: michael@0: for (unsigned i = 0, n = script->bindings.numArgs(); i < n; ++i) { michael@0: if (script->formalIsAliased(i)) { michael@0: script->funHasAnyAliasedFormal_ = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: size_t michael@0: JSScript::computedSizeOfData() const michael@0: { michael@0: return dataSize(); michael@0: } michael@0: michael@0: size_t michael@0: JSScript::sizeOfData(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: return mallocSizeOf(data); michael@0: } michael@0: michael@0: size_t michael@0: JSScript::sizeOfTypeScript(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: return types->sizeOfIncludingThis(mallocSizeOf); michael@0: } michael@0: michael@0: /* michael@0: * Nb: srcnotes are variable-length. This function computes the number of michael@0: * srcnote *slots*, which may be greater than the number of srcnotes. michael@0: */ michael@0: uint32_t michael@0: JSScript::numNotes() michael@0: { michael@0: jssrcnote *sn; michael@0: jssrcnote *notes_ = notes(); michael@0: for (sn = notes_; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) michael@0: continue; michael@0: return sn - notes_ + 1; /* +1 for the terminator */ michael@0: } michael@0: michael@0: js::GlobalObject& michael@0: JSScript::uninlinedGlobal() const michael@0: { michael@0: return global(); michael@0: } michael@0: michael@0: void michael@0: js::CallNewScriptHook(JSContext *cx, HandleScript script, HandleFunction fun) michael@0: { michael@0: if (script->selfHosted()) michael@0: return; michael@0: michael@0: JS_ASSERT(!script->isActiveEval()); michael@0: if (JSNewScriptHook hook = cx->runtime()->debugHooks.newScriptHook) { michael@0: AutoKeepAtoms keepAtoms(cx->perThreadData); michael@0: hook(cx, script->filename(), script->lineno(), script, fun, michael@0: cx->runtime()->debugHooks.newScriptHookData); michael@0: } michael@0: } michael@0: michael@0: void michael@0: js::CallDestroyScriptHook(FreeOp *fop, JSScript *script) michael@0: { michael@0: if (script->selfHosted()) michael@0: return; michael@0: michael@0: // The hook will only call into JS if a GC is not running. michael@0: if (JSDestroyScriptHook hook = fop->runtime()->debugHooks.destroyScriptHook) michael@0: hook(fop, script, fop->runtime()->debugHooks.destroyScriptHookData); michael@0: script->clearTraps(fop); michael@0: } michael@0: michael@0: void michael@0: JSScript::finalize(FreeOp *fop) michael@0: { michael@0: // NOTE: this JSScript may be partially initialized at this point. E.g. we michael@0: // may have created it and partially initialized it with michael@0: // JSScript::Create(), but not yet finished initializing it with michael@0: // fullyInitFromEmitter() or fullyInitTrivial(). michael@0: michael@0: CallDestroyScriptHook(fop, this); michael@0: fop->runtime()->spsProfiler.onScriptFinalized(this); michael@0: michael@0: if (types) michael@0: types->destroy(); michael@0: michael@0: #ifdef JS_ION michael@0: jit::DestroyIonScripts(fop, this); michael@0: #endif michael@0: michael@0: destroyScriptCounts(fop); michael@0: destroyDebugScript(fop); michael@0: michael@0: if (data) { michael@0: JS_POISON(data, 0xdb, computedSizeOfData()); michael@0: fop->free_(data); michael@0: } michael@0: michael@0: fop->runtime()->lazyScriptCache.remove(this); michael@0: } michael@0: michael@0: static const uint32_t GSN_CACHE_THRESHOLD = 100; michael@0: michael@0: void michael@0: GSNCache::purge() michael@0: { michael@0: code = nullptr; michael@0: if (map.initialized()) michael@0: map.finish(); michael@0: } michael@0: michael@0: jssrcnote * michael@0: js::GetSrcNote(GSNCache &cache, JSScript *script, jsbytecode *pc) michael@0: { michael@0: size_t target = pc - script->code(); michael@0: if (target >= script->length()) michael@0: return nullptr; michael@0: michael@0: if (cache.code == script->code()) { michael@0: JS_ASSERT(cache.map.initialized()); michael@0: GSNCache::Map::Ptr p = cache.map.lookup(pc); michael@0: return p ? p->value() : nullptr; michael@0: } michael@0: michael@0: size_t offset = 0; michael@0: jssrcnote *result; michael@0: for (jssrcnote *sn = script->notes(); ; sn = SN_NEXT(sn)) { michael@0: if (SN_IS_TERMINATOR(sn)) { michael@0: result = nullptr; michael@0: break; michael@0: } michael@0: offset += SN_DELTA(sn); michael@0: if (offset == target && SN_IS_GETTABLE(sn)) { michael@0: result = sn; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (cache.code != script->code() && script->length() >= GSN_CACHE_THRESHOLD) { michael@0: unsigned nsrcnotes = 0; michael@0: for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); michael@0: sn = SN_NEXT(sn)) { michael@0: if (SN_IS_GETTABLE(sn)) michael@0: ++nsrcnotes; michael@0: } michael@0: if (cache.code) { michael@0: JS_ASSERT(cache.map.initialized()); michael@0: cache.map.finish(); michael@0: cache.code = nullptr; michael@0: } michael@0: if (cache.map.init(nsrcnotes)) { michael@0: pc = script->code(); michael@0: for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); michael@0: sn = SN_NEXT(sn)) { michael@0: pc += SN_DELTA(sn); michael@0: if (SN_IS_GETTABLE(sn)) michael@0: JS_ALWAYS_TRUE(cache.map.put(pc, sn)); michael@0: } michael@0: cache.code = script->code(); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: jssrcnote * michael@0: js_GetSrcNote(JSContext *cx, JSScript *script, jsbytecode *pc) michael@0: { michael@0: return GetSrcNote(cx->runtime()->gsnCache, script, pc); michael@0: } michael@0: michael@0: unsigned michael@0: js::PCToLineNumber(unsigned startLine, jssrcnote *notes, jsbytecode *code, jsbytecode *pc, michael@0: unsigned *columnp) michael@0: { michael@0: unsigned lineno = startLine; michael@0: unsigned column = 0; michael@0: michael@0: /* michael@0: * Walk through source notes accumulating their deltas, keeping track of michael@0: * line-number notes, until we pass the note for pc's offset within michael@0: * script->code. michael@0: */ michael@0: ptrdiff_t offset = 0; michael@0: ptrdiff_t target = pc - code; michael@0: for (jssrcnote *sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { michael@0: offset += SN_DELTA(sn); michael@0: SrcNoteType type = (SrcNoteType) SN_TYPE(sn); michael@0: if (type == SRC_SETLINE) { michael@0: if (offset <= target) michael@0: lineno = (unsigned) js_GetSrcNoteOffset(sn, 0); michael@0: column = 0; michael@0: } else if (type == SRC_NEWLINE) { michael@0: if (offset <= target) michael@0: lineno++; michael@0: column = 0; michael@0: } michael@0: michael@0: if (offset > target) michael@0: break; michael@0: michael@0: if (type == SRC_COLSPAN) { michael@0: ptrdiff_t colspan = js_GetSrcNoteOffset(sn, 0); michael@0: michael@0: if (colspan >= SN_COLSPAN_DOMAIN / 2) michael@0: colspan -= SN_COLSPAN_DOMAIN; michael@0: JS_ASSERT(ptrdiff_t(column) + colspan >= 0); michael@0: column += colspan; michael@0: } michael@0: } michael@0: michael@0: if (columnp) michael@0: *columnp = column; michael@0: michael@0: return lineno; michael@0: } michael@0: michael@0: unsigned michael@0: js::PCToLineNumber(JSScript *script, jsbytecode *pc, unsigned *columnp) michael@0: { michael@0: /* Cope with InterpreterFrame.pc value prior to entering Interpret. */ michael@0: if (!pc) michael@0: return 0; michael@0: michael@0: return PCToLineNumber(script->lineno(), script->notes(), script->code(), pc, columnp); michael@0: } michael@0: michael@0: jsbytecode * michael@0: js_LineNumberToPC(JSScript *script, unsigned target) michael@0: { michael@0: ptrdiff_t offset = 0; michael@0: ptrdiff_t best = -1; michael@0: unsigned lineno = script->lineno(); michael@0: unsigned bestdiff = SN_MAX_OFFSET; michael@0: for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { michael@0: /* michael@0: * Exact-match only if offset is not in the prolog; otherwise use michael@0: * nearest greater-or-equal line number match. michael@0: */ michael@0: if (lineno == target && offset >= ptrdiff_t(script->mainOffset())) michael@0: goto out; michael@0: if (lineno >= target) { michael@0: unsigned diff = lineno - target; michael@0: if (diff < bestdiff) { michael@0: bestdiff = diff; michael@0: best = offset; michael@0: } michael@0: } michael@0: offset += SN_DELTA(sn); michael@0: SrcNoteType type = (SrcNoteType) SN_TYPE(sn); michael@0: if (type == SRC_SETLINE) { michael@0: lineno = (unsigned) js_GetSrcNoteOffset(sn, 0); michael@0: } else if (type == SRC_NEWLINE) { michael@0: lineno++; michael@0: } michael@0: } michael@0: if (best >= 0) michael@0: offset = best; michael@0: out: michael@0: return script->offsetToPC(offset); michael@0: } michael@0: michael@0: JS_FRIEND_API(unsigned) michael@0: js_GetScriptLineExtent(JSScript *script) michael@0: { michael@0: unsigned lineno = script->lineno(); michael@0: unsigned maxLineNo = lineno; michael@0: for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { michael@0: SrcNoteType type = (SrcNoteType) SN_TYPE(sn); michael@0: if (type == SRC_SETLINE) michael@0: lineno = (unsigned) js_GetSrcNoteOffset(sn, 0); michael@0: else if (type == SRC_NEWLINE) michael@0: lineno++; michael@0: michael@0: if (maxLineNo < lineno) michael@0: maxLineNo = lineno; michael@0: } michael@0: michael@0: return 1 + maxLineNo - script->lineno(); michael@0: } michael@0: michael@0: void michael@0: js::DescribeScriptedCallerForCompilation(JSContext *cx, MutableHandleScript maybeScript, michael@0: const char **file, unsigned *linenop, michael@0: uint32_t *pcOffset, JSPrincipals **origin, michael@0: LineOption opt) michael@0: { michael@0: if (opt == CALLED_FROM_JSOP_EVAL) { michael@0: jsbytecode *pc = nullptr; michael@0: maybeScript.set(cx->currentScript(&pc)); michael@0: JS_ASSERT(JSOp(*pc) == JSOP_EVAL || JSOp(*pc) == JSOP_SPREADEVAL); michael@0: JS_ASSERT(*(pc + (JSOp(*pc) == JSOP_EVAL ? JSOP_EVAL_LENGTH michael@0: : JSOP_SPREADEVAL_LENGTH)) == JSOP_LINENO); michael@0: *file = maybeScript->filename(); michael@0: *linenop = GET_UINT16(pc + (JSOp(*pc) == JSOP_EVAL ? JSOP_EVAL_LENGTH michael@0: : JSOP_SPREADEVAL_LENGTH)); michael@0: *pcOffset = pc - maybeScript->code(); michael@0: *origin = maybeScript->originPrincipals(); michael@0: return; michael@0: } michael@0: michael@0: NonBuiltinFrameIter iter(cx); michael@0: michael@0: if (iter.done()) { michael@0: maybeScript.set(nullptr); michael@0: *file = nullptr; michael@0: *linenop = 0; michael@0: *pcOffset = 0; michael@0: *origin = cx->compartment()->principals; michael@0: return; michael@0: } michael@0: michael@0: *file = iter.scriptFilename(); michael@0: *linenop = iter.computeLine(); michael@0: *origin = iter.originPrincipals(); michael@0: michael@0: // These values are only used for introducer fields which are debugging michael@0: // information and can be safely left null for asm.js frames. michael@0: if (iter.hasScript()) { michael@0: maybeScript.set(iter.script()); michael@0: *pcOffset = iter.pc() - maybeScript->code(); michael@0: } else { michael@0: maybeScript.set(nullptr); michael@0: *pcOffset = 0; michael@0: } michael@0: } michael@0: michael@0: template michael@0: static inline T * michael@0: Rebase(JSScript *dst, JSScript *src, T *srcp) michael@0: { michael@0: size_t off = reinterpret_cast(srcp) - src->data; michael@0: return reinterpret_cast(dst->data + off); michael@0: } michael@0: michael@0: JSScript * michael@0: js::CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript src, michael@0: NewObjectKind newKind /* = GenericObject */) michael@0: { michael@0: /* NB: Keep this in sync with XDRScript. */ michael@0: michael@0: /* Some embeddings are not careful to use ExposeObjectToActiveJS as needed. */ michael@0: JS_ASSERT(!src->sourceObject()->isMarked(gc::GRAY)); michael@0: michael@0: uint32_t nconsts = src->hasConsts() ? src->consts()->length : 0; michael@0: uint32_t nobjects = src->hasObjects() ? src->objects()->length : 0; michael@0: uint32_t nregexps = src->hasRegexps() ? src->regexps()->length : 0; michael@0: uint32_t ntrynotes = src->hasTrynotes() ? src->trynotes()->length : 0; michael@0: uint32_t nblockscopes = src->hasBlockScopes() ? src->blockScopes()->length : 0; michael@0: michael@0: /* Script data */ michael@0: michael@0: size_t size = src->dataSize(); michael@0: uint8_t *data = AllocScriptData(cx, size); michael@0: if (!data) michael@0: return nullptr; michael@0: michael@0: /* Bindings */ michael@0: michael@0: Rooted bindings(cx); michael@0: InternalHandle bindingsHandle = michael@0: InternalHandle::fromMarkedLocation(bindings.address()); michael@0: if (!Bindings::clone(cx, bindingsHandle, data, src)) michael@0: return nullptr; michael@0: michael@0: /* Objects */ michael@0: michael@0: AutoObjectVector objects(cx); michael@0: if (nobjects != 0) { michael@0: HeapPtrObject *vector = src->objects()->vector; michael@0: for (unsigned i = 0; i < nobjects; i++) { michael@0: RootedObject obj(cx, vector[i]); michael@0: RootedObject clone(cx); michael@0: if (obj->is()) { michael@0: Rooted innerBlock(cx, &obj->as()); michael@0: michael@0: RootedObject enclosingScope(cx); michael@0: if (NestedScopeObject *enclosingBlock = innerBlock->enclosingNestedScope()) michael@0: enclosingScope = objects[FindScopeObjectIndex(src, *enclosingBlock)]; michael@0: else michael@0: enclosingScope = fun; michael@0: michael@0: clone = CloneNestedScopeObject(cx, enclosingScope, innerBlock); michael@0: } else if (obj->is()) { michael@0: RootedFunction innerFun(cx, &obj->as()); michael@0: if (innerFun->isNative()) { michael@0: assertSameCompartment(cx, innerFun); michael@0: clone = innerFun; michael@0: } else { michael@0: if (innerFun->isInterpretedLazy()) { michael@0: AutoCompartment ac(cx, innerFun); michael@0: if (!innerFun->getOrCreateScript(cx)) michael@0: return nullptr; michael@0: } michael@0: RootedObject staticScope(cx, innerFun->nonLazyScript()->enclosingStaticScope()); michael@0: StaticScopeIter ssi(cx, staticScope); michael@0: RootedObject enclosingScope(cx); michael@0: if (ssi.done() || ssi.type() == StaticScopeIter::FUNCTION) michael@0: enclosingScope = fun; michael@0: else if (ssi.type() == StaticScopeIter::BLOCK) michael@0: enclosingScope = objects[FindScopeObjectIndex(src, ssi.block())]; michael@0: else michael@0: enclosingScope = objects[FindScopeObjectIndex(src, ssi.staticWith())]; michael@0: michael@0: clone = CloneFunctionAndScript(cx, enclosingScope, innerFun); michael@0: } michael@0: } else { michael@0: /* michael@0: * Clone object literals emitted for the JSOP_NEWOBJECT opcode. We only emit that michael@0: * instead of the less-optimized JSOP_NEWINIT for self-hosted code or code compiled michael@0: * with JSOPTION_COMPILE_N_GO set. As we don't clone the latter type of code, this michael@0: * case should only ever be hit when cloning objects from self-hosted code. michael@0: */ michael@0: clone = CloneObjectLiteral(cx, cx->global(), obj); michael@0: } michael@0: if (!clone || !objects.append(clone)) michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: /* RegExps */ michael@0: michael@0: AutoObjectVector regexps(cx); michael@0: for (unsigned i = 0; i < nregexps; i++) { michael@0: HeapPtrObject *vector = src->regexps()->vector; michael@0: for (unsigned i = 0; i < nregexps; i++) { michael@0: JSObject *clone = CloneScriptRegExpObject(cx, vector[i]->as()); michael@0: if (!clone || !regexps.append(clone)) michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Wrap the script source object as needed. Self-hosted scripts may be michael@0: * in another runtime, so lazily create a new script source object to michael@0: * use for them. michael@0: */ michael@0: RootedObject sourceObject(cx); michael@0: if (cx->runtime()->isSelfHostingCompartment(src->compartment())) { michael@0: if (!cx->compartment()->selfHostingScriptSource) { michael@0: CompileOptions options(cx); michael@0: FillSelfHostingCompileOptions(options); michael@0: michael@0: ScriptSourceObject *obj = frontend::CreateScriptSourceObject(cx, options); michael@0: if (!obj) michael@0: return nullptr; michael@0: cx->compartment()->selfHostingScriptSource = obj; michael@0: } michael@0: sourceObject = cx->compartment()->selfHostingScriptSource; michael@0: } else { michael@0: sourceObject = src->sourceObject(); michael@0: if (!cx->compartment()->wrap(cx, &sourceObject)) michael@0: return nullptr; michael@0: } michael@0: michael@0: /* Now that all fallible allocation is complete, create the GC thing. */ michael@0: michael@0: CompileOptions options(cx); michael@0: options.setOriginPrincipals(src->originPrincipals()) michael@0: .setCompileAndGo(src->compileAndGo()) michael@0: .setSelfHostingMode(src->selfHosted()) michael@0: .setNoScriptRval(src->noScriptRval()) michael@0: .setVersion(src->getVersion()); michael@0: michael@0: RootedScript dst(cx, JSScript::Create(cx, enclosingScope, src->savedCallerFun(), michael@0: options, src->staticLevel(), michael@0: sourceObject, src->sourceStart(), src->sourceEnd())); michael@0: if (!dst) { michael@0: js_free(data); michael@0: return nullptr; michael@0: } michael@0: michael@0: dst->bindings = bindings; michael@0: michael@0: /* This assignment must occur before all the Rebase calls. */ michael@0: dst->data = data; michael@0: dst->dataSize_ = size; michael@0: memcpy(data, src->data, size); michael@0: michael@0: /* Script filenames, bytecodes and atoms are runtime-wide. */ michael@0: dst->setCode(src->code()); michael@0: dst->atoms = src->atoms; michael@0: michael@0: dst->setLength(src->length()); michael@0: dst->lineno_ = src->lineno(); michael@0: dst->mainOffset_ = src->mainOffset(); michael@0: dst->natoms_ = src->natoms(); michael@0: dst->funLength_ = src->funLength(); michael@0: dst->nTypeSets_ = src->nTypeSets(); michael@0: dst->nslots_ = src->nslots(); michael@0: if (src->argumentsHasVarBinding()) { michael@0: dst->setArgumentsHasVarBinding(); michael@0: if (src->analyzedArgsUsage()) michael@0: dst->setNeedsArgsObj(src->needsArgsObj()); michael@0: } michael@0: dst->cloneHasArray(src); michael@0: dst->strict_ = src->strict(); michael@0: dst->explicitUseStrict_ = src->explicitUseStrict(); michael@0: dst->bindingsAccessedDynamically_ = src->bindingsAccessedDynamically(); michael@0: dst->funHasExtensibleScope_ = src->funHasExtensibleScope(); michael@0: dst->funNeedsDeclEnvObject_ = src->funNeedsDeclEnvObject(); michael@0: dst->funHasAnyAliasedFormal_ = src->funHasAnyAliasedFormal(); michael@0: dst->hasSingletons_ = src->hasSingletons(); michael@0: dst->treatAsRunOnce_ = src->treatAsRunOnce(); michael@0: dst->isGeneratorExp_ = src->isGeneratorExp(); michael@0: dst->setGeneratorKind(src->generatorKind()); michael@0: michael@0: /* Copy over hints. */ michael@0: dst->shouldInline_ = src->shouldInline(); michael@0: dst->shouldCloneAtCallsite_ = src->shouldCloneAtCallsite(); michael@0: dst->isCallsiteClone_ = src->isCallsiteClone(); michael@0: michael@0: if (nconsts != 0) { michael@0: HeapValue *vector = Rebase(dst, src, src->consts()->vector); michael@0: dst->consts()->vector = vector; michael@0: for (unsigned i = 0; i < nconsts; ++i) michael@0: JS_ASSERT_IF(vector[i].isMarkable(), vector[i].toString()->isAtom()); michael@0: } michael@0: if (nobjects != 0) { michael@0: HeapPtrObject *vector = Rebase >(dst, src, src->objects()->vector); michael@0: dst->objects()->vector = vector; michael@0: for (unsigned i = 0; i < nobjects; ++i) michael@0: vector[i].init(objects[i]); michael@0: } michael@0: if (nregexps != 0) { michael@0: HeapPtrObject *vector = Rebase >(dst, src, src->regexps()->vector); michael@0: dst->regexps()->vector = vector; michael@0: for (unsigned i = 0; i < nregexps; ++i) michael@0: vector[i].init(regexps[i]); michael@0: } michael@0: if (ntrynotes != 0) michael@0: dst->trynotes()->vector = Rebase(dst, src, src->trynotes()->vector); michael@0: if (nblockscopes != 0) michael@0: dst->blockScopes()->vector = Rebase(dst, src, src->blockScopes()->vector); michael@0: michael@0: return dst; michael@0: } michael@0: michael@0: bool michael@0: js::CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone, michael@0: NewObjectKind newKind /* = GenericObject */) michael@0: { michael@0: JS_ASSERT(clone->isInterpreted()); michael@0: michael@0: RootedScript script(cx, clone->nonLazyScript()); michael@0: JS_ASSERT(script); michael@0: JS_ASSERT(script->compartment() == original->compartment()); michael@0: JS_ASSERT_IF(script->compartment() != cx->compartment(), michael@0: !script->enclosingStaticScope()); michael@0: michael@0: RootedObject scope(cx, script->enclosingStaticScope()); michael@0: michael@0: clone->mutableScript().init(nullptr); michael@0: michael@0: JSScript *cscript = CloneScript(cx, scope, clone, script, newKind); michael@0: if (!cscript) michael@0: return false; michael@0: michael@0: clone->setScript(cscript); michael@0: cscript->setFunction(clone); michael@0: michael@0: script = clone->nonLazyScript(); michael@0: CallNewScriptHook(cx, script, clone); michael@0: RootedGlobalObject global(cx, script->compileAndGo() ? &script->global() : nullptr); michael@0: Debugger::onNewScript(cx, script, global); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: DebugScript * michael@0: JSScript::debugScript() michael@0: { michael@0: JS_ASSERT(hasDebugScript_); michael@0: DebugScriptMap *map = compartment()->debugScriptMap; michael@0: JS_ASSERT(map); michael@0: DebugScriptMap::Ptr p = map->lookup(this); michael@0: JS_ASSERT(p); michael@0: return p->value(); michael@0: } michael@0: michael@0: DebugScript * michael@0: JSScript::releaseDebugScript() michael@0: { michael@0: JS_ASSERT(hasDebugScript_); michael@0: DebugScriptMap *map = compartment()->debugScriptMap; michael@0: JS_ASSERT(map); michael@0: DebugScriptMap::Ptr p = map->lookup(this); michael@0: JS_ASSERT(p); michael@0: DebugScript *debug = p->value(); michael@0: map->remove(p); michael@0: hasDebugScript_ = false; michael@0: return debug; michael@0: } michael@0: michael@0: void michael@0: JSScript::destroyDebugScript(FreeOp *fop) michael@0: { michael@0: if (hasDebugScript_) { michael@0: for (jsbytecode *pc = code(); pc < codeEnd(); pc++) { michael@0: if (BreakpointSite *site = getBreakpointSite(pc)) { michael@0: /* Breakpoints are swept before finalization. */ michael@0: JS_ASSERT(site->firstBreakpoint() == nullptr); michael@0: site->clearTrap(fop, nullptr, nullptr); michael@0: JS_ASSERT(getBreakpointSite(pc) == nullptr); michael@0: } michael@0: } michael@0: fop->free_(releaseDebugScript()); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: JSScript::ensureHasDebugScript(JSContext *cx) michael@0: { michael@0: if (hasDebugScript_) michael@0: return true; michael@0: michael@0: size_t nbytes = offsetof(DebugScript, breakpoints) + length() * sizeof(BreakpointSite*); michael@0: DebugScript *debug = (DebugScript *) cx->calloc_(nbytes); michael@0: if (!debug) michael@0: return false; michael@0: michael@0: /* Create compartment's debugScriptMap if necessary. */ michael@0: DebugScriptMap *map = compartment()->debugScriptMap; michael@0: if (!map) { michael@0: map = cx->new_(); michael@0: if (!map || !map->init()) { michael@0: js_free(debug); michael@0: js_delete(map); michael@0: return false; michael@0: } michael@0: compartment()->debugScriptMap = map; michael@0: } michael@0: michael@0: if (!map->putNew(this, debug)) { michael@0: js_free(debug); michael@0: return false; michael@0: } michael@0: hasDebugScript_ = true; // safe to set this; we can't fail after this point michael@0: michael@0: /* michael@0: * Ensure that any Interpret() instances running on this script have michael@0: * interrupts enabled. The interrupts must stay enabled until the michael@0: * debug state is destroyed. michael@0: */ michael@0: for (ActivationIterator iter(cx->runtime()); !iter.done(); ++iter) { michael@0: if (iter->isInterpreter()) michael@0: iter->asInterpreter()->enableInterruptsIfRunning(this); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: JSScript::setNewStepMode(FreeOp *fop, uint32_t newValue) michael@0: { michael@0: DebugScript *debug = debugScript(); michael@0: uint32_t prior = debug->stepMode; michael@0: debug->stepMode = newValue; michael@0: michael@0: if (!prior != !newValue) { michael@0: #ifdef JS_ION michael@0: if (hasBaselineScript()) michael@0: baseline->toggleDebugTraps(this, nullptr); michael@0: #endif michael@0: michael@0: if (!stepModeEnabled() && !debug->numSites) michael@0: fop->free_(releaseDebugScript()); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: JSScript::setStepModeFlag(JSContext *cx, bool step) michael@0: { michael@0: if (!ensureHasDebugScript(cx)) michael@0: return false; michael@0: michael@0: setNewStepMode(cx->runtime()->defaultFreeOp(), michael@0: (debugScript()->stepMode & stepCountMask) | michael@0: (step ? stepFlagMask : 0)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSScript::incrementStepModeCount(JSContext *cx) michael@0: { michael@0: assertSameCompartment(cx, this); michael@0: MOZ_ASSERT(cx->compartment()->debugMode()); michael@0: michael@0: if (!ensureHasDebugScript(cx)) michael@0: return false; michael@0: michael@0: DebugScript *debug = debugScript(); michael@0: uint32_t count = debug->stepMode & stepCountMask; michael@0: MOZ_ASSERT(((count + 1) & stepCountMask) == count + 1); michael@0: michael@0: setNewStepMode(cx->runtime()->defaultFreeOp(), michael@0: (debug->stepMode & stepFlagMask) | michael@0: ((count + 1) & stepCountMask)); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: JSScript::decrementStepModeCount(FreeOp *fop) michael@0: { michael@0: DebugScript *debug = debugScript(); michael@0: uint32_t count = debug->stepMode & stepCountMask; michael@0: michael@0: setNewStepMode(fop, michael@0: (debug->stepMode & stepFlagMask) | michael@0: ((count - 1) & stepCountMask)); michael@0: } michael@0: michael@0: BreakpointSite * michael@0: JSScript::getOrCreateBreakpointSite(JSContext *cx, jsbytecode *pc) michael@0: { michael@0: if (!ensureHasDebugScript(cx)) michael@0: return nullptr; michael@0: michael@0: DebugScript *debug = debugScript(); michael@0: BreakpointSite *&site = debug->breakpoints[pcToOffset(pc)]; michael@0: michael@0: if (!site) { michael@0: site = cx->runtime()->new_(this, pc); michael@0: if (!site) { michael@0: js_ReportOutOfMemory(cx); michael@0: return nullptr; michael@0: } michael@0: debug->numSites++; michael@0: } michael@0: michael@0: return site; michael@0: } michael@0: michael@0: void michael@0: JSScript::destroyBreakpointSite(FreeOp *fop, jsbytecode *pc) michael@0: { michael@0: DebugScript *debug = debugScript(); michael@0: BreakpointSite *&site = debug->breakpoints[pcToOffset(pc)]; michael@0: JS_ASSERT(site); michael@0: michael@0: fop->delete_(site); michael@0: site = nullptr; michael@0: michael@0: if (--debug->numSites == 0 && !stepModeEnabled()) michael@0: fop->free_(releaseDebugScript()); michael@0: } michael@0: michael@0: void michael@0: JSScript::clearBreakpointsIn(FreeOp *fop, js::Debugger *dbg, JSObject *handler) michael@0: { michael@0: if (!hasAnyBreakpointsOrStepMode()) michael@0: return; michael@0: michael@0: for (jsbytecode *pc = code(); pc < codeEnd(); pc++) { michael@0: BreakpointSite *site = getBreakpointSite(pc); michael@0: if (site) { michael@0: Breakpoint *nextbp; michael@0: for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) { michael@0: nextbp = bp->nextInSite(); michael@0: if ((!dbg || bp->debugger == dbg) && (!handler || bp->getHandler() == handler)) michael@0: bp->destroy(fop); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: JSScript::hasBreakpointsAt(jsbytecode *pc) michael@0: { michael@0: BreakpointSite *site = getBreakpointSite(pc); michael@0: if (!site) michael@0: return false; michael@0: michael@0: return site->enabledCount > 0 || site->trapHandler; michael@0: } michael@0: michael@0: void michael@0: JSScript::clearTraps(FreeOp *fop) michael@0: { michael@0: if (!hasAnyBreakpointsOrStepMode()) michael@0: return; michael@0: michael@0: for (jsbytecode *pc = code(); pc < codeEnd(); pc++) { michael@0: BreakpointSite *site = getBreakpointSite(pc); michael@0: if (site) michael@0: site->clearTrap(fop); michael@0: } michael@0: } michael@0: michael@0: void michael@0: JSScript::markChildren(JSTracer *trc) michael@0: { michael@0: // NOTE: this JSScript may be partially initialized at this point. E.g. we michael@0: // may have created it and partially initialized it with michael@0: // JSScript::Create(), but not yet finished initializing it with michael@0: // fullyInitFromEmitter() or fullyInitTrivial(). michael@0: michael@0: JS_ASSERT_IF(trc->runtime()->gcStrictCompartmentChecking, zone()->isCollecting()); michael@0: michael@0: for (uint32_t i = 0; i < natoms(); ++i) { michael@0: if (atoms[i]) michael@0: MarkString(trc, &atoms[i], "atom"); michael@0: } michael@0: michael@0: if (hasObjects()) { michael@0: ObjectArray *objarray = objects(); michael@0: MarkObjectRange(trc, objarray->length, objarray->vector, "objects"); michael@0: } michael@0: michael@0: if (hasRegexps()) { michael@0: ObjectArray *objarray = regexps(); michael@0: MarkObjectRange(trc, objarray->length, objarray->vector, "objects"); michael@0: } michael@0: michael@0: if (hasConsts()) { michael@0: ConstArray *constarray = consts(); michael@0: MarkValueRange(trc, constarray->length, constarray->vector, "consts"); michael@0: } michael@0: michael@0: if (sourceObject()) { michael@0: JS_ASSERT(sourceObject()->compartment() == compartment()); michael@0: MarkObject(trc, &sourceObject_, "sourceObject"); michael@0: } michael@0: michael@0: if (functionNonDelazifying()) michael@0: MarkObject(trc, &function_, "function"); michael@0: michael@0: if (enclosingScopeOrOriginalFunction_) michael@0: MarkObject(trc, &enclosingScopeOrOriginalFunction_, "enclosing"); michael@0: michael@0: if (maybeLazyScript()) michael@0: MarkLazyScriptUnbarriered(trc, &lazyScript, "lazyScript"); michael@0: michael@0: if (IS_GC_MARKING_TRACER(trc)) { michael@0: compartment()->mark(); michael@0: michael@0: if (code()) michael@0: MarkScriptData(trc->runtime(), code()); michael@0: } michael@0: michael@0: bindings.trace(trc); michael@0: michael@0: if (hasAnyBreakpointsOrStepMode()) { michael@0: for (unsigned i = 0; i < length(); i++) { michael@0: BreakpointSite *site = debugScript()->breakpoints[i]; michael@0: if (site && site->trapHandler) michael@0: MarkValue(trc, &site->trapClosure, "trap closure"); michael@0: } michael@0: } michael@0: michael@0: #ifdef JS_ION michael@0: jit::TraceIonScripts(trc, this); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: LazyScript::markChildren(JSTracer *trc) michael@0: { michael@0: if (function_) michael@0: MarkObject(trc, &function_, "function"); michael@0: michael@0: if (sourceObject_) michael@0: MarkObject(trc, &sourceObject_, "sourceObject"); michael@0: michael@0: if (enclosingScope_) michael@0: MarkObject(trc, &enclosingScope_, "enclosingScope"); michael@0: michael@0: if (script_) michael@0: MarkScript(trc, &script_, "realScript"); michael@0: michael@0: HeapPtrAtom *freeVariables = this->freeVariables(); michael@0: for (size_t i = 0; i < numFreeVariables(); i++) michael@0: MarkString(trc, &freeVariables[i], "lazyScriptFreeVariable"); michael@0: michael@0: HeapPtrFunction *innerFunctions = this->innerFunctions(); michael@0: for (size_t i = 0; i < numInnerFunctions(); i++) michael@0: MarkObject(trc, &innerFunctions[i], "lazyScriptInnerFunction"); michael@0: } michael@0: michael@0: void michael@0: LazyScript::finalize(FreeOp *fop) michael@0: { michael@0: if (table_) michael@0: fop->free_(table_); michael@0: } michael@0: michael@0: NestedScopeObject * michael@0: JSScript::getStaticScope(jsbytecode *pc) michael@0: { michael@0: JS_ASSERT(containsPC(pc)); michael@0: michael@0: if (!hasBlockScopes()) michael@0: return nullptr; michael@0: michael@0: ptrdiff_t offset = pc - main(); michael@0: michael@0: if (offset < 0) michael@0: return nullptr; michael@0: michael@0: BlockScopeArray *scopes = blockScopes(); michael@0: NestedScopeObject *blockChain = nullptr; michael@0: michael@0: // Find the innermost block chain using a binary search. michael@0: size_t bottom = 0; michael@0: size_t top = scopes->length; michael@0: michael@0: while (bottom < top) { michael@0: size_t mid = bottom + (top - bottom) / 2; michael@0: const BlockScopeNote *note = &scopes->vector[mid]; michael@0: if (note->start <= offset) { michael@0: // Block scopes are ordered in the list by their starting offset, and since michael@0: // blocks form a tree ones earlier in the list may cover the pc even if michael@0: // later blocks end before the pc. This only happens when the earlier block michael@0: // is a parent of the later block, so we need to check parents of |mid| in michael@0: // the searched range for coverage. michael@0: size_t check = mid; michael@0: while (check >= bottom) { michael@0: const BlockScopeNote *checkNote = &scopes->vector[check]; michael@0: JS_ASSERT(checkNote->start <= offset); michael@0: if (offset < checkNote->start + checkNote->length) { michael@0: // We found a matching block chain but there may be inner ones michael@0: // at a higher block chain index than mid. Continue the binary search. michael@0: if (checkNote->index == BlockScopeNote::NoBlockScopeIndex) michael@0: blockChain = nullptr; michael@0: else michael@0: blockChain = &getObject(checkNote->index)->as(); michael@0: break; michael@0: } michael@0: if (checkNote->parent == UINT32_MAX) michael@0: break; michael@0: check = checkNote->parent; michael@0: } michael@0: bottom = mid + 1; michael@0: } else { michael@0: top = mid; michael@0: } michael@0: } michael@0: michael@0: return blockChain; michael@0: } michael@0: michael@0: void michael@0: JSScript::setArgumentsHasVarBinding() michael@0: { michael@0: argsHasVarBinding_ = true; michael@0: #ifdef JS_ION michael@0: needsArgsAnalysis_ = true; michael@0: #else michael@0: // The arguments analysis is performed by IonBuilder. michael@0: needsArgsObj_ = true; michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: JSScript::setNeedsArgsObj(bool needsArgsObj) michael@0: { michael@0: JS_ASSERT_IF(needsArgsObj, argumentsHasVarBinding()); michael@0: needsArgsAnalysis_ = false; michael@0: needsArgsObj_ = needsArgsObj; michael@0: } michael@0: michael@0: void michael@0: js::SetFrameArgumentsObject(JSContext *cx, AbstractFramePtr frame, michael@0: HandleScript script, JSObject *argsobj) michael@0: { michael@0: /* michael@0: * Replace any optimized arguments in the frame with an explicit arguments michael@0: * object. Note that 'arguments' may have already been overwritten. michael@0: */ michael@0: michael@0: InternalBindingsHandle bindings(script, &script->bindings); michael@0: const uint32_t var = Bindings::argumentsVarIndex(cx, bindings); michael@0: michael@0: if (script->varIsAliased(var)) { michael@0: /* michael@0: * Scan the script to find the slot in the call object that 'arguments' michael@0: * is assigned to. michael@0: */ michael@0: jsbytecode *pc = script->code(); michael@0: while (*pc != JSOP_ARGUMENTS) michael@0: pc += GetBytecodeLength(pc); michael@0: pc += JSOP_ARGUMENTS_LENGTH; michael@0: JS_ASSERT(*pc == JSOP_SETALIASEDVAR); michael@0: michael@0: // Note that here and below, it is insufficient to only check for michael@0: // JS_OPTIMIZED_ARGUMENTS, as Ion could have optimized out the michael@0: // arguments slot. michael@0: if (IsOptimizedPlaceholderMagicValue(frame.callObj().as().aliasedVar(pc))) michael@0: frame.callObj().as().setAliasedVar(cx, pc, cx->names().arguments, ObjectValue(*argsobj)); michael@0: } else { michael@0: if (IsOptimizedPlaceholderMagicValue(frame.unaliasedLocal(var))) michael@0: frame.unaliasedLocal(var) = ObjectValue(*argsobj); michael@0: } michael@0: } michael@0: michael@0: /* static */ bool michael@0: JSScript::argumentsOptimizationFailed(JSContext *cx, HandleScript script) michael@0: { michael@0: JS_ASSERT(script->functionNonDelazifying()); michael@0: JS_ASSERT(script->analyzedArgsUsage()); michael@0: JS_ASSERT(script->argumentsHasVarBinding()); michael@0: michael@0: /* michael@0: * It is possible that the arguments optimization has already failed, michael@0: * everything has been fixed up, but there was an outstanding magic value michael@0: * on the stack that has just now flowed into an apply. In this case, there michael@0: * is nothing to do; GuardFunApplySpeculation will patch in the real michael@0: * argsobj. michael@0: */ michael@0: if (script->needsArgsObj()) michael@0: return true; michael@0: michael@0: JS_ASSERT(!script->isGenerator()); michael@0: michael@0: script->needsArgsObj_ = true; michael@0: michael@0: #ifdef JS_ION michael@0: /* michael@0: * Since we can't invalidate baseline scripts, set a flag that's checked from michael@0: * JIT code to indicate the arguments optimization failed and JSOP_ARGUMENTS michael@0: * should create an arguments object next time. michael@0: */ michael@0: if (script->hasBaselineScript()) michael@0: script->baselineScript()->setNeedsArgsObj(); michael@0: #endif michael@0: michael@0: /* michael@0: * By design, the arguments optimization is only made when there are no michael@0: * outstanding cases of MagicValue(JS_OPTIMIZED_ARGUMENTS) at any points michael@0: * where the optimization could fail, other than an active invocation of michael@0: * 'f.apply(x, arguments)'. Thus, there are no outstanding values of michael@0: * MagicValue(JS_OPTIMIZED_ARGUMENTS) on the stack. However, there are michael@0: * three things that need fixup: michael@0: * - there may be any number of activations of this script that don't have michael@0: * an argsObj that now need one. michael@0: * - jit code compiled (and possible active on the stack) with the static michael@0: * assumption of !script->needsArgsObj(); michael@0: * - type inference data for the script assuming script->needsArgsObj michael@0: */ michael@0: for (AllFramesIter i(cx); !i.done(); ++i) { michael@0: /* michael@0: * We cannot reliably create an arguments object for Ion activations of michael@0: * this script. To maintain the invariant that "script->needsArgsObj michael@0: * implies fp->hasArgsObj", the Ion bail mechanism will create an michael@0: * arguments object right after restoring the BaselineFrame and before michael@0: * entering Baseline code (in jit::FinishBailoutToBaseline). michael@0: */ michael@0: if (i.isIon()) michael@0: continue; michael@0: AbstractFramePtr frame = i.abstractFramePtr(); michael@0: if (frame.isFunctionFrame() && frame.script() == script) { michael@0: ArgumentsObject *argsobj = ArgumentsObject::createExpected(cx, frame); michael@0: if (!argsobj) { michael@0: /* michael@0: * We can't leave stack frames with script->needsArgsObj but no michael@0: * arguments object. It is, however, safe to leave frames with michael@0: * an arguments object but !script->needsArgsObj. michael@0: */ michael@0: script->needsArgsObj_ = false; michael@0: return false; michael@0: } michael@0: michael@0: SetFrameArgumentsObject(cx, frame, script, argsobj); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JSScript::varIsAliased(uint32_t varSlot) michael@0: { michael@0: return bindings.bindingIsAliased(bindings.numArgs() + varSlot); michael@0: } michael@0: michael@0: bool michael@0: JSScript::formalIsAliased(unsigned argSlot) michael@0: { michael@0: return bindings.bindingIsAliased(argSlot); michael@0: } michael@0: michael@0: bool michael@0: JSScript::formalLivesInArgumentsObject(unsigned argSlot) michael@0: { michael@0: return argsObjAliasesFormals() && !formalIsAliased(argSlot); michael@0: } michael@0: michael@0: LazyScript::LazyScript(JSFunction *fun, void *table, uint64_t packedFields, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column) michael@0: : script_(nullptr), michael@0: function_(fun), michael@0: enclosingScope_(nullptr), michael@0: sourceObject_(nullptr), michael@0: table_(table), michael@0: packedFields_(packedFields), michael@0: begin_(begin), michael@0: end_(end), michael@0: lineno_(lineno), michael@0: column_(column) michael@0: { michael@0: JS_ASSERT(begin <= end); michael@0: } michael@0: michael@0: void michael@0: LazyScript::initScript(JSScript *script) michael@0: { michael@0: JS_ASSERT(script && !script_); michael@0: script_ = script; michael@0: } michael@0: michael@0: void michael@0: LazyScript::resetScript() michael@0: { michael@0: JS_ASSERT(script_); michael@0: script_ = nullptr; michael@0: } michael@0: michael@0: void michael@0: LazyScript::setParent(JSObject *enclosingScope, ScriptSourceObject *sourceObject) michael@0: { michael@0: JS_ASSERT(!sourceObject_ && !enclosingScope_); michael@0: JS_ASSERT_IF(enclosingScope, function_->compartment() == enclosingScope->compartment()); michael@0: JS_ASSERT(function_->compartment() == sourceObject->compartment()); michael@0: michael@0: enclosingScope_ = enclosingScope; michael@0: sourceObject_ = sourceObject; michael@0: } michael@0: michael@0: ScriptSourceObject * michael@0: LazyScript::sourceObject() const michael@0: { michael@0: return sourceObject_ ? &sourceObject_->as() : nullptr; michael@0: } michael@0: michael@0: /* static */ LazyScript * michael@0: LazyScript::CreateRaw(ExclusiveContext *cx, HandleFunction fun, michael@0: uint64_t packedFields, uint32_t begin, uint32_t end, michael@0: uint32_t lineno, uint32_t column) michael@0: { michael@0: union { michael@0: PackedView p; michael@0: uint64_t packed; michael@0: }; michael@0: michael@0: packed = packedFields; michael@0: michael@0: // Reset runtime flags to obtain a fresh LazyScript. michael@0: p.hasBeenCloned = false; michael@0: p.treatAsRunOnce = false; michael@0: michael@0: size_t bytes = (p.numFreeVariables * sizeof(HeapPtrAtom)) michael@0: + (p.numInnerFunctions * sizeof(HeapPtrFunction)); michael@0: michael@0: ScopedJSFreePtr table(bytes ? cx->malloc_(bytes) : nullptr); michael@0: if (bytes && !table) michael@0: return nullptr; michael@0: michael@0: LazyScript *res = js_NewGCLazyScript(cx); michael@0: if (!res) michael@0: return nullptr; michael@0: michael@0: cx->compartment()->scheduleDelazificationForDebugMode(); michael@0: michael@0: return new (res) LazyScript(fun, table.forget(), packed, begin, end, lineno, column); michael@0: } michael@0: michael@0: /* static */ LazyScript * michael@0: LazyScript::CreateRaw(ExclusiveContext *cx, HandleFunction fun, michael@0: uint32_t numFreeVariables, uint32_t numInnerFunctions, JSVersion version, michael@0: uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column) michael@0: { michael@0: union { michael@0: PackedView p; michael@0: uint64_t packedFields; michael@0: }; michael@0: michael@0: p.version = version; michael@0: p.numFreeVariables = numFreeVariables; michael@0: p.numInnerFunctions = numInnerFunctions; michael@0: p.generatorKindBits = GeneratorKindAsBits(NotGenerator); michael@0: p.strict = false; michael@0: p.bindingsAccessedDynamically = false; michael@0: p.hasDebuggerStatement = false; michael@0: p.directlyInsideEval = false; michael@0: p.usesArgumentsAndApply = false; michael@0: michael@0: LazyScript *res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, lineno, column); michael@0: JS_ASSERT_IF(res, res->version() == version); michael@0: return res; michael@0: } michael@0: michael@0: /* static */ LazyScript * michael@0: LazyScript::Create(ExclusiveContext *cx, HandleFunction fun, michael@0: uint64_t packedFields, uint32_t begin, uint32_t end, michael@0: uint32_t lineno, uint32_t column) michael@0: { michael@0: // Dummy atom which is not a valid property name. michael@0: RootedAtom dummyAtom(cx, cx->names().comma); michael@0: michael@0: // Dummy function which is not a valid function as this is the one which is michael@0: // holding this lazy script. michael@0: HandleFunction dummyFun = fun; michael@0: michael@0: LazyScript *res = LazyScript::CreateRaw(cx, fun, packedFields, begin, end, lineno, column); michael@0: if (!res) michael@0: return nullptr; michael@0: michael@0: // Fill with dummies, to be GC-safe after the initialization of the free michael@0: // variables and inner functions. michael@0: size_t i, num; michael@0: HeapPtrAtom *variables = res->freeVariables(); michael@0: for (i = 0, num = res->numFreeVariables(); i < num; i++) michael@0: variables[i].init(dummyAtom); michael@0: michael@0: HeapPtrFunction *functions = res->innerFunctions(); michael@0: for (i = 0, num = res->numInnerFunctions(); i < num; i++) michael@0: functions[i].init(dummyFun); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: void michael@0: LazyScript::initRuntimeFields(uint64_t packedFields) michael@0: { michael@0: union { michael@0: PackedView p; michael@0: uint64_t packed; michael@0: }; michael@0: michael@0: packed = packedFields; michael@0: p_.hasBeenCloned = p.hasBeenCloned; michael@0: p_.treatAsRunOnce = p.treatAsRunOnce; michael@0: } michael@0: michael@0: bool michael@0: LazyScript::hasUncompiledEnclosingScript() const michael@0: { michael@0: // It can happen that we created lazy scripts while compiling an enclosing michael@0: // script, but we errored out while compiling that script. When we iterate michael@0: // over lazy script in a compartment, we might see lazy scripts that never michael@0: // escaped to script and should be ignored. michael@0: // michael@0: // If the enclosing scope is a function with a null script or has a script michael@0: // without code, it was not successfully compiled. michael@0: michael@0: if (!enclosingScope() || !enclosingScope()->is()) michael@0: return false; michael@0: michael@0: JSFunction &fun = enclosingScope()->as(); michael@0: return fun.isInterpreted() && (!fun.mutableScript() || !fun.nonLazyScript()->code()); michael@0: } michael@0: michael@0: uint32_t michael@0: LazyScript::staticLevel(JSContext *cx) const michael@0: { michael@0: for (StaticScopeIter ssi(enclosingScope()); !ssi.done(); ssi++) { michael@0: if (ssi.type() == StaticScopeIter::FUNCTION) michael@0: return ssi.funScript()->staticLevel() + 1; michael@0: } michael@0: return 1; michael@0: } michael@0: michael@0: void michael@0: JSScript::updateBaselineOrIonRaw() michael@0: { michael@0: #ifdef JS_ION michael@0: if (hasIonScript()) { michael@0: baselineOrIonRaw = ion->method()->raw(); michael@0: baselineOrIonSkipArgCheck = ion->method()->raw() + ion->getSkipArgCheckEntryOffset(); michael@0: } else if (hasBaselineScript()) { michael@0: baselineOrIonRaw = baseline->method()->raw(); michael@0: baselineOrIonSkipArgCheck = baseline->method()->raw(); michael@0: } else { michael@0: baselineOrIonRaw = nullptr; michael@0: baselineOrIonSkipArgCheck = nullptr; michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: bool michael@0: JSScript::hasLoops() michael@0: { michael@0: if (!hasTrynotes()) michael@0: return false; michael@0: JSTryNote *tn = trynotes()->vector; michael@0: JSTryNote *tnlimit = tn + trynotes()->length; michael@0: for (; tn < tnlimit; tn++) { michael@0: if (tn->kind == JSTRY_ITER || tn->kind == JSTRY_LOOP) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static inline void michael@0: LazyScriptHash(uint32_t lineno, uint32_t column, uint32_t begin, uint32_t end, michael@0: HashNumber hashes[3]) michael@0: { michael@0: HashNumber hash = lineno; michael@0: hash = RotateLeft(hash, 4) ^ column; michael@0: hash = RotateLeft(hash, 4) ^ begin; michael@0: hash = RotateLeft(hash, 4) ^ end; michael@0: michael@0: hashes[0] = hash; michael@0: hashes[1] = RotateLeft(hashes[0], 4) ^ begin; michael@0: hashes[2] = RotateLeft(hashes[1], 4) ^ end; michael@0: } michael@0: michael@0: void michael@0: LazyScriptHashPolicy::hash(const Lookup &lookup, HashNumber hashes[3]) michael@0: { michael@0: LazyScript *lazy = lookup.lazy; michael@0: LazyScriptHash(lazy->lineno(), lazy->column(), lazy->begin(), lazy->end(), hashes); michael@0: } michael@0: michael@0: void michael@0: LazyScriptHashPolicy::hash(JSScript *script, HashNumber hashes[3]) michael@0: { michael@0: LazyScriptHash(script->lineno(), script->column(), script->sourceStart(), script->sourceEnd(), hashes); michael@0: } michael@0: michael@0: bool michael@0: LazyScriptHashPolicy::match(JSScript *script, const Lookup &lookup) michael@0: { michael@0: JSContext *cx = lookup.cx; michael@0: LazyScript *lazy = lookup.lazy; michael@0: michael@0: // To be a match, the script and lazy script need to have the same line michael@0: // and column and to be at the same position within their respective michael@0: // source blobs, and to have the same source contents and version. michael@0: // michael@0: // While the surrounding code in the source may differ, this is michael@0: // sufficient to ensure that compiling the lazy script will yield an michael@0: // identical result to compiling the original script. michael@0: // michael@0: // Note that the filenames and origin principals of the lazy script and michael@0: // original script can differ. If there is a match, these will be fixed michael@0: // up in the resulting clone by the caller. michael@0: michael@0: if (script->lineno() != lazy->lineno() || michael@0: script->column() != lazy->column() || michael@0: script->getVersion() != lazy->version() || michael@0: script->sourceStart() != lazy->begin() || michael@0: script->sourceEnd() != lazy->end()) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: SourceDataCache::AutoHoldEntry holder; michael@0: michael@0: const jschar *scriptChars = script->scriptSource()->chars(cx, holder); michael@0: if (!scriptChars) michael@0: return false; michael@0: michael@0: const jschar *lazyChars = lazy->source()->chars(cx, holder); michael@0: if (!lazyChars) michael@0: return false; michael@0: michael@0: size_t begin = script->sourceStart(); michael@0: size_t length = script->sourceEnd() - begin; michael@0: return !memcmp(scriptChars + begin, lazyChars + begin, length); michael@0: }