/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * JS script operations. */ #include "jsscript.h" #include #include "mozilla/PodOperations.h" #include "jstypes.h" #include "jsutil.h" #include "jsapi.h" #include "jsatom.h" #include "jscntxt.h" #include "jsdbgapi.h" #include "jsfun.h" #include "jsgc.h" #include "jsopcode.h" #include "gc/Marking.h" #include "frontend/BytecodeEmitter.h" #include "jit/IonCode.h" #include "jit/BaselineJIT.h" #include "vm/Debugger.h" #include "vm/Interpreter.h" #include "vm/Shape.h" #include "vm/Xdr.h" #include "jsinferinlines.h" #include "jsscriptinlines.h" #include "vm/Interpreter-inl.h" #include "vm/RegExpObject-inl.h" #include "vm/ScopeObject-inl.h" using namespace js; using namespace js::gc; using namespace js::frontend; using mozilla::PodCopy; using mozilla::PodZero; typedef Rooted RootedGlobalObject; /* static */ unsigned Bindings::argumentsVarIndex(JSContext *cx, InternalBindingsHandle bindings) { HandlePropertyName arguments = cx->names().arguments; BindingIter bi(bindings); while (bi->name() != arguments) bi++; return bi.frameIndex(); } bool Bindings::initWithTemporaryStorage(JSContext *cx, InternalBindingsHandle self, unsigned numArgs, unsigned numVars, Binding *bindingArray) { JS_ASSERT(!self->callObjShape_); JS_ASSERT(self->bindingArrayAndFlag_ == TEMPORARY_STORAGE_BIT); if (numArgs > UINT16_MAX || numVars > UINT16_MAX) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, self->numArgs_ > self->numVars_ ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_LOCALS); return false; } JS_ASSERT(!(uintptr_t(bindingArray) & TEMPORARY_STORAGE_BIT)); self->bindingArrayAndFlag_ = uintptr_t(bindingArray) | TEMPORARY_STORAGE_BIT; self->numArgs_ = numArgs; self->numVars_ = numVars; /* * Get the initial shape to use when creating CallObjects for this script. * Since unaliased variables are, by definition, only accessed by local * operations and never through the scope chain, only give shapes to * aliased variables. While the debugger may observe any scope object at * any time, such accesses are mediated by DebugScopeProxy (see * DebugScopeProxy::handleUnaliasedAccess). */ JS_STATIC_ASSERT(CallObject::RESERVED_SLOTS == 2); gc::AllocKind allocKind = gc::FINALIZE_OBJECT2_BACKGROUND; JS_ASSERT(gc::GetGCKindSlots(allocKind) == CallObject::RESERVED_SLOTS); RootedShape initial(cx, EmptyShape::getInitialShape(cx, &CallObject::class_, NULL, cx->global(), NULL, allocKind, BaseShape::VAROBJ | BaseShape::DELEGATE)); if (!initial) return false; self->callObjShape_.init(initial); #ifdef DEBUG HashSet added(cx); if (!added.init()) return false; #endif BindingIter bi(self); unsigned slot = CallObject::RESERVED_SLOTS; for (unsigned i = 0, n = self->count(); i < n; i++, bi++) { if (!bi->aliased()) continue; #ifdef DEBUG /* The caller ensures no duplicate aliased names. */ JS_ASSERT(!added.has(bi->name())); if (!added.put(bi->name())) return false; #endif StackBaseShape base(cx->compartment(), &CallObject::class_, cx->global(), NULL, BaseShape::VAROBJ | BaseShape::DELEGATE); UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base); if (!nbase) return false; RootedId id(cx, NameToId(bi->name())); unsigned attrs = JSPROP_PERMANENT | JSPROP_ENUMERATE | (bi->kind() == CONSTANT ? JSPROP_READONLY : 0); unsigned frameIndex = bi.frameIndex(); StackShape child(nbase, id, slot++, 0, attrs, Shape::HAS_SHORTID, frameIndex); Shape *shape = self->callObjShape_->getChildBinding(cx, child); if (!shape) return false; self->callObjShape_ = shape; } JS_ASSERT(!bi); return true; } uint8_t * Bindings::switchToScriptStorage(Binding *newBindingArray) { JS_ASSERT(bindingArrayUsingTemporaryStorage()); JS_ASSERT(!(uintptr_t(newBindingArray) & TEMPORARY_STORAGE_BIT)); PodCopy(newBindingArray, bindingArray(), count()); bindingArrayAndFlag_ = uintptr_t(newBindingArray); return reinterpret_cast(newBindingArray + count()); } bool Bindings::clone(JSContext *cx, InternalBindingsHandle self, uint8_t *dstScriptData, HandleScript srcScript) { /* The clone has the same bindingArray_ offset as 'src'. */ Bindings &src = srcScript->bindings; ptrdiff_t off = (uint8_t *)src.bindingArray() - srcScript->data; JS_ASSERT(off >= 0); JS_ASSERT(off <= srcScript->dataSize); Binding *dstPackedBindings = (Binding *)(dstScriptData + off); /* * Since atoms are shareable throughout the runtime, we can simply copy * the source's bindingArray directly. */ if (!initWithTemporaryStorage(cx, self, src.numArgs(), src.numVars(), src.bindingArray())) return false; self->switchToScriptStorage(dstPackedBindings); return true; } /* static */ Bindings GCMethods::initial() { return Bindings(); } template static bool XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, unsigned numVars, HandleScript script) { JSContext *cx = xdr->cx(); if (mode == XDR_ENCODE) { for (BindingIter bi(script); bi; bi++) { RootedAtom atom(cx, bi->name()); if (!XDRAtom(xdr, &atom)) return false; } for (BindingIter bi(script); bi; bi++) { uint8_t u8 = (uint8_t(bi->kind()) << 1) | uint8_t(bi->aliased()); if (!xdr->codeUint8(&u8)) return false; } } else { unsigned nameCount = numArgs + numVars; AutoValueVector atoms(cx); if (!atoms.resize(nameCount)) return false; for (unsigned i = 0; i < nameCount; i++) { RootedAtom atom(cx); if (!XDRAtom(xdr, &atom)) return false; atoms[i] = StringValue(atom); } Binding *bindingArray = las.alloc().newArrayUninitialized(nameCount); if (!bindingArray) return false; for (unsigned i = 0; i < nameCount; i++) { uint8_t u8; if (!xdr->codeUint8(&u8)) return false; PropertyName *name = atoms[i].toString()->asAtom().asPropertyName(); BindingKind kind = BindingKind(u8 >> 1); bool aliased = bool(u8 & 1); bindingArray[i] = Binding(name, kind, aliased); } InternalBindingsHandle bindings(script, &script->bindings); if (!Bindings::initWithTemporaryStorage(cx, bindings, numArgs, numVars, bindingArray)) return false; } return true; } bool Bindings::bindingIsAliased(unsigned bindingIndex) { JS_ASSERT(bindingIndex < count()); return bindingArray()[bindingIndex].aliased(); } void Bindings::trace(JSTracer *trc) { if (callObjShape_) MarkShape(trc, &callObjShape_, "callObjShape"); /* * As the comment in Bindings explains, bindingsArray may point into freed * storage when bindingArrayUsingTemporaryStorage so we don't mark it. * Note: during compilation, atoms are already kept alive by gcKeepAtoms. */ if (bindingArrayUsingTemporaryStorage()) return; for (Binding *b = bindingArray(), *end = b + count(); b != end; b++) { PropertyName *name = b->name(); MarkStringUnbarriered(trc, &name, "bindingArray"); } } bool js::FillBindingVector(HandleScript fromScript, BindingVector *vec) { for (BindingIter bi(fromScript); bi; bi++) { if (!vec->append(*bi)) return false; } return true; } template static bool XDRScriptConst(XDRState *xdr, HeapValue *vp) { JSContext *cx = xdr->cx(); /* * A script constant can be an arbitrary primitive value as they are used * to implement JSOP_LOOKUPSWITCH. But they cannot be objects, see * bug 407186. */ enum ConstTag { SCRIPT_INT = 0, SCRIPT_DOUBLE = 1, SCRIPT_ATOM = 2, SCRIPT_TRUE = 3, SCRIPT_FALSE = 4, SCRIPT_NULL = 5, SCRIPT_VOID = 6 }; uint32_t tag; if (mode == XDR_ENCODE) { if (vp->isInt32()) { tag = SCRIPT_INT; } else if (vp->isDouble()) { tag = SCRIPT_DOUBLE; } else if (vp->isString()) { tag = SCRIPT_ATOM; } else if (vp->isTrue()) { tag = SCRIPT_TRUE; } else if (vp->isFalse()) { tag = SCRIPT_FALSE; } else if (vp->isNull()) { tag = SCRIPT_NULL; } else { JS_ASSERT(vp->isUndefined()); tag = SCRIPT_VOID; } } if (!xdr->codeUint32(&tag)) return false; switch (tag) { case SCRIPT_INT: { uint32_t i; if (mode == XDR_ENCODE) i = uint32_t(vp->toInt32()); if (!xdr->codeUint32(&i)) return JS_FALSE; if (mode == XDR_DECODE) vp->init(Int32Value(int32_t(i))); break; } case SCRIPT_DOUBLE: { double d; if (mode == XDR_ENCODE) d = vp->toDouble(); if (!xdr->codeDouble(&d)) return false; if (mode == XDR_DECODE) vp->init(DoubleValue(d)); break; } case SCRIPT_ATOM: { RootedAtom atom(cx); if (mode == XDR_ENCODE) atom = &vp->toString()->asAtom(); if (!XDRAtom(xdr, &atom)) return false; if (mode == XDR_DECODE) vp->init(StringValue(atom)); break; } case SCRIPT_TRUE: if (mode == XDR_DECODE) vp->init(BooleanValue(true)); break; case SCRIPT_FALSE: if (mode == XDR_DECODE) vp->init(BooleanValue(false)); break; case SCRIPT_NULL: if (mode == XDR_DECODE) vp->init(NullValue()); break; case SCRIPT_VOID: if (mode == XDR_DECODE) vp->init(UndefinedValue()); break; } return true; } static inline uint32_t FindBlockIndex(JSScript *script, StaticBlockObject &block) { ObjectArray *objects = script->objects(); HeapPtrObject *vector = objects->vector; unsigned length = objects->length; for (unsigned i = 0; i < length; ++i) { if (vector[i] == &block) return i; } JS_NOT_REACHED("Block not found"); return UINT32_MAX; } template bool js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enclosingScript, HandleFunction fun, MutableHandleScript scriptp) { /* NB: Keep this in sync with CloneScript. */ enum ScriptBits { NoScriptRval, SavedCallerFun, Strict, ContainsDynamicNameAccess, FunHasExtensibleScope, FunNeedsDeclEnvObject, FunHasAnyAliasedFormal, ArgumentsHasVarBinding, NeedsArgsObj, IsGenerator, IsGeneratorExp, OwnSource, ExplicitUseStrict, SelfHosted }; uint32_t length, lineno, nslots; uint32_t natoms, nsrcnotes, ntrynotes, nobjects, nregexps, nconsts, i; uint32_t prologLength, version; uint32_t ndefaults = 0; uint32_t nTypeSets = 0; uint32_t scriptBits = 0; JSContext *cx = xdr->cx(); RootedScript script(cx); nsrcnotes = ntrynotes = natoms = nobjects = nregexps = nconsts = 0; /* XDR arguments and vars. */ uint16_t nargs = 0, nvars = 0; uint32_t argsVars = 0; if (mode == XDR_ENCODE) { script = scriptp.get(); JS_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment()); nargs = script->bindings.numArgs(); nvars = script->bindings.numVars(); argsVars = (nargs << 16) | nvars; } if (!xdr->codeUint32(&argsVars)) return false; if (mode == XDR_DECODE) { nargs = argsVars >> 16; nvars = argsVars & 0xFFFF; } if (mode == XDR_ENCODE) length = script->length; if (!xdr->codeUint32(&length)) return JS_FALSE; if (mode == XDR_ENCODE) { prologLength = script->mainOffset; JS_ASSERT(script->getVersion() != JSVERSION_UNKNOWN); version = (uint32_t)script->getVersion() | (script->nfixed << 16); lineno = script->lineno; nslots = (uint32_t)script->nslots; nslots = (uint32_t)((script->staticLevel << 16) | script->nslots); natoms = script->natoms; nsrcnotes = script->numNotes(); if (script->hasConsts()) nconsts = script->consts()->length; if (script->hasObjects()) nobjects = script->objects()->length; if (script->hasRegexps()) nregexps = script->regexps()->length; if (script->hasTrynotes()) ntrynotes = script->trynotes()->length; nTypeSets = script->nTypeSets; ndefaults = script->ndefaults; if (script->noScriptRval) scriptBits |= (1 << NoScriptRval); if (script->savedCallerFun) scriptBits |= (1 << SavedCallerFun); if (script->strict) scriptBits |= (1 << Strict); if (script->explicitUseStrict) scriptBits |= (1 << ExplicitUseStrict); if (script->selfHosted) scriptBits |= (1 << SelfHosted); if (script->bindingsAccessedDynamically) scriptBits |= (1 << ContainsDynamicNameAccess); if (script->funHasExtensibleScope) scriptBits |= (1 << FunHasExtensibleScope); if (script->funNeedsDeclEnvObject) scriptBits |= (1 << FunNeedsDeclEnvObject); if (script->funHasAnyAliasedFormal) scriptBits |= (1 << FunHasAnyAliasedFormal); if (script->argumentsHasVarBinding()) scriptBits |= (1 << ArgumentsHasVarBinding); if (script->analyzedArgsUsage() && script->needsArgsObj()) scriptBits |= (1 << NeedsArgsObj); if (!enclosingScript || enclosingScript->scriptSource() != script->scriptSource()) scriptBits |= (1 << OwnSource); if (script->isGenerator) scriptBits |= (1 << IsGenerator); if (script->isGeneratorExp) scriptBits |= (1 << IsGeneratorExp); JS_ASSERT(!script->compileAndGo); JS_ASSERT(!script->hasSingletons); } if (!xdr->codeUint32(&prologLength)) return JS_FALSE; if (!xdr->codeUint32(&version)) return JS_FALSE; /* * To fuse allocations, we need srcnote, atom, objects, regexp, and trynote * counts early. */ if (!xdr->codeUint32(&natoms)) return JS_FALSE; if (!xdr->codeUint32(&nsrcnotes)) return JS_FALSE; if (!xdr->codeUint32(&ntrynotes)) return JS_FALSE; if (!xdr->codeUint32(&nobjects)) return JS_FALSE; if (!xdr->codeUint32(&nregexps)) return JS_FALSE; if (!xdr->codeUint32(&nconsts)) return JS_FALSE; if (!xdr->codeUint32(&nTypeSets)) return JS_FALSE; if (!xdr->codeUint32(&ndefaults)) return JS_FALSE; if (!xdr->codeUint32(&scriptBits)) return JS_FALSE; if (mode == XDR_DECODE) { /* Note: version is packed into the 32b space with another 16b value. */ JSVersion version_ = JSVersion(version & JS_BITMASK(16)); JS_ASSERT((version_ & VersionFlags::MASK) == unsigned(version_)); // principals and originPrincipals are set with xdr->initScriptPrincipals(script) below. // staticLevel is set below. CompileOptions options(cx); options.setVersion(version_) .setNoScriptRval(!!(scriptBits & (1 << NoScriptRval))) .setSelfHostingMode(!!(scriptBits & (1 << SelfHosted))); JS::RootedScriptSource sourceObject(cx); if (scriptBits & (1 << OwnSource)) { ScriptSource *ss = cx->new_(); if (!ss) return false; sourceObject = ScriptSourceObject::create(cx, ss); if (!sourceObject) return false; } else { JS_ASSERT(enclosingScript); sourceObject = enclosingScript->sourceObject(); } script = JSScript::Create(cx, enclosingScope, !!(scriptBits & (1 << SavedCallerFun)), options, /* staticLevel = */ 0, sourceObject, 0, 0); if (!script) return false; } /* JSScript::partiallyInit assumes script->bindings is fully initialized. */ LifoAllocScope las(&cx->tempLifoAlloc()); if (!XDRScriptBindings(xdr, las, nargs, nvars, script)) return false; if (mode == XDR_DECODE) { if (!JSScript::partiallyInit(cx, script, nobjects, nregexps, ntrynotes, nconsts, nTypeSets)) return false; JS_ASSERT(!script->mainOffset); script->mainOffset = prologLength; script->length = length; script->nfixed = uint16_t(version >> 16); script->ndefaults = ndefaults; scriptp.set(script); if (scriptBits & (1 << Strict)) script->strict = true; if (scriptBits & (1 << ExplicitUseStrict)) script->explicitUseStrict = true; if (scriptBits & (1 << ContainsDynamicNameAccess)) script->bindingsAccessedDynamically = true; if (scriptBits & (1 << FunHasExtensibleScope)) script->funHasExtensibleScope = true; if (scriptBits & (1 << FunNeedsDeclEnvObject)) script->funNeedsDeclEnvObject = true; if (scriptBits & (1 << FunHasAnyAliasedFormal)) script->funHasAnyAliasedFormal = true; if (scriptBits & (1 << ArgumentsHasVarBinding)) script->setArgumentsHasVarBinding(); if (scriptBits & (1 << NeedsArgsObj)) script->setNeedsArgsObj(true); if (scriptBits & (1 << IsGenerator)) script->isGenerator = true; if (scriptBits & (1 << IsGeneratorExp)) script->isGeneratorExp = true; } JS_STATIC_ASSERT(sizeof(jsbytecode) == 1); JS_STATIC_ASSERT(sizeof(jssrcnote) == 1); if (scriptBits & (1 << OwnSource)) { if (!script->scriptSource()->performXDR(xdr)) return false; } if (!xdr->codeUint32(&script->sourceStart)) return false; if (!xdr->codeUint32(&script->sourceEnd)) return false; if (!xdr->codeUint32(&lineno) || !xdr->codeUint32(&nslots)) return false; if (mode == XDR_DECODE) { script->lineno = lineno; script->nslots = uint16_t(nslots); script->staticLevel = uint16_t(nslots >> 16); xdr->initScriptPrincipals(script); } jsbytecode *code = script->code; SharedScriptData *ssd; if (mode == XDR_DECODE) { ssd = SharedScriptData::new_(cx, length, nsrcnotes, natoms); if (!ssd) return false; code = ssd->data; if (natoms != 0) { script->natoms = natoms; script->atoms = ssd->atoms(length, nsrcnotes); } } if (!xdr->codeBytes(code, length) || !xdr->codeBytes(code + length, nsrcnotes)) { if (mode == XDR_DECODE) js_free(ssd); return false; } for (i = 0; i != natoms; ++i) { if (mode == XDR_DECODE) { RootedAtom tmp(cx); if (!XDRAtom(xdr, &tmp)) return false; script->atoms[i].init(tmp); } else { RootedAtom tmp(cx, script->atoms[i]); if (!XDRAtom(xdr, &tmp)) return false; } } if (mode == XDR_DECODE) { if (!SaveSharedScriptData(cx, script, ssd)) return false; } /* * Here looping from 0-to-length to xdr objects is essential to ensure that * all references to enclosing blocks (via FindBlockIndex below) happen * after the enclosing block has been XDR'd. */ for (i = 0; i != nobjects; ++i) { HeapPtr *objp = &script->objects()->vector[i]; uint32_t isBlock; if (mode == XDR_ENCODE) { JSObject *obj = *objp; JS_ASSERT(obj->is() || obj->is()); isBlock = obj->is() ? 1 : 0; } if (!xdr->codeUint32(&isBlock)) return false; if (isBlock == 0) { /* Code the nested function's enclosing scope. */ uint32_t funEnclosingScopeIndex = 0; if (mode == XDR_ENCODE) { JSScript *innerScript = (*objp)->as().getOrCreateScript(cx); if (!innerScript) return false; RootedObject staticScope(cx, innerScript->enclosingStaticScope()); StaticScopeIter ssi(cx, staticScope); if (ssi.done() || ssi.type() == StaticScopeIter::FUNCTION) { JS_ASSERT(ssi.done() == !fun); funEnclosingScopeIndex = UINT32_MAX; } else { funEnclosingScopeIndex = FindBlockIndex(script, ssi.block()); JS_ASSERT(funEnclosingScopeIndex < i); } } if (!xdr->codeUint32(&funEnclosingScopeIndex)) return false; Rooted funEnclosingScope(cx); if (mode == XDR_DECODE) { if (funEnclosingScopeIndex == UINT32_MAX) { funEnclosingScope = fun; } else { JS_ASSERT(funEnclosingScopeIndex < i); funEnclosingScope = script->objects()->vector[funEnclosingScopeIndex]; } } RootedObject tmp(cx, *objp); if (!XDRInterpretedFunction(xdr, funEnclosingScope, script, &tmp)) return false; *objp = tmp; } else { /* Code the nested block's enclosing scope. */ JS_ASSERT(isBlock == 1); uint32_t blockEnclosingScopeIndex = 0; if (mode == XDR_ENCODE) { if (StaticBlockObject *block = (*objp)->as().enclosingBlock()) blockEnclosingScopeIndex = FindBlockIndex(script, *block); else blockEnclosingScopeIndex = UINT32_MAX; } if (!xdr->codeUint32(&blockEnclosingScopeIndex)) return false; Rooted blockEnclosingScope(cx); if (mode == XDR_DECODE) { if (blockEnclosingScopeIndex != UINT32_MAX) { JS_ASSERT(blockEnclosingScopeIndex < i); blockEnclosingScope = script->objects()->vector[blockEnclosingScopeIndex]; } else { blockEnclosingScope = fun; } } Rooted tmp(cx, static_cast(objp->get())); if (!XDRStaticBlockObject(xdr, blockEnclosingScope, script, tmp.address())) return false; *objp = tmp; } } for (i = 0; i != nregexps; ++i) { if (!XDRScriptRegExpObject(xdr, &script->regexps()->vector[i])) return false; } if (ntrynotes != 0) { /* * We combine tn->kind and tn->stackDepth when serializing as XDR is not * efficient when serializing small integer types. */ JSTryNote *tn, *tnfirst; uint32_t kindAndDepth; JS_STATIC_ASSERT(sizeof(tn->kind) == sizeof(uint8_t)); JS_STATIC_ASSERT(sizeof(tn->stackDepth) == sizeof(uint16_t)); tnfirst = script->trynotes()->vector; JS_ASSERT(script->trynotes()->length == ntrynotes); tn = tnfirst + ntrynotes; do { --tn; if (mode == XDR_ENCODE) { kindAndDepth = (uint32_t(tn->kind) << 16) | uint32_t(tn->stackDepth); } if (!xdr->codeUint32(&kindAndDepth) || !xdr->codeUint32(&tn->start) || !xdr->codeUint32(&tn->length)) { return false; } if (mode == XDR_DECODE) { tn->kind = uint8_t(kindAndDepth >> 16); tn->stackDepth = uint16_t(kindAndDepth); } } while (tn != tnfirst); } if (nconsts) { HeapValue *vector = script->consts()->vector; for (i = 0; i != nconsts; ++i) { if (!XDRScriptConst(xdr, &vector[i])) return false; } } if (mode == XDR_DECODE) { if (cx->hasOption(JSOPTION_PCCOUNT)) (void) script->initScriptCounts(cx); scriptp.set(script); } return true; } template bool js::XDRScript(XDRState *, HandleObject, HandleScript, HandleFunction, MutableHandleScript); template bool js::XDRScript(XDRState *, HandleObject, HandleScript, HandleFunction, MutableHandleScript); void JSScript::setSourceObject(js::ScriptSourceObject *object) { sourceObject_ = object; } js::ScriptSourceObject * JSScript::sourceObject() const { return &sourceObject_->as(); } bool JSScript::initScriptCounts(JSContext *cx) { JS_ASSERT(!hasScriptCounts); size_t n = 0; jsbytecode *pc, *next; for (pc = code; pc < code + length; pc = next) { n += PCCounts::numCounts(JSOp(*pc)); next = pc + GetBytecodeLength(pc); } size_t bytes = (length * sizeof(PCCounts)) + (n * sizeof(double)); char *base = (char *) cx->calloc_(bytes); if (!base) return false; /* Create compartment's scriptCountsMap if necessary. */ ScriptCountsMap *map = compartment()->scriptCountsMap; if (!map) { map = cx->new_(); if (!map || !map->init()) { js_free(base); js_delete(map); return false; } compartment()->scriptCountsMap = map; } char *cursor = base; ScriptCounts scriptCounts; scriptCounts.pcCountsVector = (PCCounts *) cursor; cursor += length * sizeof(PCCounts); for (pc = code; pc < code + length; pc = next) { JS_ASSERT(uintptr_t(cursor) % sizeof(double) == 0); scriptCounts.pcCountsVector[pc - code].counts = (double *) cursor; size_t capacity = PCCounts::numCounts(JSOp(*pc)); #ifdef DEBUG scriptCounts.pcCountsVector[pc - code].capacity = capacity; #endif cursor += capacity * sizeof(double); next = pc + GetBytecodeLength(pc); } if (!map->putNew(this, scriptCounts)) { js_free(base); return false; } hasScriptCounts = true; // safe to set this; we can't fail after this point JS_ASSERT(size_t(cursor - base) == bytes); /* Enable interrupts in any interpreter frames running on this script. */ InterpreterFrames *frames; for (frames = cx->runtime()->interpreterFrames; frames; frames = frames->older) frames->enableInterruptsIfRunning(this); return true; } static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript *script) { JS_ASSERT(script->hasScriptCounts); ScriptCountsMap *map = script->compartment()->scriptCountsMap; ScriptCountsMap::Ptr p = map->lookup(script); JS_ASSERT(p); return p; } js::PCCounts JSScript::getPCCounts(jsbytecode *pc) { JS_ASSERT(size_t(pc - code) < length); ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); return p->value.pcCountsVector[pc - code]; } void JSScript::addIonCounts(jit::IonScriptCounts *ionCounts) { ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); if (p->value.ionCounts) ionCounts->setPrevious(p->value.ionCounts); p->value.ionCounts = ionCounts; } jit::IonScriptCounts * JSScript::getIonCounts() { ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); return p->value.ionCounts; } ScriptCounts JSScript::releaseScriptCounts() { ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); ScriptCounts counts = p->value; compartment()->scriptCountsMap->remove(p); hasScriptCounts = false; return counts; } void JSScript::destroyScriptCounts(FreeOp *fop) { if (hasScriptCounts) { ScriptCounts scriptCounts = releaseScriptCounts(); scriptCounts.destroy(fop); } } void ScriptSourceObject::setSource(ScriptSource *source) { if (source) source->incref(); if (this->source()) this->source()->decref(); setReservedSlot(SOURCE_SLOT, PrivateValue(source)); } void ScriptSourceObject::finalize(FreeOp *fop, JSObject *obj) { // ScriptSource::setSource automatically takes care of the refcount obj->as().setSource(NULL); } Class ScriptSourceObject::class_ = { "ScriptSource", JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_IS_ANONYMOUS, JS_PropertyStub, /* addProperty */ JS_DeletePropertyStub, /* delProperty */ JS_PropertyStub, /* getProperty */ JS_StrictPropertyStub, /* setProperty */ JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, ScriptSourceObject::finalize }; ScriptSourceObject * ScriptSourceObject::create(JSContext *cx, ScriptSource *source) { RootedObject object(cx, NewObjectWithGivenProto(cx, &class_, NULL, cx->global())); if (!object) return NULL; JS::RootedScriptSource sourceObject(cx, &object->as()); sourceObject->setSlot(SOURCE_SLOT, PrivateValue(source)); source->incref(); return sourceObject; } #ifdef JS_THREADSAFE void SourceCompressorThread::compressorThread(void *arg) { PR_SetCurrentThreadName("JS Source Compressing Thread"); static_cast(arg)->threadLoop(); } bool SourceCompressorThread::init() { JS_ASSERT(!thread); lock = PR_NewLock(); if (!lock) return false; wakeup = PR_NewCondVar(lock); if (!wakeup) return false; done = PR_NewCondVar(lock); if (!done) return false; thread = PR_CreateThread(PR_USER_THREAD, compressorThread, this, PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); if (!thread) return false; return true; } void SourceCompressorThread::finish() { if (thread) { PR_Lock(lock); // We should only be compressing things when in the compiler. JS_ASSERT(state == IDLE); PR_NotifyCondVar(wakeup); state = SHUTDOWN; PR_Unlock(lock); PR_JoinThread(thread); } if (wakeup) PR_DestroyCondVar(wakeup); if (done) PR_DestroyCondVar(done); if (lock) PR_DestroyLock(lock); } const jschar * SourceCompressorThread::currentChars() const { JS_ASSERT(tok); return tok->chars; } bool SourceCompressorThread::internalCompress() { JS_ASSERT(state == COMPRESSING); JS_ASSERT(tok); ScriptSource *ss = tok->ss; JS_ASSERT(!ss->ready()); size_t compressedLength = 0; size_t nbytes = sizeof(jschar) * ss->length_; // Memory allocation functions on JSRuntime and JSContext are not // threadsafe. We have to use the js_* variants. #ifdef USE_ZLIB const size_t COMPRESS_THRESHOLD = 512; if (nbytes >= COMPRESS_THRESHOLD) { // Try to keep the maximum memory usage down by only allocating half the // size of the string, first. size_t firstSize = nbytes / 2; if (!ss->adjustDataSize(firstSize)) return false; Compressor comp(reinterpret_cast(tok->chars), nbytes); if (!comp.init()) return false; comp.setOutput(ss->data.compressed, firstSize); bool cont = !stop; while (cont) { switch (comp.compressMore()) { case Compressor::CONTINUE: break; case Compressor::MOREOUTPUT: { if (comp.outWritten() == nbytes) { cont = false; break; } // The compressed output is greater than half the size of the // original string. Reallocate to the full size. if (!ss->adjustDataSize(nbytes)) return false; comp.setOutput(ss->data.compressed, nbytes); break; } case Compressor::DONE: cont = false; break; case Compressor::OOM: return false; } cont = cont && !stop; } compressedLength = comp.outWritten(); if (stop || compressedLength == nbytes) compressedLength = 0; } #endif if (compressedLength == 0) { if (!ss->adjustDataSize(nbytes)) return false; PodCopy(ss->data.source, tok->chars, ss->length()); } else { // Shrink the buffer to the size of the compressed data. Shouldn't fail. JS_ALWAYS_TRUE(ss->adjustDataSize(compressedLength)); } ss->compressedLength_ = compressedLength; return true; } void SourceCompressorThread::threadLoop() { PR_Lock(lock); while (true) { switch (state) { case SHUTDOWN: PR_Unlock(lock); return; case IDLE: PR_WaitCondVar(wakeup, PR_INTERVAL_NO_TIMEOUT); break; case COMPRESSING: if (!internalCompress()) tok->oom = true; // We hold the lock, so no one should have changed this. JS_ASSERT(state == COMPRESSING); state = IDLE; PR_NotifyCondVar(done); break; } } } void SourceCompressorThread::compress(SourceCompressionToken *sct) { if (tok) // We have reentered the compiler. Complete the current compression // before starting the next one. waitOnCompression(tok); JS_ASSERT(state == IDLE); JS_ASSERT(!tok); stop = false; PR_Lock(lock); sct->ss->ready_ = false; tok = sct; state = COMPRESSING; PR_NotifyCondVar(wakeup); PR_Unlock(lock); } void SourceCompressorThread::waitOnCompression(SourceCompressionToken *userTok) { JS_ASSERT(userTok == tok); PR_Lock(lock); while (state == COMPRESSING) PR_WaitCondVar(done, PR_INTERVAL_NO_TIMEOUT); JS_ASSERT(state == IDLE); SourceCompressionToken *saveTok = tok; tok = NULL; PR_Unlock(lock); JS_ASSERT(!saveTok->ss->ready()); saveTok->ss->ready_ = true; // Update memory accounting. if (!saveTok->oom) saveTok->cx->runtime()->updateMallocCounter(NULL, saveTok->ss->computedSizeOfData()); saveTok->ss = NULL; saveTok->chars = NULL; } void SourceCompressorThread::abort(SourceCompressionToken *userTok) { JS_ASSERT(userTok == tok); stop = true; } #endif /* JS_THREADSAFE */ static const unsigned char emptySource[] = ""; /* Adjust the amount of memory this script source uses for source data, reallocating if needed. */ bool ScriptSource::adjustDataSize(size_t nbytes) { // Allocating 0 bytes has undefined behavior, so special-case it. if (nbytes == 0) { if (data.compressed != emptySource) js_free(data.compressed); data.compressed = const_cast(emptySource); return true; } // |data.compressed| can be NULL. void *buf = js_realloc(data.compressed, nbytes); if (!buf && data.compressed != emptySource) js_free(data.compressed); data.compressed = static_cast(buf); return !!data.compressed; } /* static */ bool JSScript::loadSource(JSContext *cx, HandleScript script, bool *worked) { JS_ASSERT(!script->scriptSource()->hasSourceData()); *worked = false; if (!cx->runtime()->sourceHook || !script->scriptSource()->sourceRetrievable()) return true; jschar *src = NULL; uint32_t length; if (!cx->runtime()->sourceHook(cx, script, &src, &length)) return false; if (!src) return true; ScriptSource *ss = script->scriptSource(); ss->setSource(src, length); *worked = true; return true; } JSFlatString * JSScript::sourceData(JSContext *cx) { JS_ASSERT(scriptSource()->hasSourceData()); return scriptSource()->substring(cx, sourceStart, sourceEnd); } JSStableString * SourceDataCache::lookup(ScriptSource *ss) { if (!map_) return NULL; if (Map::Ptr p = map_->lookup(ss)) return p->value; return NULL; } void SourceDataCache::put(ScriptSource *ss, JSStableString *str) { if (!map_) { map_ = js_new(); if (!map_) return; if (!map_->init()) { purge(); return; } } (void) map_->put(ss, str); } void SourceDataCache::purge() { js_delete(map_); map_ = NULL; } const jschar * ScriptSource::chars(JSContext *cx) { #ifdef JS_THREADSAFE if (!ready()) return cx->runtime()->sourceCompressorThread.currentChars(); #endif #ifdef USE_ZLIB if (compressed()) { JSStableString *cached = cx->runtime()->sourceDataCache.lookup(this); if (!cached) { const size_t nbytes = sizeof(jschar) * (length_ + 1); jschar *decompressed = static_cast(cx->malloc_(nbytes)); if (!decompressed) return NULL; if (!DecompressString(data.compressed, compressedLength_, reinterpret_cast(decompressed), nbytes)) { JS_ReportOutOfMemory(cx); js_free(decompressed); return NULL; } decompressed[length_] = 0; cached = js_NewString(cx, decompressed, length_); if (!cached) { js_free(decompressed); return NULL; } cx->runtime()->sourceDataCache.put(this, cached); } return cached->chars().get(); } #endif return data.source; } JSStableString * ScriptSource::substring(JSContext *cx, uint32_t start, uint32_t stop) { JS_ASSERT(start <= stop); const jschar *chars = this->chars(cx); if (!chars) return NULL; JSFlatString *flatStr = js_NewStringCopyN(cx, chars + start, stop - start); if (!flatStr) return NULL; return flatStr->ensureStable(cx); } bool ScriptSource::setSourceCopy(JSContext *cx, const jschar *src, uint32_t length, bool argumentsNotIncluded, SourceCompressionToken *tok) { JS_ASSERT(!hasSourceData()); length_ = length; argumentsNotIncluded_ = argumentsNotIncluded; #ifdef JS_THREADSAFE if (tok && cx->runtime()->useHelperThreads()) { tok->ss = this; tok->chars = src; cx->runtime()->sourceCompressorThread.compress(tok); } else #endif { if (!adjustDataSize(sizeof(jschar) * length)) return false; PodCopy(data.source, src, length_); } return true; } void ScriptSource::setSource(const jschar *src, uint32_t length) { JS_ASSERT(!hasSourceData()); length_ = length; JS_ASSERT(!argumentsNotIncluded_); data.source = const_cast(src); } bool SourceCompressionToken::complete() { JS_ASSERT_IF(!ss, !chars); #ifdef JS_THREADSAFE if (active()) { cx->runtime()->sourceCompressorThread.waitOnCompression(this); JS_ASSERT(!active()); } if (oom) { JS_ReportOutOfMemory(cx); return false; } #endif return true; } void SourceCompressionToken::abort() { JS_ASSERT(active()); #ifdef JS_THREADSAFE cx->runtime()->sourceCompressorThread.abort(this); #endif } void ScriptSource::destroy() { JS_ASSERT(ready()); adjustDataSize(0); js_free(filename_); js_free(sourceMap_); ready_ = false; js_free(this); } size_t ScriptSource::sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) { // |data| is a union, but both members are pointers to allocated memory, // |emptySource|, or NULL, so just using |data.compressed| will work. size_t n = mallocSizeOf(this); n += (ready() && data.compressed != emptySource) ? mallocSizeOf(data.compressed) : 0; n += mallocSizeOf(filename_); return n; } template bool ScriptSource::performXDR(XDRState *xdr) { uint8_t hasSource = hasSourceData(); if (!xdr->codeUint8(&hasSource)) return false; uint8_t retrievable = sourceRetrievable_; if (!xdr->codeUint8(&retrievable)) return false; sourceRetrievable_ = retrievable; if (hasSource && !sourceRetrievable_) { // Only set members when we know decoding cannot fail. This prevents the // script source from being partially initialized. uint32_t length = length_; if (!xdr->codeUint32(&length)) return false; uint32_t compressedLength = compressedLength_; if (!xdr->codeUint32(&compressedLength)) return false; uint8_t argumentsNotIncluded = argumentsNotIncluded_; if (!xdr->codeUint8(&argumentsNotIncluded)) return false; size_t byteLen = compressedLength ? compressedLength : (length * sizeof(jschar)); if (mode == XDR_DECODE) { if (!adjustDataSize(byteLen)) return false; } if (!xdr->codeBytes(data.compressed, byteLen)) { if (mode == XDR_DECODE) { js_free(data.compressed); data.compressed = NULL; } return false; } length_ = length; compressedLength_ = compressedLength; argumentsNotIncluded_ = argumentsNotIncluded; } uint8_t haveSourceMap = hasSourceMap(); if (!xdr->codeUint8(&haveSourceMap)) return false; if (haveSourceMap) { uint32_t sourceMapLen = (mode == XDR_DECODE) ? 0 : js_strlen(sourceMap_); if (!xdr->codeUint32(&sourceMapLen)) return false; if (mode == XDR_DECODE) { size_t byteLen = (sourceMapLen + 1) * sizeof(jschar); sourceMap_ = static_cast(xdr->cx()->malloc_(byteLen)); if (!sourceMap_) return false; } if (!xdr->codeChars(sourceMap_, sourceMapLen)) { if (mode == XDR_DECODE) { js_free(sourceMap_); sourceMap_ = NULL; } return false; } sourceMap_[sourceMapLen] = '\0'; } uint8_t haveFilename = !!filename_; if (!xdr->codeUint8(&haveFilename)) return false; if (haveFilename) { const char *fn = filename(); if (!xdr->codeCString(&fn)) return false; if (mode == XDR_DECODE && !setFilename(xdr->cx(), fn)) return false; } if (mode == XDR_DECODE) ready_ = true; return true; } bool ScriptSource::setFilename(JSContext *cx, const char *filename) { JS_ASSERT(!filename_); size_t len = strlen(filename) + 1; if (len == 1) return true; filename_ = cx->pod_malloc(len); if (!filename_) return false; js_memcpy(filename_, filename, len); return true; } bool ScriptSource::setSourceMap(JSContext *cx, jschar *sourceMapURL, const char *filename) { JS_ASSERT(sourceMapURL); if (hasSourceMap()) { if (!JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING, js_GetErrorMessage, NULL, JSMSG_ALREADY_HAS_SOURCEMAP, filename)) { js_free(sourceMapURL); return false; } } sourceMap_ = sourceMapURL; return true; } const jschar * ScriptSource::sourceMap() { JS_ASSERT(hasSourceMap()); return sourceMap_; } /* * Shared script data management. */ SharedScriptData * js::SharedScriptData::new_(JSContext *cx, uint32_t codeLength, uint32_t srcnotesLength, uint32_t natoms) { uint32_t baseLength = codeLength + srcnotesLength; uint32_t padding = sizeof(JSAtom *) - baseLength % sizeof(JSAtom *); uint32_t length = baseLength + padding + sizeof(JSAtom *) * natoms; SharedScriptData *entry = (SharedScriptData *)cx->malloc_(length + offsetof(SharedScriptData, data)); if (!entry) return NULL; entry->marked = false; entry->length = length; memset(entry->data + baseLength, 0, padding); return entry; } bool js::SaveSharedScriptData(JSContext *cx, Handle script, SharedScriptData *ssd) { ASSERT(script != NULL); ASSERT(ssd != NULL); JSRuntime *rt = cx->runtime(); ScriptBytecodeHasher::Lookup l(ssd); ScriptDataTable::AddPtr p = rt->scriptDataTable.lookupForAdd(l); if (p) { js_free(ssd); ssd = *p; } else { if (!rt->scriptDataTable.add(p, ssd)) { js_free(ssd); JS_ReportOutOfMemory(cx); return false; } } #ifdef JSGC_INCREMENTAL /* * During the IGC we need to ensure that bytecode is marked whenever it is * accessed even if the bytecode was already in the table: at this point * old scripts or exceptions pointing to the bytecode may no longer be * reachable. This is effectively a read barrier. */ if (JS::IsIncrementalGCInProgress(rt) && rt->gcIsFull) ssd->marked = true; #endif script->code = ssd->data; script->atoms = ssd->atoms(script->length, script->numNotes()); return true; } void js::SweepScriptData(JSRuntime *rt) { JS_ASSERT(rt->gcIsFull); ScriptDataTable &table = rt->scriptDataTable; for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) { SharedScriptData *entry = e.front(); if (entry->marked) { entry->marked = false; } else if (!rt->gcKeepAtoms) { js_free(entry); e.removeFront(); } } } void js::FreeScriptData(JSRuntime *rt) { ScriptDataTable &table = rt->scriptDataTable; if (!table.initialized()) return; for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) js_free(e.front()); table.clear(); } /* * JSScript::data and SharedScriptData::data have complex, * manually-controlled, memory layouts. * * JSScript::data begins with some optional array headers. They are optional * because they often aren't needed, i.e. the corresponding arrays often have * zero elements. Each header has a bit in JSScript::hasArrayBits that * indicates if it's present within |data|; from this the offset of each * present array header can be computed. Each header has an accessor function * in JSScript that encapsulates this offset computation. * * Array type Array elements Accessor * ---------- -------------- -------- * ConstArray Consts consts() * ObjectArray Objects objects() * ObjectArray Regexps regexps() * TryNoteArray Try notes trynotes() * * Then are the elements of several arrays. * - Most of these arrays have headers listed above (if present). For each of * these, the array pointer and the array length is stored in the header. * - The remaining arrays have pointers and lengths that are stored directly in * JSScript. This is because, unlike the others, they are nearly always * non-zero length and so the optional-header space optimization isn't * worthwhile. * * Array elements Pointed to by Length * -------------- ------------- ------ * Consts consts()->vector consts()->length * Objects objects()->vector objects()->length * Regexps regexps()->vector regexps()->length * Try notes trynotes()->vector trynotes()->length * * IMPORTANT: This layout has two key properties. * - It ensures that everything has sufficient alignment; in particular, the * consts() elements need jsval alignment. * - It ensures there are no gaps between elements, which saves space and makes * manual layout easy. In particular, in the second part, arrays with larger * elements precede arrays with smaller elements. * * SharedScriptData::data contains data that can be shared within a * runtime. These items' layout is manually controlled to make it easier to * manage both during (temporary) allocation and during matching against * existing entries in the runtime. As the jsbytecode has to come first to * enable lookup by bytecode identity, SharedScriptData::data, the atoms part * has to manually be aligned sufficiently by adding padding after the notes * part. * * Array elements Pointed to by Length * -------------- ------------- ------ * jsbytecode code length * jsscrnote notes() numNotes() * Atoms atoms natoms * * The following static assertions check JSScript::data's alignment properties. */ #define KEEPS_JSVAL_ALIGNMENT(T) \ (JS_ALIGNMENT_OF(jsval) % JS_ALIGNMENT_OF(T) == 0 && \ sizeof(T) % sizeof(jsval) == 0) #define HAS_JSVAL_ALIGNMENT(T) \ (JS_ALIGNMENT_OF(jsval) == JS_ALIGNMENT_OF(T) && \ sizeof(T) == sizeof(jsval)) #define NO_PADDING_BETWEEN_ENTRIES(T1, T2) \ (JS_ALIGNMENT_OF(T1) % JS_ALIGNMENT_OF(T2) == 0) /* * These assertions ensure that there is no padding between the array headers, * and also that the consts() elements (which follow immediately afterward) are * jsval-aligned. (There is an assumption that |data| itself is jsval-aligned; * we check this below). */ JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(ConstArray)); JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(ObjectArray)); /* there are two of these */ JS_STATIC_ASSERT(KEEPS_JSVAL_ALIGNMENT(TryNoteArray)); /* These assertions ensure there is no padding required between array elements. */ JS_STATIC_ASSERT(HAS_JSVAL_ALIGNMENT(HeapValue)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapValue, HeapPtrObject)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, HeapPtrObject)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(HeapPtrObject, JSTryNote)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(JSTryNote, uint32_t)); JS_STATIC_ASSERT(NO_PADDING_BETWEEN_ENTRIES(uint32_t, uint32_t)); static inline size_t ScriptDataSize(uint32_t nbindings, uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nconsts) { size_t size = 0; if (nconsts != 0) size += sizeof(ConstArray) + nconsts * sizeof(Value); if (nobjects != 0) size += sizeof(ObjectArray) + nobjects * sizeof(JSObject *); if (nregexps != 0) size += sizeof(ObjectArray) + nregexps * sizeof(JSObject *); if (ntrynotes != 0) size += sizeof(TryNoteArray) + ntrynotes * sizeof(JSTryNote); if (nbindings != 0) { // Make sure bindings are sufficiently aligned. size = JS_ROUNDUP(size, JS_ALIGNMENT_OF(Binding)) + nbindings * sizeof(Binding); } return size; } JSScript * JSScript::Create(JSContext *cx, HandleObject enclosingScope, bool savedCallerFun, const CompileOptions &options, unsigned staticLevel, JS::HandleScriptSource sourceObject, uint32_t bufStart, uint32_t bufEnd) { JS_ASSERT(bufStart <= bufEnd); RootedScript script(cx, js_NewGCScript(cx)); if (!script) return NULL; PodZero(script.get()); new (&script->bindings) Bindings; script->enclosingScopeOrOriginalFunction_ = enclosingScope; script->savedCallerFun = savedCallerFun; script->compartment_ = cx->compartment(); /* Establish invariant: principals implies originPrincipals. */ if (options.principals) { JS_ASSERT(options.principals == cx->compartment()->principals); script->originPrincipals = options.originPrincipals ? options.originPrincipals : options.principals; JS_HoldPrincipals(script->originPrincipals); } else if (options.originPrincipals) { script->originPrincipals = options.originPrincipals; JS_HoldPrincipals(script->originPrincipals); } script->compileAndGo = options.compileAndGo; script->selfHosted = options.selfHostingMode; script->noScriptRval = options.noScriptRval; script->version = options.version; JS_ASSERT(script->getVersion() == options.version); // assert that no overflow occurred // This is an unsigned-to-uint16_t conversion, test for too-high values. // In practice, recursion in Parser and/or BytecodeEmitter will blow the // stack if we nest functions more than a few hundred deep, so this will // never trigger. Oh well. if (staticLevel > UINT16_MAX) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TOO_DEEP, js_function_str); return NULL; } script->staticLevel = uint16_t(staticLevel); script->sourceObject_ = sourceObject; script->sourceStart = bufStart; script->sourceEnd = bufEnd; return script; } static inline uint8_t * AllocScriptData(JSContext *cx, size_t size) { uint8_t *data = static_cast(cx->calloc_(JS_ROUNDUP(size, sizeof(Value)))); if (!data) return NULL; // All script data is optional, so size might be 0. In that case, we don't care about alignment. JS_ASSERT(size == 0 || size_t(data) % sizeof(Value) == 0); return data; } /* static */ bool JSScript::partiallyInit(JSContext *cx, Handle script, uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nconsts, uint32_t nTypeSets) { size_t size = ScriptDataSize(script->bindings.count(), nobjects, nregexps, ntrynotes, nconsts); script->data = AllocScriptData(cx, size); if (!script->data) return false; script->dataSize = size; JS_ASSERT(nTypeSets <= UINT16_MAX); script->nTypeSets = uint16_t(nTypeSets); uint8_t *cursor = script->data; if (nconsts != 0) { script->setHasArray(CONSTS); cursor += sizeof(ConstArray); } if (nobjects != 0) { script->setHasArray(OBJECTS); cursor += sizeof(ObjectArray); } if (nregexps != 0) { script->setHasArray(REGEXPS); cursor += sizeof(ObjectArray); } if (ntrynotes != 0) { script->setHasArray(TRYNOTES); cursor += sizeof(TryNoteArray); } if (nconsts != 0) { JS_ASSERT(reinterpret_cast(cursor) % sizeof(jsval) == 0); script->consts()->length = nconsts; script->consts()->vector = (HeapValue *)cursor; cursor += nconsts * sizeof(script->consts()->vector[0]); } if (nobjects != 0) { script->objects()->length = nobjects; script->objects()->vector = (HeapPtr *)cursor; cursor += nobjects * sizeof(script->objects()->vector[0]); } if (nregexps != 0) { script->regexps()->length = nregexps; script->regexps()->vector = (HeapPtr *)cursor; cursor += nregexps * sizeof(script->regexps()->vector[0]); } if (ntrynotes != 0) { script->trynotes()->length = ntrynotes; script->trynotes()->vector = reinterpret_cast(cursor); size_t vectorSize = ntrynotes * sizeof(script->trynotes()->vector[0]); #ifdef DEBUG memset(cursor, 0, vectorSize); #endif cursor += vectorSize; } if (script->bindings.count() != 0) { // Make sure bindings are sufficiently aligned. cursor = reinterpret_cast (JS_ROUNDUP(reinterpret_cast(cursor), JS_ALIGNMENT_OF(Binding))); } cursor = script->bindings.switchToScriptStorage(reinterpret_cast(cursor)); JS_ASSERT(cursor == script->data + size); return true; } /* static */ bool JSScript::fullyInitTrivial(JSContext *cx, Handle script) { if (!partiallyInit(cx, script, 0, 0, 0, 0, 0)) return false; SharedScriptData *ssd = SharedScriptData::new_(cx, 1, 1, 0); if (!ssd) return false; ssd->data[0] = JSOP_STOP; ssd->data[1] = SRC_NULL; script->length = 1; return SaveSharedScriptData(cx, script, ssd); } /* static */ bool JSScript::fullyInitFromEmitter(JSContext *cx, Handle script, BytecodeEmitter *bce) { /* The counts of indexed things must be checked during code generation. */ JS_ASSERT(bce->atomIndices->count() <= INDEX_LIMIT); JS_ASSERT(bce->objectList.length <= INDEX_LIMIT); JS_ASSERT(bce->regexpList.length <= INDEX_LIMIT); uint32_t mainLength = bce->offset(); uint32_t prologLength = bce->prologOffset(); uint32_t nsrcnotes = uint32_t(bce->countFinalSourceNotes()); uint32_t natoms = bce->atomIndices->count(); if (!partiallyInit(cx, script, bce->objectList.length, bce->regexpList.length, bce->tryNoteList.length(), bce->constList.length(), bce->typesetCount)) { return false; } JS_ASSERT(script->mainOffset == 0); script->mainOffset = prologLength; script->lineno = bce->firstLine; script->length = prologLength + mainLength; script->natoms = natoms; SharedScriptData *ssd = SharedScriptData::new_(cx, script->length, nsrcnotes, natoms); if (!ssd) return false; jsbytecode *code = ssd->data; PodCopy(code, bce->prolog.code.begin(), prologLength); PodCopy(code + prologLength, bce->code().begin(), mainLength); if (!FinishTakingSrcNotes(cx, bce, (jssrcnote *)(code + script->length))) return false; InitAtomMap(cx, bce->atomIndices.getMap(), ssd->atoms(script->length, nsrcnotes)); if (!SaveSharedScriptData(cx, script, ssd)) return false; uint32_t nfixed = bce->sc->isFunctionBox() ? script->bindings.numVars() : 0; JS_ASSERT(nfixed < SLOTNO_LIMIT); script->nfixed = uint16_t(nfixed); if (script->nfixed + bce->maxStackDepth >= JS_BIT(16)) { bce->reportError(NULL, JSMSG_NEED_DIET, "script"); return false; } script->nslots = script->nfixed + bce->maxStackDepth; FunctionBox *funbox = bce->sc->isFunctionBox() ? bce->sc->asFunctionBox() : NULL; if (bce->tryNoteList.length() != 0) bce->tryNoteList.finish(script->trynotes()); if (bce->objectList.length != 0) bce->objectList.finish(script->objects()); if (bce->regexpList.length != 0) bce->regexpList.finish(script->regexps()); if (bce->constList.length() != 0) bce->constList.finish(script->consts()); script->strict = bce->sc->strict; script->explicitUseStrict = bce->sc->hasExplicitUseStrict(); script->bindingsAccessedDynamically = bce->sc->bindingsAccessedDynamically(); script->funHasExtensibleScope = funbox ? funbox->hasExtensibleScope() : false; script->funNeedsDeclEnvObject = funbox ? funbox->needsDeclEnvObject() : false; script->hasSingletons = bce->hasSingletons; if (funbox) { if (funbox->argumentsHasLocalBinding()) { // This must precede the script->bindings.transfer() call below script->setArgumentsHasVarBinding(); if (funbox->definitelyNeedsArgsObj()) script->setNeedsArgsObj(true); } else { JS_ASSERT(!funbox->definitelyNeedsArgsObj()); } script->ndefaults = funbox->ndefaults; } RootedFunction fun(cx, NULL); if (funbox) { JS_ASSERT(!bce->script->noScriptRval); script->isGenerator = funbox->isGenerator(); script->isGeneratorExp = funbox->inGenexpLambda; script->setFunction(funbox->function()); } /* * initScriptCounts updates scriptCountsMap if necessary. The other script * maps in JSCompartment are populated lazily. */ if (cx->hasOption(JSOPTION_PCCOUNT)) (void) script->initScriptCounts(cx); for (unsigned i = 0, n = script->bindings.numArgs(); i < n; ++i) { if (script->formalIsAliased(i)) { script->funHasAnyAliasedFormal = true; break; } } return true; } size_t JSScript::computedSizeOfData() { return dataSize; } size_t JSScript::sizeOfData(JSMallocSizeOfFun mallocSizeOf) { return mallocSizeOf(data); } /* * Nb: srcnotes are variable-length. This function computes the number of * srcnote *slots*, which may be greater than the number of srcnotes. */ uint32_t JSScript::numNotes() { jssrcnote *sn; jssrcnote *notes_ = notes(); for (sn = notes_; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) continue; return sn - notes_ + 1; /* +1 for the terminator */ } bool JSScript::isShortRunning() { return length < 100 && hasAnalysis() && !analysis()->hasFunctionCalls(); } bool JSScript::enclosingScriptsCompiledSuccessfully() const { /* * When a nested script is succesfully compiled, it is eagerly given the * static JSFunction of its enclosing script. The enclosing function's * 'script' field will be NULL until the enclosing script successfully * compiles. Thus, we can detect failed compilation by looking for * JSFunctions in the enclosingScope chain without scripts. */ JSObject *enclosing = enclosingStaticScope(); while (enclosing) { if (enclosing->is()) { JSFunction *fun = &enclosing->as(); if (!fun->hasScript() || !fun->nonLazyScript()) return false; enclosing = fun->nonLazyScript()->enclosingStaticScope(); } else { enclosing = enclosing->as().enclosingStaticScope(); } } return true; } void js::CallNewScriptHook(JSContext *cx, HandleScript script, HandleFunction fun) { if (script->selfHosted) return; JS_ASSERT(!script->isActiveEval); if (JSNewScriptHook hook = cx->runtime()->debugHooks.newScriptHook) { AutoKeepAtoms keep(cx->runtime()); hook(cx, script->filename(), script->lineno, script, fun, cx->runtime()->debugHooks.newScriptHookData); } } void js::CallDestroyScriptHook(FreeOp *fop, JSScript *script) { if (script->selfHosted) return; // The hook will only call into JS if a GC is not running. if (JSDestroyScriptHook hook = fop->runtime()->debugHooks.destroyScriptHook) hook(fop, script, fop->runtime()->debugHooks.destroyScriptHookData); script->clearTraps(fop); } void JSScript::finalize(FreeOp *fop) { // NOTE: this JSScript may be partially initialized at this point. E.g. we // may have created it and partially initialized it with // JSScript::Create(), but not yet finished initializing it with // fullyInitFromEmitter() or fullyInitTrivial(). CallDestroyScriptHook(fop, this); fop->runtime()->spsProfiler.onScriptFinalized(this); if (originPrincipals) JS_DropPrincipals(fop->runtime(), originPrincipals); if (types) types->destroy(); #ifdef JS_ION jit::DestroyIonScripts(fop, this); #endif destroyScriptCounts(fop); destroyDebugScript(fop); if (data) { JS_POISON(data, 0xdb, computedSizeOfData()); fop->free_(data); } fop->runtime()->lazyScriptCache.remove(this); } static const uint32_t GSN_CACHE_THRESHOLD = 100; static const uint32_t GSN_CACHE_MAP_INIT_SIZE = 20; void GSNCache::purge() { code = NULL; if (map.initialized()) map.finish(); } jssrcnote * js_GetSrcNote(JSContext *cx, JSScript *script, jsbytecode *pc) { GSNCache *cache = &cx->runtime()->gsnCache; cx = NULL; // nulling |cx| ensures GC can't be triggered, so |JSScript *script| is safe size_t target = pc - script->code; if (target >= size_t(script->length)) return NULL; if (cache->code == script->code) { JS_ASSERT(cache->map.initialized()); GSNCache::Map::Ptr p = cache->map.lookup(pc); return p ? p->value : NULL; } size_t offset = 0; jssrcnote *result; for (jssrcnote *sn = script->notes(); ; sn = SN_NEXT(sn)) { if (SN_IS_TERMINATOR(sn)) { result = NULL; break; } offset += SN_DELTA(sn); if (offset == target && SN_IS_GETTABLE(sn)) { result = sn; break; } } if (cache->code != script->code && script->length >= GSN_CACHE_THRESHOLD) { unsigned nsrcnotes = 0; for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { if (SN_IS_GETTABLE(sn)) ++nsrcnotes; } if (cache->code) { JS_ASSERT(cache->map.initialized()); cache->map.finish(); cache->code = NULL; } if (cache->map.init(nsrcnotes)) { pc = script->code; for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { pc += SN_DELTA(sn); if (SN_IS_GETTABLE(sn)) JS_ALWAYS_TRUE(cache->map.put(pc, sn)); } cache->code = script->code; } } return result; } unsigned js::PCToLineNumber(unsigned startLine, jssrcnote *notes, jsbytecode *code, jsbytecode *pc, unsigned *columnp) { unsigned lineno = startLine; unsigned column = 0; /* * Walk through source notes accumulating their deltas, keeping track of * line-number notes, until we pass the note for pc's offset within * script->code. */ ptrdiff_t offset = 0; ptrdiff_t target = pc - code; for (jssrcnote *sn = notes; !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { offset += SN_DELTA(sn); SrcNoteType type = (SrcNoteType) SN_TYPE(sn); if (type == SRC_SETLINE) { if (offset <= target) lineno = (unsigned) js_GetSrcNoteOffset(sn, 0); column = 0; } else if (type == SRC_NEWLINE) { if (offset <= target) lineno++; column = 0; } if (offset > target) break; if (type == SRC_COLSPAN) { ptrdiff_t colspan = js_GetSrcNoteOffset(sn, 0); if (colspan >= SN_COLSPAN_DOMAIN / 2) colspan -= SN_COLSPAN_DOMAIN; JS_ASSERT(ptrdiff_t(column) + colspan >= 0); column += colspan; } } if (columnp) *columnp = column; return lineno; } unsigned js::PCToLineNumber(JSScript *script, jsbytecode *pc, unsigned *columnp) { /* Cope with StackFrame.pc value prior to entering js_Interpret. */ if (!pc) return 0; return PCToLineNumber(script->lineno, script->notes(), script->code, pc, columnp); } /* The line number limit is the same as the jssrcnote offset limit. */ #define SN_LINE_LIMIT (SN_3BYTE_OFFSET_FLAG << 16) jsbytecode * js_LineNumberToPC(JSScript *script, unsigned target) { ptrdiff_t offset = 0; ptrdiff_t best = -1; unsigned lineno = script->lineno; unsigned bestdiff = SN_LINE_LIMIT; for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { /* * Exact-match only if offset is not in the prolog; otherwise use * nearest greater-or-equal line number match. */ if (lineno == target && offset >= ptrdiff_t(script->mainOffset)) goto out; if (lineno >= target) { unsigned diff = lineno - target; if (diff < bestdiff) { bestdiff = diff; best = offset; } } offset += SN_DELTA(sn); SrcNoteType type = (SrcNoteType) SN_TYPE(sn); if (type == SRC_SETLINE) { lineno = (unsigned) js_GetSrcNoteOffset(sn, 0); } else if (type == SRC_NEWLINE) { lineno++; } } if (best >= 0) offset = best; out: return script->code + offset; } JS_FRIEND_API(unsigned) js_GetScriptLineExtent(JSScript *script) { unsigned lineno = script->lineno; unsigned maxLineNo = 0; bool counting = true; for (jssrcnote *sn = script->notes(); !SN_IS_TERMINATOR(sn); sn = SN_NEXT(sn)) { SrcNoteType type = (SrcNoteType) SN_TYPE(sn); if (type == SRC_SETLINE) { if (maxLineNo < lineno) maxLineNo = lineno; lineno = (unsigned) js_GetSrcNoteOffset(sn, 0); counting = true; if (maxLineNo < lineno) maxLineNo = lineno; else counting = false; } else if (type == SRC_NEWLINE) { if (counting) lineno++; } } if (maxLineNo > lineno) lineno = maxLineNo; return 1 + lineno - script->lineno; } void js::CurrentScriptFileLineOriginSlow(JSContext *cx, const char **file, unsigned *linenop, JSPrincipals **origin) { NonBuiltinScriptFrameIter iter(cx); if (iter.done()) { *file = NULL; *linenop = 0; *origin = NULL; return; } JSScript *script = iter.script(); *file = script->filename(); *linenop = PCToLineNumber(iter.script(), iter.pc()); *origin = script->originPrincipals; } template static inline T * Rebase(JSScript *dst, JSScript *src, T *srcp) { size_t off = reinterpret_cast(srcp) - src->data; return reinterpret_cast(dst->data + off); } JSScript * js::CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript src, NewObjectKind newKind /* = GenericObject */) { /* NB: Keep this in sync with XDRScript. */ uint32_t nconsts = src->hasConsts() ? src->consts()->length : 0; uint32_t nobjects = src->hasObjects() ? src->objects()->length : 0; uint32_t nregexps = src->hasRegexps() ? src->regexps()->length : 0; uint32_t ntrynotes = src->hasTrynotes() ? src->trynotes()->length : 0; /* Script data */ size_t size = src->dataSize; uint8_t *data = AllocScriptData(cx, size); if (!data) return NULL; /* Bindings */ Rooted bindings(cx); InternalHandle bindingsHandle = InternalHandle::fromMarkedLocation(bindings.address()); if (!Bindings::clone(cx, bindingsHandle, data, src)) return NULL; /* Objects */ AutoObjectVector objects(cx); if (nobjects != 0) { HeapPtrObject *vector = src->objects()->vector; for (unsigned i = 0; i < nobjects; i++) { RootedObject obj(cx, vector[i]); RootedObject clone(cx); if (obj->is()) { Rooted innerBlock(cx, &obj->as()); RootedObject enclosingScope(cx); if (StaticBlockObject *enclosingBlock = innerBlock->enclosingBlock()) enclosingScope = objects[FindBlockIndex(src, *enclosingBlock)]; else enclosingScope = fun; clone = CloneStaticBlockObject(cx, enclosingScope, innerBlock); } else if (obj->is()) { RootedFunction innerFun(cx, &obj->as()); if (innerFun->isNative()) { assertSameCompartment(cx, innerFun); clone = innerFun; } else { if (innerFun->isInterpretedLazy()) { AutoCompartment ac(cx, innerFun); if (!innerFun->getOrCreateScript(cx)) return NULL; } RootedObject staticScope(cx, innerFun->nonLazyScript()->enclosingStaticScope()); StaticScopeIter ssi(cx, staticScope); RootedObject enclosingScope(cx); if (!ssi.done() && ssi.type() == StaticScopeIter::BLOCK) enclosingScope = objects[FindBlockIndex(src, ssi.block())]; else enclosingScope = fun; clone = CloneFunctionAndScript(cx, enclosingScope, innerFun); } } else { /* * Clone object literals emitted for the JSOP_NEWOBJECT opcode. We only emit that * instead of the less-optimized JSOP_NEWINIT for self-hosted code or code compiled * with JSOPTION_COMPILE_N_GO set. As we don't clone the latter type of code, this * case should only ever be hit when cloning objects from self-hosted code. */ clone = CloneObjectLiteral(cx, cx->global(), obj); } if (!clone || !objects.append(clone)) return NULL; } } /* RegExps */ AutoObjectVector regexps(cx); for (unsigned i = 0; i < nregexps; i++) { HeapPtrObject *vector = src->regexps()->vector; for (unsigned i = 0; i < nregexps; i++) { JSObject *clone = CloneScriptRegExpObject(cx, vector[i]->as()); if (!clone || !regexps.append(clone)) return NULL; } } /* Now that all fallible allocation is complete, create the GC thing. */ CompileOptions options(cx); options.setPrincipals(cx->compartment()->principals) .setOriginPrincipals(src->originPrincipals) .setCompileAndGo(src->compileAndGo) .setSelfHostingMode(src->selfHosted) .setNoScriptRval(src->noScriptRval) .setVersion(src->getVersion()); /* Make sure we clone the script source object with the script */ JS::RootedScriptSource sourceObject(cx, ScriptSourceObject::create(cx, src->scriptSource())); if (!sourceObject) return NULL; RootedScript dst(cx, JSScript::Create(cx, enclosingScope, src->savedCallerFun, options, src->staticLevel, sourceObject, src->sourceStart, src->sourceEnd)); if (!dst) { js_free(data); return NULL; } dst->bindings = bindings; /* This assignment must occur before all the Rebase calls. */ dst->data = data; dst->dataSize = size; memcpy(data, src->data, size); /* Script filenames, bytecodes and atoms are runtime-wide. */ dst->code = src->code; dst->atoms = src->atoms; dst->length = src->length; dst->lineno = src->lineno; dst->mainOffset = src->mainOffset; dst->natoms = src->natoms; dst->nfixed = src->nfixed; dst->nTypeSets = src->nTypeSets; dst->nslots = src->nslots; if (src->argumentsHasVarBinding()) { dst->setArgumentsHasVarBinding(); if (src->analyzedArgsUsage()) dst->setNeedsArgsObj(src->needsArgsObj()); } dst->cloneHasArray(src); dst->strict = src->strict; dst->explicitUseStrict = src->explicitUseStrict; dst->bindingsAccessedDynamically = src->bindingsAccessedDynamically; dst->funHasExtensibleScope = src->funHasExtensibleScope; dst->funNeedsDeclEnvObject = src->funNeedsDeclEnvObject; dst->funHasAnyAliasedFormal = src->funHasAnyAliasedFormal; dst->hasSingletons = src->hasSingletons; dst->isGenerator = src->isGenerator; dst->isGeneratorExp = src->isGeneratorExp; /* Copy over hints. */ dst->shouldCloneAtCallsite = src->shouldCloneAtCallsite; dst->isCallsiteClone = src->isCallsiteClone; /* * initScriptCounts updates scriptCountsMap if necessary. The other script * maps in JSCompartment are populated lazily. */ if (cx->hasOption(JSOPTION_PCCOUNT)) (void) dst->initScriptCounts(cx); if (nconsts != 0) { HeapValue *vector = Rebase(dst, src, src->consts()->vector); dst->consts()->vector = vector; for (unsigned i = 0; i < nconsts; ++i) JS_ASSERT_IF(vector[i].isMarkable(), vector[i].toString()->isAtom()); } if (nobjects != 0) { HeapPtrObject *vector = Rebase >(dst, src, src->objects()->vector); dst->objects()->vector = vector; for (unsigned i = 0; i < nobjects; ++i) vector[i].init(objects[i]); } if (nregexps != 0) { HeapPtrObject *vector = Rebase >(dst, src, src->regexps()->vector); dst->regexps()->vector = vector; for (unsigned i = 0; i < nregexps; ++i) vector[i].init(regexps[i]); } if (ntrynotes != 0) dst->trynotes()->vector = Rebase(dst, src, src->trynotes()->vector); return dst; } bool js::CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone, NewObjectKind newKind /* = GenericObject */) { JS_ASSERT(clone->isInterpreted()); RootedScript script(cx, clone->nonLazyScript()); JS_ASSERT(script); JS_ASSERT(script->compartment() == original->compartment()); JS_ASSERT_IF(script->compartment() != cx->compartment(), !script->enclosingStaticScope()); RootedObject scope(cx, script->enclosingStaticScope()); clone->mutableScript().init(NULL); JSScript *cscript = CloneScript(cx, scope, clone, script, newKind); if (!cscript) return false; clone->setScript(cscript); cscript->setFunction(clone); RootedGlobalObject global(cx, script->compileAndGo ? &script->global() : NULL); script = clone->nonLazyScript(); CallNewScriptHook(cx, script, clone); Debugger::onNewScript(cx, script, global); return true; } DebugScript * JSScript::debugScript() { JS_ASSERT(hasDebugScript); DebugScriptMap *map = compartment()->debugScriptMap; JS_ASSERT(map); DebugScriptMap::Ptr p = map->lookup(this); JS_ASSERT(p); return p->value; } DebugScript * JSScript::releaseDebugScript() { JS_ASSERT(hasDebugScript); DebugScriptMap *map = compartment()->debugScriptMap; JS_ASSERT(map); DebugScriptMap::Ptr p = map->lookup(this); JS_ASSERT(p); DebugScript *debug = p->value; map->remove(p); hasDebugScript = false; return debug; } void JSScript::destroyDebugScript(FreeOp *fop) { if (hasDebugScript) { jsbytecode *end = code + length; for (jsbytecode *pc = code; pc < end; pc++) { if (BreakpointSite *site = getBreakpointSite(pc)) { /* Breakpoints are swept before finalization. */ JS_ASSERT(site->firstBreakpoint() == NULL); site->clearTrap(fop, NULL, NULL); JS_ASSERT(getBreakpointSite(pc) == NULL); } } fop->free_(releaseDebugScript()); } } bool JSScript::ensureHasDebugScript(JSContext *cx) { if (hasDebugScript) return true; size_t nbytes = offsetof(DebugScript, breakpoints) + length * sizeof(BreakpointSite*); DebugScript *debug = (DebugScript *) cx->calloc_(nbytes); if (!debug) return false; /* Create compartment's debugScriptMap if necessary. */ DebugScriptMap *map = compartment()->debugScriptMap; if (!map) { map = cx->new_(); if (!map || !map->init()) { js_free(debug); js_delete(map); return false; } compartment()->debugScriptMap = map; } if (!map->putNew(this, debug)) { js_free(debug); return false; } hasDebugScript = true; // safe to set this; we can't fail after this point /* * Ensure that any Interpret() instances running on this script have * interrupts enabled. The interrupts must stay enabled until the * debug state is destroyed. */ InterpreterFrames *frames; for (frames = cx->runtime()->interpreterFrames; frames; frames = frames->older) frames->enableInterruptsIfRunning(this); return true; } void JSScript::recompileForStepMode(FreeOp *fop) { #ifdef JS_ION if (hasBaselineScript()) baseline->toggleDebugTraps(this, NULL); #endif } bool JSScript::tryNewStepMode(JSContext *cx, uint32_t newValue) { JS_ASSERT(hasDebugScript); DebugScript *debug = debugScript(); uint32_t prior = debug->stepMode; debug->stepMode = newValue; if (!prior != !newValue) { /* Step mode has been enabled or disabled. Alert the methodjit. */ recompileForStepMode(cx->runtime()->defaultFreeOp()); if (!stepModeEnabled() && !debug->numSites) js_free(releaseDebugScript()); } return true; } bool JSScript::setStepModeFlag(JSContext *cx, bool step) { if (!ensureHasDebugScript(cx)) return false; return tryNewStepMode(cx, (debugScript()->stepMode & stepCountMask) | (step ? stepFlagMask : 0)); } bool JSScript::changeStepModeCount(JSContext *cx, int delta) { if (!ensureHasDebugScript(cx)) return false; assertSameCompartment(cx, this); JS_ASSERT_IF(delta > 0, cx->compartment()->debugMode()); DebugScript *debug = debugScript(); uint32_t count = debug->stepMode & stepCountMask; JS_ASSERT(((count + delta) & stepCountMask) == count + delta); return tryNewStepMode(cx, (debug->stepMode & stepFlagMask) | ((count + delta) & stepCountMask)); } BreakpointSite * JSScript::getOrCreateBreakpointSite(JSContext *cx, jsbytecode *pc) { JS_ASSERT(size_t(pc - code) < length); if (!ensureHasDebugScript(cx)) return NULL; DebugScript *debug = debugScript(); BreakpointSite *&site = debug->breakpoints[pc - code]; if (!site) { site = cx->runtime()->new_(this, pc); if (!site) { js_ReportOutOfMemory(cx); return NULL; } debug->numSites++; } return site; } void JSScript::destroyBreakpointSite(FreeOp *fop, jsbytecode *pc) { JS_ASSERT(unsigned(pc - code) < length); DebugScript *debug = debugScript(); BreakpointSite *&site = debug->breakpoints[pc - code]; JS_ASSERT(site); fop->delete_(site); site = NULL; if (--debug->numSites == 0 && !stepModeEnabled()) fop->free_(releaseDebugScript()); } void JSScript::clearBreakpointsIn(FreeOp *fop, js::Debugger *dbg, JSObject *handler) { if (!hasAnyBreakpointsOrStepMode()) return; jsbytecode *end = code + length; for (jsbytecode *pc = code; pc < end; pc++) { BreakpointSite *site = getBreakpointSite(pc); if (site) { Breakpoint *nextbp; for (Breakpoint *bp = site->firstBreakpoint(); bp; bp = nextbp) { nextbp = bp->nextInSite(); if ((!dbg || bp->debugger == dbg) && (!handler || bp->getHandler() == handler)) bp->destroy(fop); } } } } bool JSScript::hasBreakpointsAt(jsbytecode *pc) { BreakpointSite *site = getBreakpointSite(pc); if (!site) return false; return site->enabledCount > 0 || site->trapHandler; } void JSScript::clearTraps(FreeOp *fop) { if (!hasAnyBreakpointsOrStepMode()) return; jsbytecode *end = code + length; for (jsbytecode *pc = code; pc < end; pc++) { BreakpointSite *site = getBreakpointSite(pc); if (site) site->clearTrap(fop); } } void JSScript::markChildren(JSTracer *trc) { // NOTE: this JSScript may be partially initialized at this point. E.g. we // may have created it and partially initialized it with // JSScript::Create(), but not yet finished initializing it with // fullyInitFromEmitter() or fullyInitTrivial(). JS_ASSERT_IF(trc->runtime->gcStrictCompartmentChecking, zone()->isCollecting()); for (uint32_t i = 0; i < natoms; ++i) { if (atoms[i]) MarkString(trc, &atoms[i], "atom"); } if (hasObjects()) { ObjectArray *objarray = objects(); MarkObjectRange(trc, objarray->length, objarray->vector, "objects"); } if (hasRegexps()) { ObjectArray *objarray = regexps(); MarkObjectRange(trc, objarray->length, objarray->vector, "objects"); } if (hasConsts()) { ConstArray *constarray = consts(); MarkValueRange(trc, constarray->length, constarray->vector, "consts"); } if (sourceObject()) MarkObject(trc, &sourceObject_, "sourceObject"); if (function()) MarkObject(trc, &function_, "function"); if (enclosingScopeOrOriginalFunction_) MarkObject(trc, &enclosingScopeOrOriginalFunction_, "enclosing"); if (IS_GC_MARKING_TRACER(trc)) { compartment()->mark(); if (code) MarkScriptBytecode(trc->runtime, code); } bindings.trace(trc); if (hasAnyBreakpointsOrStepMode()) { for (unsigned i = 0; i < length; i++) { BreakpointSite *site = debugScript()->breakpoints[i]; if (site && site->trapHandler) MarkValue(trc, &site->trapClosure, "trap closure"); } } #ifdef JS_ION jit::TraceIonScripts(trc, this); #endif } void LazyScript::markChildren(JSTracer *trc) { if (function_) MarkObject(trc, &function_, "function"); if (sourceObject_) MarkObject(trc, &sourceObject_, "sourceObject"); if (enclosingScope_) MarkObject(trc, &enclosingScope_, "enclosingScope"); if (script_) MarkScript(trc, &script_, "realScript"); HeapPtrAtom *freeVariables = this->freeVariables(); for (size_t i = 0; i < numFreeVariables(); i++) MarkString(trc, &freeVariables[i], "lazyScriptFreeVariable"); HeapPtrFunction *innerFunctions = this->innerFunctions(); for (size_t i = 0; i < numInnerFunctions(); i++) MarkObject(trc, &innerFunctions[i], "lazyScriptInnerFunction"); } void LazyScript::finalize(FreeOp *fop) { if (table_) fop->free_(table_); if (originPrincipals_) JS_DropPrincipals(fop->runtime(), originPrincipals_); } void JSScript::setArgumentsHasVarBinding() { argsHasVarBinding_ = true; needsArgsAnalysis_ = true; } void JSScript::setNeedsArgsObj(bool needsArgsObj) { JS_ASSERT(!analyzedArgsUsage()); JS_ASSERT_IF(needsArgsObj, argumentsHasVarBinding()); needsArgsAnalysis_ = false; needsArgsObj_ = needsArgsObj; } void js::SetFrameArgumentsObject(JSContext *cx, AbstractFramePtr frame, HandleScript script, JSObject *argsobj) { /* * Replace any optimized arguments in the frame with an explicit arguments * object. Note that 'arguments' may have already been overwritten. */ InternalBindingsHandle bindings(script, &script->bindings); const unsigned var = Bindings::argumentsVarIndex(cx, bindings); if (script->varIsAliased(var)) { /* * Scan the script to find the slot in the call object that 'arguments' * is assigned to. */ jsbytecode *pc = script->code; while (*pc != JSOP_ARGUMENTS) pc += GetBytecodeLength(pc); pc += JSOP_ARGUMENTS_LENGTH; JS_ASSERT(*pc == JSOP_SETALIASEDVAR); if (frame.callObj().as().aliasedVar(pc).isMagic(JS_OPTIMIZED_ARGUMENTS)) frame.callObj().as().setAliasedVar(cx, pc, cx->names().arguments, ObjectValue(*argsobj)); } else { if (frame.unaliasedLocal(var).isMagic(JS_OPTIMIZED_ARGUMENTS)) frame.unaliasedLocal(var) = ObjectValue(*argsobj); } } /* static */ bool JSScript::argumentsOptimizationFailed(JSContext *cx, HandleScript script) { JS_ASSERT(script->function()); JS_ASSERT(script->analyzedArgsUsage()); JS_ASSERT(script->argumentsHasVarBinding()); /* * It is possible that the arguments optimization has already failed, * everything has been fixed up, but there was an outstanding magic value * on the stack that has just now flowed into an apply. In this case, there * is nothing to do; GuardFunApplySpeculation will patch in the real * argsobj. */ if (script->needsArgsObj()) return true; JS_ASSERT(!script->isGenerator); script->needsArgsObj_ = true; #ifdef JS_ION /* * Since we can't invalidate baseline scripts, set a flag that's checked from * JIT code to indicate the arguments optimization failed and JSOP_ARGUMENTS * should create an arguments object next time. */ if (script->hasBaselineScript()) script->baselineScript()->setNeedsArgsObj(); #endif /* * By design, the arguments optimization is only made when there are no * outstanding cases of MagicValue(JS_OPTIMIZED_ARGUMENTS) at any points * where the optimization could fail, other than an active invocation of * 'f.apply(x, arguments)'. Thus, there are no outstanding values of * MagicValue(JS_OPTIMIZED_ARGUMENTS) on the stack. However, there are * three things that need fixup: * - there may be any number of activations of this script that don't have * an argsObj that now need one. * - jit code compiled (and possible active on the stack) with the static * assumption of !script->needsArgsObj(); * - type inference data for the script assuming script->needsArgsObj */ for (AllFramesIter i(cx); !i.done(); ++i) { /* * We cannot reliably create an arguments object for Ion activations of * this script. To maintain the invariant that "script->needsArgsObj * implies fp->hasArgsObj", the Ion bail mechanism will create an * arguments object right after restoring the StackFrame and before * entering the interpreter (in jit::ThunkToInterpreter). This delay is * safe since the engine avoids any observation of a StackFrame when it's * runningInJit (see ScriptFrameIter::interpFrame comment). */ if (i.isIon()) continue; AbstractFramePtr frame = i.abstractFramePtr(); if (frame.isFunctionFrame() && frame.script() == script) { ArgumentsObject *argsobj = ArgumentsObject::createExpected(cx, frame); if (!argsobj) { /* * We can't leave stack frames with script->needsArgsObj but no * arguments object. It is, however, safe to leave frames with * an arguments object but !script->needsArgsObj. */ script->needsArgsObj_ = false; return false; } SetFrameArgumentsObject(cx, frame, script, argsobj); } } if (script->hasAnalysis() && script->analysis()->ranInference()) { types::AutoEnterAnalysis enter(cx); types::TypeScript::MonitorUnknown(cx, script, script->argumentsBytecode()); } return true; } bool JSScript::varIsAliased(unsigned varSlot) { return bindings.bindingIsAliased(bindings.numArgs() + varSlot); } bool JSScript::formalIsAliased(unsigned argSlot) { return bindings.bindingIsAliased(argSlot); } bool JSScript::formalLivesInArgumentsObject(unsigned argSlot) { return argsObjAliasesFormals() && !formalIsAliased(argSlot); } LazyScript::LazyScript(JSFunction *fun, void *table, uint32_t numFreeVariables, uint32_t numInnerFunctions, JSVersion version, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column) : script_(NULL), function_(fun), enclosingScope_(NULL), sourceObject_(NULL), table_(table), originPrincipals_(NULL), version_(version), numFreeVariables_(numFreeVariables), numInnerFunctions_(numInnerFunctions), strict_(false), bindingsAccessedDynamically_(false), hasDebuggerStatement_(false), directlyInsideEval_(false), usesArgumentsAndApply_(false), hasBeenCloned_(false), begin_(begin), end_(end), lineno_(lineno), column_(column) { JS_ASSERT(this->version() == version); JS_ASSERT(begin <= end); } void LazyScript::initScript(JSScript *script) { JS_ASSERT(script && !script_); script_ = script; } void LazyScript::setParent(JSObject *enclosingScope, ScriptSourceObject *sourceObject, JSPrincipals *originPrincipals) { JS_ASSERT(sourceObject && !sourceObject_ && !enclosingScope_ && !originPrincipals_); enclosingScope_ = enclosingScope; sourceObject_ = sourceObject; originPrincipals_ = originPrincipals; if (originPrincipals) JS_HoldPrincipals(originPrincipals); } ScriptSourceObject * LazyScript::sourceObject() const { return sourceObject_ ? &sourceObject_->as() : NULL; } /* static */ LazyScript * LazyScript::Create(JSContext *cx, HandleFunction fun, uint32_t numFreeVariables, uint32_t numInnerFunctions, JSVersion version, uint32_t begin, uint32_t end, uint32_t lineno, uint32_t column) { JS_ASSERT(begin <= end); size_t bytes = (numFreeVariables * sizeof(HeapPtrAtom)) + (numInnerFunctions * sizeof(HeapPtrFunction)); void *table = NULL; if (bytes) { table = cx->malloc_(bytes); if (!table) return NULL; } LazyScript *res = js_NewGCLazyScript(cx); if (!res) return NULL; return new (res) LazyScript(fun, table, numFreeVariables, numInnerFunctions, version, begin, end, lineno, column); } uint32_t LazyScript::staticLevel(JSContext *cx) const { for (StaticScopeIter ssi(cx, enclosingScope()); !ssi.done(); ssi++) { if (ssi.type() == StaticScopeIter::FUNCTION) return ssi.funScript()->staticLevel + 1; } return 1; } void JSScript::updateBaselineOrIonRaw() { #ifdef JS_ION if (hasIonScript()) { baselineOrIonRaw = ion->method()->raw(); baselineOrIonSkipArgCheck = ion->method()->raw() + ion->getSkipArgCheckEntryOffset(); } else if (hasBaselineScript()) { baselineOrIonRaw = baseline->method()->raw(); baselineOrIonSkipArgCheck = baseline->method()->raw(); } else { baselineOrIonRaw = NULL; baselineOrIonSkipArgCheck = NULL; } #endif } static inline void LazyScriptHash(uint32_t lineno, uint32_t column, uint32_t begin, uint32_t end, HashNumber hashes[3]) { HashNumber hash = lineno; hash = JS_ROTATE_LEFT32(hash, 4) ^ column; hash = JS_ROTATE_LEFT32(hash, 4) ^ begin; hash = JS_ROTATE_LEFT32(hash, 4) ^ end; hashes[0] = hash; hashes[1] = JS_ROTATE_LEFT32(hashes[0], 4) ^ begin; hashes[2] = JS_ROTATE_LEFT32(hashes[1], 4) ^ end; } void LazyScriptHashPolicy::hash(const Lookup &lookup, HashNumber hashes[3]) { LazyScript *lazy = lookup.lazy; LazyScriptHash(lazy->lineno(), lazy->column(), lazy->begin(), lazy->end(), hashes); } void LazyScriptHashPolicy::hash(JSScript *script, HashNumber hashes[3]) { LazyScriptHash(script->lineno, script->column, script->sourceStart, script->sourceEnd, hashes); } bool LazyScriptHashPolicy::match(JSScript *script, const Lookup &lookup) { JSContext *cx = lookup.cx; LazyScript *lazy = lookup.lazy; // To be a match, the script and lazy script need to have the same line // and column and to be at the same position within their respective // source blobs, and to have the same source contents and version. // // While the surrounding code in the source may differ, this is // sufficient to ensure that compiling the lazy script will yield an // identical result to compiling the original script. // // Note that the filenames and origin principals of the lazy script and // original script can differ. If there is a match, these will be fixed // up in the resulting clone by the caller. if (script->lineno != lazy->lineno() || script->column != lazy->column() || script->getVersion() != lazy->version() || script->sourceStart != lazy->begin() || script->sourceEnd != lazy->end()) { return false; } // GC activity may destroy the character pointers being compared below. AutoSuppressGC suppress(cx); const jschar *scriptChars = script->scriptSource()->chars(cx); if (!scriptChars) return false; const jschar *lazyChars = lazy->source()->chars(cx); if (!lazyChars) return false; size_t begin = script->sourceStart; size_t length = script->sourceEnd - begin; return !memcmp(scriptChars + begin, lazyChars + begin, length); }