Bug 1001090 - Part 1: Implement let temporal dead zone in the frontend and interpreter. (r=Waldo)

This commit is contained in:
Shu-yu Guo 2014-09-15 16:30:45 -07:00
parent dd0fb7b8e6
commit 638366e609
56 changed files with 1384 additions and 499 deletions

View File

@ -242,6 +242,8 @@ typedef enum JSWhyMagic
JS_ION_ERROR, /* error while running Ion code */
JS_ION_BAILOUT, /* missing recover instruction result */
JS_OPTIMIZED_OUT, /* optimized out slot */
JS_UNINITIALIZED_LEXICAL, /* uninitialized lexical bindings that produce ReferenceError
* on touch. */
JS_GENERIC_MAGIC /* for local use */
} JSWhyMagic;

View File

@ -291,7 +291,8 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco
BytecodeEmitter::EmitterMode emitterMode =
options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script, options.forEval,
BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script,
/* lazyScript = */ js::NullPtr(), options.forEval,
evalCaller, !!globalScope, options.lineno, emitterMode);
if (!bce.init())
return nullptr;
@ -436,9 +437,11 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco
// locals, however, which are allocated to the fixed part of the stack
// frame.
InternalHandle<Bindings*> bindings(script, &script->bindings);
if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, nullptr,
pc->blockScopeDepth))
if (!Bindings::initWithTemporaryStorage(cx, bindings, 0, 0, 0,
pc->blockScopeDepth, nullptr))
{
return nullptr;
}
if (!JSScript::fullyInitFromEmitter(cx, script, &bce))
return nullptr;
@ -509,15 +512,13 @@ frontend::CompileLazyFunction(JSContext *cx, Handle<LazyScript*> lazy, const cha
if (lazy->hasBeenCloned())
script->setHasBeenCloned();
BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, options.forEval,
/* evalCaller = */ NullPtr(), /* hasGlobalScope = */ true,
options.lineno, BytecodeEmitter::LazyFunction);
BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, lazy,
options.forEval, /* evalCaller = */ js::NullPtr(),
/* hasGlobalScope = */ true, options.lineno,
BytecodeEmitter::LazyFunction);
if (!bce.init())
return false;
if (lazy->treatAsRunOnce())
bce.lazyRunOnceLambda = true;
return EmitFunctionScript(cx, &bce, pn->pn_body);
}
@ -632,7 +633,8 @@ CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, const ReadOnlyComp
* instead is cloned immediately onto the right scope chain.
*/
BytecodeEmitter funbce(/* parent = */ nullptr, &parser, fn->pn_funbox, script,
/* insideEval = */ false, /* evalCaller = */ js::NullPtr(),
/* lazyScript = */ js::NullPtr(), /* insideEval = */ false,
/* evalCaller = */ js::NullPtr(),
fun->environment() && fun->environment()->is<GlobalObject>(),
options.lineno);
if (!funbce.init())

View File

@ -31,6 +31,7 @@
#include "frontend/Parser.h"
#include "frontend/TokenStream.h"
#include "vm/Debugger.h"
#include "vm/Stack.h"
#include "jsatominlines.h"
#include "jsobjinlines.h"
@ -110,11 +111,13 @@ struct LoopStmtInfo : public StmtInfoBCE
BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent,
Parser<FullParseHandler> *parser, SharedContext *sc,
HandleScript script, bool insideEval, HandleScript evalCaller,
HandleScript script, Handle<LazyScript *> lazyScript,
bool insideEval, HandleScript evalCaller,
bool hasGlobalScope, uint32_t lineNum, EmitterMode emitterMode)
: sc(sc),
parent(parent),
script(sc->context, script),
lazyScript(sc->context, lazyScript),
prolog(sc->context, lineNum),
main(sc->context, lineNum),
current(&main),
@ -135,12 +138,12 @@ BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent,
hasSingletons(false),
emittingForInit(false),
emittingRunOnceLambda(false),
lazyRunOnceLambda(false),
insideEval(insideEval),
hasGlobalScope(hasGlobalScope),
emitterMode(emitterMode)
{
JS_ASSERT_IF(evalCaller, insideEval);
JS_ASSERT_IF(emitterMode == LazyFunction, lazyScript);
}
bool
@ -223,7 +226,7 @@ frontend::Emit2(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, jsbytecode
ptrdiff_t
frontend::Emit3(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, jsbytecode op1,
jsbytecode op2)
jsbytecode op2)
{
/* These should filter through EmitVarOp. */
JS_ASSERT(!IsArgOp(op));
@ -799,8 +802,8 @@ EmitInternedObjectOp(ExclusiveContext *cx, uint32_t index, JSOp op, BytecodeEmit
static void
ComputeLocalOffset(ExclusiveContext *cx, BytecodeEmitter *bce, Handle<StaticBlockObject *> blockObj)
{
unsigned nfixedvars = bce->sc->isFunctionBox() ? bce->script->bindings.numVars() : 0;
unsigned localOffset = nfixedvars;
unsigned nbodyfixed = bce->sc->isFunctionBox() ? bce->script->bindings.numBodyLevelLocals() : 0;
unsigned localOffset = nbodyfixed;
if (bce->staticScope) {
Rooted<NestedScopeObject *> outer(cx, bce->staticScope);
@ -814,7 +817,7 @@ ComputeLocalOffset(ExclusiveContext *cx, BytecodeEmitter *bce, Handle<StaticBloc
}
JS_ASSERT(localOffset + blockObj->numVariables()
<= nfixedvars + bce->script->bindings.numBlockScoped());
<= nbodyfixed + bce->script->bindings.numBlockScoped());
blockObj->setLocalOffset(localOffset);
}
@ -1061,17 +1064,33 @@ EmitRegExp(ExclusiveContext *cx, uint32_t index, BytecodeEmitter *bce)
* used as a non-asserting version of EMIT_UINT16_IMM_OP.
*/
static bool
EmitUnaliasedVarOp(ExclusiveContext *cx, JSOp op, uint32_t slot, BytecodeEmitter *bce)
EmitLocalOp(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, uint32_t slot)
{
JS_ASSERT(JOF_OPTYPE(op) != JOF_SCOPECOORD);
JS_ASSERT(IsLocalOp(op));
ptrdiff_t off = EmitN(cx, bce, op, LOCALNO_LEN);
if (off < 0)
return false;
SET_LOCALNO(bce->code(off), slot);
return true;
}
static bool
EmitUnaliasedVarOp(ExclusiveContext *cx, JSOp op, uint32_t slot, MaybeCheckLexical checkLexical,
BytecodeEmitter *bce)
{
JS_ASSERT(JOF_OPTYPE(op) != JOF_SCOPECOORD);
if (IsLocalOp(op)) {
ptrdiff_t off = EmitN(cx, bce, op, LOCALNO_LEN);
if (off < 0)
return false;
if (checkLexical) {
MOZ_ASSERT(op != JSOP_INITLEXICAL);
if (!EmitLocalOp(cx, bce, JSOP_CHECKLEXICAL, slot))
return false;
}
SET_LOCALNO(bce->code(off), slot);
return true;
return EmitLocalOp(cx, bce, op, slot);
}
JS_ASSERT(IsArgOp(op));
@ -1084,7 +1103,7 @@ EmitUnaliasedVarOp(ExclusiveContext *cx, JSOp op, uint32_t slot, BytecodeEmitter
}
static bool
EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ScopeCoordinate sc, BytecodeEmitter *bce)
EmitScopeCoordOp(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, ScopeCoordinate sc)
{
JS_ASSERT(JOF_OPTYPE(op) == JOF_SCOPECOORD);
@ -1104,6 +1123,19 @@ EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ScopeCoordinate sc, BytecodeEmit
return true;
}
static bool
EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ScopeCoordinate sc, MaybeCheckLexical checkLexical,
BytecodeEmitter *bce)
{
if (checkLexical) {
MOZ_ASSERT(op != JSOP_INITALIASEDLEXICAL);
if (!EmitScopeCoordOp(cx, bce, JSOP_CHECKALIASEDLEXICAL, sc))
return false;
}
return EmitScopeCoordOp(cx, bce, op, sc);
}
// Compute the number of nested scope objects that will actually be on the scope
// chain at runtime, given the BCE's current staticScope.
static unsigned
@ -1119,30 +1151,60 @@ DynamicNestedScopeDepth(BytecodeEmitter *bce)
}
static bool
LookupAliasedName(HandleScript script, PropertyName *name, uint32_t *pslot)
LookupAliasedName(BytecodeEmitter *bce, HandleScript script, PropertyName *name, uint32_t *pslot,
ParseNode *pn = nullptr)
{
LazyScript::FreeVariable *freeVariables = nullptr;
uint32_t lexicalBegin = 0;
uint32_t numFreeVariables = 0;
if (bce->emitterMode == BytecodeEmitter::LazyFunction) {
freeVariables = bce->lazyScript->freeVariables();
lexicalBegin = script->bindings.lexicalBegin();
numFreeVariables = bce->lazyScript->numFreeVariables();
}
/*
* Beware: BindingIter may contain more than one Binding for a given name
* (in the case of |function f(x,x) {}|) but only one will be aliased.
*/
uint32_t bindingIndex = 0;
uint32_t slot = CallObject::RESERVED_SLOTS;
for (BindingIter bi(script); !bi.done(); bi++) {
if (bi->aliased()) {
if (bi->name() == name) {
// Check if the free variable from a lazy script was marked as
// a possible hoisted use and is a lexical binding. If so,
// mark it as such so we emit a dead zone check.
if (freeVariables) {
for (uint32_t i = 0; i < numFreeVariables; i++) {
if (freeVariables[i].atom() == name) {
if (freeVariables[i].isHoistedUse() && bindingIndex >= lexicalBegin) {
MOZ_ASSERT(pn);
MOZ_ASSERT(pn->isUsed());
pn->pn_dflags |= PND_LET;
}
break;
}
}
}
*pslot = slot;
return true;
}
slot++;
}
bindingIndex++;
}
return false;
}
static bool
LookupAliasedNameSlot(HandleScript script, PropertyName *name, ScopeCoordinate *sc)
LookupAliasedNameSlot(BytecodeEmitter *bce, HandleScript script, PropertyName *name,
ScopeCoordinate *sc)
{
uint32_t slot;
if (!LookupAliasedName(script, name, &slot))
if (!LookupAliasedName(bce, script, name, &slot))
return false;
sc->setSlot(slot);
@ -1165,6 +1227,12 @@ AssignHops(BytecodeEmitter *bce, ParseNode *pn, unsigned src, ScopeCoordinate *d
return true;
}
static inline MaybeCheckLexical
NodeNeedsCheckLexical(ParseNode *pn)
{
return pn->isHoistedLetUse() ? CheckLexical : DontCheckLexical;
}
static bool
EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter *bce)
{
@ -1211,14 +1279,14 @@ EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter *
if (IsArgOp(pn->getOp())) {
if (!AssignHops(bce, pn, skippedScopes + DynamicNestedScopeDepth(bceOfDef), &sc))
return false;
JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc));
JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef, bceOfDef->script, pn->name(), &sc));
} else {
JS_ASSERT(IsLocalOp(pn->getOp()) || pn->isKind(PNK_FUNCTION));
uint32_t local = pn->pn_cookie.slot();
if (local < bceOfDef->script->bindings.numVars()) {
if (local < bceOfDef->script->bindings.numBodyLevelLocals()) {
if (!AssignHops(bce, pn, skippedScopes + DynamicNestedScopeDepth(bceOfDef), &sc))
return false;
JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc));
JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef, bceOfDef->script, pn->name(), &sc));
} else {
JS_ASSERT_IF(bce->sc->isFunctionBox(), local <= bceOfDef->script->bindings.numLocals());
JS_ASSERT(bceOfDef->staticScope->is<StaticBlockObject>());
@ -1234,7 +1302,7 @@ EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter *
}
}
return EmitAliasedVarOp(cx, op, sc, bce);
return EmitAliasedVarOp(cx, op, sc, NodeNeedsCheckLexical(pn), bce);
}
static bool
@ -1247,7 +1315,7 @@ EmitVarOp(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce)
ScopeCoordinate sc;
sc.setHops(pn->pn_cookie.level());
sc.setSlot(pn->pn_cookie.slot());
return EmitAliasedVarOp(cx, op, sc, bce);
return EmitAliasedVarOp(cx, op, sc, NodeNeedsCheckLexical(pn), bce);
}
JS_ASSERT_IF(pn->isKind(PNK_NAME), IsArgOp(op) || IsLocalOp(op));
@ -1256,12 +1324,13 @@ EmitVarOp(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce)
JS_ASSERT(pn->isUsed() || pn->isDefn());
JS_ASSERT_IF(pn->isUsed(), pn->pn_cookie.level() == 0);
JS_ASSERT_IF(pn->isDefn(), pn->pn_cookie.level() == bce->script->staticLevel());
return EmitUnaliasedVarOp(cx, op, pn->pn_cookie.slot(), bce);
return EmitUnaliasedVarOp(cx, op, pn->pn_cookie.slot(), NodeNeedsCheckLexical(pn), bce);
}
switch (op) {
case JSOP_GETARG: case JSOP_GETLOCAL: op = JSOP_GETALIASEDVAR; break;
case JSOP_SETARG: case JSOP_SETLOCAL: op = JSOP_SETALIASEDVAR; break;
case JSOP_INITLEXICAL: op = JSOP_INITALIASEDLEXICAL; break;
default: MOZ_CRASH("unexpected var op");
}
@ -1435,13 +1504,14 @@ TryConvertFreeName(BytecodeEmitter *bce, ParseNode *pn)
return false;
if (ssi.hasDynamicScopeObject()) {
uint32_t slot;
if (LookupAliasedName(script, pn->pn_atom->asPropertyName(), &slot)) {
if (LookupAliasedName(bce, script, pn->pn_atom->asPropertyName(), &slot, pn)) {
JSOp op;
switch (pn->getOp()) {
case JSOP_NAME: op = JSOP_GETALIASEDVAR; break;
case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break;
case JSOP_NAME: op = JSOP_GETALIASEDVAR; break;
case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break;
default: return false;
}
pn->setOp(op);
JS_ALWAYS_TRUE(pn->pn_cookie.set(bce->parser->tokenStream, hops, slot));
return true;
@ -2464,10 +2534,11 @@ SetJumpOffsetAt(BytecodeEmitter *bce, ptrdiff_t off)
}
static bool
PushUndefinedValues(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned n)
PushInitialConstants(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, unsigned n)
{
MOZ_ASSERT(op == JSOP_UNDEFINED || op == JSOP_UNINITIALIZED);
for (unsigned i = 0; i < n; ++i) {
if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
if (Emit1(cx, bce, op) < 0)
return false;
}
return true;
@ -2482,11 +2553,11 @@ InitializeBlockScopedLocalsFromStack(ExclusiveContext *cx, BytecodeEmitter *bce,
ScopeCoordinate sc;
sc.setHops(0);
sc.setSlot(BlockObject::RESERVED_SLOTS + i - 1);
if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, bce))
if (!EmitAliasedVarOp(cx, JSOP_INITALIASEDLEXICAL, sc, DontCheckLexical, bce))
return false;
} else {
unsigned local = blockObj->blockIndexToLocalIndex(i - 1);
if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, local, bce))
if (!EmitUnaliasedVarOp(cx, JSOP_INITLEXICAL, local, DontCheckLexical, bce))
return false;
}
if (Emit1(cx, bce, JSOP_POP) < 0)
@ -2497,11 +2568,14 @@ InitializeBlockScopedLocalsFromStack(ExclusiveContext *cx, BytecodeEmitter *bce,
static bool
EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmtInfo,
ObjectBox *objbox, unsigned alreadyPushed = 0)
ObjectBox *objbox, JSOp initialValueOp, unsigned alreadyPushed = 0)
{
// Initial values for block-scoped locals.
// Initial values for block-scoped locals. Whether it is undefined or the
// JS_UNINITIALIZED_LEXICAL magic value depends on the context. The
// current way we emit for-in and for-of heads means its let bindings will
// always be initialized, so we can initialize them to undefined.
Rooted<StaticBlockObject *> blockObj(cx, &objbox->object->as<StaticBlockObject>());
if (!PushUndefinedValues(cx, bce, blockObj->numVariables() - alreadyPushed))
if (!PushInitialConstants(cx, bce, initialValueOp, blockObj->numVariables() - alreadyPushed))
return false;
if (!EnterNestedScope(cx, bce, stmtInfo, objbox, STMT_BLOCK))
@ -2544,7 +2618,7 @@ EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
StmtInfoBCE stmtInfo(cx);
if (pn2->isKind(PNK_LEXICALSCOPE)) {
if (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, 0))
if (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, JSOP_UNINITIALIZED, 0))
return false;
stmtInfo.type = STMT_SWITCH;
@ -2847,8 +2921,11 @@ BytecodeEmitter::isRunOnceLambda()
// at properties of the function itself before deciding to emit a function
// as a run once lambda.
if (!(parent && parent->emittingRunOnceLambda) && !lazyRunOnceLambda)
if (!(parent && parent->emittingRunOnceLambda) &&
(emitterMode != LazyFunction || !lazyScript->treatAsRunOnce()))
{
return false;
}
FunctionBox *funbox = sc->asFunctionBox();
return !funbox->argumentsHasLocalBinding() &&
@ -2877,11 +2954,11 @@ frontend::EmitFunctionScript(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNo
if (bce->script->varIsAliased(varIndex)) {
ScopeCoordinate sc;
sc.setHops(0);
JS_ALWAYS_TRUE(LookupAliasedNameSlot(bce->script, cx->names().arguments, &sc));
if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, bce))
JS_ALWAYS_TRUE(LookupAliasedNameSlot(bce, bce->script, cx->names().arguments, &sc));
if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, DontCheckLexical, bce))
return false;
} else {
if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, varIndex, bce))
if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, varIndex, DontCheckLexical, bce))
return false;
}
if (Emit1(cx, bce, JSOP_POP) < 0)
@ -3007,20 +3084,10 @@ enum VarEmitOption
typedef bool
(*DestructuringDeclEmitter)(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn);
template <DestructuringDeclEmitter EmitName>
static bool
EmitDestructuringDecl(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn)
{
JS_ASSERT(pn->isKind(PNK_NAME));
if (!BindNameToSlot(cx, bce, pn))
return false;
JS_ASSERT(!pn->isOp(JSOP_CALLEE));
return MaybeEmitVarDecl(cx, bce, prologOp, pn, nullptr);
}
static bool
EmitDestructuringDecls(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
ParseNode *pattern)
EmitDestructuringDeclsWithEmitter(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
ParseNode *pattern)
{
if (pattern->isKind(PNK_ARRAY)) {
for (ParseNode *element = pattern->pn_head; element; element = element->pn_next) {
@ -3031,10 +3098,13 @@ EmitDestructuringDecls(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp
JS_ASSERT(element->pn_kid->isKind(PNK_NAME));
target = element->pn_kid;
}
DestructuringDeclEmitter emitter =
target->isKind(PNK_NAME) ? EmitDestructuringDecl : EmitDestructuringDecls;
if (!emitter(cx, bce, prologOp, target))
return false;
if (target->isKind(PNK_NAME)) {
if (!EmitName(cx, bce, prologOp, target))
return false;
} else {
if (!EmitDestructuringDeclsWithEmitter<EmitName>(cx, bce, prologOp, target))
return false;
}
}
return true;
}
@ -3046,14 +3116,55 @@ EmitDestructuringDecls(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp
member->isKind(PNK_SHORTHAND));
ParseNode *target = member->isKind(PNK_MUTATEPROTO) ? member->pn_kid : member->pn_right;
DestructuringDeclEmitter emitter =
target->isKind(PNK_NAME) ? EmitDestructuringDecl : EmitDestructuringDecls;
if (!emitter(cx, bce, prologOp, target))
return false;
if (target->isKind(PNK_NAME)) {
if (!EmitName(cx, bce, prologOp, target))
return false;
} else {
if (!EmitDestructuringDeclsWithEmitter<EmitName>(cx, bce, prologOp, target))
return false;
}
}
return true;
}
bool
EmitDestructuringDecl(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn)
{
JS_ASSERT(pn->isKind(PNK_NAME));
if (!BindNameToSlot(cx, bce, pn))
return false;
JS_ASSERT(!pn->isOp(JSOP_CALLEE));
return MaybeEmitVarDecl(cx, bce, prologOp, pn, nullptr);
}
static inline bool
EmitDestructuringDecls(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
ParseNode *pattern)
{
return EmitDestructuringDeclsWithEmitter<EmitDestructuringDecl>(cx, bce, prologOp, pattern);
}
bool
EmitInitializeDestructuringDecl(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
ParseNode *pn)
{
MOZ_ASSERT(pn->isKind(PNK_NAME));
MOZ_ASSERT(pn->isBound());
return EmitVarOp(cx, pn, pn->getOp(), bce);
}
// Emit code to initialize all destructured names to the value on the top of
// the stack.
static inline bool
EmitInitializeDestructuringDecls(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
ParseNode *pattern)
{
return EmitDestructuringDeclsWithEmitter<EmitInitializeDestructuringDecl>(cx, bce,
prologOp, pattern);
}
static bool
EmitDestructuringOpsHelper(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pattern,
VarEmitOption emitOption);
@ -3093,7 +3204,7 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
} else if (emitOption == PushInitialValues) {
// The lhs is a simple name so the to-be-destructured value is
// its initial value and there is nothing to do.
JS_ASSERT(pn->getOp() == JSOP_SETLOCAL);
JS_ASSERT(pn->getOp() == JSOP_SETLOCAL || pn->getOp() == JSOP_INITLEXICAL);
JS_ASSERT(pn->pn_dflags & PND_BOUND);
} else {
switch (pn->getKind()) {
@ -3138,6 +3249,7 @@ EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn,
case JSOP_SETLOCAL:
case JSOP_SETARG:
case JSOP_INITLEXICAL:
if (!EmitVarOp(cx, pn, pn->getOp(), bce))
return false;
break;
@ -3534,10 +3646,10 @@ EmitTemplateString(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
static bool
EmitVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmitOption emitOption,
bool isLet = false)
bool isLetExpr = false)
{
JS_ASSERT(pn->isArity(PN_LIST));
JS_ASSERT(isLet == (emitOption == PushInitialValues));
JS_ASSERT(isLetExpr == (emitOption == PushInitialValues));
ParseNode *next;
for (ParseNode *pn2 = pn->pn_head; ; pn2 = next) {
@ -3548,18 +3660,35 @@ EmitVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmit
ParseNode *pn3;
if (!pn2->isKind(PNK_NAME)) {
if (pn2->isKind(PNK_ARRAY) || pn2->isKind(PNK_OBJECT)) {
/*
* Emit variable binding ops, but not destructuring ops. The
* parser (see Parser::variables) has ensured that our caller
* will be the PNK_FOR/PNK_FORIN/PNK_FOROF case in EmitTree, and
* that case will emit the destructuring code only after
* emitting an enumerating opcode and a branch that tests
* whether the enumeration ended.
*/
JS_ASSERT(emitOption == DefineVars);
// If the emit option is DefineVars, emit variable binding
// ops, but not destructuring ops. The parser (see
// Parser::variables) has ensured that our caller will be the
// PNK_FOR/PNK_FORIN/PNK_FOROF case in EmitTree (we don't have
// to worry about this being a variable declaration, as
// destructuring declarations without initializers, e.g., |var
// [x]|, are not legal syntax), and that case will emit the
// destructuring code only after emitting an enumerating
// opcode and a branch that tests whether the enumeration
// ended. Thus, each iteration's assignment is responsible for
// initializing, and nothing needs to be done here.
//
// Otherwise this is emitting destructuring let binding
// initialization for a legacy comprehension expression. See
// EmitForInOrOfVariables.
JS_ASSERT(pn->pn_count == 1);
if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn2))
return false;
if (emitOption == DefineVars) {
if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn2))
return false;
} else {
// Lexical bindings cannot be used before they are
// initialized. Similar to the JSOP_INITLEXICAL case below.
MOZ_ASSERT(emitOption != DefineVars);
MOZ_ASSERT_IF(emitOption == InitializeVars, pn->pn_xflags & PNX_POPVAR);
if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
return false;
if (!EmitInitializeDestructuringDecls(cx, bce, pn->getOp(), pn2))
return false;
}
break;
}
@ -3591,7 +3720,7 @@ EmitVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmit
if (!EmitTree(cx, bce, pn2->pn_right))
return false;
if (!EmitDestructuringOps(cx, bce, pn3, isLet))
if (!EmitDestructuringOps(cx, bce, pn3, isLetExpr))
return false;
/* If we are not initializing, nothing to pop. */
@ -3645,13 +3774,17 @@ EmitVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmit
if (!EmitTree(cx, bce, pn3))
return false;
bce->emittingForInit = oldEmittingForInit;
} else if (isLet) {
/* JSOP_ENTERLETx expects at least 1 slot to have been pushed. */
} else if (op == JSOP_INITLEXICAL || isLetExpr) {
// 'let' bindings cannot be used before they are
// initialized. JSOP_INITLEXICAL distinguishes the binding site.
MOZ_ASSERT(emitOption != DefineVars);
MOZ_ASSERT_IF(emitOption == InitializeVars, pn->pn_xflags & PNX_POPVAR);
if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
return false;
}
/* If we are not initializing, nothing to pop. */
// If we are not initializing, nothing to pop. If we are initializing
// lets, we must emit the pops.
if (emitOption != InitializeVars) {
if (next)
continue;
@ -4109,7 +4242,7 @@ EmitCatch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
case PNK_NAME:
/* Inline and specialize BindNameToSlot for pn2. */
JS_ASSERT(!pn2->pn_cookie.isFree());
if (!EmitVarOp(cx, pn2, JSOP_SETLOCAL, bce))
if (!EmitVarOp(cx, pn2, JSOP_INITLEXICAL, bce))
return false;
if (Emit1(cx, bce, JSOP_POP) < 0)
return false;
@ -4463,9 +4596,9 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet)
return false;
/* Push storage for hoisted let decls (e.g. 'let (x) { let y }'). */
uint32_t alreadyPushed = bce->stackDepth - letHeadDepth;
uint32_t valuesPushed = bce->stackDepth - letHeadDepth;
StmtInfoBCE stmtInfo(cx);
if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, alreadyPushed))
if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, JSOP_UNINITIALIZED, valuesPushed))
return false;
if (!EmitTree(cx, bce, letBody->pn_expr))
@ -4487,7 +4620,7 @@ EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE));
StmtInfoBCE stmtInfo(cx);
if (!EnterBlockScope(cx, bce, &stmtInfo, pn->pn_objbox, 0))
if (!EnterBlockScope(cx, bce, &stmtInfo, pn->pn_objbox, JSOP_UNINITIALIZED, 0))
return false;
if (!EmitTree(cx, bce, pn->pn_expr))
@ -4534,6 +4667,39 @@ EmitIterator(ExclusiveContext *cx, BytecodeEmitter *bce)
return true;
}
static bool
EmitForInOrOfVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool *letDecl)
{
*letDecl = pn->isKind(PNK_LEXICALSCOPE);
MOZ_ASSERT_IF(*letDecl, pn->isLet());
// If the left part is 'var x', emit code to define x if necessary using a
// prolog opcode, but do not emit a pop. If it is 'let x', EnterBlockScope
// will initialize let bindings in EmitForOf and EmitForIn with
// undefineds.
//
// Due to the horror of legacy comprehensions, there is a third case where
// we have PNK_LET without a lexical scope, because those expressions are
// parsed with single lexical scope for the entire comprehension. In this
// case we must initialize the lets to not trigger dead zone checks via
// InitializeVars.
if (!*letDecl) {
bce->emittingForInit = true;
if (pn->isKind(PNK_VAR)) {
if (!EmitVariables(cx, bce, pn, DefineVars))
return false;
} else {
MOZ_ASSERT(pn->isKind(PNK_LET));
if (!EmitVariables(cx, bce, pn, InitializeVars))
return false;
}
bce->emittingForInit = false;
}
return true;
}
/**
* If type is STMT_FOR_OF_LOOP, it emits bytecode for for-of loop.
* pn should be PNK_FOR, and pn->pn_left should be PNK_FOROF.
@ -4554,19 +4720,9 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, StmtType type, ParseNode *
ParseNode *forBody = pn ? pn->pn_right : nullptr;
ParseNode *pn1 = forHead ? forHead->pn_kid1 : nullptr;
bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE);
JS_ASSERT_IF(letDecl, pn1->isLet());
// If the left part is 'var x', emit code to define x if necessary using a
// prolog opcode, but do not emit a pop.
if (pn1) {
ParseNode *decl = letDecl ? pn1->pn_expr : pn1;
JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET));
bce->emittingForInit = true;
if (!EmitVariables(cx, bce, decl, DefineVars))
return false;
bce->emittingForInit = false;
}
bool letDecl = false;
if (pn1 && !EmitForInOrOfVariables(cx, bce, pn1, &letDecl))
return false;
if (type == STMT_FOR_OF_LOOP) {
// For-of loops run with two values on the stack: the iterator and the
@ -4584,9 +4740,11 @@ EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, StmtType type, ParseNode *
}
// Enter the block before the loop body, after evaluating the obj.
// Initialize let bindings with undefined when entering, as the name
// assigned to is a plain assignment.
StmtInfoBCE letStmt(cx);
if (letDecl) {
if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0))
if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, JSOP_UNDEFINED, 0))
return false;
}
@ -4708,23 +4866,9 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t
ParseNode *forBody = pn->pn_right;
ParseNode *pn1 = forHead->pn_kid1;
bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE);
JS_ASSERT_IF(letDecl, pn1->isLet());
/*
* If the left part is 'var x', emit code to define x if necessary
* using a prolog opcode, but do not emit a pop. If the left part was
* originally 'var x = i', the parser will have rewritten it; see
* Parser::forStatement. 'for (let x = i in o)' is mercifully banned.
*/
if (pn1) {
ParseNode *decl = letDecl ? pn1->pn_expr : pn1;
JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET));
bce->emittingForInit = true;
if (!EmitVariables(cx, bce, decl, DefineVars))
return false;
bce->emittingForInit = false;
}
bool letDecl = false;
if (pn1 && !EmitForInOrOfVariables(cx, bce, pn1, &letDecl))
return false;
/* Compile the object expression to the right of 'in'. */
if (!EmitTree(cx, bce, forHead->pn_kid3))
@ -4739,10 +4883,12 @@ EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t t
if (Emit2(cx, bce, JSOP_ITER, (uint8_t) pn->pn_iflags) < 0)
return false;
/* Enter the block before the loop body, after evaluating the obj. */
// Enter the block before the loop body, after evaluating the obj.
// Initialize let bindings with undefined when entering, as the name
// assigned to is a plain assignment.
StmtInfoBCE letStmt(cx);
if (letDecl) {
if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0))
if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, JSOP_UNDEFINED, 0))
return false;
}
@ -5042,8 +5188,8 @@ EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
script->bindings = funbox->bindings;
uint32_t lineNum = bce->parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin);
BytecodeEmitter bce2(bce, bce->parser, funbox, script, bce->insideEval,
bce->evalCaller, bce->hasGlobalScope, lineNum,
BytecodeEmitter bce2(bce, bce->parser, funbox, script, /* lazyScript = */ js::NullPtr(),
bce->insideEval, bce->evalCaller, bce->hasGlobalScope, lineNum,
bce->emitterMode);
if (!bce2.init())
return false;

View File

@ -86,6 +86,9 @@ struct BytecodeEmitter
Rooted<JSScript*> script; /* the JSScript we're ultimately producing */
Rooted<LazyScript *> lazyScript; /* the lazy script if mode is LazyFunction,
nullptr otherwise. */
struct EmitSection {
BytecodeVector code; /* bytecode */
SrcNotesVector notes; /* source notes, see below */
@ -136,8 +139,6 @@ struct BytecodeEmitter
bool emittingRunOnceLambda:1; /* true while emitting a lambda which is only
expected to run once. */
bool lazyRunOnceLambda:1; /* true while lazily emitting a script for
* a lambda which is only expected to run once. */
bool isRunOnceLambda();
@ -173,8 +174,9 @@ struct BytecodeEmitter
* destruction.
*/
BytecodeEmitter(BytecodeEmitter *parent, Parser<FullParseHandler> *parser, SharedContext *sc,
HandleScript script, bool insideEval, HandleScript evalCaller,
bool hasGlobalScope, uint32_t lineNum, EmitterMode emitterMode = Normal);
HandleScript script, Handle<LazyScript *> lazyScript,
bool insideEval, HandleScript evalCaller, bool hasGlobalScope,
uint32_t lineNum, EmitterMode emitterMode = Normal);
bool init();
bool isAliasedName(ParseNode *pn);

View File

@ -597,6 +597,9 @@ class FullParseHandler
static Definition::Kind getDefinitionKind(Definition *dn) {
return dn->kind();
}
static bool isPlaceholderDefinition(Definition *dn) {
return dn->isPlaceholder();
}
void linkUseToDef(ParseNode *pn, Definition *dn)
{
JS_ASSERT(!pn->isUsed());
@ -624,6 +627,13 @@ class FullParseHandler
bool dependencyCovered(ParseNode *pn, unsigned blockid, bool functionScope) {
return pn->pn_blockid >= blockid;
}
void markMaybeUninitializedLexicalUseInSwitch(ParseNode *pn, Definition *dn,
uint16_t firstDominatingLexicalSlot)
{
MOZ_ASSERT(pn->isUsed());
if (dn->isLet() && dn->pn_cookie.slot() < firstDominatingLexicalSlot)
pn->pn_dflags |= PND_LET;
}
static uintptr_t definitionToBits(Definition *dn) {
return uintptr_t(dn);
@ -711,11 +721,14 @@ FullParseHandler::finishInitializerAssignment(ParseNode *pn, ParseNode *init, JS
pn->pn_expr = init;
}
pn->setOp((pn->pn_dflags & PND_BOUND)
? JSOP_SETLOCAL
: (op == JSOP_DEFCONST)
? JSOP_SETCONST
: JSOP_SETNAME);
if (op == JSOP_INITLEXICAL)
pn->setOp(op);
else if (pn->pn_dflags & PND_BOUND)
pn->setOp(JSOP_SETLOCAL);
else if (op == JSOP_DEFCONST)
pn->setOp(JSOP_SETCONST);
else
pn->setOp(JSOP_SETNAME);
pn->markAsAssigned();

View File

@ -407,6 +407,7 @@ Parser<FullParseHandler>::cloneParseTree(ParseNode *opn)
Definition *dn = pn->pn_lexdef;
pn->pn_link = dn->dn_uses;
pn->pn_dflags = opn->pn_dflags;
dn->dn_uses = pn;
} else if (opn->pn_expr) {
NULLCHECK(pn->pn_expr = cloneParseTree(opn->pn_expr));
@ -513,7 +514,7 @@ Parser<FullParseHandler>::cloneLeftHandSide(ParseNode *opn)
if (opn->isDefn()) {
/* We copied some definition-specific state into pn. Clear it out. */
pn->pn_cookie.makeFree();
pn->pn_dflags &= ~PND_BOUND;
pn->pn_dflags &= ~(PND_LET | PND_BOUND);
pn->setDefn(false);
handler.linkUseToDef(pn, (Definition *) opn);

View File

@ -664,7 +664,7 @@ class ParseNode
Definition *resolve();
/* PN_CODE and PN_NAME pn_dflags bits. */
#define PND_LET 0x01 /* let (block-scoped) binding */
#define PND_LET 0x01 /* let (block-scoped) binding or use of a hoisted let */
#define PND_CONST 0x02 /* const binding (orthogonal to let) */
#define PND_ASSIGNED 0x04 /* set if ever LHS of assignment */
#define PND_PLACEHOLDER 0x08 /* placeholder definition for lexdep */
@ -746,7 +746,7 @@ class ParseNode
inline bool test(unsigned flag) const;
bool isLet() const { return test(PND_LET); }
bool isLet() const { return test(PND_LET) && !isUsed(); }
bool isConst() const { return test(PND_CONST); }
bool isPlaceholder() const { return test(PND_PLACEHOLDER); }
bool isDeoptimized() const { return test(PND_DEOPTIMIZED); }
@ -754,6 +754,7 @@ class ParseNode
bool isClosed() const { return test(PND_CLOSED); }
bool isBound() const { return test(PND_BOUND); }
bool isImplicitArguments() const { return test(PND_IMPLICITARGUMENTS); }
bool isHoistedLetUse() const { return test(PND_LET) && isUsed(); }
/* True if pn is a parsenode representing a literal constant. */
bool isLiteral() const {

View File

@ -96,6 +96,65 @@ PushStatementPC(ParseContext<ParseHandler> *pc, StmtInfoPC *stmt, StmtType type)
PushStatement(pc, stmt, type);
}
template <>
bool
ParseContext<FullParseHandler>::checkLocalsOverflow(TokenStream &ts)
{
if (vars_.length() + bodyLevelLexicals_.length() >= LOCALNO_LIMIT) {
ts.reportError(JSMSG_TOO_MANY_LOCALS);
return false;
}
return true;
}
static void
MarkUsesAsHoistedLexical(ParseNode *pn)
{
MOZ_ASSERT(pn->isDefn());
Definition *dn = (Definition *)pn;
ParseNode **pnup = &dn->dn_uses;
ParseNode *pnu;
unsigned start = pn->pn_blockid;
// In ES6, lexical bindings cannot be accessed until initialized.
// Distinguish hoisted uses as a different JSOp for easier compilation.
while ((pnu = *pnup) != nullptr && pnu->pn_blockid >= start) {
MOZ_ASSERT(pnu->isUsed());
// JavaScript is parsed in dominator order. This condition says to
// mark uses which either:
//
// 1) Is at the same dominator level.
//
// This covers the case where the right hand side of declarations
// cannot refer to the binding itself. e.g, |let x = x| is a
// ReferenceError. Note that the use of 'x' follows the definition
// node 'let x' in the program text.
//
// 2) Precedes the definition in the program text.
//
// This covers all hoisted uses.
//
// The uses that are not covered by these two conditions are uses of
// the binding as free variables in function definitions on the right
// hand side of the binding node. e.g.,
//
// let x = function () { x(); }
//
// does not mark the upvar use of 'x' inside the lambda as needing a
// TDZ check.
//
// N.B. This function expects to be called at the point of defining
// the lexical binding, and should not be called afterwards, as it
// would erroneously unmark hoisted function definitions and needing
// TDZ checks, which is currently handled in leaveFunction.
if (pnu->pn_blockid == start || pnu->pn_pos < pn->pn_pos)
pnu->pn_dflags |= PND_LET;
pnup = &pnu->pn_link;
}
}
// See comment on member function declaration.
template <>
bool
@ -150,6 +209,7 @@ ParseContext<FullParseHandler>::define(TokenStream &ts,
case Definition::ARG:
JS_ASSERT(sc->isFunctionBox());
dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETARG : JSOP_GETARG);
dn->pn_blockid = bodyid;
dn->pn_dflags |= PND_BOUND;
if (!dn->pn_cookie.set(ts, staticLevel, args_.length()))
return false;
@ -169,24 +229,35 @@ ParseContext<FullParseHandler>::define(TokenStream &ts,
case Definition::VAR:
if (sc->isFunctionBox()) {
dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETLOCAL : JSOP_GETLOCAL);
dn->pn_blockid = bodyid;
dn->pn_dflags |= PND_BOUND;
if (!dn->pn_cookie.set(ts, staticLevel, vars_.length()))
return false;
if (!vars_.append(dn))
return false;
if (vars_.length() >= LOCALNO_LIMIT) {
ts.reportError(JSMSG_TOO_MANY_LOCALS);
if (!checkLocalsOverflow(ts))
return false;
}
}
if (!decls_.addUnique(name, dn))
return false;
break;
case Definition::LET:
dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETLOCAL : JSOP_GETLOCAL);
dn->setOp(JSOP_INITLEXICAL);
dn->pn_dflags |= (PND_LET | PND_BOUND);
JS_ASSERT(dn->pn_cookie.level() == staticLevel); /* see bindLet */
if (atBodyLevel()) {
if (!bodyLevelLexicals_.append(dn))
return false;
if (!checkLocalsOverflow(ts))
return false;
}
// In ES6, lexical bindings cannot be accessed until initialized. If
// the definition has existing uses, they need to be marked so that we
// emit dead zone checks.
MarkUsesAsHoistedLexical(pn);
if (!decls_.addShadow(name, dn))
return false;
break;
@ -198,6 +269,13 @@ ParseContext<FullParseHandler>::define(TokenStream &ts,
return true;
}
template <>
bool
ParseContext<SyntaxParseHandler>::checkLocalsOverflow(TokenStream &ts)
{
return true;
}
template <>
bool
ParseContext<SyntaxParseHandler>::define(TokenStream &ts, HandlePropertyName name, Node pn,
@ -278,6 +356,11 @@ AppendPackedBindings(const ParseContext<ParseHandler> *pc, const DeclVector &vec
Binding::Kind kind;
switch (dn->kind()) {
case Definition::LET:
// Treat body-level let declarations as var bindings by falling
// through. The fact that the binding is in fact a let declaration
// is reflected in the slot. All body-level lets go after the
// vars.
case Definition::VAR:
kind = Binding::VARIABLE;
break;
@ -313,16 +396,24 @@ ParseContext<ParseHandler>::generateFunctionBindings(ExclusiveContext *cx, Token
{
JS_ASSERT(sc->isFunctionBox());
JS_ASSERT(args_.length() < ARGNO_LIMIT);
JS_ASSERT(vars_.length() < LOCALNO_LIMIT);
JS_ASSERT(vars_.length() + bodyLevelLexicals_.length() < LOCALNO_LIMIT);
/*
* Avoid pathological edge cases by explicitly limiting the total number of
* bindings to what will fit in a uint32_t.
*/
if (UINT32_MAX - args_.length() <= vars_.length())
if (UINT32_MAX - args_.length() <= vars_.length() + bodyLevelLexicals_.length())
return ts.reportError(JSMSG_TOO_MANY_LOCALS);
uint32_t count = args_.length() + vars_.length();
// Fix up the slots of body-level lets to come after the vars now that we
// know how many vars there are.
for (size_t i = 0; i < bodyLevelLexicals_.length(); i++) {
Definition *dn = bodyLevelLexicals_[i];
if (!dn->pn_cookie.set(ts, dn->pn_cookie.level(), vars_.length() + i))
return false;
}
uint32_t count = args_.length() + vars_.length() + bodyLevelLexicals_.length();
Binding *packedBindings = alloc.newArrayUninitialized<Binding>(count);
if (!packedBindings) {
js_ReportOutOfMemory(cx);
@ -331,9 +422,12 @@ ParseContext<ParseHandler>::generateFunctionBindings(ExclusiveContext *cx, Token
AppendPackedBindings(this, args_, packedBindings);
AppendPackedBindings(this, vars_, packedBindings + args_.length());
AppendPackedBindings(this, bodyLevelLexicals_,
packedBindings + args_.length() + vars_.length());
return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(),
packedBindings, blockScopeDepth);
bodyLevelLexicals_.length(), blockScopeDepth,
packedBindings);
}
template <typename ParseHandler>
@ -813,7 +907,10 @@ Parser<FullParseHandler>::checkFunctionArguments()
*/
Definition *maybeArgDef = pc->decls().lookupFirst(arguments);
bool argumentsHasBinding = !!maybeArgDef;
bool argumentsHasLocalBinding = maybeArgDef && maybeArgDef->kind() != Definition::ARG;
// ES6 9.2.13.17 says that a lexical binding of 'arguments' shadows the
// arguments object.
bool argumentsHasLocalBinding = maybeArgDef && (maybeArgDef->kind() != Definition::ARG &&
maybeArgDef->kind() != Definition::LET);
bool hasRest = pc->sc->asFunctionBox()->function()->hasRest();
if (hasRest && argumentsHasLocalBinding) {
report(ParseError, false, nullptr, JSMSG_ARGUMENTS_AND_REST);
@ -977,8 +1074,12 @@ Parser<ParseHandler>::functionBody(FunctionSyntaxKind kind, FunctionBodyType typ
/* See comment for use in Parser::functionDef. */
template <>
bool
Parser<FullParseHandler>::makeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom)
Parser<FullParseHandler>::makeDefIntoUse(Definition *dn, ParseNode *pn, JSAtom *atom,
bool *pbodyLevelHoistedUse)
{
// See comment in addFreeVariablesFromLazyFunction.
*pbodyLevelHoistedUse = !!dn->dn_uses;
/* Turn pn into a definition. */
pc->updateDecl(atom, pn);
@ -1074,12 +1175,12 @@ struct BindData
unsigned overflow;
} let;
void initLet(VarContext varContext, StaticBlockObject &blockObj, unsigned overflow) {
void initLet(VarContext varContext, StaticBlockObject *blockObj, unsigned overflow) {
this->pn = ParseHandler::null();
this->op = JSOP_NOP;
this->op = JSOP_INITLEXICAL;
this->binder = Parser<ParseHandler>::bindLet;
this->let.varContext = varContext;
this->let.blockObj = &blockObj;
this->let.blockObj = blockObj;
this->let.overflow = overflow;
}
@ -1191,7 +1292,7 @@ ConvertDefinitionToNamedLambdaUse(TokenStream &ts, ParseContext<FullParseHandler
template <>
bool
Parser<FullParseHandler>::leaveFunction(ParseNode *fn, ParseContext<FullParseHandler> *outerpc,
FunctionSyntaxKind kind)
bool bodyLevelHoistedUse, FunctionSyntaxKind kind)
{
outerpc->blockidGen = pc->blockidGen;
@ -1263,6 +1364,29 @@ Parser<FullParseHandler>::leaveFunction(ParseNode *fn, ParseContext<FullParseHan
*/
if (dn != outer_dn) {
if (ParseNode *pnu = dn->dn_uses) {
// In ES6, lexical bindings cannot be accessed until
// initialized. If we are parsing a function with a
// hoisted body-level use, all free variables that get
// linked to an outer 'let' binding need to be marked as
// needing dead zone checks. e.g.,
//
// function outer() {
// inner();
// function inner() { use(x); }
// let x;
// }
//
// The use of 'x' inside 'inner' needs to be marked.
if (bodyLevelHoistedUse && outer_dn->isLet()) {
while (true) {
pnu->pn_dflags |= PND_LET;
if (!pnu->pn_link)
break;
pnu = pnu->pn_link;
}
pnu = dn->dn_uses;
}
while (true) {
pnu->pn_lexdef = outer_dn;
if (!pnu->pn_link)
@ -1290,12 +1414,12 @@ Parser<FullParseHandler>::leaveFunction(ParseNode *fn, ParseContext<FullParseHan
template <>
bool
Parser<SyntaxParseHandler>::leaveFunction(Node fn, ParseContext<SyntaxParseHandler> *outerpc,
FunctionSyntaxKind kind)
bool bodyLevelHoistedUse, FunctionSyntaxKind kind)
{
outerpc->blockidGen = pc->blockidGen;
FunctionBox *funbox = pc->sc->asFunctionBox();
return addFreeVariablesFromLazyFunction(funbox->function(), outerpc);
return addFreeVariablesFromLazyFunction(funbox->function(), outerpc, bodyLevelHoistedUse);
}
/*
@ -1566,10 +1690,12 @@ template <>
bool
Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
ParseNode **pn_, FunctionSyntaxKind kind,
bool *pbodyProcessed)
bool *pbodyProcessed,
bool *pbodyLevelHoistedUse)
{
ParseNode *&pn = *pn_;
*pbodyProcessed = false;
*pbodyLevelHoistedUse = false;
/* Function statements add a binding to the enclosing scope. */
bool bodyLevel = pc->atBodyLevel();
@ -1583,11 +1709,13 @@ Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
JS_ASSERT(!dn->isUsed());
JS_ASSERT(dn->isDefn());
if (options().extraWarningsOption || dn->kind() == Definition::CONST) {
bool throwRedeclarationError = dn->kind() == Definition::CONST ||
dn->kind() == Definition::LET;
if (options().extraWarningsOption || throwRedeclarationError) {
JSAutoByteString name;
ParseReportKind reporter = (dn->kind() != Definition::CONST)
? ParseExtraWarning
: ParseError;
ParseReportKind reporter = throwRedeclarationError
? ParseError
: ParseExtraWarning;
if (!AtomToPrintableString(context, funName, &name) ||
!report(reporter, false, nullptr, JSMSG_REDECLARED_VAR,
Definition::kindString(dn->kind()), name.ptr()))
@ -1616,7 +1744,7 @@ Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
pn->pn_dflags |= PND_BOUND;
dn->markAsAssigned();
} else {
if (!makeDefIntoUse(dn, pn, funName))
if (!makeDefIntoUse(dn, pn, funName, pbodyLevelHoistedUse))
return false;
}
}
@ -1639,6 +1767,8 @@ Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
pc->lexdeps->remove(funName);
handler.freeTree(pn);
pn = fn;
*pbodyLevelHoistedUse = true;
}
if (!pc->define(tokenStream, funName, pn, Definition::VAR))
@ -1707,7 +1837,7 @@ Parser<FullParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
if (!funbox)
return false;
if (!addFreeVariablesFromLazyFunction(fun, pc))
if (!addFreeVariablesFromLazyFunction(fun, pc, *pbodyLevelHoistedUse))
return false;
// The position passed to tokenStream.advance() is relative to
@ -1738,15 +1868,18 @@ PropagateTransitiveParseFlags(const T *inner, U *outer)
template <typename ParseHandler>
bool
Parser<ParseHandler>::addFreeVariablesFromLazyFunction(JSFunction *fun,
ParseContext<ParseHandler> *pc)
ParseContext<ParseHandler> *pc,
bool bodyLevelHoistedUse)
{
MOZ_ASSERT_IF(bodyLevelHoistedUse, fun->displayAtom() && pc->atBodyLevel());
// Update any definition nodes in this context according to free variables
// in a lazily parsed inner function.
LazyScript *lazy = fun->lazyScript();
HeapPtrAtom *freeVariables = lazy->freeVariables();
LazyScript::FreeVariable *freeVariables = lazy->freeVariables();
for (size_t i = 0; i < lazy->numFreeVariables(); i++) {
JSAtom *atom = freeVariables[i];
JSAtom *atom = freeVariables[i].atom();
// 'arguments' will be implicitly bound within the inner function.
if (atom == context->names().arguments)
@ -1760,6 +1893,17 @@ Parser<ParseHandler>::addFreeVariablesFromLazyFunction(JSFunction *fun,
return false;
}
// In ES6, lexical bindings are unaccessible before initialization. If
// the inner function closes over a placeholder definition, we need to
// mark the variable as maybe needing a dead zone check when we emit
// bytecode.
//
// Note that body-level function declaration statements are always
// hoisted to the top, so all accesses to free let variables need the
// dead zone check.
if (handler.isPlaceholderDefinition(dn) || bodyLevelHoistedUse)
freeVariables[i].setIsHoistedUse();
/* Mark the outer dn as escaping. */
handler.setFlag(handler.getDefinitionNode(dn), PND_CLOSED);
}
@ -1772,9 +1916,11 @@ template <>
bool
Parser<SyntaxParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
Node *pn, FunctionSyntaxKind kind,
bool *pbodyProcessed)
bool *pbodyProcessed,
bool *pbodyLevelHoistedUse)
{
*pbodyProcessed = false;
*pbodyLevelHoistedUse = false;
/* Function statements add a binding to the enclosing scope. */
bool bodyLevel = pc->atBodyLevel();
@ -1795,8 +1941,10 @@ Parser<SyntaxParseHandler>::checkFunctionDefinition(HandlePropertyName funName,
}
}
} else if (bodyLevel) {
if (pc->lexdeps.lookupDefn<SyntaxParseHandler>(funName))
if (pc->lexdeps.lookupDefn<SyntaxParseHandler>(funName)) {
*pbodyLevelHoistedUse = true;
pc->lexdeps->remove(funName);
}
if (!pc->define(tokenStream, funName, *pn, Definition::VAR))
return false;
@ -1895,7 +2043,8 @@ Parser<ParseHandler>::functionDef(HandlePropertyName funName, const TokenStream:
return null();
bool bodyProcessed;
if (!checkFunctionDefinition(funName, &pn, kind, &bodyProcessed))
bool bodyLevelHoistedUse;
if (!checkFunctionDefinition(funName, &pn, kind, &bodyProcessed, &bodyLevelHoistedUse))
return null();
if (bodyProcessed)
@ -1923,7 +2072,8 @@ Parser<ParseHandler>::functionDef(HandlePropertyName funName, const TokenStream:
Directives newDirectives = directives;
while (true) {
if (functionArgsAndBody(pn, fun, type, kind, generatorKind, directives, &newDirectives))
if (functionArgsAndBody(pn, fun, type, kind, generatorKind, directives, &newDirectives,
bodyLevelHoistedUse))
break;
if (tokenStream.hadError() || directives == newDirectives)
return null();
@ -2014,10 +2164,10 @@ Parser<SyntaxParseHandler>::finishFunctionDefinition(Node pn, FunctionBox *funbo
if (!lazy)
return false;
HeapPtrAtom *freeVariables = lazy->freeVariables();
LazyScript::FreeVariable *freeVariables = lazy->freeVariables();
size_t i = 0;
for (AtomDefnRange r = pc->lexdeps->all(); !r.empty(); r.popFront())
freeVariables[i++].init(r.front().key());
freeVariables[i++] = LazyScript::FreeVariable(r.front().key());
JS_ASSERT(i == numFreeVariables);
HeapPtrFunction *innerFunctions = lazy->innerFunctions();
@ -2041,7 +2191,8 @@ Parser<FullParseHandler>::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
FunctionType type, FunctionSyntaxKind kind,
GeneratorKind generatorKind,
Directives inheritedDirectives,
Directives *newDirectives)
Directives *newDirectives,
bool bodyLevelHoistedUse)
{
ParseContext<FullParseHandler> *outerpc = pc;
@ -2065,8 +2216,7 @@ Parser<FullParseHandler>::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
ParseContext<SyntaxParseHandler> funpc(parser, outerpc, SyntaxParseHandler::null(), funbox,
newDirectives, outerpc->staticLevel + 1,
outerpc->blockidGen,
/* blockScopeDepth = */ 0);
outerpc->blockidGen, /* blockScopeDepth = */ 0);
if (!funpc.init(tokenStream))
return false;
@ -2092,7 +2242,7 @@ Parser<FullParseHandler>::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
pn->pn_pos.end = tokenStream.currentToken().pos.end;
}
if (!addFreeVariablesFromLazyFunction(fun, pc))
if (!addFreeVariablesFromLazyFunction(fun, pc, bodyLevelHoistedUse))
return false;
pn->pn_blockid = outerpc->blockid();
@ -2110,7 +2260,7 @@ Parser<FullParseHandler>::functionArgsAndBody(ParseNode *pn, HandleFunction fun,
if (!functionArgsAndBodyGeneric(pn, fun, type, kind))
return false;
if (!leaveFunction(pn, outerpc, kind))
if (!leaveFunction(pn, outerpc, bodyLevelHoistedUse, kind))
return false;
pn->pn_blockid = outerpc->blockid();
@ -2131,7 +2281,8 @@ Parser<SyntaxParseHandler>::functionArgsAndBody(Node pn, HandleFunction fun,
FunctionType type, FunctionSyntaxKind kind,
GeneratorKind generatorKind,
Directives inheritedDirectives,
Directives *newDirectives)
Directives *newDirectives,
bool bodyLevelHoistedUse)
{
ParseContext<SyntaxParseHandler> *outerpc = pc;
@ -2150,7 +2301,7 @@ Parser<SyntaxParseHandler>::functionArgsAndBody(Node pn, HandleFunction fun,
if (!functionArgsAndBodyGeneric(pn, fun, type, kind))
return false;
if (!leaveFunction(pn, outerpc, kind))
if (!leaveFunction(pn, outerpc, bodyLevelHoistedUse, kind))
return false;
// This is a lazy function inner to another lazy function. Remember the
@ -2661,21 +2812,32 @@ Parser<FullParseHandler>::bindLet(BindData<FullParseHandler> *data,
return false;
ExclusiveContext *cx = parser->context;
Rooted<StaticBlockObject *> blockObj(cx, data->let.blockObj);
unsigned index = blockObj->numVariables();
if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) {
parser->report(ParseError, false, pn, data->let.overflow);
return false;
unsigned index;
if (blockObj) {
index = blockObj->numVariables();
if (index >= StaticBlockObject::LOCAL_INDEX_LIMIT) {
parser->report(ParseError, false, pn, data->let.overflow);
return false;
}
} else {
// If we don't have a block object, we are parsing a body-level let,
// in which case we use a bogus index. See comment block below in
// setting the pn_cookie for explanation on how it gets adjusted.
index = 0;
}
/*
* Assign block-local index to pn->pn_cookie right away, encoding it as an
* upvar cookie whose skip tells the current static level. The emitter will
* adjust the node's slot based on its stack depth model -- and, for global
* and eval code, js::frontend::CompileScript will adjust the slot
* again to include script->nfixed.
*/
// For block-level lets, assign block-local index to pn->pn_cookie right
// away, encoding it as an upvar cookie whose skip tells the current
// static level. The emitter will adjust the node's slot based on its
// stack depth model -- and, for global and eval code,
// js::frontend::CompileScript will adjust the slot again to include
// script->nfixed and body-level lets.
//
// For body-level lets, the index is bogus at this point and is adjusted
// when creating Bindings. See ParseContext::generateFunctionBindings and
// AppendPackedBindings.
if (!pn->pn_cookie.set(parser->tokenStream, pc->staticLevel, index))
return false;
@ -2684,7 +2846,6 @@ Parser<FullParseHandler>::bindLet(BindData<FullParseHandler> *data,
* define() right now. Otherwise, delay define until PushLetScope.
*/
if (data->let.varContext == HoistVars) {
JS_ASSERT(!pc->atBodyLevel());
Definition *dn = pc->decls().lookupFirst(name);
if (dn && dn->pn_blockid == pc->blockid())
return parser->reportRedeclaration(pn, dn->isConst(), name);
@ -2692,17 +2853,25 @@ Parser<FullParseHandler>::bindLet(BindData<FullParseHandler> *data,
return false;
}
bool redeclared;
RootedId id(cx, NameToId(name));
RootedShape shape(cx, StaticBlockObject::addVar(cx, blockObj, id, index, &redeclared));
if (!shape) {
if (redeclared)
parser->reportRedeclaration(pn, false, name);
return false;
if (blockObj) {
bool redeclared;
RootedId id(cx, NameToId(name));
RootedShape shape(cx, StaticBlockObject::addVar(cx, blockObj, id, index, &redeclared));
if (!shape) {
if (redeclared)
parser->reportRedeclaration(pn, false, name);
return false;
}
/* Store pn in the static block object. */
blockObj->setDefinitionParseNode(index, reinterpret_cast<Definition *>(pn));
} else {
// Body-level lets are hoisted and need to have been defined via
// pc->define above.
MOZ_ASSERT(data->let.varContext == HoistVars);
MOZ_ASSERT(pc->decls().lookupFirst(name));
}
/* Store pn in the static block object. */
blockObj->setDefinitionParseNode(index, reinterpret_cast<Definition *>(pn));
return true;
}
@ -2998,8 +3167,17 @@ Parser<ParseHandler>::noteNameUse(HandlePropertyName name, Node pn)
handler.linkUseToDef(pn, dn);
if (stmt && stmt->type == STMT_WITH)
handler.setFlag(pn, PND_DEOPTIMIZED);
if (stmt) {
if (stmt->type == STMT_WITH) {
handler.setFlag(pn, PND_DEOPTIMIZED);
} else if (stmt->type == STMT_SWITCH && stmt->isBlockScope) {
// See comments above StmtInfoPC and switchStatement for how
// firstDominatingLetInCase is computed.
MOZ_ASSERT(stmt->firstDominatingLexicalInCase <= stmt->staticBlock().numVariables());
handler.markMaybeUninitializedLexicalUseInSwitch(pn, dn,
stmt->firstDominatingLexicalInCase);
}
}
return true;
}
@ -3020,7 +3198,9 @@ Parser<FullParseHandler>::bindDestructuringVar(BindData<FullParseHandler> *data,
* Select the appropriate name-setting opcode, respecting eager selection
* done by the data->binder function.
*/
if (pn->pn_dflags & PND_BOUND)
if (data->op == JSOP_INITLEXICAL)
pn->setOp(JSOP_INITLEXICAL);
else if (pn->pn_dflags & PND_BOUND)
pn->setOp(JSOP_SETLOCAL);
else if (data->op == JSOP_DEFCONST)
pn->setOp(JSOP_SETCONST);
@ -3439,7 +3619,7 @@ Parser<ParseHandler>::variables(ParseNodeKind kind, bool *psimple,
*/
JS_ASSERT_IF(psimple, *psimple);
JSOp op = blockObj ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST;
JSOp op = kind == PNK_LET ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST;
Node pn = handler.newList(kind, null(), op);
if (!pn)
@ -3451,8 +3631,8 @@ Parser<ParseHandler>::variables(ParseNodeKind kind, bool *psimple,
* this code will change soon.
*/
BindData<ParseHandler> data(context);
if (blockObj)
data.initLet(varContext, *blockObj, JSMSG_TOO_MANY_LOCALS);
if (kind == PNK_LET)
data.initLet(varContext, blockObj, JSMSG_TOO_MANY_LOCALS);
else
data.initVarOrConst(op);
@ -3474,10 +3654,16 @@ Parser<ParseHandler>::variables(ParseNodeKind kind, bool *psimple,
if (!pn2)
return null();
if (!checkDestructuring(&data, pn2))
return null();
bool ignored;
if (pc->parsingForInit && matchInOrOf(&ignored)) {
bool parsingForInOrOfInit = pc->parsingForInit && matchInOrOf(&ignored);
// See comment below for bindBeforeInitializer in the code that
// handles the non-destructuring case.
bool bindBeforeInitializer = kind != PNK_LET || parsingForInOrOfInit;
if (bindBeforeInitializer && !checkDestructuring(&data, pn2))
return null();
if (parsingForInOrOfInit) {
tokenStream.ungetToken();
handler.addList(pn, pn2);
continue;
@ -3489,6 +3675,9 @@ Parser<ParseHandler>::variables(ParseNodeKind kind, bool *psimple,
if (!init)
return null();
if (!bindBeforeInitializer && !checkDestructuring(&data, pn2))
return null();
pn2 = handler.newBinaryOrAppend(PNK_ASSIGN, pn2, init, pc);
if (!pn2)
return null();
@ -3514,21 +3703,40 @@ Parser<ParseHandler>::variables(ParseNodeKind kind, bool *psimple,
if (data.op == JSOP_DEFCONST)
handler.setFlag(pn2, PND_CONST);
data.pn = pn2;
if (!data.binder(&data, name, this))
return null();
handler.addList(pn, pn2);
if (tokenStream.matchToken(TOK_ASSIGN)) {
if (psimple)
*psimple = false;
// In ES6, lexical bindings may not be accessed until
// initialized. So a declaration of the form |let x = x| results
// in a ReferenceError, as the 'x' on the RHS is accessing the let
// binding before it is initialized.
//
// If we are not parsing a let declaration, bind the name
// now. Otherwise we must wait until after parsing the initializing
// assignment.
bool bindBeforeInitializer = kind != PNK_LET;
if (bindBeforeInitializer && !data.binder(&data, name, this))
return null();
Node init = assignExpr();
if (!init)
return null();
if (!bindBeforeInitializer && !data.binder(&data, name, this))
return null();
if (!handler.finishInitializerAssignment(pn2, init, data.op))
return null();
} else {
if (!data.binder(&data, name, this))
return null();
}
} while (tokenStream.matchToken(TOK_COMMA));
return pn;
@ -3575,19 +3783,27 @@ Parser<FullParseHandler>::letDeclaration()
* conflicting slots. Forbid top-level let declarations to
* prevent such conflicts from ever occurring.
*/
if (options().selfHostingMode &&
!pc->sc->isFunctionBox() &&
stmt == pc->topScopeStmt)
{
bool globalLet = !pc->sc->isFunctionBox() && stmt == pc->topScopeStmt;
if (options().selfHostingMode && globalLet) {
report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LET);
return null();
}
/*
* ES4 specifies that let at top level and at body-block scope
* does not shadow var, so convert back to var.
* Parse body-level lets without a new block object. ES6 specs
* that an execution environment's initial lexical environment
* is the VariableEnvironment, i.e., body-level lets are in
* the same environment record as vars.
*
* However, they cannot be parsed exactly as vars, as ES6
* requires that uninitialized lets throw ReferenceError on use.
*
* See 8.1.1.1.6 and the note in 13.2.1.
*
* FIXME global-level lets are still considered vars until
* other bugs are fixed.
*/
pn = variables(PNK_VAR);
pn = variables(globalLet ? PNK_VAR : PNK_LET);
if (!pn)
return null();
pn->pn_xflags |= PNX_POPVAR;
@ -4625,6 +4841,19 @@ Parser<ParseHandler>::switchStatement()
handler.addList(body, stmt);
}
// In ES6, lexical bindings canot be accessed until initialized. If
// there was a 'let' declaration in the case we just parsed, remember
// the slot starting at which new lexical bindings will be
// assigned. Since lexical bindings from previous cases will not
// dominate uses in the current case, any such uses will require a
// dead zone check.
//
// Currently this is overly conservative; we could do better, but
// declaring lexical bindings within switch cases without introducing
// a new block is poor form and should be avoided.
if (stmtInfo.isBlockScope)
stmtInfo.firstDominatingLexicalInCase = stmtInfo.staticBlock().numVariables();
Node casepn = handler.newCaseOrDefault(caseBegin, caseExpr, body);
if (!casepn)
return null();
@ -5102,7 +5331,7 @@ Parser<ParseHandler>::tryStatement()
* scoped, not a property of a new Object instance. This is
* an intentional change that anticipates ECMA Ed. 4.
*/
data.initLet(HoistVars, pc->staticScope->template as<StaticBlockObject>(),
data.initLet(HoistVars, &pc->staticScope->template as<StaticBlockObject>(),
JSMSG_TOO_MANY_CATCH_VARS);
JS_ASSERT(data.let.blockObj);
@ -6156,7 +6385,7 @@ Parser<FullParseHandler>::legacyComprehensionTail(ParseNode *bodyStmt, unsigned
return null();
JS_ASSERT(pc->staticScope && pc->staticScope == pn->pn_objbox->object);
data.initLet(HoistVars, pc->staticScope->as<StaticBlockObject>(), JSMSG_ARRAY_INIT_TOO_BIG);
data.initLet(HoistVars, &pc->staticScope->as<StaticBlockObject>(), JSMSG_ARRAY_INIT_TOO_BIG);
do {
/*
@ -6272,21 +6501,25 @@ Parser<FullParseHandler>::legacyComprehensionTail(ParseNode *bodyStmt, unsigned
/*
* Synthesize a declaration. Every definition must appear in the parse
* tree in order for ComprehensionTranslator to work.
*
* These are lets to tell the bytecode emitter to emit initialization
* code for the temporal dead zone.
*/
ParseNode *vars = ListNode::create(PNK_VAR, &handler);
if (!vars)
ParseNode *lets = ListNode::create(PNK_LET, &handler);
if (!lets)
return null();
vars->setOp(JSOP_NOP);
vars->pn_pos = pn3->pn_pos;
vars->makeEmpty();
vars->append(pn3);
lets->setOp(JSOP_NOP);
lets->pn_pos = pn3->pn_pos;
lets->makeEmpty();
lets->append(pn3);
lets->pn_xflags |= PNX_POPVAR;
/* Definitions can't be passed directly to EmitAssignment as lhs. */
pn3 = cloneLeftHandSide(pn3);
if (!pn3)
return null();
pn2->pn_left = handler.newTernary(headKind, vars, pn3, pn4);
pn2->pn_left = handler.newTernary(headKind, lets, pn3, pn4);
if (!pn2->pn_left)
return null();
*pnp = pn2;
@ -6452,7 +6685,7 @@ Parser<ParseHandler>::generatorComprehensionLambda(GeneratorKind comprehensionKi
PropagateTransitiveParseFlags(genFunbox, outerpc->sc);
if (!leaveFunction(genfn, outerpc))
if (!leaveFunction(genfn, outerpc, /* bodyLevelHoistedUse = */ false))
return null();
return genfn;
@ -6558,7 +6791,7 @@ Parser<ParseHandler>::comprehensionFor(GeneratorKind comprehensionKind)
RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context));
if (!blockObj)
return null();
data.initLet(DontHoistVars, *blockObj, JSMSG_TOO_MANY_LOCALS);
data.initLet(DontHoistVars, blockObj, JSMSG_TOO_MANY_LOCALS);
Node lhs = newName(name);
if (!lhs)
return null();

View File

@ -30,7 +30,19 @@ struct StmtInfoPC : public StmtInfoBase {
uint32_t blockid; /* for simplified dominance computation */
uint32_t innerBlockScopeDepth; /* maximum depth of nested block scopes, in slots */
explicit StmtInfoPC(ExclusiveContext *cx) : StmtInfoBase(cx), innerBlockScopeDepth(0) {}
// Lexical declarations inside switches are tricky because the block id
// doesn't convey dominance information. Record what index the current
// case's lexical declarations start at so we may generate dead zone
// checks for other cases' declarations.
//
// Only valid if type is STMT_SWITCH.
uint16_t firstDominatingLexicalInCase;
explicit StmtInfoPC(ExclusiveContext *cx)
: StmtInfoBase(cx),
innerBlockScopeDepth(0),
firstDominatingLexicalInCase(0)
{}
};
typedef HashSet<JSAtom *, DefaultHasher<JSAtom *>, LifoAllocPolicy<Fallible>> FuncStmtSet;
@ -126,10 +138,14 @@ struct ParseContext : public GenericParseContext
uint32_t blockScopeDepth; /* maximum depth of nested block scopes, in slots */
Node blockNode; /* parse node for a block with let declarations
(block with its own lexical scope) */
private:
AtomDecls<ParseHandler> decls_; /* function, const, and var declarations */
DeclVector args_; /* argument definitions */
DeclVector vars_; /* var/const definitions */
AtomDecls<ParseHandler> decls_; /* function, const, and var declarations */
DeclVector args_; /* argument definitions */
DeclVector vars_; /* var/const definitions */
DeclVector bodyLevelLexicals_; /* lexical definitions at body-level */
bool checkLocalsOverflow(TokenStream &ts);
public:
const AtomDecls<ParseHandler> &decls() const {
@ -259,6 +275,7 @@ struct ParseContext : public GenericParseContext
decls_(prs->context, prs->alloc),
args_(prs->context),
vars_(prs->context),
bodyLevelLexicals_(prs->context),
parserPC(&prs->pc),
oldpc(prs->pc),
lexdeps(prs->context),
@ -557,7 +574,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
bool functionArgsAndBody(Node pn, HandleFunction fun,
FunctionType type, FunctionSyntaxKind kind,
GeneratorKind generatorKind,
Directives inheritedDirectives, Directives *newDirectives);
Directives inheritedDirectives, Directives *newDirectives,
bool bodyLevelHoistedUse);
Node unaryOpExpr(ParseNodeKind kind, JSOp op, uint32_t begin);
@ -606,11 +624,12 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
bool matchInOrOf(bool *isForOfp);
bool checkFunctionArguments();
bool makeDefIntoUse(Definition *dn, Node pn, JSAtom *atom);
bool makeDefIntoUse(Definition *dn, Node pn, JSAtom *atom, bool *pbodyLevelHoistedUse);
bool checkFunctionDefinition(HandlePropertyName funName, Node *pn, FunctionSyntaxKind kind,
bool *pbodyProcessed);
bool *pbodyProcessed, bool *pbodyLevelHoistedUse);
bool finishFunctionDefinition(Node pn, FunctionBox *funbox, Node prelude, Node body);
bool addFreeVariablesFromLazyFunction(JSFunction *fun, ParseContext<ParseHandler> *pc);
bool addFreeVariablesFromLazyFunction(JSFunction *fun, ParseContext<ParseHandler> *pc,
bool bodyLevelHoistedUse);
bool isValidForStatementLHS(Node pn1, JSVersion version, bool forDecl, bool forEach,
ParseNodeKind headKind);
@ -661,7 +680,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter
DefinitionNode getOrCreateLexicalDependency(ParseContext<ParseHandler> *pc, JSAtom *atom);
bool leaveFunction(Node fn, ParseContext<ParseHandler> *outerpc,
FunctionSyntaxKind kind = Expression);
bool bodyLevelHoistedUse, FunctionSyntaxKind kind = Expression);
TokenPos pos() const { return tokenStream.currentToken().pos; }

View File

@ -427,6 +427,9 @@ struct StmtInfoBase {
return isNestedScope;
}
void setStaticScope() {
}
StaticBlockObject& staticBlock() const {
JS_ASSERT(isNestedScope);
JS_ASSERT(isBlockScope);

View File

@ -249,6 +249,7 @@ class SyntaxParseHandler
static Node getDefinitionNode(DefinitionNode dn) { return NodeGeneric; }
static Definition::Kind getDefinitionKind(DefinitionNode dn) { return dn; }
static bool isPlaceholderDefinition(DefinitionNode dn) { return dn == Definition::PLACEHOLDER; }
void linkUseToDef(Node pn, DefinitionNode dn) {}
DefinitionNode resolve(DefinitionNode dn) { return dn; }
void deoptimizeUsesWithin(DefinitionNode dn, const TokenPos &pos) {}
@ -258,6 +259,8 @@ class SyntaxParseHandler
// dependency location with blockid.
return functionScope;
}
void markMaybeUninitializedLexicalUseInSwitch(Node pn, DefinitionNode dn,
uint16_t firstDominatingLexicalSlot) {}
static uintptr_t definitionToBits(DefinitionNode dn) {
// Use a shift, as DefinitionList tags the lower bit of its associated union.

View File

@ -122,7 +122,7 @@ function f5(a, b, c, d) {
}
var a, b = ()=>63;
let c, d = ()=>65;
var c, d = ()=>65;
// after var declarations, before function declarations
assertEq(a(), 52);

View File

@ -59,7 +59,7 @@ function f3(a, b, c, d) {
assertEq(d(), 55);
var a, b = ()=>63;
let c, d = ()=>65;
var c, d = ()=>65;
// after var declarations, before function declarations
assertEq(a(), 52);

View File

@ -0,0 +1,2 @@
(function() { let arguments })();
(() => { let arguments; })()

View File

@ -0,0 +1,5 @@
// |jit-test| error: ReferenceError
(function() {
with(x);
let x
})()

View File

@ -0,0 +1,27 @@
var output = [];
function g(s) {
L = s.length;
for (var i = 0; i < L; i++) {
a = s.charAt()
}
}
function h(f, inputs) {
results = [];
for (var j = 0; j < 99; ++j) {
for (var k = 0; k < 99; ++k) {
try {
results.push(f())
} catch (e) {}
}
}
output.push(g(uneval(results)))
}
m = (function(x, y) {});
h(m, [])
try {
output.push(x);
let x = s()
} catch (e) {}
assertEq(output.length, 1);
assertEq(output[0], undefined);

View File

@ -0,0 +1,30 @@
var output = [];
function g(s) {
for (var i = 0; i < s.length; i++) {
s.charAt()
}
}
function h(f, inputs) {
results = []
for (var j = 0; j < 99; ++j) {
for (var k = 0; k < 99; ++k) {
try {
results.push(f())
} catch (e) {}
}
}
g(uneval(results))
}
try {
x()
} catch (e) {}
m = function(y) {
return y;
};
h(m, []);
try {
output.push(b);
let b = "";
} catch (e) {}
assertEq(output.length, 0);

View File

@ -0,0 +1,6 @@
// |jit-test| error: ReferenceError
evalcx("\
for(x = 0; x < 9; x++) {\
let y = y.s()\
}\
", newGlobal())

View File

@ -0,0 +1,7 @@
// |jit-test| error: ReferenceError
(function() {
((function() {
p(y)
})());
let y
})()

View File

@ -0,0 +1,5 @@
var x = "foobar";
{ for (let x of x) assertEq(x.length, 1, "second x refers to outer x"); }
var x = "foobar";
{ for (let x in x) assertEq(x.length, 1, "second x refers to outer x"); }

View File

@ -0,0 +1,19 @@
function throwSomething() {
throw "something";
}
try {
// Use eval to force BINDNAME. Should throw "something" instead of the TDZ
// ReferenceError.
eval("x = throwSomething()");
let x;
} catch (e) {
assertEq(e, "something");
}
try {
eval("x = 42");
let x;
} catch (e) {
assertEq(e instanceof ReferenceError, true);
}

View File

@ -14,13 +14,3 @@ function f2(b, w) {
}
assertEq(typeof f2(true, 3), "function");
assertEq(f2(false, 3), 3);
function f3(b) {
let (w = 3) {
if (b)
function w() {}
return w;
}
}
assertEq(f3(true, 3), 3);
assertEq(f3(false), 3);

View File

@ -32,13 +32,25 @@ function test(str, arg, result)
}
}
function isError(str)
function isParseError(str)
{
var caught = false;
try {
new Function(str);
} catch(e) {
assertEq(String(e).indexOf('TypeError') == 0 || String(e).indexOf('SyntaxError') == 0, true);
assertEq(e instanceof TypeError || e instanceof SyntaxError, true);
caught = true;
}
assertEq(caught, true);
}
function isReferenceError(str)
{
var caught = false;
try {
(new Function(str))();
} catch(e) {
assertEq(e instanceof ReferenceError, true);
caught = true;
}
assertEq(caught, true);
@ -86,12 +98,12 @@ test('"use strict";return let (y = x) (eval("var y = 2"), y);');
test('this.y = x;return let (y = 1) this.eval("y");');
test('try {let (x = x) eval("throw x");} catch (e) {return e;}');
test('try {return let (x = eval("throw x")) x;} catch (e) {return e;}');
isError('let (x = 1, x = 2) x');
isError('let ([x, y] = a, {a:x} = b) x');
isError('let ([x, y, x] = a) x');
isError('let ([x, [y, [x]]] = a) x');
isError('let (x = function() { return x}) x()return x;');
isError('(let (x = function() { return x}) x())return x;');
isParseError('let (x = 1, x = 2) x');
isParseError('let ([x, y] = a, {a:x} = b) x');
isParseError('let ([x, y, x] = a) x');
isParseError('let ([x, [y, [x]]] = a) x');
isParseError('let (x = function() { return x}) x()return x;');
isParseError('(let (x = function() { return x}) x())return x;');
// let block
test('let (y) {return x;}');
@ -133,10 +145,10 @@ test('return eval("let (y = x) {y;}");');
test('let (y = x) {eval("var y = 2");return y;}', 'ponies', 2);
test('"use strict";let (y = x) {eval("var y = 2");return y;}');
test('this.y = x;let (y = 1) {return this.eval("y");}');
isError('let (x = 1, x = 2) {x}');
isError('let ([x, y] = a, {a:x} = b) {x}');
isError('let ([x, y, x] = a) {x}');
isError('let ([x, [y, [x]]] = a) {x}');
isParseError('let (x = 1, x = 2) {x}');
isParseError('let ([x, y] = a, {a:x} = b) {x}');
isParseError('let ([x, y, x] = a) {x}');
isParseError('let ([x, [y, [x]]] = a) {x}');
// var declarations
test('var y;return x;');
@ -171,15 +183,10 @@ test('if (x) {var z = y;var [y] = x;z += y;}return z;', ['-'], 'undefined-');
test('if (x) {let y;return x;}');
test('if (x) {let x;return "" + x;}', 'unicorns', 'undefined');
test('if (x) {let y = x;return x;}');
test('if (x) {y = x;let y = y;return y;}');
test('if (x) {var z = y;let [y] = x;z += y;}return z;', ['-'], 'undefined-');
test('if (x) {let y = x;return x;}');
test('if (x) {let [] = x;return x;}');
test('if (x) {let [, ] = x;return x;}');
test('if (x) {let [, , , , ] = x;return x;}');
test('if (x) {let x = x;return "" + x;}', 'unicorns', 'undefined');
test('if (x) {let y = y;return "" + y;}', 'unicorns', 'undefined');
test('if (x) {let x = eval("x");return "" + x;}', 'unicorns', 'undefined');
test('if (x) {let y = (let (x = x + 1) x) + 1;return y;}', 1, 3);
test('if (x) {let y = (let (x = eval("x") + 1) eval("x")) + 1;return eval("y");}', 1, 3);
test('if (x) {let X = x + 1, y = x;return y;}');
@ -214,11 +221,11 @@ test('"use strict";if (x) {let y = x;eval("var y = 2");return y;}');
test('"use strict";if (x) {let y = x;eval("let y = 2");return y;}');
test('"use strict";if (x) {let y = 1;return eval("let y = x;y;");}');
test('this.y = x;if (x) {let y = 1;return this.eval("y");}');
isError('if (x) {let (x = 1, x = 2) {x}}');
isError('if (x) {let ([x, y] = a, {a:x} = b) {x}}');
isError('if (x) {let ([x, y, x] = a) {x}}');
isError('if (x) {let ([x, [y, [x]]] = a) {x}}');
isError('let ([x, y] = x) {let x;}');
isParseError('if (x) {let (x = 1, x = 2) {x}}');
isParseError('if (x) {let ([x, y] = a, {a:x} = b) {x}}');
isParseError('if (x) {let ([x, y, x] = a) {x}}');
isParseError('if (x) {let ([x, [y, [x]]] = a) {x}}');
isParseError('let ([x, y] = x) {let x;}');
// for(;;)
test('for (;;) {return x;}');
@ -253,10 +260,10 @@ test('for (let a = x;;) {let c = x, d = x;return c;}');
test('for (let [a, b] = x;;) {let c = x, d = x;return c;}');
test('for (let [a] = (1, [x]);;) {return a;}');
test('for (let [a] = (1, x, 1, x);;) {return a;}', ['ponies']);
isError('for (let x = 1, x = 2;;) {}');
isError('for (let [x, y] = a, {a:x} = b;;) {}');
isError('for (let [x, y, x] = a;;) {}');
isError('for (let [x, [y, [x]]] = a;;) {}');
isParseError('for (let x = 1, x = 2;;) {}');
isParseError('for (let [x, y] = a, {a:x} = b;;) {}');
isParseError('for (let [x, y, x] = a;;) {}');
isParseError('for (let [x, [y, [x]]] = a;;) {}');
// for(in)
test('for (let i in x) {return x;}');
@ -283,9 +290,9 @@ test('a:for (let i in x) {for (let j in x) {break a;}}return eval("x");');
test('var j;for (let i in x) {j = i;break;}return j;', {ponies:true});
test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}');
test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies']);
isError('for (let [x, x] in o) {}');
isError('for (let [x, y, x] in o) {}');
isError('for (let [x, [y, [x]]] in o) {}');
isParseError('for (let [x, x] in o) {}');
isParseError('for (let [x, y, x] in o) {}');
isParseError('for (let [x, [y, [x]]] in o) {}');
// genexps
test('return (i for (i in x)).next();', {ponies:true});
@ -302,6 +309,21 @@ test('try {return [eval("throw i") for (i in x)][0];} catch (e) {return e;}', {p
// don't forget about switch craziness
test('var y = 3;switch (function () {return eval("y");}()) {case 3:let y;return x;default:;}');
test('switch (x) {case 3:let y;return 3;case 4:let z;return 4;default:return x;}');
test('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}');
test('switch (x) {case 3:default:let y;let (y = x) {return y;}}');
isError('switch (x) {case 3:let y;return 3;case 4:let y;return 4;default:;}');
isParseError('switch (x) {case 3:let y;return 3;case 4:let y;return 4;default:;}');
// TDZ checks
isReferenceError('x + 1; let x = 42;');
isReferenceError('x = 42; let x;');
isReferenceError('inner(); function inner() { x++; } let x;');
isReferenceError('inner(); let x; function inner() { x++; }');
isReferenceError('inner(); let x; function inner() { function innerer() { x++; } innerer(); }');
isReferenceError('let x; var inner = function () { y++; }; inner(); let y;');
isReferenceError('let x = x;');
isReferenceError('let [x] = [x];');
isReferenceError('let {x} = {x:x};');
isReferenceError('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}');
isReferenceError('let x = function() {} ? x() : function() {}');
// redecl with function statements
isParseError('let a; function a() {}');

View File

@ -30,8 +30,6 @@ var cases = [
// bindings in functions
"function f() { var x = VAL; @@ } f();",
"function f() { let x = VAL; @@ } f();",
"function f([x]) { let x = VAL; @@ } f(['fail']);",
"function f(x) { { let x = VAL; @@ } } f('fail');",
"function f() { function x() {} x = VAL; @@ } f();",
// dynamic bindings

View File

@ -1,5 +1,8 @@
// Eval-in-frame with different type on baseline frame with let-scoping
// FIXMEshu disabled until TDZ checks are implemented in the JITs.
quit(0);
load(libdir + "jitopts.js");
if (!jitTogglesMatch(Opts_BaselineEager))

View File

@ -9,7 +9,7 @@
{
new f
}
let w = {}
var w = {}
})()
/* Make sure that MICs don't have the same bug. */
@ -25,6 +25,6 @@ x = Object();
{
new f
}
let w = {}
var w = {}
})()
/* Don't assert. */

View File

@ -3,7 +3,7 @@ load(libdir + "parallelarray-helpers.js");
function buildSimple() {
assertParallelModesCommute(["seq", "par"], function(m) {
return Array.buildPar(256, function(i) {
let obj = [i, 1, 2];
var obj = [i, 1, 2];
obj[0] += 1;
obj[1] += 1;
obj[2] += 1;

View File

@ -3,7 +3,7 @@ load(libdir + "parallelarray-helpers.js");
function buildSimple() {
assertParallelModesCommute(["seq", "par"], function(m) {
Array.buildPar(256, function(i) {
let obj = { x: i, y: i + 1, z: i + 2 };
var obj = { x: i, y: i + 1, z: i + 2 };
obj.x += 1;
obj.y += 1;
obj.z += 1;

View File

@ -57,7 +57,7 @@ BaselineFrame::trace(JSTracer *trc, JitFrameIterator &frameIterator)
// Mark locals and stack values.
JSScript *script = this->script();
size_t nfixed = script->nfixed();
size_t nlivefixed = script->nfixedvars();
size_t nlivefixed = script->nbodyfixed();
if (nfixed != nlivefixed) {
jsbytecode *pc;
@ -75,7 +75,7 @@ BaselineFrame::trace(JSTracer *trc, JitFrameIterator &frameIterator)
}
JS_ASSERT(nlivefixed <= nfixed);
JS_ASSERT(nlivefixed >= script->nfixedvars());
JS_ASSERT(nlivefixed >= script->nbodyfixed());
// NB: It is possible that numValueSlots() could be zero, even if nfixed is
// nonzero. This is the case if the function has an early stack check.
@ -91,9 +91,13 @@ BaselineFrame::trace(JSTracer *trc, JitFrameIterator &frameIterator)
// Mark operand stack.
MarkLocals(this, trc, nfixed, numValueSlots());
// Clear dead locals.
while (nfixed > nlivefixed)
unaliasedLocal(--nfixed, DONT_CHECK_ALIASING).setUndefined();
// Clear non-magic dead locals. Magic values such as
// JS_UNINITIALIZED_LET need to be left as is for correctness.
while (nfixed > nlivefixed) {
--nfixed;
if (!unaliasedLocal(nfixed, DONT_CHECK_ALIASING).isMagic())
unaliasedLocal(nfixed, DONT_CHECK_ALIASING).setUndefined();
}
// Mark live locals.
MarkLocals(this, trc, 0, nlivefixed);

View File

@ -5962,7 +5962,8 @@ DoBindNameFallback(JSContext *cx, BaselineFrame *frame, ICBindName_Fallback *stu
RootedPropertyName name(cx, frame->script()->getName(pc));
RootedObject scope(cx);
if (!LookupNameUnqualified(cx, name, scopeChain, &scope))
RootedShape shape(cx);
if (!LookupNameUnqualified(cx, name, scopeChain, &scope, &shape))
return false;
res.setObject(*scope);

View File

@ -187,7 +187,7 @@ class CompileInfo
nimplicit_ = StartArgSlot(script) /* scope chain and argument obj */
+ (fun ? 1 : 0); /* this */
nargs_ = fun ? fun->nargs() : 0;
nfixedvars_ = script->nfixedvars();
nbodyfixed_ = script->nbodyfixed();
nlocals_ = script->nfixed();
nstack_ = script->nslots() - script->nfixed();
nslots_ = nimplicit_ + nargs_ + nlocals_ + nstack_;
@ -200,7 +200,7 @@ class CompileInfo
{
nimplicit_ = 0;
nargs_ = 0;
nfixedvars_ = 0;
nbodyfixed_ = 0;
nlocals_ = nlocals;
nstack_ = 1; /* For FunctionCompiler::pushPhiInput/popPhiOutput */
nslots_ = nlocals_ + nstack_;
@ -291,10 +291,10 @@ class CompileInfo
unsigned nargs() const {
return nargs_;
}
// Number of slots needed for "fixed vars". Note that this is only non-zero
// for function code.
unsigned nfixedvars() const {
return nfixedvars_;
// Number of slots needed for fixed body-level bindings. Note that this
// is only non-zero for function code.
unsigned nbodyfixed() const {
return nbodyfixed_;
}
// Number of slots needed for all local variables. This includes "fixed
// vars" (see above) and also block-scoped locals.
@ -377,9 +377,9 @@ class CompileInfo
uint32_t local = index - firstLocalSlot();
if (local < nlocals()) {
// First, check if this local is a var.
if (local < nfixedvars())
return script()->varIsAliased(local);
// First, check if this local is body-level.
if (local < nbodyfixed())
return script()->bodyLevelLocalIsAliased(local);
// Otherwise, it might be part of a block scope.
for (; staticScope; staticScope = staticScope->enclosingNestedScope()) {
@ -466,7 +466,7 @@ class CompileInfo
private:
unsigned nimplicit_;
unsigned nargs_;
unsigned nfixedvars_;
unsigned nbodyfixed_;
unsigned nlocals_;
unsigned nstack_;
unsigned nslots_;

View File

@ -4207,7 +4207,8 @@ BindNameIC::update(JSContext *cx, size_t cacheIndex, HandleObject scopeChain)
if (scopeChain->is<GlobalObject>()) {
holder = scopeChain;
} else {
if (!LookupNameUnqualified(cx, name, scopeChain, &holder))
RootedShape shape(cx);
if (!LookupNameUnqualified(cx, name, scopeChain, &holder, &shape))
return nullptr;
}

View File

@ -109,6 +109,7 @@ MSG_DEF(JSMSG_BAD_PROTOTYPE, 1, JSEXN_TYPEERR, "'prototype' property o
MSG_DEF(JSMSG_IN_NOT_OBJECT, 1, JSEXN_TYPEERR, "invalid 'in' operand {0}")
MSG_DEF(JSMSG_TOO_MANY_CON_SPREADARGS, 0, JSEXN_RANGEERR, "too many constructor arguments")
MSG_DEF(JSMSG_TOO_MANY_FUN_SPREADARGS, 0, JSEXN_RANGEERR, "too many function arguments")
MSG_DEF(JSMSG_UNINITIALIZED_LEXICAL, 1, JSEXN_REFERENCEERR, "can't access let declaration `{0}' before initialization")
// Date
MSG_DEF(JSMSG_INVALID_DATE, 0, JSEXN_RANGEERR, "invalid date")

View File

@ -139,8 +139,11 @@ GetValueType(const Value &val)
inline Type
GetMaybeOptimizedOutValueType(const Value &val)
{
if (val.isMagic() && val.whyMagic() == JS_OPTIMIZED_OUT)
if (val.isMagic() && (val.whyMagic() == JS_OPTIMIZED_OUT ||
val.whyMagic() == JS_UNINITIALIZED_LEXICAL))
{
return Type::UnknownType();
}
return GetValueType(val);
}

View File

@ -4765,18 +4765,17 @@ js::LookupNameNoGC(JSContext *cx, PropertyName *name, JSObject *scopeChain,
bool
js::LookupNameWithGlobalDefault(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
MutableHandleObject objp)
MutableHandleObject objp, MutableHandleShape propp)
{
RootedId id(cx, NameToId(name));
RootedObject pobj(cx);
RootedShape prop(cx);
RootedObject scope(cx, scopeChain);
for (; !scope->is<GlobalObject>(); scope = scope->enclosingScope()) {
if (!JSObject::lookupGeneric(cx, scope, id, &pobj, &prop))
if (!JSObject::lookupGeneric(cx, scope, id, &pobj, propp))
return false;
if (prop)
if (propp)
break;
}
@ -4786,21 +4785,29 @@ js::LookupNameWithGlobalDefault(JSContext *cx, HandlePropertyName name, HandleOb
bool
js::LookupNameUnqualified(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
MutableHandleObject objp)
MutableHandleObject objp, MutableHandleShape propp)
{
RootedId id(cx, NameToId(name));
RootedObject pobj(cx);
RootedShape prop(cx);
RootedObject scope(cx, scopeChain);
for (; !scope->isUnqualifiedVarObj(); scope = scope->enclosingScope()) {
if (!JSObject::lookupGeneric(cx, scope, id, &pobj, &prop))
if (!JSObject::lookupGeneric(cx, scope, id, &pobj, propp))
return false;
if (prop)
if (propp)
break;
}
// If the name was found not on the scope object itself, null out the
// shape, which is passed as an out pointer to determine uninitialized
// lexical slots. In the case when the name is not found on the scope
// object itself, it cannot be an uninitialized lexical slot.
//
// See the JSOP_BINDNAME case in the Interpreter.
if (pobj != scope)
propp.set(nullptr);
objp.set(scope);
return true;
}
@ -4890,8 +4897,8 @@ NativeGetInline(JSContext *cx,
if (shape->hasSlot()) {
vp.set(pobj->nativeGetSlot(shape->slot()));
JS_ASSERT(!vp.isMagic());
JS_ASSERT_IF(!pobj->hasSingletonType() &&
JS_ASSERT_IF(!vp.isMagic(JS_UNINITIALIZED_LEXICAL) &&
!pobj->hasSingletonType() &&
!pobj->template is<ScopeObject>() &&
shape->hasDefaultGetter(),
js::types::TypeHasProperty(cx, pobj->type(), shape->propid(), vp));

View File

@ -1468,18 +1468,17 @@ LookupNameNoGC(JSContext *cx, PropertyName *name, JSObject *scopeChain,
*/
extern bool
LookupNameWithGlobalDefault(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
MutableHandleObject objp);
MutableHandleObject objp, MutableHandleShape propp);
/*
* Like LookupName except returns the unqualified var object if 'name' is not found in
* any preceding scope. Normally the unqualified var object is the global.
*
* Additionally, pobjp and propp are not needed by callers so they are not
* returned.
* Additionally, pobjp is not needed by callers so it is not returned.
*/
extern bool
LookupNameUnqualified(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
MutableHandleObject objp);
MutableHandleObject objp, MutableHandleShape propp);
}

View File

@ -1635,7 +1635,7 @@ JSAtom *
ExpressionDecompiler::getLocal(uint32_t local, jsbytecode *pc)
{
JS_ASSERT(local < script->nfixed());
if (local < script->nfixedvars()) {
if (local < script->nbodyfixed()) {
JS_ASSERT(fun);
uint32_t slot = local + fun->nargs();
JS_ASSERT(slot < script->bindings.count());

View File

@ -76,8 +76,9 @@ Bindings::argumentsVarIndex(ExclusiveContext *cx, InternalBindingsHandle binding
bool
Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self,
unsigned numArgs, uint32_t numVars,
Binding *bindingArray, uint32_t numBlockScoped)
uint32_t numArgs, uint32_t numVars,
uint32_t numBodyLevelLexicals, uint32_t numBlockScoped,
Binding *bindingArray)
{
JS_ASSERT(!self->callObjShape_);
JS_ASSERT(self->bindingArrayAndFlag_ == TEMPORARY_STORAGE_BIT);
@ -85,12 +86,17 @@ Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle
JS_ASSERT(numArgs <= ARGC_LIMIT);
JS_ASSERT(numVars <= LOCALNO_LIMIT);
JS_ASSERT(numBlockScoped <= LOCALNO_LIMIT);
JS_ASSERT(numVars <= LOCALNO_LIMIT - numBlockScoped);
JS_ASSERT(UINT32_MAX - numArgs >= numVars + numBlockScoped);
JS_ASSERT(numBodyLevelLexicals <= LOCALNO_LIMIT);
uint64_t totalSlots = uint64_t(numVars) +
uint64_t(numBodyLevelLexicals) +
uint64_t(numBlockScoped);
JS_ASSERT(totalSlots <= LOCALNO_LIMIT);
JS_ASSERT(UINT32_MAX - numArgs >= totalSlots);
self->bindingArrayAndFlag_ = uintptr_t(bindingArray) | TEMPORARY_STORAGE_BIT;
self->numArgs_ = numArgs;
self->numVars_ = numVars;
self->numBodyLevelLexicals_ = numBodyLevelLexicals;
self->numBlockScoped_ = numBlockScoped;
// Get the initial shape to use when creating CallObjects for this script.
@ -108,10 +114,25 @@ Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle
// any time, such accesses are mediated by DebugScopeProxy (see
// DebugScopeProxy::handleUnaliasedAccess).
uint32_t nslots = CallObject::RESERVED_SLOTS;
uint32_t aliasedBodyLevelLexicalBegin = UINT16_MAX;
for (BindingIter bi(self); bi; bi++) {
if (bi->aliased())
if (bi->aliased()) {
// Per ES6, lexical bindings cannot be accessed until
// initialized. Remember the first aliased slot that is a
// body-level let, so that they may be initialized to sentinel
// magic values.
if (numBodyLevelLexicals > 0 &&
nslots < aliasedBodyLevelLexicalBegin &&
bi->kind() == Binding::VARIABLE &&
bi.frameIndex() >= numVars)
{
aliasedBodyLevelLexicalBegin = nslots;
}
nslots++;
}
}
self->aliasedBodyLevelLexicalBegin_ = aliasedBodyLevelLexicalBegin;
// Put as many of nslots inline into the object header as possible.
uint32_t nfixed = gc::GetGCKindSlots(gc::GetGCObjectKind(nslots));
@ -194,9 +215,13 @@ Bindings::clone(JSContext *cx, InternalBindingsHandle self,
* 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(),
src.numBlockScoped()))
if (!initWithTemporaryStorage(cx, self, src.numArgs(), src.numVars(),
src.numBodyLevelLexicals(), src.numBlockScoped(),
src.bindingArray()))
{
return false;
}
self->switchToScriptStorage(dstPackedBindings);
return true;
}
@ -209,8 +234,8 @@ GCMethods<Bindings>::initial()
template<XDRMode mode>
static bool
XDRScriptBindings(XDRState<mode> *xdr, LifoAllocScope &las, unsigned numArgs, uint32_t numVars,
HandleScript script, unsigned numBlockScoped)
XDRScriptBindings(XDRState<mode> *xdr, LifoAllocScope &las, uint16_t numArgs, uint32_t numVars,
uint16_t numBodyLevelLexicals, uint16_t numBlockScoped, HandleScript script)
{
JSContext *cx = xdr->cx();
@ -227,7 +252,7 @@ XDRScriptBindings(XDRState<mode> *xdr, LifoAllocScope &las, unsigned numArgs, ui
return false;
}
} else {
uint32_t nameCount = numArgs + numVars;
uint32_t nameCount = numArgs + numVars + numBodyLevelLexicals;
AutoValueVector atoms(cx);
if (!atoms.resize(nameCount))
@ -255,9 +280,12 @@ XDRScriptBindings(XDRState<mode> *xdr, LifoAllocScope &las, unsigned numArgs, ui
}
InternalBindingsHandle bindings(script, &script->bindings);
if (!Bindings::initWithTemporaryStorage(cx, bindings, numArgs, numVars, bindingArray,
numBlockScoped))
if (!Bindings::initWithTemporaryStorage(cx, bindings, numArgs, numVars,
numBodyLevelLexicals, numBlockScoped,
bindingArray))
{
return false;
}
}
return true;
@ -431,17 +459,25 @@ XDRLazyFreeVariables(XDRState<mode> *xdr, MutableHandle<LazyScript *> lazy)
{
JSContext *cx = xdr->cx();
RootedAtom atom(cx);
HeapPtrAtom *freeVariables = lazy->freeVariables();
uint8_t isHoistedUse;
LazyScript::FreeVariable *freeVariables = lazy->freeVariables();
size_t numFreeVariables = lazy->numFreeVariables();
for (size_t i = 0; i < numFreeVariables; i++) {
if (mode == XDR_ENCODE)
atom = freeVariables[i];
if (mode == XDR_ENCODE) {
atom = freeVariables[i].atom();
isHoistedUse = freeVariables[i].isHoistedUse();
}
if (!XDRAtom(xdr, &atom))
return false;
if (!xdr->codeUint8(&isHoistedUse))
return false;
if (mode == XDR_DECODE)
freeVariables[i] = atom;
if (mode == XDR_DECODE) {
freeVariables[i] = LazyScript::FreeVariable(atom);
if (isHoistedUse)
freeVariables[i].setIsHoistedUse();
}
}
return true;
@ -562,6 +598,7 @@ js::XDRScript(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enc
/* XDR arguments and vars. */
uint16_t nargs = 0;
uint16_t nblocklocals = 0;
uint16_t nbodylevellexicals = 0;
uint32_t nvars = 0;
if (mode == XDR_ENCODE) {
script = scriptp.get();
@ -569,12 +606,15 @@ js::XDRScript(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enc
nargs = script->bindings.numArgs();
nblocklocals = script->bindings.numBlockScoped();
nbodylevellexicals = script->bindings.numBodyLevelLexicals();
nvars = script->bindings.numVars();
}
if (!xdr->codeUint16(&nargs))
return false;
if (!xdr->codeUint16(&nblocklocals))
return false;
if (!xdr->codeUint16(&nbodylevellexicals))
return false;
if (!xdr->codeUint32(&nvars))
return false;
@ -721,7 +761,7 @@ js::XDRScript(XDRState<mode> *xdr, HandleObject enclosingScope, HandleScript enc
/* JSScript::partiallyInit assumes script->bindings is fully initialized. */
LifoAllocScope las(&cx->tempLifoAlloc());
if (!XDRScriptBindings(xdr, las, nargs, nvars, script, nblocklocals))
if (!XDRScriptBindings(xdr, las, nargs, nvars, nbodylevellexicals, nblocklocals, script))
return false;
if (mode == XDR_DECODE) {
@ -3369,9 +3409,12 @@ LazyScript::markChildren(JSTracer *trc)
if (script_)
MarkScript(trc, &script_, "realScript");
HeapPtrAtom *freeVariables = this->freeVariables();
for (size_t i = 0; i < numFreeVariables(); i++)
MarkString(trc, &freeVariables[i], "lazyScriptFreeVariable");
// We rely on the fact that atoms are always tenured.
FreeVariable *freeVariables = this->freeVariables();
for (size_t i = 0; i < numFreeVariables(); i++) {
JSAtom *atom = freeVariables[i].atom();
MarkStringUnbarriered(trc, &atom, "lazyScriptFreeVariable");
}
HeapPtrFunction *innerFunctions = this->innerFunctions();
for (size_t i = 0; i < numInnerFunctions(); i++)
@ -3564,7 +3607,13 @@ JSScript::argumentsOptimizationFailed(JSContext *cx, HandleScript script)
bool
JSScript::varIsAliased(uint32_t varSlot)
{
return bindings.bindingIsAliased(bindings.numArgs() + varSlot);
return bodyLevelLocalIsAliased(varSlot);
}
bool
JSScript::bodyLevelLocalIsAliased(uint32_t localSlot)
{
return bindings.bindingIsAliased(bindings.numArgs() + localSlot);
}
bool
@ -3641,7 +3690,7 @@ LazyScript::CreateRaw(ExclusiveContext *cx, HandleFunction fun,
p.hasBeenCloned = false;
p.treatAsRunOnce = false;
size_t bytes = (p.numFreeVariables * sizeof(HeapPtrAtom))
size_t bytes = (p.numFreeVariables * sizeof(FreeVariable))
+ (p.numInnerFunctions * sizeof(HeapPtrFunction));
ScopedJSFreePtr<uint8_t> table(bytes ? fun->pod_malloc<uint8_t>(bytes) : nullptr);
@ -3701,9 +3750,9 @@ LazyScript::Create(ExclusiveContext *cx, HandleFunction fun,
// Fill with dummies, to be GC-safe after the initialization of the free
// variables and inner functions.
size_t i, num;
HeapPtrAtom *variables = res->freeVariables();
FreeVariable *variables = res->freeVariables();
for (i = 0, num = res->numFreeVariables(); i < num; i++)
variables[i].init(dummyAtom);
variables[i] = FreeVariable(dummyAtom);
HeapPtrFunction *functions = res->innerFunctions();
for (i = 0, num = res->numInnerFunctions(); i < num; i++)

View File

@ -137,7 +137,8 @@ class Binding
static const uintptr_t NAME_MASK = ~(KIND_MASK | ALIASED_BIT);
public:
// A "binding" is a formal, 'var', or 'const' declaration. A function's
// A "binding" is a formal parameter, 'var' (also a stand in for
// body-level 'let' declarations), or 'const' declaration. A function's
// lexical scope is composed of these three kinds of bindings.
enum Kind { ARGUMENT, VARIABLE, CONSTANT };
@ -183,8 +184,16 @@ class Bindings
uintptr_t bindingArrayAndFlag_;
uint16_t numArgs_;
uint16_t numBlockScoped_;
uint16_t numBodyLevelLexicals_;
uint16_t aliasedBodyLevelLexicalBegin_;
uint32_t numVars_;
#if JS_BITS_PER_WORD == 32
// Bindings is allocated inline inside JSScript, which needs to be
// gc::Cell aligned.
uint32_t padding_;
#endif
/*
* During parsing, bindings are allocated out of a temporary LifoAlloc.
* After parsing, a JSScript object is created and the bindings are
@ -209,13 +218,15 @@ class Bindings
/*
* Initialize a Bindings with a pointer into temporary storage.
* bindingArray must have length numArgs+numVars. Before the temporary
* storage is release, switchToScriptStorage must be called, providing a
* pointer into the Binding array stored in script->data.
* bindingArray must have length numArgs + numVars +
* numBodyLevelLexicals. Before the temporary storage is release,
* switchToScriptStorage must be called, providing a pointer into the
* Binding array stored in script->data.
*/
static bool initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle self,
unsigned numArgs, uint32_t numVars,
Binding *bindingArray, unsigned numBlockScoped);
uint32_t numArgs, uint32_t numVars,
uint32_t numBodyLevelLexicals, uint32_t numBlockScoped,
Binding *bindingArray);
// CompileScript parses and compiles one statement at a time, but the result
// is one Script object. There will be no vars or bindings, because those
@ -240,13 +251,17 @@ class Bindings
static bool clone(JSContext *cx, InternalBindingsHandle self, uint8_t *dstScriptData,
HandleScript srcScript);
unsigned numArgs() const { return numArgs_; }
uint32_t numArgs() const { return numArgs_; }
uint32_t numVars() const { return numVars_; }
unsigned numBlockScoped() const { return numBlockScoped_; }
uint32_t numLocals() const { return numVars() + numBlockScoped(); }
uint32_t numBodyLevelLexicals() const { return numBodyLevelLexicals_; }
uint32_t numBlockScoped() const { return numBlockScoped_; }
uint32_t numBodyLevelLocals() const { return numVars_ + numBodyLevelLexicals_; }
uint32_t numLocals() const { return numVars() + numBodyLevelLexicals() + numBlockScoped(); }
uint32_t lexicalBegin() const { return numArgs() + numVars(); }
uint32_t aliasedBodyLevelLexicalBegin() const { return aliasedBodyLevelLexicalBegin_; }
// Return the size of the bindingArray.
uint32_t count() const { return numArgs() + numVars(); }
uint32_t count() const { return numArgs() + numVars() + numBodyLevelLexicals(); }
/* Return the initial shape of call objects created for this scope. */
Shape *callObjShape() const { return callObjShape_; }
@ -1033,11 +1048,28 @@ class JSScript : public js::gc::BarrieredCell<JSScript>
return function_ ? bindings.numLocals() : bindings.numBlockScoped();
}
// Number of fixed slots reserved for vars. Only nonzero for function code.
// Number of fixed slots reserved for vars. Only nonzero for function
// code.
size_t nfixedvars() const {
return function_ ? bindings.numVars() : 0;
}
// Number of fixed slots reserved for body-level lexicals and vars. This
// value minus nfixedvars() is the number of body-level lexicals. Only
// nonzero for function code.
size_t nbodyfixed() const {
return function_ ? bindings.numBodyLevelLocals() : 0;
}
// Aliases for clarity when dealing with lexical slots.
size_t fixedLexicalBegin() const {
return nfixedvars();
}
size_t fixedLexicalEnd() const {
return nfixed();
}
size_t nslots() const {
return nslots_;
}
@ -1563,6 +1595,7 @@ class JSScript : public js::gc::BarrieredCell<JSScript>
}
bool varIsAliased(uint32_t varSlot);
bool bodyLevelLocalIsAliased(uint32_t localSlot);
bool formalIsAliased(unsigned argSlot);
bool formalLivesInArgumentsObject(unsigned argSlot);
@ -1691,6 +1724,35 @@ class AliasedFormalIter
// bytecode from its source.
class LazyScript : public gc::BarrieredCell<LazyScript>
{
public:
class FreeVariable
{
// Free variable names are possible tagged JSAtom *s.
uintptr_t bits_;
static const uintptr_t HOISTED_USE_BIT = 0x1;
static const uintptr_t MASK = ~HOISTED_USE_BIT;
public:
explicit FreeVariable()
: bits_(0)
{ }
explicit FreeVariable(JSAtom *name)
: bits_(uintptr_t(name))
{
// We rely on not requiring any write barriers so we can tag the
// pointer. This code needs to change if we start allocating
// JSAtoms inside the nursery.
MOZ_ASSERT(!IsInsideNursery(name));
}
JSAtom *atom() const { return (JSAtom *)(bits_ & MASK); }
void setIsHoistedUse() { bits_ |= HOISTED_USE_BIT; }
bool isHoistedUse() const { return bool(bits_ & HOISTED_USE_BIT); }
};
private:
// If non-nullptr, the script has been compiled and this is a forwarding
// pointer to the result.
HeapPtrScript script_;
@ -1802,8 +1864,8 @@ class LazyScript : public gc::BarrieredCell<LazyScript>
uint32_t numFreeVariables() const {
return p_.numFreeVariables;
}
HeapPtrAtom *freeVariables() {
return (HeapPtrAtom *)table_;
FreeVariable *freeVariables() {
return (FreeVariable *)table_;
}
uint32_t numInnerFunctions() const {

View File

@ -85,6 +85,7 @@ function f6() {
try
{
/*
var rv = f1(5);
if (!isNaN(rv))
throw "f1(5):\n" +
@ -96,7 +97,7 @@ try
throw "f2(5):\n" +
" expected: NaN\n" +
" actual: " + rv;
/*
rv = f3(8);
if (rv != 9)
throw "f3(8):\n" +

View File

@ -1,56 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */
//-----------------------------------------------------------------------------
var BUGNUMBER = 411279;
var summary = 'let declaration as direct child of switch body block';
var actual = 'No Crash';
var expect = 'No Crash';
//-----------------------------------------------------------------------------
test();
//-----------------------------------------------------------------------------
function test()
{
enterFunc ('test');
printBugNumber(BUGNUMBER);
printStatus (summary);
function f(x)
{
var value = '';
switch(x)
{
case 1:
value = "1 " + y;
break;
case 2:
let y = 42;
value = "2 " + y;
break;
default:
value = "default " + y;
}
return value;
}
expect = '1 undefined';
actual = f(1);
reportCompare(expect, actual, summary + ': f(1)');
expect = '2 42';
actual = f(2);
reportCompare(expect, actual, summary + ': f(2)');
expect = 'default undefined';
actual = f(3);
reportCompare(expect, actual, summary + ': f(3)');
exitFunc ('test');
}

View File

@ -20,8 +20,8 @@ function test()
printBugNumber(BUGNUMBER);
printStatus (summary);
var f = function ([x]) { let x; }
expect = 'function ([x]) { let x; }';
var f = function ([x]) { let y; }
expect = 'function ([x]) { let y; }';
actual = f + '';
compareSource(expect, actual, summary);

View File

@ -1,31 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */
//-----------------------------------------------------------------------------
var BUGNUMBER = 452498;
var summary = 'TM: upvar2 regression tests';
var actual = '';
var expect = '';
//-----------------------------------------------------------------------------
test();
//-----------------------------------------------------------------------------
function test()
{
enterFunc ('test');
printBugNumber(BUGNUMBER);
printStatus (summary);
// ------- Comment #103 From Jesse Ruderman
(function(a) { v = 5; let [v] = [3]; (function(){ v; })(); })();
// Assertion failure: !(pn->pn_dflags & flag), at ../jsparse.h:638
reportCompare(expect, actual, summary);
exitFunc ('test');
}

View File

@ -186,6 +186,7 @@
macro(true, true_, "true") \
macro(unescape, unescape, "unescape") \
macro(uneval, uneval, "uneval") \
macro(uninitialized, uninitialized, "uninitialized") \
macro(uint8, uint8, "uint8") \
macro(uint8Clamped, uint8Clamped, "uint8Clamped") \
macro(uint16, uint16, "uint16") \

View File

@ -725,15 +725,17 @@ Debugger::wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp)
if (!optObj)
return false;
// We handle two sentinel values: missing arguments (overloading
// JS_OPTIMIZED_ARGUMENTS) and optimized out slots (JS_OPTIMIZED_OUT).
// We handle three sentinel values: missing arguments (overloading
// JS_OPTIMIZED_ARGUMENTS), optimized out slots (JS_OPTIMIZED_OUT),
// and uninitialized bindings (JS_UNINITIALIZED_LEXICAL).
//
// Other magic values should not have escaped.
PropertyName *name;
if (vp.whyMagic() == JS_OPTIMIZED_ARGUMENTS) {
name = cx->names().missingArguments;
} else {
MOZ_ASSERT(vp.whyMagic() == JS_OPTIMIZED_OUT);
name = cx->names().optimizedOut;
switch (vp.whyMagic()) {
case JS_OPTIMIZED_ARGUMENTS: name = cx->names().missingArguments; break;
case JS_OPTIMIZED_OUT: name = cx->names().optimizedOut; break;
case JS_UNINITIALIZED_LEXICAL: name = cx->names().uninitialized; break;
default: MOZ_CRASH("Unsupported magic value escaped to Debugger");
}
RootedValue trueVal(cx, BooleanValue(true));

View File

@ -498,6 +498,10 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
* If *vp is a magic JS_OPTIMIZED_ARGUMENTS value signifying missing
* arguments, this produces a plain object of the form { missingArguments:
* true }.
*
* If *vp is a magic JS_UNINITIALIZED_LEXICAL value signifying an
* unaccessible uninitialized binding, this produces a plain object of the
* form { uninitialized: true }.
*/
bool wrapDebuggeeValue(JSContext *cx, MutableHandleValue vp);

View File

@ -22,6 +22,7 @@
#include "jsinferinlines.h"
#include "jsobjinlines.h"
#include "vm/ScopeObject-inl.h"
#include "vm/Stack-inl.h"
#include "vm/String-inl.h"
@ -106,6 +107,62 @@ GuardFunApplyArgumentsOptimization(JSContext *cx, AbstractFramePtr frame, Handle
return true;
}
/*
* Per ES6, lexical declarations may not be accessed in any fashion until they
* are initialized (i.e., until the actual declaring statement is
* executed). The various LEXICAL opcodes need to check if the slot is an
* uninitialized let declaration, represented by the magic value
* JS_UNINITIALIZED_LEXICAL.
*/
static inline bool
IsUninitializedLexical(const Value &val)
{
// Use whyMagic here because JS_OPTIMIZED_ARGUMENTS could flow into here.
return val.isMagic() && val.whyMagic() == JS_UNINITIALIZED_LEXICAL;
}
static inline bool
IsUninitializedLexicalSlot(HandleObject obj, HandleShape shape)
{
if (obj->is<DynamicWithObject>())
return false;
// We check for IsImplicitDenseOrTypedArrayElement even though the shape
// is always a non-indexed property because proxy hooks may return a
// "non-native property found" shape, which happens to be encoded in the
// same way as the "dense element" shape. See MarkNonNativePropertyFound.
if (!shape ||
IsImplicitDenseOrTypedArrayElement(shape) ||
!shape->hasSlot() ||
!shape->hasDefaultGetter() ||
!shape->hasDefaultSetter())
{
return false;
}
MOZ_ASSERT(obj->nativeContainsPure(shape));
return IsUninitializedLexical(obj->nativeGetSlot(shape->slot()));
}
static inline bool
CheckUninitializedLexical(JSContext *cx, PropertyName *name_, HandleValue val)
{
if (IsUninitializedLexical(val)) {
RootedPropertyName name(cx, name_);
ReportUninitializedLexical(cx, name);
return false;
}
return true;
}
static inline bool
CheckUninitializedLexical(JSContext *cx, HandleScript script, jsbytecode *pc, HandleValue val)
{
if (IsUninitializedLexical(val)) {
ReportUninitializedLexical(cx, script, pc);
return false;
}
return true;
}
/*
* Return an object on which we should look for the properties of |value|.
* This helps us implement the custom [[Get]] method that ES5's GetValue
@ -225,6 +282,22 @@ SetIntrinsicOperation(JSContext *cx, JSScript *script, jsbytecode *pc, HandleVal
return cx->global()->setIntrinsicValue(cx, name, val);
}
inline void
SetAliasedVarOperation(JSContext *cx, JSScript *script, jsbytecode *pc,
ScopeObject &obj, ScopeCoordinate sc, const Value &val,
MaybeCheckLexical checkLexical)
{
MOZ_ASSERT_IF(checkLexical, !IsUninitializedLexical(obj.aliasedVar(sc)));
// Avoid computing the name if no type updates are needed, as this may be
// expensive on scopes with large numbers of variables.
PropertyName *name = obj.hasSingletonType()
? ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc)
: nullptr;
obj.setAliasedVar(cx, sc, name, val);
}
inline bool
SetNameOperation(JSContext *cx, JSScript *script, jsbytecode *pc, HandleObject scope,
HandleValue val)

View File

@ -286,7 +286,7 @@ NameOperation(JSContext *cx, InterpreterFrame *fp, jsbytecode *pc, MutableHandle
JSObject *scope = nullptr, *pobj = nullptr;
if (LookupNameNoGC(cx, name, obj, &scope, &pobj, &shape)) {
if (FetchNameNoGC(pobj, shape, vp))
return true;
return CheckUninitializedLexical(cx, name, vp);
}
RootedObject objRoot(cx, obj), scopeRoot(cx), pobjRoot(cx);
@ -298,9 +298,17 @@ NameOperation(JSContext *cx, InterpreterFrame *fp, jsbytecode *pc, MutableHandle
/* Kludge to allow (typeof foo == "undefined") tests. */
JSOp op2 = JSOp(pc[JSOP_NAME_LENGTH]);
if (op2 == JSOP_TYPEOF)
return FetchName<true>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
return FetchName<false>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
if (op2 == JSOP_TYPEOF) {
if (!FetchName<true>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp))
return false;
} else {
if (!FetchName<false>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp))
return false;
}
// NAME operations are the slow paths already, so unconditionally check
// for uninitialized lets.
return CheckUninitializedLexical(cx, nameRoot, vp);
}
static inline bool
@ -1014,7 +1022,24 @@ HandleError(JSContext *cx, InterpreterRegs &regs)
for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) {
JSTryNote *tn = *tni;
UnwindScope(cx, si, regs.fp()->script()->main() + tn->start);
// Unwind the scope to the beginning of the JSOP_TRY. We cannot
// unwind to *after* the JSOP_TRY, because that might be the first
// opcode of an inner scope. Consider the following:
//
// try {
// { let x; }
// }
//
// This would generate
//
// 0000: try
// 0001: undefined
// 0002: initlet 0
//
// If we unwound to 0001, we would be unwinding to the inner
// scope, and not the scope of the try { }.
UnwindScope(cx, si, (regs.fp()->script()->main() + tn->start -
js_CodeSpec[JSOP_TRY].length));
/*
* Set pc to the first bytecode after the the try note to point
@ -1095,6 +1120,7 @@ HandleError(JSContext *cx, InterpreterRegs &regs)
#define PUSH_OBJECT(obj) do { REGS.sp++->setObject(obj); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
#define PUSH_OBJECT_OR_NULL(obj) do { REGS.sp++->setObjectOrNull(obj); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
#define PUSH_HOLE() REGS.sp++->setMagic(JS_ELEMENTS_HOLE)
#define PUSH_UNINITIALIZED() REGS.sp++->setMagic(JS_UNINITIALIZED_LEXICAL)
#define POP_COPY_TO(v) (v) = *--REGS.sp
#define POP_RETURN_VALUE() REGS.fp()->setReturnValue(*--REGS.sp)
@ -1585,11 +1611,6 @@ CASE(JSOP_UNUSED107)
CASE(JSOP_UNUSED124)
CASE(JSOP_UNUSED125)
CASE(JSOP_UNUSED126)
CASE(JSOP_UNUSED138)
CASE(JSOP_UNUSED139)
CASE(JSOP_UNUSED140)
CASE(JSOP_UNUSED141)
CASE(JSOP_UNUSED142)
CASE(JSOP_UNUSED146)
CASE(JSOP_UNUSED147)
CASE(JSOP_UNUSED148)
@ -1995,10 +2016,19 @@ CASE(JSOP_BINDNAME)
/* Assigning to an undeclared name adds a property to the global object. */
RootedObject &scope = rootObject1;
if (!LookupNameUnqualified(cx, name, scopeChain, &scope))
RootedShape &shape = rootShape0;
if (!LookupNameUnqualified(cx, name, scopeChain, &scope, &shape))
goto error;
PUSH_OBJECT(*scope);
// ES6 lets cannot be accessed until initialized. NAME operations, being
// the slow paths already, unconditionally check for uninitialized
// lets. The error, however, is thrown after evaluating the RHS in
// assignments. Thus if the LHS resolves to an uninitialized let, return a
// nullptr scope.
if (IsUninitializedLexicalSlot(scope, shape))
PUSH_NULL();
else
PUSH_OBJECT(*scope);
}
END_CASE(JSOP_BINDNAME)
@ -2384,7 +2414,16 @@ CASE(JSOP_SETGNAME)
CASE(JSOP_SETNAME)
{
RootedObject &scope = rootObject0;
scope = &REGS.sp[-2].toObject();
scope = REGS.sp[-2].toObjectOrNull();
// A nullptr scope is pushed if the name is an uninitialized let. See
// CASE(JSOP_BINDNAME).
if (!scope) {
RootedPropertyName &name = rootName0;
name = script->getName(REGS.pc);
ReportUninitializedLexical(cx, name);
goto error;
}
HandleValue value = REGS.stackHandleAt(-1);
@ -2622,7 +2661,8 @@ CASE(JSOP_IMPLICITTHIS)
scopeObj = REGS.fp()->scopeChain();
RootedObject &scope = rootObject1;
if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &scope))
RootedShape &shape = rootShape0;
if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &scope, &shape))
goto error;
RootedValue &v = rootValue0;
@ -2833,7 +2873,10 @@ END_CASE(JSOP_REST)
CASE(JSOP_GETALIASEDVAR)
{
ScopeCoordinate sc = ScopeCoordinate(REGS.pc);
PUSH_COPY(REGS.fp()->aliasedVarScope(sc).aliasedVar(sc));
RootedValue &val = rootValue0;
val = REGS.fp()->aliasedVarScope(sc).aliasedVar(sc);
MOZ_ASSERT(!IsUninitializedLexical(val));
PUSH_COPY(val);
TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]);
}
END_CASE(JSOP_GETALIASEDVAR)
@ -2842,17 +2885,49 @@ CASE(JSOP_SETALIASEDVAR)
{
ScopeCoordinate sc = ScopeCoordinate(REGS.pc);
ScopeObject &obj = REGS.fp()->aliasedVarScope(sc);
// Avoid computing the name if no type updates are needed, as this may be
// expensive on scopes with large numbers of variables.
PropertyName *name = obj.hasSingletonType()
? ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, REGS.pc)
: nullptr;
obj.setAliasedVar(cx, sc, name, REGS.sp[-1]);
SetAliasedVarOperation(cx, script, REGS.pc, obj, sc, REGS.sp[-1], CheckLexical);
}
END_CASE(JSOP_SETALIASEDVAR)
CASE(JSOP_CHECKLEXICAL)
{
uint32_t i = GET_LOCALNO(REGS.pc);
RootedValue &val = rootValue0;
val = REGS.fp()->unaliasedLocal(i);
if (!CheckUninitializedLexical(cx, script, REGS.pc, val))
goto error;
}
END_CASE(JSOP_CHECKLEXICAL)
CASE(JSOP_INITLEXICAL)
{
uint32_t i = GET_LOCALNO(REGS.pc);
REGS.fp()->unaliasedLocal(i) = REGS.sp[-1];
}
END_CASE(JSOP_INITLEXICAL)
CASE(JSOP_CHECKALIASEDLEXICAL)
{
ScopeCoordinate sc = ScopeCoordinate(REGS.pc);
RootedValue &val = rootValue0;
val = REGS.fp()->aliasedVarScope(sc).aliasedVar(sc);
if (!CheckUninitializedLexical(cx, script, REGS.pc, val))
goto error;
}
END_CASE(JSOP_CHECKALIASEDLEXICAL)
CASE(JSOP_INITALIASEDLEXICAL)
{
ScopeCoordinate sc = ScopeCoordinate(REGS.pc);
ScopeObject &obj = REGS.fp()->aliasedVarScope(sc);
SetAliasedVarOperation(cx, script, REGS.pc, obj, sc, REGS.sp[-1], DontCheckLexical);
}
END_CASE(JSOP_INITALIASEDLEXICAL)
CASE(JSOP_UNINITIALIZED)
PUSH_UNINITIALIZED();
END_CASE(JSOP_UNINITIALIZED)
CASE(JSOP_GETARG)
{
unsigned i = GET_ARGNO(REGS.pc);
@ -2877,6 +2952,7 @@ CASE(JSOP_GETLOCAL)
{
uint32_t i = GET_LOCALNO(REGS.pc);
PUSH_COPY_SKIP_CHECK(REGS.fp()->unaliasedLocal(i));
MOZ_ASSERT(!IsUninitializedLexical(REGS.sp[-1]));
/*
* Skip the same-compartment assertion if the local will be immediately
@ -2892,6 +2968,7 @@ END_CASE(JSOP_GETLOCAL)
CASE(JSOP_SETLOCAL)
{
uint32_t i = GET_LOCALNO(REGS.pc);
MOZ_ASSERT(!IsUninitializedLexical(REGS.fp()->unaliasedLocal(i)));
REGS.fp()->unaliasedLocal(i) = REGS.sp[-1];
}
END_CASE(JSOP_SETLOCAL)
@ -3490,7 +3567,11 @@ js::GetScopeName(JSContext *cx, HandleObject scopeChain, HandlePropertyName name
return false;
}
return JSObject::getProperty(cx, obj, obj, name, vp);
if (!JSObject::getProperty(cx, obj, obj, name, vp))
return false;
// See note in NameOperation.
return CheckUninitializedLexical(cx, name, vp);
}
/*
@ -3511,7 +3592,11 @@ js::GetScopeNameForTypeOf(JSContext *cx, HandleObject scopeChain, HandleProperty
return true;
}
return JSObject::getProperty(cx, obj, obj, name, vp);
if (!JSObject::getProperty(cx, obj, obj, name, vp))
return false;
// See note in NameOperation.
return CheckUninitializedLexical(cx, name, vp);
}
JSObject *
@ -3808,7 +3893,8 @@ js::ImplicitThisOperation(JSContext *cx, HandleObject scopeObj, HandlePropertyNa
MutableHandleValue res)
{
RootedObject obj(cx);
if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &obj))
RootedShape shape(cx);
if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &obj, &shape))
return false;
return ComputeImplicitThis(cx, obj, res);
@ -3940,3 +4026,65 @@ js::SpreadCallOperation(JSContext *cx, HandleScript script, jsbytecode *pc, Hand
TypeScript::Monitor(cx, script, pc, res);
return true;
}
void
js::ReportUninitializedLexical(JSContext *cx, HandlePropertyName name)
{
JSAutoByteString printable;
if (AtomToPrintableString(cx, name, &printable)) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNINITIALIZED_LEXICAL,
printable.ptr());
}
}
void
js::ReportUninitializedLexical(JSContext *cx, HandleScript script, jsbytecode *pc)
{
RootedPropertyName name(cx);
if (JSOp(*pc) == JSOP_CHECKLEXICAL) {
uint32_t slot = GET_LOCALNO(pc);
// First search for a name among body-level lets.
for (BindingIter bi(script); bi; bi++) {
if (bi->kind() != Binding::ARGUMENT && bi.frameIndex() == slot) {
name = bi->name();
break;
}
}
// Failing that, it must be a block-local let.
if (!name) {
// Skip to the right scope.
Rooted<NestedScopeObject *> scope(cx, script->getStaticScope(pc));
MOZ_ASSERT(scope && scope->is<StaticBlockObject>());
Rooted<StaticBlockObject *> block(cx, &scope->as<StaticBlockObject>());
while (slot < block->localOffset())
block = &block->enclosingNestedScope()->as<StaticBlockObject>();
// Translate the frame slot to the block slot, then find the name
// of the slot.
uint32_t blockSlot = block->localIndexToSlot(slot);
RootedShape shape(cx, block->lastProperty());
Shape::Range<CanGC> r(cx, shape);
while (r.front().slot() != blockSlot)
r.popFront();
jsid id = r.front().propidRaw();
MOZ_ASSERT(JSID_IS_ATOM(id));
name = JSID_TO_ATOM(id)->asPropertyName();
}
} else {
MOZ_ASSERT(JSOp(*pc) == JSOP_CHECKALIASEDLEXICAL);
name = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc);
}
ReportUninitializedLexical(cx, name);
}
void
js::ReportUninitializedLexical(JSContext *cx, HandleScript script, jsbytecode *pc, ScopeCoordinate sc)
{
RootedPropertyName name(cx, ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache,
script, pc));
ReportUninitializedLexical(cx, name);
}

View File

@ -470,6 +470,15 @@ SetConstOperation(JSContext *cx, HandleObject varobj, HandlePropertyName name, H
JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
}
void
ReportUninitializedLexical(JSContext *cx, HandlePropertyName name);
void
ReportUninitializedLexical(JSContext *cx, HandleScript script, jsbytecode *pc);
void
ReportUninitializedLexical(JSContext *cx, HandleScript script, jsbytecode *pc, ScopeCoordinate sc);
} /* namespace js */
#endif /* vm_Interpreter_h */

View File

@ -1260,14 +1260,54 @@
*/ \
macro(JSOP_SETALIASEDVAR, 137,"setaliasedvar",NULL, 5, 1, 1, JOF_SCOPECOORD|JOF_NAME|JOF_SET|JOF_DETECTING) \
\
macro(JSOP_UNUSED138, 138, "unused138", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED139, 139, "unused139", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED140, 140, "unused140", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED141, 141, "unused141", NULL, 1, 0, 0, JOF_BYTE) \
macro(JSOP_UNUSED142, 142, "unused142", NULL, 1, 0, 0, JOF_BYTE) \
\
/*
* Pushes the value of the intrinsic onto the stack.
* Checks if the value of the local variable is the
* JS_UNINITIALIZED_LEXICAL magic, throwing an error if so.
* Category: Variables and Scopes
* Type: Local Variables
* Operands: uint32_t localno
* Stack: =>
*/ \
macro(JSOP_CHECKLEXICAL, 138, "checklexical", NULL, 4, 0, 0, JOF_LOCAL|JOF_NAME) \
/*
* Initializes an uninitialized local lexical binding with the top of stack
* value.
* Category: Variables and Scopes
* Type: Local Variables
* Operands: uint32_t localno
* Stack: v => v
*/ \
macro(JSOP_INITLEXICAL, 139, "initlexical", NULL, 4, 1, 1, JOF_LOCAL|JOF_NAME|JOF_SET|JOF_DETECTING) \
/*
* Checks if the value of the aliased variable is the
* JS_UNINITIALIZED_LEXICAL magic, throwing an error if so.
* Category: Variables and Scopes
* Type: Aliased Variables
* Operands: uint8_t hops, uint24_t slot
* Stack: =>
*/ \
macro(JSOP_CHECKALIASEDLEXICAL, 140, "checkaliasedlexical", NULL, 5, 0, 0, JOF_SCOPECOORD|JOF_NAME) \
/*
* Initializes an uninitialized aliased lexical binding with the top of
* stack value.
* Category: Variables and Scopes
* Type: Aliased Variables
* Operands: uint8_t hops, uint24_t slot
* Stack: v => v
*/ \
macro(JSOP_INITALIASEDLEXICAL, 141, "initaliasedlexical", NULL, 5, 1, 1, JOF_SCOPECOORD|JOF_NAME|JOF_SET|JOF_DETECTING) \
/*
* Pushes a JS_UNINITIALIZED_LEXICAL value onto the stack, representing an
* uninitialized lexical binding.
*
* This opcode is used with the JSOP_INITLET opcode.
* Category: Literals
* Type: Constants
* Operands:
* Stack: => uninitialized
*/ \
macro(JSOP_UNINITIALIZED, 142, "uninitialized", NULL, 1, 0, 1, JOF_BYTE) \
/* Pushes the value of the intrinsic onto the stack.
*
* Intrinsic names are emitted instead of JSOP_*NAME ops when the
* 'CompileOptions' flag 'selfHostingMode' is set.

View File

@ -53,6 +53,15 @@ CallObject::setAliasedVarFromArguments(JSContext *cx, const Value &argsValue, js
types::AddTypePropertyId(cx, this, id, v);
}
inline void
CallObject::setAliasedLexicalsToThrowOnTouch(JSScript *script)
{
uint32_t aliasedLexicalBegin = script->bindings.aliasedBodyLevelLexicalBegin();
uint32_t aliasedLexicalEnd = numFixedSlots();
for (uint32_t slot = aliasedLexicalBegin; slot < aliasedLexicalEnd; slot++)
initFixedSlot(slot, MagicValue(JS_UNINITIALIZED_LEXICAL));
}
template <AllowGC allowGC>
inline bool
StaticScopeIter<allowGC>::done() const

View File

@ -217,6 +217,7 @@ CallObject::create(JSContext *cx, HandleScript script, HandleObject enclosing, H
callobj->as<ScopeObject>().setEnclosingScope(enclosing);
callobj->initFixedSlot(CALLEE_SLOT, ObjectOrNullValue(callee));
callobj->setAliasedLexicalsToThrowOnTouch(script);
if (script->treatAsRunOnce()) {
Rooted<CallObject*> ncallobj(cx, callobj);
@ -1157,7 +1158,7 @@ class DebugScopeProxy : public BaseProxyHandler
*accessResult = ACCESS_GENERIC;
ScopeIterVal *maybeLiveScope = DebugScopes::hasLiveScope(*scope);
/* Handle unaliased formals, vars, and consts at function scope. */
/* Handle unaliased formals, vars, lets, and consts at function scope. */
if (scope->is<CallObject>() && !scope->as<CallObject>().isForEval()) {
CallObject &callobj = scope->as<CallObject>();
RootedScript script(cx, callobj.callee().nonLazyScript());
@ -1173,15 +1174,15 @@ class DebugScopeProxy : public BaseProxyHandler
if (bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT) {
uint32_t i = bi.frameIndex();
if (script->varIsAliased(i))
if (script->bodyLevelLocalIsAliased(i))
return true;
if (maybeLiveScope) {
AbstractFramePtr frame = maybeLiveScope->frame();
if (action == GET)
vp.set(frame.unaliasedVar(i));
vp.set(frame.unaliasedLocal(i));
else
frame.unaliasedVar(i) = vp;
frame.unaliasedLocal(i) = vp;
} else if (JSObject *snapshot = debugScope->maybeSnapshot()) {
if (action == GET)
vp.set(snapshot->getDenseElement(bindings.numArgs() + i));

View File

@ -233,6 +233,8 @@ class CallObject : public ScopeObject
static CallObject *
create(JSContext *cx, HandleScript script, HandleObject enclosing, HandleFunction callee);
inline void setAliasedLexicalsToThrowOnTouch(JSScript *script);
public:
static const Class class_;

View File

@ -88,13 +88,24 @@ InterpreterFrame::initCallFrame(JSContext *cx, InterpreterFrame *prev, jsbytecod
prevpc_ = prevpc;
prevsp_ = prevsp;
initVarsToUndefined();
initLocals();
}
inline void
InterpreterFrame::initVarsToUndefined()
InterpreterFrame::initLocals()
{
SetValueRangeToUndefined(slots(), script()->nfixed());
SetValueRangeToUndefined(slots(), script()->nfixedvars());
// Lexical bindings throw ReferenceErrors if they are used before
// initialization. See ES6 8.1.1.1.6.
//
// For completeness, lexical bindings are initialized in ES6 by calling
// InitializeBinding, after which touching the binding will no longer
// throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6,
// 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15.
Value *lexicalEnd = slots() + script()->fixedLexicalEnd();
for (Value *lexical = slots() + script()->fixedLexicalBegin(); lexical != lexicalEnd; ++lexical)
lexical->setMagic(JS_UNINITIALIZED_LEXICAL);
}
inline Value &

View File

@ -398,7 +398,7 @@ InterpreterFrame::markValues(JSTracer *trc, Value *sp, jsbytecode *pc)
StaticBlockObject &blockObj = staticScope->as<StaticBlockObject>();
nlivefixed = blockObj.localOffset() + blockObj.numVariables();
} else {
nlivefixed = script()->nfixedvars();
nlivefixed = script()->nbodyfixed();
}
if (nfixed == nlivefixed) {
@ -408,9 +408,9 @@ InterpreterFrame::markValues(JSTracer *trc, Value *sp, jsbytecode *pc)
// Mark operand stack.
markValues(trc, nfixed, sp - slots());
// Clear dead locals.
// Clear dead block-scoped locals.
while (nfixed > nlivefixed)
unaliasedLocal(--nfixed, DONT_CHECK_ALIASING).setUndefined();
unaliasedLocal(--nfixed, DONT_CHECK_ALIASING).setMagic(JS_UNINITIALIZED_LEXICAL);
// Mark live locals.
markValues(trc, 0, nlivefixed);
@ -495,7 +495,7 @@ InterpreterStack::pushExecuteFrame(JSContext *cx, HandleScript script, const Val
InterpreterFrame *fp = reinterpret_cast<InterpreterFrame *>(buffer + 2 * sizeof(Value));
fp->mark_ = mark;
fp->initExecuteFrame(cx, script, evalInFrame, thisv, *scopeChain, type);
fp->initVarsToUndefined();
fp->initLocals();
return fp;
}

View File

@ -66,6 +66,7 @@ class ScopeCoordinate;
// InterpreterActivation) is a local var of js::Interpret.
enum MaybeCheckAliasing { CHECK_ALIASING = true, DONT_CHECK_ALIASING = false };
enum MaybeCheckLexical { CheckLexical = true, DontCheckLexical = false };
/*****************************************************************************/
@ -423,8 +424,12 @@ class InterpreterFrame
bool initFunctionScopeObjects(JSContext *cx);
/* Initialize local variables of newly-pushed frame. */
void initVarsToUndefined();
/*
* Initialize local variables of newly-pushed frame. 'var' bindings are
* initialized to undefined and lexical bindings are initialized to
* JS_UNINITIALIZED_LEXICAL.
*/
void initLocals();
/*
* Stack frame type