Bug 753145 - Attach static scope nesting information to scripts (r=jimb)

--HG--
extra : rebase_source : 0eaf04cf7f302308ce7f76c2012f75195e41f1bf
This commit is contained in:
Luke Wagner 2012-07-03 10:24:36 -07:00
parent 8a3f01ec9d
commit 7f687bc16b
18 changed files with 430 additions and 157 deletions

View File

@ -65,6 +65,7 @@ frontend::CompileScript(JSContext *cx, HandleObject scopeChain, StackFrame *call
bool savedCallerFun = compileAndGo && callerFrame && callerFrame->isFunctionFrame();
Rooted<JSScript*> script(cx, JSScript::Create(cx,
/* enclosingScope = */ NullPtr(),
savedCallerFun,
principals,
originPrincipals,
@ -231,6 +232,7 @@ frontend::CompileFunctionBody(JSContext *cx, HandleFunction fun,
return false;
Rooted<JSScript*> script(cx, JSScript::Create(cx,
/* enclosingScope = */ NullPtr(),
/* savedCallerFun = */ false,
principals,
originPrincipals,

View File

@ -673,12 +673,31 @@ PushStatementBCE(BytecodeEmitter *bce, StmtInfoBCE *stmt, StmtType type, ptrdiff
PushStatement(bce, stmt, type);
}
/*
* Return the enclosing lexical scope, which is the innermost enclosing static
* block object or compiler created function.
*/
static JSObject *
EnclosingStaticScope(BytecodeEmitter *bce)
{
if (bce->blockChain)
return bce->blockChain;
if (!bce->sc->inFunction()) {
JS_ASSERT(!bce->parent);
return NULL;
}
return bce->sc->fun();
}
// Push a block scope statement and link blockObj into bce->blockChain.
static void
PushBlockScopeBCE(BytecodeEmitter *bce, StmtInfoBCE *stmt, StaticBlockObject &blockObj,
ptrdiff_t top)
{
PushStatementBCE(bce, stmt, STMT_BLOCK, top);
blockObj.initEnclosingStaticScope(EnclosingStaticScope(bce));
FinishPushBlockScope(bce, stmt, blockObj);
}
@ -4219,7 +4238,6 @@ EmitIf(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
* destructure y
* pick 1
* dup +1 SRC_DESTRUCTLET + offset to enterlet0
* pick
* destructure z
* pick 1
* pop -1
@ -4827,7 +4845,9 @@ EmitFunc(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
// Inherit most things (principals, version, etc) from the parent.
Rooted<JSScript*> parent(cx, bce->script);
Rooted<JSObject*> enclosingScope(cx, EnclosingStaticScope(bce));
Rooted<JSScript*> script(cx, JSScript::Create(cx,
enclosingScope,
/* savedCallerFun = */ false,
parent->principals,
parent->originPrincipals,

View File

@ -102,14 +102,6 @@ PushStatementTC(TreeContext *tc, StmtInfoTC *stmt, StmtType type)
stmt->isFunctionBodyBlock = false;
}
// Push a block scope statement and link blockObj into tc->blockChain.
static void
PushBlockScopeTC(TreeContext *tc, StmtInfoTC *stmt, StaticBlockObject &blockObj)
{
PushStatementTC(tc, stmt, STMT_BLOCK);
FinishPushBlockScope(tc, stmt, blockObj);
}
Parser::Parser(JSContext *cx, JSPrincipals *prin, JSPrincipals *originPrin,
const jschar *chars, size_t length, const char *fn, unsigned ln, JSVersion v,
bool foldConstants, bool compileAndGo)
@ -2144,12 +2136,16 @@ struct RemoveDecl {
static void
PopStatementTC(TreeContext *tc)
{
if (tc->topStmt->isBlockScope) {
StaticBlockObject &blockObj = *tc->topStmt->blockObj;
JS_ASSERT(!blockObj.inDictionaryMode());
ForEachLetDef(tc, blockObj, RemoveDecl());
}
StaticBlockObject *blockObj = tc->topStmt->blockObj;
JS_ASSERT(!!blockObj == (tc->topStmt->isBlockScope));
FinishPopStatement(tc);
if (blockObj) {
JS_ASSERT(!blockObj->inDictionaryMode());
ForEachLetDef(tc, *blockObj, RemoveDecl());
blockObj->resetPrevBlockChainFromParser();
}
}
static inline bool
@ -2758,22 +2754,27 @@ Parser::returnOrYield(bool useAssignExpr)
}
static ParseNode *
PushLexicalScope(JSContext *cx, Parser *parser, StaticBlockObject &obj, StmtInfoTC *stmt)
PushLexicalScope(JSContext *cx, Parser *parser, StaticBlockObject &blockObj, StmtInfoTC *stmt)
{
ParseNode *pn = LexicalScopeNode::create(PNK_LEXICALSCOPE, parser);
if (!pn)
return NULL;
ObjectBox *blockbox = parser->newObjectBox(&obj);
ObjectBox *blockbox = parser->newObjectBox(&blockObj);
if (!blockbox)
return NULL;
PushBlockScopeTC(parser->tc, stmt, obj);
TreeContext *tc = parser->tc;
PushStatementTC(tc, stmt, STMT_BLOCK);
blockObj.initPrevBlockChainFromParser(tc->blockChain);
FinishPushBlockScope(tc, stmt, blockObj);
pn->setOp(JSOP_LEAVEBLOCK);
pn->pn_objbox = blockbox;
pn->pn_cookie.makeFree();
pn->pn_dflags = 0;
if (!GenerateBlockId(parser->tc, stmt->blockid))
if (!GenerateBlockId(tc, stmt->blockid))
return NULL;
pn->pn_blockid = stmt->blockid;
return pn;
@ -3775,7 +3776,7 @@ Parser::letStatement()
stmt->downScope = tc->topScopeStmt;
tc->topScopeStmt = stmt;
blockObj->setEnclosingBlock(tc->blockChain);
blockObj->initPrevBlockChainFromParser(tc->blockChain);
tc->blockChain = blockObj;
stmt->blockObj = blockObj;

View File

@ -145,7 +145,6 @@ frontend::FinishPushBlockScope(ContextT *ct, typename ContextT::StmtInfo *stmt,
StaticBlockObject &blockObj)
{
stmt->isBlockScope = true;
blockObj.setEnclosingBlock(ct->blockChain);
stmt->downScope = ct->topScopeStmt;
ct->topScopeStmt = stmt;
ct->blockChain = &blockObj;

View File

@ -4666,8 +4666,15 @@ JS_CloneFunctionObject(JSContext *cx, JSObject *funobj, JSObject *parent_)
return NULL;
}
/*
* If a function was compiled as compile-and-go or was compiled to be
* lexically nested inside some other script, we cannot clone it without
* breaking the compiler's assumptions.
*/
RootedFunction fun(cx, funobj->toFunction());
if (fun->isInterpreted() && fun->script()->compileAndGo) {
if (fun->isInterpreted() &&
(fun->script()->compileAndGo || fun->script()->enclosingStaticScope()))
{
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_BAD_CLONE_FUNOBJ_SCOPE);
return NULL;
@ -5377,7 +5384,7 @@ JS_ExecuteScript(JSContext *cx, JSObject *obj, JSScript *scriptArg_, jsval *rval
* mozilla, but there doesn't seem to be one, so we handle it here.
*/
if (scriptArg->compartment() != obj->compartment()) {
script = CloneScript(cx, scriptArg);
script = CloneScript(cx, NullPtr(), NullPtr(), scriptArg);
if (!script.get())
return false;
} else {

View File

@ -4645,6 +4645,10 @@ extern JS_PUBLIC_API(JSFunction *)
JS_DefineFunctionById(JSContext *cx, JSObject *obj, jsid id, JSNative call,
unsigned nargs, unsigned attrs);
/*
* Clone a top-level function into a new scope. This function will dynamically
* fail if funobj was lexically nested inside some other function.
*/
extern JS_PUBLIC_API(JSObject *)
JS_CloneFunctionObject(JSContext *cx, JSObject *funobj, JSObject *parent);

View File

@ -336,7 +336,8 @@ fun_resolve(JSContext *cx, HandleObject obj, HandleId id, unsigned flags,
template<XDRMode mode>
bool
js::XDRInterpretedFunction(XDRState<mode> *xdr, JSObject **objp, JSScript *parentScript)
js::XDRInterpretedFunction(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enclosingScript,
JSObject **objp)
{
/* NB: Keep this in sync with CloneInterpretedFunction. */
JSAtom *atom;
@ -382,7 +383,7 @@ js::XDRInterpretedFunction(XDRState<mode> *xdr, JSObject **objp, JSScript *paren
if (!xdr->codeUint32(&flagsword))
return false;
if (!XDRScript(xdr, &script, parentScript))
if (!XDRScript(xdr, enclosingScope, enclosingScript, fun, &script))
return false;
if (mode == XDR_DECODE) {
@ -403,13 +404,13 @@ js::XDRInterpretedFunction(XDRState<mode> *xdr, JSObject **objp, JSScript *paren
}
template bool
js::XDRInterpretedFunction(XDRState<XDR_ENCODE> *xdr, JSObject **objp, JSScript *parentScript);
js::XDRInterpretedFunction(XDRState<XDR_ENCODE> *, HandleObject, HandleScript, JSObject **);
template bool
js::XDRInterpretedFunction(XDRState<XDR_DECODE> *xdr, JSObject **objp, JSScript *parentScript);
js::XDRInterpretedFunction(XDRState<XDR_DECODE> *, HandleObject, HandleScript, JSObject **);
JSObject *
js::CloneInterpretedFunction(JSContext *cx, HandleFunction srcFun)
js::CloneInterpretedFunction(JSContext *cx, HandleObject enclosingScope, HandleFunction srcFun)
{
/* NB: Keep this in sync with XDRInterpretedFunction. */
@ -423,7 +424,7 @@ js::CloneInterpretedFunction(JSContext *cx, HandleFunction srcFun)
return NULL;
Rooted<JSScript*> srcScript(cx, srcFun->script());
JSScript *clonedScript = CloneScript(cx, srcScript);
JSScript *clonedScript = CloneScript(cx, enclosingScope, clone, srcScript);
if (!clonedScript)
return NULL;
@ -1281,16 +1282,20 @@ js_CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent,
} else {
/*
* Across compartments we have to clone the script for interpreted
* functions.
* functions. Cross-compartment cloning only happens via JSAPI
* (JS_CloneFunctionObject) which dynamically ensures that 'script' has
* no enclosing lexical scope (only the global scope).
*/
if (clone->isInterpreted()) {
RootedScript script(cx, clone->script());
JS_ASSERT(script);
JS_ASSERT(script->compartment() == fun->compartment());
JS_ASSERT(script->compartment() != cx->compartment);
JS_ASSERT(!script->enclosingStaticScope());
clone->mutableScript().init(NULL);
JSScript *cscript = CloneScript(cx, script);
JSScript *cscript = CloneScript(cx, NullPtr(), clone, script);
if (!cscript)
return NULL;

View File

@ -72,6 +72,7 @@ struct JSFunction : public JSObject
bool isNullClosure() const { return kind() == JSFUN_NULL_CLOSURE; }
bool isFunctionPrototype() const { return flags & JSFUN_PROTOTYPE; }
bool isInterpretedConstructor() const { return isInterpreted() && !isFunctionPrototype(); }
bool isNamedLambda() const { return (flags & JSFUN_LAMBDA) && atom; }
uint16_t kind() const { return flags & JSFUN_KINDMASK; }
void setKind(uint16_t k) {
@ -253,17 +254,15 @@ JSFunction::toExtended() const
return static_cast<const js::FunctionExtended *>(this);
}
inline bool
js_IsNamedLambda(JSFunction *fun) { return (fun->flags & JSFUN_LAMBDA) && fun->atom; }
namespace js {
template<XDRMode mode>
bool
XDRInterpretedFunction(XDRState<mode> *xdr, JSObject **objp, JSScript *parentScript);
XDRInterpretedFunction(XDRState<mode> *xdr, HandleObject enclosingScope,
HandleScript enclosingScript, JSObject **objp);
extern JSObject *
CloneInterpretedFunction(JSContext *cx, HandleFunction fun);
CloneInterpretedFunction(JSContext *cx, HandleObject enclosingScope, HandleFunction fun);
} /* namespace js */

View File

@ -51,8 +51,6 @@ using namespace js;
using namespace js::gc;
using namespace js::frontend;
namespace js {
BindingKind
Bindings::lookup(JSContext *cx, JSAtom *name, unsigned *indexp) const
{
@ -247,8 +245,6 @@ Bindings::trace(JSTracer *trc)
MarkShape(trc, &lastBinding, "shape");
}
} /* namespace js */
template<XDRMode mode>
static bool
XDRScriptConst(XDRState<mode> *xdr, HeapValue *vp)
@ -342,9 +338,25 @@ XDRScriptConst(XDRState<mode> *xdr, HeapValue *vp)
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<XDRMode mode>
bool
js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
js::XDRScript(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enclosingScript,
HandleFunction fun, JSScript **scriptp)
{
/* NB: Keep this in sync with CloneScript. */
@ -372,16 +384,12 @@ js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
nsrcnotes = ntrynotes = natoms = nobjects = nregexps = nconsts = nClosedArgs = nClosedVars = 0;
jssrcnote *notes = NULL;
/* XDR arguments, var vars, and upvars. */
uint16_t nargs, nvars;
#if defined(DEBUG) || defined(__GNUC__) /* quell GCC overwarning */
script = NULL;
nargs = nvars = Bindings::BINDING_COUNT_LIMIT;
#endif
uint32_t argsVars;
/* XDR arguments and vars. */
uint16_t nargs = 0, nvars = 0;
uint32_t argsVars = 0;
if (mode == XDR_ENCODE) {
script = *scriptp;
JS_ASSERT_IF(parentScript, parentScript->compartment() == script->compartment());
JS_ASSERT_IF(enclosingScript, enclosingScript->compartment() == script->compartment());
nargs = script->bindings.numArgs();
nvars = script->bindings.numVars();
@ -515,7 +523,7 @@ js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
if (script->analyzedArgsUsage() && script->needsArgsObj())
scriptBits |= (1 << NeedsArgsObj);
if (script->filename) {
scriptBits |= (parentScript && parentScript->filename == script->filename)
scriptBits |= (enclosingScript && enclosingScript->filename == script->filename)
? (1 << ParentFilename)
: (1 << OwnFilename);
}
@ -564,6 +572,7 @@ js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
// principals and originPrincipals are set with xdr->initScriptPrincipals(script) below.
// staticLevel is set below.
script = JSScript::Create(cx,
enclosingScope,
!!(scriptBits & (1 << SavedCallerFun)),
/* principals = */ NULL,
/* originPrincipals = */ NULL,
@ -621,9 +630,9 @@ js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
return false;
}
} else if (scriptBits & (1 << ParentFilename)) {
JS_ASSERT(parentScript);
JS_ASSERT(enclosingScript);
if (mode == XDR_DECODE)
script->filename = parentScript->filename;
script->filename = enclosingScript->filename;
}
if (mode == XDR_DECODE) {
@ -647,10 +656,9 @@ js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
}
/*
* Here looping from 0-to-length to xdr objects is essential. It ensures
* that block objects from the script->objects array will be written and
* restored in the outer-to-inner order. js_XDRBlockObject relies on this
* to restore the parent chain.
* 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<JSObject> *objp = &script->objects()->vector[i];
@ -663,14 +671,58 @@ js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
if (!xdr->codeUint32(&isBlock))
return false;
if (isBlock == 0) {
/* Code the nested function's enclosing scope. */
uint32_t funEnclosingScopeIndex = 0;
if (mode == XDR_ENCODE) {
StaticScopeIter ssi((*objp)->toFunction()->script()->enclosingStaticScope());
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<JSObject*> funEnclosingScope(cx);
if (mode == XDR_DECODE) {
if (funEnclosingScopeIndex == UINT32_MAX) {
funEnclosingScope = fun;
} else {
JS_ASSERT(funEnclosingScopeIndex < i);
funEnclosingScope = script->objects()->vector[funEnclosingScopeIndex];
}
}
JSObject *tmp = *objp;
if (!XDRInterpretedFunction(xdr, &tmp, parentScript))
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)->asStaticBlock().enclosingBlock())
blockEnclosingScopeIndex = FindBlockIndex(script, *block);
else
blockEnclosingScopeIndex = UINT32_MAX;
}
if (!xdr->codeUint32(&blockEnclosingScopeIndex))
return false;
Rooted<JSObject*> blockEnclosingScope(cx);
if (mode == XDR_DECODE) {
if (blockEnclosingScopeIndex != UINT32_MAX) {
JS_ASSERT(blockEnclosingScopeIndex < i);
blockEnclosingScope = script->objects()->vector[blockEnclosingScopeIndex];
} else {
blockEnclosingScope = fun;
}
}
StaticBlockObject *tmp = static_cast<StaticBlockObject *>(objp->get());
if (!XDRStaticBlockObject(xdr, script, &tmp))
if (!XDRStaticBlockObject(xdr, blockEnclosingScope, script, &tmp))
return false;
*objp = tmp;
}
@ -737,10 +789,10 @@ js::XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript)
}
template bool
js::XDRScript(XDRState<XDR_ENCODE> *xdr, JSScript **scriptp, JSScript *parentScript);
js::XDRScript(XDRState<XDR_ENCODE> *, HandleObject, HandleScript, HandleFunction, JSScript **);
template bool
js::XDRScript(XDRState<XDR_DECODE> *xdr, JSScript **scriptp, JSScript *parentScript);
js::XDRScript(XDRState<XDR_DECODE> *, HandleObject, HandleScript, HandleFunction, JSScript **);
bool
JSScript::initScriptCounts(JSContext *cx)
@ -1073,9 +1125,9 @@ ScriptDataSize(uint32_t length, uint32_t nsrcnotes, uint32_t natoms,
}
JSScript *
JSScript::Create(JSContext *cx, bool savedCallerFun, JSPrincipals *principals,
JSPrincipals *originPrincipals, bool compileAndGo, bool noScriptRval,
JSVersion version, unsigned staticLevel)
JSScript::Create(JSContext *cx, HandleObject enclosingScope, bool savedCallerFun,
JSPrincipals *principals, JSPrincipals *originPrincipals,
bool compileAndGo, bool noScriptRval, JSVersion version, unsigned staticLevel)
{
JSScript *script = js_NewGCScript(cx);
if (!script)
@ -1083,6 +1135,7 @@ JSScript::Create(JSContext *cx, bool savedCallerFun, JSPrincipals *principals,
PodZero(script);
script->enclosingScope_ = enclosingScope;
script->savedCallerFun = savedCallerFun;
/* Establish invariant: principals implies originPrincipals. */
@ -1098,7 +1151,7 @@ JSScript::Create(JSContext *cx, bool savedCallerFun, JSPrincipals *principals,
script->compileAndGo = compileAndGo;
script->noScriptRval = noScriptRval;
script->version = version;
JS_ASSERT(script->getVersion() == version); // assert that no overflow occurred
@ -1314,7 +1367,7 @@ JSScript::fullyInitFromEmitter(JSContext *cx, Handle<JSScript*> script, Bytecode
if (bce->sc->funArgumentsHasLocalBinding()) {
// This must precede the script->bindings.transfer() call below
script->setArgumentsHasVarBinding();
if (bce->sc->funDefinitelyNeedsArgsObj())
if (bce->sc->funDefinitelyNeedsArgsObj())
script->setNeedsArgsObj(true);
} else {
JS_ASSERT(!bce->sc->funDefinitelyNeedsArgsObj());
@ -1646,7 +1699,7 @@ Rebase(JSScript *dst, JSScript *src, T *srcp)
}
JSScript *
js::CloneScript(JSContext *cx, HandleScript src)
js::CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript src)
{
/* NB: Keep this in sync with XDRScript. */
@ -1695,13 +1748,29 @@ js::CloneScript(JSContext *cx, HandleScript src)
if (nobjects != 0) {
HeapPtrObject *vector = src->objects()->vector;
for (unsigned i = 0; i < nobjects; i++) {
JSObject &obj = *vector[i];
JSObject *clone;
if (vector[i]->isStaticBlock()) {
Rooted<StaticBlockObject*> block(cx, &vector[i]->asStaticBlock());
clone = CloneStaticBlockObject(cx, block, objects, src);
if (obj.isStaticBlock()) {
Rooted<StaticBlockObject*> innerBlock(cx, &obj.asStaticBlock());
Rooted<JSObject*> enclosingScope(cx);
if (StaticBlockObject *enclosingBlock = innerBlock->enclosingBlock())
enclosingScope = objects[FindBlockIndex(src, *enclosingBlock)];
else
enclosingScope = fun;
clone = CloneStaticBlockObject(cx, enclosingScope, innerBlock);
} else {
RootedFunction fun(cx, vector[i]->toFunction());
clone = CloneInterpretedFunction(cx, fun);
Rooted<JSFunction*> innerFun(cx, obj.toFunction());
StaticScopeIter ssi(innerFun->script()->enclosingStaticScope());
Rooted<JSObject*> enclosingScope(cx);
if (!ssi.done() && ssi.type() == StaticScopeIter::BLOCK)
enclosingScope = objects[FindBlockIndex(src, ssi.block())];
else
enclosingScope = fun;
clone = CloneInterpretedFunction(cx, enclosingScope, innerFun);
}
if (!clone || !objects.append(clone))
return NULL;
@ -1722,7 +1791,7 @@ js::CloneScript(JSContext *cx, HandleScript src)
/* Now that all fallible allocation is complete, create the GC thing. */
JSScript *dst = JSScript::Create(cx, src->savedCallerFun,
JSScript *dst = JSScript::Create(cx, enclosingScope, src->savedCallerFun,
cx->compartment->principals, src->originPrincipals,
src->compileAndGo, src->noScriptRval,
src->getVersion(), src->staticLevel);
@ -2049,6 +2118,9 @@ JSScript::markChildren(JSTracer *trc)
if (function())
MarkObject(trc, &function_, "function");
if (enclosingScope_)
MarkObject(trc, &enclosingScope_, "enclosing");
if (IS_GC_MARKING_TRACER(trc) && filename)
MarkScriptFilename(trc->runtime, filename);

View File

@ -414,8 +414,8 @@ struct JSScript : public js::gc::Cell
#ifdef JS_METHODJIT
JITScriptSet *jitInfo;
#endif
js::HeapPtrFunction function_;
js::HeapPtrObject enclosingScope_;
// 32-bit fields.
@ -434,10 +434,6 @@ struct JSScript : public js::gc::Cell
* or has had backedges taken. Reset if the
* script's JIT code is forcibly discarded. */
#if JS_BITS_PER_WORD == 32
uint32_t pad32;
#endif
#ifdef DEBUG
// Unique identifier within the compartment for this script, used for
// printing analysis information.
@ -526,7 +522,7 @@ struct JSScript : public js::gc::Cell
//
public:
static JSScript *Create(JSContext *cx, bool savedCallerFun,
static JSScript *Create(JSContext *cx, js::HandleObject enclosingScope, bool savedCallerFun,
JSPrincipals *principals, JSPrincipals *originPrincipals,
bool compileAndGo, bool noScriptRval,
JSVersion version, unsigned staticLevel);
@ -614,6 +610,9 @@ struct JSScript : public js::gc::Cell
inline js::GlobalObject &global() const;
/* See StaticScopeIter comment. */
JSObject *enclosingStaticScope() const { return enclosingScope_; }
private:
bool makeTypes(JSContext *cx);
bool makeAnalysis(JSContext *cx);
@ -1007,7 +1006,7 @@ inline void
CurrentScriptFileLineOrigin(JSContext *cx, unsigned *linenop, LineOption = NOT_CALLED_FROM_JSOP_EVAL);
extern JSScript *
CloneScript(JSContext *cx, HandleScript script);
CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript script);
/*
* NB: after a successful XDR_DECODE, XDRScript callers must do any required
@ -1016,7 +1015,8 @@ CloneScript(JSContext *cx, HandleScript script);
*/
template<XDRMode mode>
bool
XDRScript(XDRState<mode> *xdr, JSScript **scriptp, JSScript *parentScript);
XDRScript(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enclosingScript,
HandleFunction fun, JSScript **scriptp);
} /* namespace js */

View File

@ -27,7 +27,7 @@ function test()
else {
expect = 'PASSED';
f = Function("a", "return (function () { return a * a;});")();
f = Function("return a * a;");
g = clone(f, {a: 3});
f = null;
gc();

View File

@ -114,6 +114,7 @@ GlobalObject::initFunctionAndObjectClasses(JSContext *cx)
functionProto->flags |= JSFUN_PROTOTYPE;
Rooted<JSScript*> script(cx, JSScript::Create(cx,
/* enclosingScope = */ NullPtr(),
/* savedCallerFun = */ false,
/* principals = */ NULL,
/* originPrincipals = */ NULL,

View File

@ -152,17 +152,36 @@ BlockObject::setSlotValue(unsigned i, const Value &v)
setSlot(RESERVED_SLOTS + i, v);
}
inline void
StaticBlockObject::initPrevBlockChainFromParser(StaticBlockObject *prev)
{
setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev));
}
inline void
StaticBlockObject::resetPrevBlockChainFromParser()
{
setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue());
}
inline void
StaticBlockObject::initEnclosingStaticScope(JSObject *obj)
{
JS_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined());
setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj));
}
inline StaticBlockObject *
StaticBlockObject::enclosingBlock() const
{
JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
return obj ? &obj->asStaticBlock() : NULL;
return obj && obj->isStaticBlock() ? &obj->asStaticBlock() : NULL;
}
inline void
StaticBlockObject::setEnclosingBlock(StaticBlockObject *blockObj)
inline JSObject *
StaticBlockObject::enclosingStaticScope() const
{
setFixedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(blockObj));
return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
}
inline void

View File

@ -23,6 +23,65 @@ using namespace js::types;
/*****************************************************************************/
StaticScopeIter::StaticScopeIter(JSObject *obj)
: obj(obj), onNamedLambda(false)
{
JS_ASSERT_IF(obj, obj->isStaticBlock() || obj->isFunction());
}
bool
StaticScopeIter::done() const
{
return obj == NULL;
}
void
StaticScopeIter::operator++(int)
{
if (obj->isStaticBlock()) {
obj = obj->asStaticBlock().enclosingStaticScope();
} else if (onNamedLambda || !obj->toFunction()->isNamedLambda()) {
onNamedLambda = false;
obj = obj->toFunction()->script()->enclosingStaticScope();
} else {
onNamedLambda = true;
}
JS_ASSERT_IF(obj, obj->isStaticBlock() || obj->isFunction());
JS_ASSERT_IF(onNamedLambda, obj->isFunction());
}
bool
StaticScopeIter::hasDynamicScopeObject() const
{
return obj->isStaticBlock()
? obj->asStaticBlock().needsClone()
: obj->toFunction()->isHeavyweight();
}
StaticScopeIter::Type
StaticScopeIter::type() const
{
if (onNamedLambda)
return NAMED_LAMBDA;
return obj->isStaticBlock() ? BLOCK : FUNCTION;
}
StaticBlockObject &
StaticScopeIter::block() const
{
JS_ASSERT(type() == BLOCK);
return obj->asStaticBlock();
}
JSScript *
StaticScopeIter::funScript() const
{
JS_ASSERT(type() == FUNCTION);
return obj->toFunction()->script();
}
/*****************************************************************************/
StaticBlockObject *
js::ScopeCoordinateBlockChain(JSScript *script, jsbytecode *pc)
{
@ -140,7 +199,7 @@ CallObject::createForFunction(JSContext *cx, StackFrame *fp)
* For a named function expression Call's parent points to an environment
* object holding function's name.
*/
if (js_IsNamedLambda(fp->fun())) {
if (fp->fun()->isNamedLambda()) {
scopeChain = DeclEnvObject::create(cx, fp);
if (!scopeChain)
return NULL;
@ -681,45 +740,21 @@ Class js::BlockClass = {
JS_ConvertStub
};
#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(JSScript *script, StaticBlockObject *maybeBlock)
{
if (!maybeBlock || !script->hasObjects())
return NO_PARENT_INDEX;
ObjectArray *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;
}
template<XDRMode mode>
bool
js::XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObject **objp)
js::XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript script,
StaticBlockObject **objp)
{
/* NB: Keep this in sync with CloneStaticBlockObject. */
JSContext *cx = xdr->cx();
Rooted<StaticBlockObject*> obj(cx);
uint32_t parentId = 0;
uint32_t count = 0;
uint32_t depthAndCount = 0;
if (mode == XDR_ENCODE) {
obj = *objp;
parentId = FindObjectIndex(script, obj->enclosingBlock());
uint32_t depth = obj->stackDepth();
JS_ASSERT(depth <= UINT16_MAX);
count = obj->slotCount();
@ -727,23 +762,14 @@ js::XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObjec
depthAndCount = (depth << 16) | uint16_t(count);
}
/* First, XDR the parent atomid. */
if (!xdr->codeUint32(&parentId))
return false;
if (mode == XDR_DECODE) {
obj = StaticBlockObject::create(cx);
if (!obj)
return false;
obj->initEnclosingStaticScope(enclosingScope);
*objp = obj;
obj->setEnclosingBlock(parentId == NO_PARENT_INDEX
? NULL
: &script->getObject(parentId)->asStaticBlock());
}
AutoObjectRooter tvr(cx, obj);
if (!xdr->codeUint32(&depthAndCount))
return false;
@ -818,14 +844,13 @@ js::XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObjec
}
template bool
js::XDRStaticBlockObject(XDRState<XDR_ENCODE> *xdr, JSScript *script, StaticBlockObject **objp);
js::XDRStaticBlockObject(XDRState<XDR_ENCODE> *, HandleObject, HandleScript, StaticBlockObject **);
template bool
js::XDRStaticBlockObject(XDRState<XDR_DECODE> *xdr, JSScript *script, StaticBlockObject **objp);
js::XDRStaticBlockObject(XDRState<XDR_DECODE> *, HandleObject, HandleScript, StaticBlockObject **);
JSObject *
js::CloneStaticBlockObject(JSContext *cx, Handle<StaticBlockObject*> srcBlock,
const AutoObjectVector &objects, JSScript *src)
js::CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> srcBlock)
{
/* NB: Keep this in sync with XDRStaticBlockObject. */
@ -833,11 +858,7 @@ js::CloneStaticBlockObject(JSContext *cx, Handle<StaticBlockObject*> srcBlock,
if (!clone)
return NULL;
uint32_t parentId = FindObjectIndex(src, srcBlock->enclosingBlock());
clone->setEnclosingBlock(parentId == NO_PARENT_INDEX
? NULL
: &objects[parentId]->asStaticBlock());
clone->initEnclosingStaticScope(enclosingScope);
clone->setStackDepth(srcBlock->stackDepth());
/* Shape::Range is reverse order, so build a list in forward order. */

View File

@ -18,14 +18,71 @@ namespace js {
/*****************************************************************************/
/*
* All function scripts have an "enclosing static scope" that refers to the
* innermost enclosing let or function in the program text. This allows full
* reconstruction of the lexical scope for debugging or compiling efficient
* access to variables in enclosing scopes. The static scope is represented at
* runtime by a tree of compiler-created objects representing each scope:
* - a StaticBlockObject is created for 'let' and 'catch' scopes
* - a JSFunction+JSScript+Bindings trio is created for function scopes
* (These objects are primarily used to clone objects scopes for the
* dynamic scope chain.)
*
* There is an additional scope for named lambdas. E.g., in:
*
* (function f() { var x; function g() { } })
*
* g's innermost enclosing scope will first be the function scope containing
* 'x', enclosed by a scope containing only the name 'f'. (This separate scope
* is necessary due to the fact that declarations in the function scope shadow
* (dynamically, in the case of 'eval') the lambda name.)
*
* There are two limitations to the current lexical nesting information:
*
* - 'with' is completely absent; this isn't a problem for the current use
* cases since 'with' causes every static scope to be on the dynamic scope
* chain (so the debugger can find everything) and inhibits all upvar
* optimization.
*
* - The "enclosing static scope" chain stops at 'eval'. For example in:
* let (x) { eval("function f() {}") }
* f does not have an enclosing static scope. This is fine for current uses
* for the same reason as 'with'.
*
* (See also AssertDynamicScopeMatchesStaticScope.)
*/
class StaticScopeIter
{
JSObject *obj;
bool onNamedLambda;
public:
explicit StaticScopeIter(JSObject *obj);
bool done() const;
void operator++(int);
/* Return whether this static scope will be on the dynamic scope chain. */
bool hasDynamicScopeObject() const;
enum Type { BLOCK, FUNCTION, NAMED_LAMBDA };
Type type() const;
StaticBlockObject &block() const;
JSScript *funScript() const;
};
/*****************************************************************************/
/*
* A "scope coordinate" describes how to get from head of the scope chain to a
* given lexically-enclosing variable. A scope coordinate has two dimensions:
* - hops: the number of scope objects on the scope chain to skip
* - binding: which binding on the scope object
* - slot: the slot on the scope object holding the variable's value
* Additionally (as described in jsopcode.tbl) there is a 'block' index, but
* this is only needed for decompilation/inference so it is not included in the
* main ScopeCoordinate struct: use ScopeCoordinate{BlockChain,Atom} instead.
* main ScopeCoordinate struct: use ScopeCoordinate{BlockChain,Name} instead.
*/
struct ScopeCoordinate
{
@ -228,12 +285,40 @@ class StaticBlockObject : public BlockObject
public:
static StaticBlockObject *create(JSContext *cx);
inline StaticBlockObject *enclosingBlock() const;
inline void setEnclosingBlock(StaticBlockObject *blockObj);
/* See StaticScopeIter comment. */
inline JSObject *enclosingStaticScope() const;
void setStackDepth(uint32_t depth);
/*
* A refinement of enclosingStaticScope that returns NULL if the enclosing
* static scope is a JSFunction.
*/
inline StaticBlockObject *enclosingBlock() const;
/*
* Return whether this StaticBlockObject contains a variable stored at
* the given stack depth (i.e., fp->base()[depth]).
*/
bool containsVarAtDepth(uint32_t depth);
/*
* A let binding is aliased if accessed lexically by nested functions or
* dynamically through dynamic name lookup (eval, with, function::, etc).
*/
bool isAliased(unsigned i);
/*
* A static block object is cloned (when entering the block) iff some
* variable of the block isAliased.
*/
bool needsClone();
/* Frontend-only functions ***********************************************/
/* Initialization functions for above fields. */
void setAliased(unsigned i, bool aliased);
void setStackDepth(uint32_t depth);
void initEnclosingStaticScope(JSObject *obj);
/*
* Frontend compilation temporarily uses the object's slots to link
* a let var to its associated Definition parse node.
@ -242,17 +327,13 @@ class StaticBlockObject : public BlockObject
Definition *maybeDefinitionParseNode(unsigned i);
/*
* A let binding is aliased is accessed lexically by nested functions or
* dynamically through dynamic name lookup (eval, with, function::, etc).
* The parser uses 'enclosingBlock' as the prev-link in the tc->blockChain
* stack. Note: in the case of hoisting, this prev-link will not ultimately
* be the same as enclosingBlock, initEnclosingStaticScope must be called
* separately in the emitter. 'reset' is just for asserting stackiness.
*/
void setAliased(unsigned i, bool aliased);
bool isAliased(unsigned i);
/*
* A static block object is cloned (when entering the block) iff some
* variable of the block isAliased.
*/
bool needsClone();
void initPrevBlockChainFromParser(StaticBlockObject *prev);
void resetPrevBlockChainFromParser();
static Shape *addVar(JSContext *cx, Handle<StaticBlockObject*> block, HandleId id,
int index, bool *redeclared);
@ -277,11 +358,11 @@ class ClonedBlockObject : public BlockObject
template<XDRMode mode>
bool
XDRStaticBlockObject(XDRState<mode> *xdr, JSScript *script, StaticBlockObject **objp);
XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript script,
StaticBlockObject **objp);
extern JSObject *
CloneStaticBlockObject(JSContext *cx, Handle<StaticBlockObject*> srcBlock,
const AutoObjectVector &objects, JSScript *src);
CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> src);
/*****************************************************************************/

View File

@ -222,6 +222,42 @@ StackFrame::pcQuadratic(const ContextStack &stack, size_t maxDepth)
return regs.fp()->script()->code;
}
static inline void
AssertDynamicScopeMatchesStaticScope(JSScript *script, JSObject *scope)
{
#ifdef DEBUG
for (StaticScopeIter i(script->enclosingStaticScope()); !i.done(); i++) {
if (i.hasDynamicScopeObject()) {
/*
* 'with' does not participate in the static scope of the script,
* but it does in the dynamic scope, so skip them here.
*/
while (scope->isWith())
scope = &scope->asWith().enclosingScope();
switch (i.type()) {
case StaticScopeIter::BLOCK:
JS_ASSERT(i.block() == scope->asClonedBlock().staticBlock());
scope = &scope->asClonedBlock().enclosingScope();
break;
case StaticScopeIter::FUNCTION:
JS_ASSERT(i.funScript() == scope->asCall().callee().script());
scope = &scope->asCall().enclosingScope();
break;
case StaticScopeIter::NAMED_LAMBDA:
scope = &scope->asDeclEnv().enclosingScope();
break;
}
}
}
/*
* Ideally, we'd JS_ASSERT(!scope->isScope()) but the enclosing lexical
* scope chain stops at eval() boundaries. See StaticScopeIter comment.
*/
#endif
}
bool
StackFrame::prologue(JSContext *cx, bool newType)
{
@ -247,6 +283,7 @@ StackFrame::prologue(JSContext *cx, bool newType)
}
JS_ASSERT(isNonEvalFunctionFrame());
AssertDynamicScopeMatchesStaticScope(script(), scopeChain());
if (fun()->isHeavyweight()) {
CallObject *callobj = CallObject::createForFunction(cx, this);
@ -299,12 +336,11 @@ StackFrame::epilogue(JSContext *cx)
}
JS_ASSERT(isNonEvalFunctionFrame());
if (fun()->isHeavyweight()) {
if (fun()->isHeavyweight())
JS_ASSERT_IF(hasCallObj(), scopeChain()->asCall().callee().script() == script());
} else {
JS_ASSERT(!scopeChain()->isCall() || scopeChain()->asCall().isForEval() ||
scopeChain()->asCall().callee().script() != script());
}
else
AssertDynamicScopeMatchesStaticScope(script(), scopeChain());
if (cx->compartment->debugMode())
cx->runtime->debugScopes->onPopCall(this, cx);

View File

@ -123,7 +123,10 @@ XDRState<mode>::codeFunction(JSObject **objp)
if (mode == XDR_DECODE)
*objp = NULL;
return VersionCheck(this) && XDRInterpretedFunction(this, objp, NULL);
if (!VersionCheck(this))
return false;
return XDRInterpretedFunction(this, NullPtr(), NullPtr(), objp);
}
template<XDRMode mode>
@ -138,7 +141,10 @@ XDRState<mode>::codeScript(JSScript **scriptp)
script = *scriptp;
}
if (!VersionCheck(this) || !XDRScript(this, &script, NULL))
if (!VersionCheck(this))
return false;
if (!XDRScript(this, NullPtr(), NullPtr(), NullPtr(), &script))
return false;
if (mode == XDR_DECODE) {

View File

@ -25,7 +25,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 - 119);
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 120);
class XDRBuffer {
public: