diff --git a/js/src/jit-test/tests/basic/testScriptCloning.js b/js/src/jit-test/tests/basic/testScriptCloning.js new file mode 100644 index 00000000000..ca77ea9380f --- /dev/null +++ b/js/src/jit-test/tests/basic/testScriptCloning.js @@ -0,0 +1,24 @@ +var g = newGlobal('new-compartment'); + +g.f = new Function('return function(x) { return x }'); +assertEq(g.eval("clone(f)()(9)"), 9); + +g.f = new Function('return function(x) { let(y = x+1) { return y } }'); +assertEq(g.eval("clone(f)()(9)"), 10); + +g.f = new Function('return function(x) { let(y = x, z = 1) { return y+z } }'); +assertEq(g.eval("clone(f)()(9)"), 10); + +g.f = new Function('return function(x) { return x.search(/ponies/) }'); +assertEq(g.eval("clone(f)()('123ponies')"), 3); + +g.f = new Function('return function(x,y) { return x.search(/a/) + y.search(/b/) }'); +assertEq(g.eval("clone(f)()('12a','foo')"), 1); + +g.f = new Function('return [function(x) x+2, function(y) let(z=y+1) z]'); +assertEq(g.eval("let ([f,g] = clone(f)()) f(g(4))"), 7); + +g.f = new Function('return function(x) { switch(x) { case "a": return "b"; case null: return "c" } }'); +assertEq(g.eval("clone(f)()('a')"), "b"); +assertEq(g.eval("clone(f)()(null)"), "c"); +assertEq(g.eval("clone(f)()(3)"), undefined); diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index fc9485d1789..189025b6bab 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -374,6 +374,7 @@ template bool js::XDRInterpretedFunction(XDRState *xdr, JSObject **objp, JSScript *parentScript) { + /* NB: Keep this in sync with CloneInterpretedFunction. */ JSFunction *fun; JSAtom *atom; uint32_t firstword; /* flag telling whether fun->atom is non-null, @@ -442,6 +443,35 @@ js::XDRInterpretedFunction(XDRState *xdr, JSObject **objp, JSScript template bool js::XDRInterpretedFunction(XDRState *xdr, JSObject **objp, JSScript *parentScript); +JSObject * +js::CloneInterpretedFunction(JSContext *cx, JSFunction *srcFun) +{ + /* NB: Keep this in sync with XDRInterpretedFunction. */ + + RootedVarObject parent(cx, NULL); + JSFunction *clone = js_NewFunction(cx, NULL, NULL, 0, JSFUN_INTERPRETED, parent, NULL); + if (!clone) + return NULL; + if (!clone->clearParent(cx)) + return NULL; + if (!clone->clearType(cx)) + return NULL; + + JSScript *clonedScript = CloneScript(cx, srcFun->script()); + if (!clonedScript) + return NULL; + + clone->nargs = srcFun->nargs; + clone->flags = srcFun->flags; + clone->atom.init(srcFun->atom); + clone->initScript(clonedScript); + if (!clonedScript->typeSetFunction(cx, clone)) + return NULL; + + js_CallNewScriptHook(cx, clone->script(), clone); + return clone; +} + /* * [[HasInstance]] internal method for Function objects: fetch the .prototype * property of its 'this' parameter, and walks the prototype chain of v (only diff --git a/js/src/jsfun.h b/js/src/jsfun.h index d4ff16a27dd..d5c7c49af9f 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -311,6 +311,9 @@ template bool XDRInterpretedFunction(XDRState *xdr, JSObject **objp, JSScript *parentScript); +extern JSObject * +CloneInterpretedFunction(JSContext *cx, JSFunction *fun); + } /* namespace js */ extern JSBool diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index f4222898392..d7cf53080a2 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -76,6 +76,8 @@ #include "jsobjinlines.h" #include "jsscriptinlines.h" +#include "vm/RegExpObject-inl.h" + using namespace js; using namespace js::gc; using namespace js::frontend; @@ -217,7 +219,8 @@ bool Bindings::getLocalNameArray(JSContext *cx, BindingNames *namesp) { JS_ASSERT(lastBinding); - JS_ASSERT(count() > 0); + if (count() == 0) + return true; BindingNames &names = *namesp; JS_ASSERT(names.empty()); @@ -395,6 +398,8 @@ template bool js::XDRScript(XDRState *xdr, JSScript **scriptp, JSScript *parentScript) { + /* NB: Keep this in sync with CloneScript. */ + enum ScriptBits { NoScriptRval, SavedCallerFun, @@ -1120,11 +1125,10 @@ JS_STATIC_ASSERT(sizeof(JSConstArray) + < JSScript::INVALID_OFFSET); JS_STATIC_ASSERT(JSScript::INVALID_OFFSET <= 255); -JSScript * -JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t natoms, - uint32_t nobjects, uint32_t nregexps, - uint32_t ntrynotes, uint32_t nconsts, uint32_t nglobals, - uint16_t nClosedArgs, uint16_t nClosedVars, uint32_t nTypeSets, JSVersion version) +static inline size_t +ScriptDataSize(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t natoms, + uint32_t nobjects, uint32_t nregexps, uint32_t ntrynotes, uint32_t nconsts, + uint32_t nglobals, uint16_t nClosedArgs, uint16_t nClosedVars) { size_t size = 0; @@ -1146,16 +1150,33 @@ JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t size += length * sizeof(jsbytecode); size += nsrcnotes * sizeof(jssrcnote); + return size; +} - /* - * We assume that calloc aligns on sizeof(Value) if the size we ask to - * allocate divides sizeof(Value). - */ - JS_STATIC_ASSERT(sizeof(Value) == sizeof(double)); +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; + JS_ASSERT(size_t(data) % sizeof(Value) == 0); + return data; +} + +JSScript * +JSScript::NewScript(JSContext *cx, uint32_t length, uint32_t nsrcnotes, uint32_t natoms, + uint32_t nobjects, uint32_t nregexps, + uint32_t ntrynotes, uint32_t nconsts, uint32_t nglobals, + uint16_t nClosedArgs, uint16_t nClosedVars, uint32_t nTypeSets, JSVersion version) +{ + size_t size = ScriptDataSize(cx, length, nsrcnotes, natoms, nobjects, nregexps, + ntrynotes, nconsts, nglobals, nClosedArgs, nClosedVars); + + uint8_t *data = AllocScriptData(cx, size); + if (!data) + return NULL; + JSScript *script = js_NewGCScript(cx); if (!script) { Foreground::free_(data); @@ -1449,6 +1470,10 @@ JSScript::NewScriptFromEmitter(JSContext *cx, BytecodeEmitter *bce) Debugger::onNewScript(cx, script, compileAndGoGlobal); } + /* + * initScriptCounts updates scriptCountsMap if necessary. The other script + * maps in JSCompartment are populated lazily. + */ if (cx->hasRunOption(JSOPTION_PCCOUNT)) (void) script->initScriptCounts(cx); @@ -1738,28 +1763,186 @@ CurrentScriptFileLineOriginSlow(JSContext *cx, const char **file, unsigned *line } /* namespace js */ -JSScript * -js::CloneScript(JSContext *cx, JSScript *script) +template +static inline T * +Rebase(JSScript *dst, JSScript *src, T *srcp) { - JS_ASSERT(cx->compartment != script->compartment()); + size_t off = reinterpret_cast(srcp) - src->data; + return reinterpret_cast(dst->data + off); +} - /* Serialize script. */ - XDREncoder encoder(cx); +JSScript * +js::CloneScript(JSContext *cx, JSScript *src) +{ + /* NB: Keep this in sync with XDRScript. */ - if (!XDRScript(&encoder, &script, NULL)) + uint32_t nconsts = JSScript::isValidOffset(src->constsOffset) ? src->consts()->length : 0; + uint32_t nobjects = JSScript::isValidOffset(src->objectsOffset) ? src->objects()->length : 0; + uint32_t nregexps = JSScript::isValidOffset(src->regexpsOffset) ? src->regexps()->length : 0; + uint32_t ntrynotes = JSScript::isValidOffset(src->trynotesOffset) ? src->trynotes()->length : 0; + uint32_t nClosedArgs = src->numClosedArgs(); + uint32_t nClosedVars = src->numClosedVars(); + JS_ASSERT(!JSScript::isValidOffset(src->globalsOffset)); + uint32_t nglobals = 0; + + /* Script data */ + + size_t size = ScriptDataSize(cx, src->length, src->numNotes(), src->natoms, + nobjects, nregexps, ntrynotes, nconsts, nglobals, + nClosedArgs, nClosedVars); + + uint8_t *data = AllocScriptData(cx, size); + if (!data) return NULL; - uint32_t nbytes; - const void *p = encoder.getData(&nbytes); + /* Bindings */ - /* De-serialize script. */ - XDRDecoder decoder(cx, p, nbytes, cx->compartment->principals, script->originPrincipals); + Bindings bindings(cx); + BindingNames names(cx); + if (!src->bindings.getLocalNameArray(cx, &names)) + return false; - JSScript *newScript; - if (!XDRScript(&decoder, &newScript, NULL)) + for (unsigned i = 0; i < names.length(); ++i) { + if (JSAtom *atom = names[i].maybeAtom) { + if (!bindings.add(cx, RootedVarAtom(cx, atom), names[i].kind)) + return false; + } else { + uint16_t _; + if (!bindings.addDestructuring(cx, &_)) + return false; + } + } + + if (!bindings.ensureShape(cx)) + return false; + bindings.makeImmutable(); + + /* Objects */ + + AutoObjectVector objects(cx); + if (nobjects != 0) { + HeapPtrObject *vector = src->objects()->vector; + for (unsigned i = 0; i < nobjects; i++) { + JSObject *clone = vector[i]->isStaticBlock() + ? CloneStaticBlockObject(cx, vector[i]->asStaticBlock(), objects, src) + : CloneInterpretedFunction(cx, vector[i]->toFunction()); + if (!clone || !objects.append(clone)) + return false; + } + } + + /* RegExps */ + + AutoObjectVector regexps(cx); + for (unsigned i = 0; i < nregexps; i++) { + HeapPtrObject *vector = src->regexps()->vector; + for (unsigned i = 0; i < nregexps; i++) { + JSObject *clone = CloneScriptRegExpObject(cx, vector[i]->asRegExp()); + if (!clone || !regexps.append(clone)) + return false; + } + } + + /* Now that all fallible allocation is complete, create the GC thing. */ + + JSScript *dst = js_NewGCScript(cx); + if (!dst) { + Foreground::free_(data); return NULL; + } - return newScript; + PodZero(dst); + + new (&dst->bindings) Bindings(cx); + dst->bindings.transfer(cx, &bindings); + + /* This assignment must occur before all the Rebase calls. */ + dst->data = data; + memcpy(data, src->data, size); + + dst->code = Rebase(dst, src, src->code); + + /* Script filenames are runtime-wide. */ + dst->filename = src->filename; + + /* Atoms are runtime-wide. */ + if (src->natoms != 0) + dst->atoms = Rebase(dst, src, src->atoms); + + dst->principals = cx->compartment->principals; + if (dst->principals) + JS_HoldPrincipals(dst->principals); + + /* Establish invariant: principals implies originPrincipals. */ + dst->originPrincipals = src->originPrincipals; + if (!dst->originPrincipals) + dst->originPrincipals = dst->principals; + if (dst->originPrincipals) + JS_HoldPrincipals(dst->originPrincipals); + + dst->length = src->length; + dst->lineno = src->lineno; + dst->mainOffset = src->mainOffset; + dst->natoms = src->natoms; + dst->setVersion(src->getVersion()); + dst->nfixed = src->nfixed; + dst->nTypeSets = src->nTypeSets; + dst->nslots = src->nslots; + dst->staticLevel = src->staticLevel; + if (src->argumentsHasLocalBinding()) { + dst->setArgumentsHasLocalBinding(src->argumentsLocalSlot()); + if (src->analyzedArgsUsage()) + dst->setNeedsArgsObj(src->needsArgsObj()); + } + dst->constsOffset = src->constsOffset; + dst->objectsOffset = src->objectsOffset; + dst->regexpsOffset = src->regexpsOffset; + dst->trynotesOffset = src->trynotesOffset; + dst->globalsOffset = src->globalsOffset; + dst->closedArgsOffset = src->closedArgsOffset; + dst->closedVarsOffset = src->closedVarsOffset; + dst->noScriptRval = src->noScriptRval; + dst->savedCallerFun = src->savedCallerFun; + dst->strictModeCode = src->strictModeCode; + dst->compileAndGo = src->compileAndGo; + dst->bindingsAccessedDynamically = src->bindingsAccessedDynamically; + dst->hasSingletons = src->hasSingletons; + dst->isGenerator = src->isGenerator; + + /* + * initScriptCounts updates scriptCountsMap if necessary. The other script + * maps in JSCompartment are populated lazily. + */ + if (cx->hasRunOption(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); + if (nClosedArgs != 0) + dst->closedArgs()->vector = Rebase(dst, src, src->closedArgs()->vector); + if (nClosedVars != 0) + dst->closedVars()->vector = Rebase(dst, src, src->closedVars()->vector); + JS_ASSERT(nglobals == 0); + + return dst; } DebugScript * diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 0f088c8aba7..a7534519c59 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -592,6 +592,8 @@ struct JSScript : public js::gc::Cell JSVersion version); static JSScript *NewScriptFromEmitter(JSContext *cx, js::BytecodeEmitter *bce); + void setVersion(JSVersion v) { version = v; } + /* See TCF_ARGUMENTS_HAS_LOCAL_BINDING comment. */ bool argumentsHasLocalBinding() const { return argsHasLocalBinding_; } jsbytecode *argumentsBytecode() const { JS_ASSERT(code[0] == JSOP_ARGUMENTS); return code; } diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 08086a33872..b142d9cfd28 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -760,6 +760,8 @@ template bool js::XDRScriptRegExpObject(XDRState *xdr, HeapPtrObject *objp) { + /* NB: Keep this in sync with CloneScriptRegExpObject. */ + RootedVarAtom source(xdr->cx()); uint32_t flagsword = 0; @@ -791,3 +793,19 @@ js::XDRScriptRegExpObject(XDRState *xdr, HeapPtrObject *objp); template bool js::XDRScriptRegExpObject(XDRState *xdr, HeapPtrObject *objp); + +JSObject * +js::CloneScriptRegExpObject(JSContext *cx, RegExpObject &reobj) +{ + /* NB: Keep this in sync with XDRScriptRegExpObject. */ + + RootedVarAtom source(cx, reobj.getSource()); + RegExpObject *clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), NULL); + if (!clone) + return NULL; + if (!clone->clearParent(cx)) + return false; + if (!clone->clearType(cx)) + return false; + return clone; +} diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 0691626eeb6..b27a7098083 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -474,6 +474,9 @@ template bool XDRScriptRegExpObject(XDRState *xdr, HeapPtrObject *objp); +extern JSObject * +CloneScriptRegExpObject(JSContext *cx, RegExpObject &re); + } /* namespace js */ #endif diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 88ba7643896..ae015dcef31 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -878,18 +878,23 @@ Class js::BlockClass = { #define NO_PARENT_INDEX UINT32_MAX +/* + * If there's a parent id, then get the parent out of our script's object + * array. We know that we clone block objects in outer-to-inner order, which + * means that getting the parent now will work. + */ static uint32_t -FindObjectIndex(JSObjectArray *array, JSObject *obj) +FindObjectIndex(JSScript *script, StaticBlockObject *maybeBlock) { - size_t i; + if (!maybeBlock || !JSScript::isValidOffset(script->objectsOffset)) + return NO_PARENT_INDEX; - if (array) { - i = array->length; - do { - - if (array->vector[--i] == obj) - return i; - } while (i != 0); + JSObjectArray *objects = script->objects(); + HeapPtrObject *vector = objects->vector; + unsigned length = objects->length; + for (unsigned i = 0; i < length; ++i) { + if (vector[i] == maybeBlock) + return i; } return NO_PARENT_INDEX; @@ -899,6 +904,8 @@ template bool js::XDRStaticBlockObject(XDRState *xdr, JSScript *script, StaticBlockObject **objp) { + /* NB: Keep this in sync with CloneStaticBlockObject. */ + JSContext *cx = xdr->cx(); StaticBlockObject *obj = NULL; @@ -907,9 +914,7 @@ js::XDRStaticBlockObject(XDRState *xdr, JSScript *script, StaticBlockObjec uint32_t depthAndCount = 0; if (mode == XDR_ENCODE) { obj = *objp; - parentId = JSScript::isValidOffset(script->objectsOffset) - ? FindObjectIndex(script->objects(), obj->enclosingBlock()) - : NO_PARENT_INDEX; + parentId = FindObjectIndex(script, obj->enclosingBlock()); uint32_t depth = obj->stackDepth(); JS_ASSERT(depth <= UINT16_MAX); count = obj->slotCount(); @@ -927,11 +932,6 @@ js::XDRStaticBlockObject(XDRState *xdr, JSScript *script, StaticBlockObjec return false; *objp = obj; - /* - * If there's a parent id, then get the parent out of our script's - * object array. We know that we XDR block object in outer-to-inner - * order, which means that getting the parent now will work. - */ obj->setEnclosingBlock(parentId == NO_PARENT_INDEX ? NULL : &script->getObject(parentId)->asStaticBlock()); @@ -976,7 +976,8 @@ js::XDRStaticBlockObject(XDRState *xdr, JSScript *script, StaticBlockObjec } } else { AutoShapeVector shapes(cx); - shapes.growBy(count); + if (!shapes.growBy(count)) + return false; for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) { const Shape *shape = &r.front(); @@ -1016,3 +1017,43 @@ js::XDRStaticBlockObject(XDRState *xdr, JSScript *script, StaticBloc template bool js::XDRStaticBlockObject(XDRState *xdr, JSScript *script, StaticBlockObject **objp); + +JSObject * +js::CloneStaticBlockObject(JSContext *cx, StaticBlockObject &srcBlock, + const AutoObjectVector &objects, JSScript *src) +{ + /* NB: Keep this in sync with XDRStaticBlockObject. */ + + StaticBlockObject *clone = StaticBlockObject::create(cx); + if (!clone) + return false; + + uint32_t parentId = FindObjectIndex(src, srcBlock.enclosingBlock()); + clone->setEnclosingBlock(parentId == NO_PARENT_INDEX + ? NULL + : &objects[parentId]->asStaticBlock()); + + clone->setStackDepth(srcBlock.stackDepth()); + + /* Shape::Range is reverse order, so build a list in forward order. */ + AutoShapeVector shapes(cx); + if (!shapes.growBy(srcBlock.slotCount())) + return false; + for (Shape::Range r = srcBlock.lastProperty()->all(); !r.empty(); r.popFront()) + shapes[r.front().shortid()] = &r.front(); + + for (const Shape **p = shapes.begin(); p != shapes.end(); ++p) { + jsid id = (*p)->propid(); + unsigned i = (*p)->shortid(); + + bool redeclared; + if (!clone->addVar(cx, id, i, &redeclared)) { + JS_ASSERT(!redeclared); + return false; + } + + clone->setAliased(i, srcBlock.isAliased(i)); + } + + return clone; +} diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index 97da34d0601..d397ffb2d18 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -299,6 +299,10 @@ template bool XDRStaticBlockObject(XDRState *xdr, JSScript *script, StaticBlockObject **objp); +extern JSObject * +CloneStaticBlockObject(JSContext *cx, StaticBlockObject &srcBlock, + const AutoObjectVector &objects, JSScript *src); + } /* namespace js */ #endif /* ScopeObject_h___ */ diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 1cf9edec0da..f442a1fca0f 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -57,7 +57,7 @@ namespace js { * and saved versions. If deserialization fails, the data should be * invalidated if possible. */ -static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 114); +static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 115); class XDRBuffer { public: