mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1001090 - Part 1: Implement let temporal dead zone in the frontend and interpreter. (r=Waldo)
This commit is contained in:
parent
dd0fb7b8e6
commit
638366e609
@ -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;
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
@ -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));
|
||||
|
||||
if (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)) {
|
||||
if (checkLexical) {
|
||||
MOZ_ASSERT(op != JSOP_INITLEXICAL);
|
||||
if (!EmitLocalOp(cx, bce, JSOP_CHECKLEXICAL, slot))
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
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,19 +3084,9 @@ 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,
|
||||
EmitDestructuringDeclsWithEmitter(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp,
|
||||
ParseNode *pattern)
|
||||
{
|
||||
if (pattern->isKind(PNK_ARRAY)) {
|
||||
@ -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))
|
||||
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))
|
||||
|
||||
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 (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))
|
||||
bool letDecl = false;
|
||||
if (pn1 && !EmitForInOrOfVariables(cx, bce, pn1, &letDecl))
|
||||
return false;
|
||||
bce->emittingForInit = 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))
|
||||
bool letDecl = false;
|
||||
if (pn1 && !EmitForInOrOfVariables(cx, bce, pn1, &letDecl))
|
||||
return false;
|
||||
bce->emittingForInit = 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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
||||
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,6 +2853,7 @@ Parser<FullParseHandler>::bindLet(BindData<FullParseHandler> *data,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blockObj) {
|
||||
bool redeclared;
|
||||
RootedId id(cx, NameToId(name));
|
||||
RootedShape shape(cx, StaticBlockObject::addVar(cx, blockObj, id, index, &redeclared));
|
||||
@ -2703,6 +2865,13 @@ Parser<FullParseHandler>::bindLet(BindData<FullParseHandler> *data,
|
||||
|
||||
/* 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));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2998,8 +3167,17 @@ Parser<ParseHandler>::noteNameUse(HandlePropertyName name, Node pn)
|
||||
|
||||
handler.linkUseToDef(pn, dn);
|
||||
|
||||
if (stmt && stmt->type == STMT_WITH)
|
||||
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();
|
||||
|
@ -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 */
|
||||
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; }
|
||||
|
||||
|
@ -427,6 +427,9 @@ struct StmtInfoBase {
|
||||
return isNestedScope;
|
||||
}
|
||||
|
||||
void setStaticScope() {
|
||||
}
|
||||
|
||||
StaticBlockObject& staticBlock() const {
|
||||
JS_ASSERT(isNestedScope);
|
||||
JS_ASSERT(isBlockScope);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
2
js/src/jit-test/tests/basic/bug1001090-1.js
Normal file
2
js/src/jit-test/tests/basic/bug1001090-1.js
Normal file
@ -0,0 +1,2 @@
|
||||
(function() { let arguments })();
|
||||
(() => { let arguments; })()
|
5
js/src/jit-test/tests/basic/bug1001090-2.js
Normal file
5
js/src/jit-test/tests/basic/bug1001090-2.js
Normal file
@ -0,0 +1,5 @@
|
||||
// |jit-test| error: ReferenceError
|
||||
(function() {
|
||||
with(x);
|
||||
let x
|
||||
})()
|
27
js/src/jit-test/tests/basic/bug1001090-3.js
Normal file
27
js/src/jit-test/tests/basic/bug1001090-3.js
Normal 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);
|
30
js/src/jit-test/tests/basic/bug1001090-4.js
Normal file
30
js/src/jit-test/tests/basic/bug1001090-4.js
Normal 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);
|
6
js/src/jit-test/tests/basic/bug1001090-5.js
Normal file
6
js/src/jit-test/tests/basic/bug1001090-5.js
Normal file
@ -0,0 +1,6 @@
|
||||
// |jit-test| error: ReferenceError
|
||||
evalcx("\
|
||||
for(x = 0; x < 9; x++) {\
|
||||
let y = y.s()\
|
||||
}\
|
||||
", newGlobal())
|
7
js/src/jit-test/tests/basic/bug1001090-6.js
Normal file
7
js/src/jit-test/tests/basic/bug1001090-6.js
Normal file
@ -0,0 +1,7 @@
|
||||
// |jit-test| error: ReferenceError
|
||||
(function() {
|
||||
((function() {
|
||||
p(y)
|
||||
})());
|
||||
let y
|
||||
})()
|
5
js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js
Normal file
5
js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js
Normal 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"); }
|
19
js/src/jit-test/tests/basic/letTDZAfterInitializer.js
Normal file
19
js/src/jit-test/tests/basic/letTDZAfterInitializer.js
Normal 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);
|
||||
}
|
@ -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);
|
||||
|
@ -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() {}');
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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. */
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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,10 +280,13 @@ 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++)
|
||||
|
@ -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 {
|
||||
|
@ -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" +
|
||||
|
@ -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');
|
||||
}
|
@ -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);
|
||||
|
@ -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');
|
||||
}
|
@ -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") \
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 ®s)
|
||||
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 ®s)
|
||||
#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,9 +2016,18 @@ 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;
|
||||
|
||||
// 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 = ®S.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);
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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 &
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user