Bug 923160 - Disallow initializers in for-of statements. r=jorendorff

This commit is contained in:
Andy Wingo 2013-10-15 16:43:55 +02:00
Родитель 8005abd5e9
Коммит a84abcd5e0
13 изменённых файлов: 154 добавлений и 92 удалений

Просмотреть файл

@ -3236,10 +3236,10 @@ EmitVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmit
/*
* 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 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.
* 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);
JS_ASSERT(pn->pn_count == 1);
@ -4745,12 +4745,12 @@ EmitNormalFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff
static inline bool
EmitFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top)
{
if (pn->pn_left->isKind(PNK_FORIN)) {
// FIXME: Give for-of loops their own PNK. Bug 922066.
if (pn->pn_iflags == JSITER_FOR_OF)
return EmitForOf(cx, bce, pn, top);
if (pn->pn_left->isKind(PNK_FORIN))
return EmitForIn(cx, bce, pn, top);
}
if (pn->pn_left->isKind(PNK_FOROF))
return EmitForOf(cx, bce, pn, top);
JS_ASSERT(pn->pn_left->isKind(PNK_FORHEAD));
return EmitNormalFor(cx, bce, pn, top);
}

Просмотреть файл

@ -332,10 +332,10 @@ class FullParseHandler
return pn;
}
ParseNode *newForHead(bool isForInOrOf, ParseNode *pn1, ParseNode *pn2, ParseNode *pn3,
ParseNode *newForHead(ParseNodeKind kind, ParseNode *pn1, ParseNode *pn2, ParseNode *pn3,
const TokenPos &pos)
{
ParseNodeKind kind = isForInOrOf ? PNK_FORIN : PNK_FORHEAD;
JS_ASSERT(kind == PNK_FORIN || kind == PNK_FOROF || kind == PNK_FORHEAD);
return new_<TernaryNode>(kind, JSOP_NOP, pn1, pn2, pn3, pos);
}

Просмотреть файл

@ -131,6 +131,7 @@ class UpvarCookie
F(LET) \
F(SEQ) \
F(FORIN) \
F(FOROF) \
F(FORHEAD) \
F(ARGSBODY) \
F(SPREAD) \
@ -252,8 +253,8 @@ enum ParseNodeKind
* pn_val: constant value if lookup or table switch
* PNK_WHILE binary pn_left: cond, pn_right: body
* PNK_DOWHILE binary pn_left: body, pn_right: cond
* PNK_FOR binary pn_left: either PNK_FORIN (for-in statement) or
* PNK_FORHEAD (for(;;) statement)
* PNK_FOR binary pn_left: either PNK_FORIN (for-in statement),
* PNK_FOROF (for-of) or PNK_FORHEAD (for(;;))
* pn_right: body
* PNK_FORIN ternary pn_kid1: PNK_VAR to left of 'in', or nullptr
* its pn_xflags may have PNX_POPVAR
@ -262,6 +263,13 @@ enum ParseNodeKind
* to left of 'in'; if pn_kid1, then this
* is a clone of pn_kid1->pn_head
* pn_kid3: object expr to right of 'in'
* PNK_FOROF ternary pn_kid1: PNK_VAR to left of 'of', or nullptr
* its pn_xflags may have PNX_POPVAR
* bit set
* pn_kid2: PNK_NAME or destructuring expr
* to left of 'of'; if pn_kid1, then this
* is a clone of pn_kid1->pn_head
* pn_kid3: expr to right of 'of'
* PNK_FORHEAD ternary pn_kid1: init expr before first ';' or nullptr
* pn_kid2: cond expr before second ';' or nullptr
* pn_kid3: update expr after second ';' or nullptr

Просмотреть файл

@ -3832,17 +3832,19 @@ Parser<ParseHandler>::matchInOrOf(bool *isForOfp)
template <>
bool
Parser<FullParseHandler>::isValidForStatementLHS(ParseNode *pn1, JSVersion version,
bool isForDecl, bool isForEach, bool isForOf)
bool isForDecl, bool isForEach,
ParseNodeKind headKind)
{
if (isForDecl) {
if (pn1->pn_count > 1)
return false;
if (pn1->isOp(JSOP_DEFCONST))
return false;
#if JS_HAS_DESTRUCTURING
// In JS 1.7 only, for (var [K, V] in EXPR) has a special meaning.
// Hence all other destructuring decls are banned there.
if (version == JSVERSION_1_7 && !isForEach && !isForOf) {
if (version == JSVERSION_1_7 && !isForEach && headKind == PNK_FORIN) {
ParseNode *lhs = pn1->pn_head;
if (lhs->isKind(PNK_ASSIGN))
lhs = lhs->pn_left;
@ -3868,7 +3870,7 @@ Parser<FullParseHandler>::isValidForStatementLHS(ParseNode *pn1, JSVersion versi
case PNK_OBJECT:
// In JS 1.7 only, for ([K, V] in EXPR) has a special meaning.
// Hence all other destructuring left-hand sides are banned there.
if (version == JSVERSION_1_7 && !isForEach && !isForOf)
if (version == JSVERSION_1_7 && !isForEach && headKind == PNK_FORIN)
return pn1->isKind(PNK_ARRAY) && pn1->pn_count == 2;
return true;
#endif
@ -3982,9 +3984,14 @@ Parser<FullParseHandler>::forStatement()
*/
StmtInfoPC letStmt(context); /* used if blockObj != nullptr. */
ParseNode *pn2, *pn3; /* forHead->pn_kid2 and pn_kid3. */
bool isForOf;
bool isForInOrOf = pn1 && matchInOrOf(&isForOf);
if (isForInOrOf) {
ParseNodeKind headKind = PNK_FORHEAD;
if (pn1) {
bool isForOf;
if (matchInOrOf(&isForOf))
headKind = isForOf ? PNK_FOROF : PNK_FORIN;
}
if (headKind == PNK_FOROF || headKind == PNK_FORIN) {
/*
* Parse the rest of the for/in or for/of head.
*
@ -3993,17 +4000,20 @@ Parser<FullParseHandler>::forStatement()
* that receives the enumeration value each iteration, and pn3 is the
* rhs of 'in'.
*/
forStmt.type = isForOf ? STMT_FOR_OF_LOOP : STMT_FOR_IN_LOOP;
/* Set iflags and rule out invalid combinations. */
if (isForOf && isForEach) {
report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP);
return null();
if (headKind == PNK_FOROF) {
forStmt.type = STMT_FOR_OF_LOOP;
forStmt.type = (headKind == PNK_FOROF) ? STMT_FOR_OF_LOOP : STMT_FOR_IN_LOOP;
if (isForEach) {
report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP);
return null();
}
} else {
forStmt.type = STMT_FOR_IN_LOOP;
iflags |= JSITER_ENUMERATE;
}
iflags |= (isForOf ? JSITER_FOR_OF : JSITER_ENUMERATE);
/* Check that the left side of the 'in' or 'of' is valid. */
if (!isValidForStatementLHS(pn1, versionNumber(), isForDecl, isForEach, isForOf)) {
if (!isValidForStatementLHS(pn1, versionNumber(), isForDecl, isForEach, headKind)) {
report(ParseError, false, pn1, JSMSG_BAD_FOR_LEFTSIDE);
return null();
}
@ -4029,12 +4039,14 @@ Parser<FullParseHandler>::forStatement()
* 'const' to hoist the initializer or the entire decl out of
* the loop head.
*/
#if JS_HAS_BLOCK_SCOPE
if (headKind == PNK_FOROF) {
report(ParseError, false, pn2, JSMSG_INVALID_FOR_OF_INIT);
return null();
}
if (blockObj) {
report(ParseError, false, pn2, JSMSG_INVALID_FOR_IN_INIT);
return null();
}
#endif /* JS_HAS_BLOCK_SCOPE */
hoistedVar = pn1;
@ -4113,7 +4125,7 @@ Parser<FullParseHandler>::forStatement()
* Destructuring for-in requires [key, value] enumeration
* in JS1.7.
*/
if (!isForEach && !isForOf)
if (!isForEach && headKind == PNK_FORIN)
iflags |= JSITER_FOREACH | JSITER_KEYVALUE;
}
break;
@ -4127,6 +4139,8 @@ Parser<FullParseHandler>::forStatement()
return null();
}
headKind = PNK_FORHEAD;
if (blockObj) {
/*
* Desugar 'for (let A; B; C) D' into 'let (A) { for (; B; C) D }'
@ -4165,7 +4179,7 @@ Parser<FullParseHandler>::forStatement()
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL);
TokenPos headPos(begin, pos().end);
ParseNode *forHead = handler.newForHead(isForInOrOf, pn1, pn2, pn3, headPos);
ParseNode *forHead = handler.newForHead(headKind, pn1, pn2, pn3, headPos);
if (!forHead)
return null();
@ -5933,13 +5947,15 @@ Parser<FullParseHandler>::comprehensionTail(ParseNode *kid, unsigned blockid, bo
report(ParseError, false, null(), JSMSG_IN_AFTER_FOR_NAME);
return null();
}
ParseNodeKind headKind = PNK_FORIN;
if (isForOf) {
if (pn2->pn_iflags != JSITER_ENUMERATE) {
JS_ASSERT(pn2->pn_iflags == (JSITER_FOREACH | JSITER_ENUMERATE));
report(ParseError, false, null(), JSMSG_BAD_FOR_EACH_LOOP);
return null();
}
pn2->pn_iflags = JSITER_FOR_OF;
pn2->pn_iflags = 0;
headKind = PNK_FOROF;
}
ParseNode *pn4 = expr();
@ -5972,6 +5988,7 @@ Parser<FullParseHandler>::comprehensionTail(ParseNode *kid, unsigned blockid, bo
JS_ASSERT(pn2->isOp(JSOP_ITER));
JS_ASSERT(pn2->pn_iflags & JSITER_ENUMERATE);
JS_ASSERT(headKind == PNK_FORIN);
pn2->pn_iflags |= JSITER_FOREACH | JSITER_KEYVALUE;
}
break;
@ -6003,7 +6020,7 @@ Parser<FullParseHandler>::comprehensionTail(ParseNode *kid, unsigned blockid, bo
if (!pn3)
return null();
pn2->pn_left = handler.newTernary(PNK_FORIN, vars, pn3, pn4);
pn2->pn_left = handler.newTernary(headKind, vars, pn3, pn4);
if (!pn2->pn_left)
return null();
*pnp = pn2;

Просмотреть файл

@ -578,8 +578,8 @@ class Parser : private AutoGCRooter, public StrictModeGetter
bool finishFunctionDefinition(Node pn, FunctionBox *funbox, Node prelude, Node body);
bool addFreeVariablesFromLazyFunction(JSFunction *fun, ParseContext<ParseHandler> *pc);
bool isValidForStatementLHS(Node pn1, JSVersion version,
bool forDecl, bool forEach, bool forOf);
bool isValidForStatementLHS(Node pn1, JSVersion version, bool forDecl, bool forEach,
ParseNodeKind headKind);
bool checkAndMarkAsIncOperand(Node kid, TokenKind tt, bool preorder);
bool checkStrictAssignment(Node lhs, AssignmentFlavor flavor);
bool checkStrictBinding(PropertyName *name, Node pn);

Просмотреть файл

@ -0,0 +1,19 @@
// For-of can't have initializers.
load(libdir + 'asserts.js');
function assertSyntaxError(str) {
assertThrowsInstanceOf(function () { return Function(str); }, SyntaxError);
}
assertSyntaxError("for (var x = 1 of []) {}");
assertSyntaxError("for (var [x] = 1 of []) {}");
assertSyntaxError("for (var {x} = 1 of []) {}");
assertSyntaxError("for (let x = 1 of []) {}");
assertSyntaxError("for (let [x] = 1 of []) {}");
assertSyntaxError("for (let {x} = 1 of []) {}");
assertSyntaxError("for (const x = 1 of []) {}");
assertSyntaxError("for (const [x] = 1 of []) {}");
assertSyntaxError("for (const {x} = 1 of []) {}");

Просмотреть файл

@ -18,7 +18,7 @@ function test() {
var a = [1, 2, 3];
var s = '';
for (var x of a)
for (var i=0 of 'y')
for (var i of 'y')
s += '' + foo()
} test();
@ -26,7 +26,7 @@ ignoreComments = [];
function bug909276() {
var actual = '';
for (var next = 0 of ignoreComments) {
for (var next of ignoreComments) {
actual += a;
for (var b in x) {
actual += b.eval("args = [-0, NaN, -1/0]; this.f(-0, NaN, -1/0);");

Просмотреть файл

@ -221,7 +221,7 @@ MSG_DEF(JSMSG_CANT_DECODE_PRINCIPALS, 167, 0, JSEXN_INTERNALERR, "can't decode J
MSG_DEF(JSMSG_CANT_SEAL_OBJECT, 168, 1, JSEXN_ERR, "can't seal {0} objects")
MSG_DEF(JSMSG_TOO_MANY_CATCH_VARS, 169, 0, JSEXN_SYNTAXERR, "too many catch variables")
MSG_DEF(JSMSG_NEGATIVE_REPETITION_COUNT, 170, 0, JSEXN_RANGEERR, "repeat count must be non-negative")
MSG_DEF(JSMSG_UNUSED171, 171, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_INVALID_FOR_OF_INIT, 171, 0, JSEXN_SYNTAXERR, "for-of loop variable declaration may not have an initializer")
MSG_DEF(JSMSG_UNUSED172, 172, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_UNUSED173, 173, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_UNUSED174, 174, 0, JSEXN_NONE, "")

Просмотреть файл

@ -639,7 +639,6 @@ ErrorFromException(JS::Value val);
#define JSITER_KEYVALUE 0x4 /* destructuring for-in wants [key, value] */
#define JSITER_OWNONLY 0x8 /* iterate over obj's own properties only */
#define JSITER_HIDDEN 0x10 /* also enumerate non-enumerable properties */
#define JSITER_FOR_OF 0x20 /* harmony for-of loop */
JS_FRIEND_API(bool)
RunningWithTrustedPrincipals(JSContext *cx);

Просмотреть файл

@ -564,37 +564,6 @@ UpdateNativeIterator(NativeIterator *ni, JSObject *obj)
bool
js::GetIterator(JSContext *cx, HandleObject obj, unsigned flags, MutableHandleValue vp)
{
if (flags == JSITER_FOR_OF) {
// for-of loop. The iterator is simply |obj[@@iterator]()|.
RootedValue method(cx);
if (!JSObject::getProperty(cx, obj, obj, cx->names().std_iterator, &method))
return false;
// Throw if obj[@@iterator] isn't callable. js::Invoke is about to check
// for this kind of error anyway, but it would throw an inscrutable
// error message about |method| rather than this nice one about |obj|.
if (!method.isObject() || !method.toObject().isCallable()) {
RootedValue val(cx, ObjectOrNullValue(obj));
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, val, NullPtr());
if (!bytes)
return false;
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, bytes);
js_free(bytes);
return false;
}
if (!Invoke(cx, ObjectOrNullValue(obj), method, 0, nullptr, vp))
return false;
JSObject *resultObj = ToObject(cx, vp);
if (!resultObj)
return false;
vp.setObject(*resultObj);
return true;
}
JS_ASSERT(!(flags & JSITER_FOR_OF));
Vector<Shape *, 8> shapes(cx);
uint32_t key = 0;
@ -1321,6 +1290,46 @@ const Class StopIterationObject::class_ = {
nullptr /* construct */
};
bool
ForOfIterator::init(HandleValue iterable)
{
RootedObject iterableObj(cx, ToObject(cx, iterable));
if (!iterableObj)
return false;
// The iterator is the result of calling obj[@@iterator]().
InvokeArgs args(cx);
if (!args.init(0))
return false;
args.setThis(ObjectValue(*iterableObj));
RootedValue callee(cx);
if (!JSObject::getProperty(cx, iterableObj, iterableObj, cx->names().std_iterator, &callee))
return false;
// Throw if obj[@@iterator] isn't callable. js::Invoke is about to check
// for this kind of error anyway, but it would throw an inscrutable
// error message about |method| rather than this nice one about |obj|.
if (!callee.isObject() || !callee.toObject().isCallable()) {
char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, iterable, NullPtr());
if (!bytes)
return false;
JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, bytes);
js_free(bytes);
return false;
}
args.setCallee(callee);
if (!Invoke(cx, args))
return false;
iterator = ToObject(cx, args.rval());
if (!iterator)
return false;
return true;
}
bool
ForOfIterator::next(MutableHandleValue vp, bool *done)
{

Просмотреть файл

@ -208,7 +208,7 @@ UnwindIteratorForUncatchableException(JSContext *cx, JSObject *obj);
bool
IteratorConstructor(JSContext *cx, unsigned argc, Value *vp);
}
} /* namespace js */
extern bool
js_SuppressDeletedProperty(JSContext *cx, js::HandleObject obj, jsid id);
@ -265,14 +265,7 @@ class ForOfIterator
public:
ForOfIterator(JSContext *cx) : cx(cx), iterator(cx) { }
bool init(HandleValue iterable) {
RootedValue iterv(cx, iterable);
if (!ValueToIterator(cx, JSITER_FOR_OF, &iterv))
return false;
iterator = &iterv.get().toObject();
return true;
}
bool init(HandleValue iterable);
bool next(MutableHandleValue val, bool *done);
};

Просмотреть файл

@ -1546,8 +1546,10 @@ class ASTSerializer
}
bool forInit(ParseNode *pn, MutableHandleValue dst);
bool forOfOrIn(ParseNode *loop, ParseNode *head, HandleValue var, HandleValue stmt,
MutableHandleValue dst);
bool forIn(ParseNode *loop, ParseNode *head, HandleValue var, HandleValue stmt,
MutableHandleValue dst);
bool forOf(ParseNode *loop, ParseNode *head, HandleValue var, HandleValue stmt,
MutableHandleValue dst);
bool statement(ParseNode *pn, MutableHandleValue dst);
bool blockStatement(ParseNode *pn, MutableHandleValue dst);
bool switchStatement(ParseNode *pn, MutableHandleValue dst);
@ -2018,17 +2020,24 @@ ASTSerializer::forInit(ParseNode *pn, MutableHandleValue dst)
}
bool
ASTSerializer::forOfOrIn(ParseNode *loop, ParseNode *head, HandleValue var, HandleValue stmt,
ASTSerializer::forOf(ParseNode *loop, ParseNode *head, HandleValue var, HandleValue stmt,
MutableHandleValue dst)
{
RootedValue expr(cx);
return expression(head->pn_kid3, &expr) &&
builder.forOfStatement(var, expr, stmt, &loop->pn_pos, dst);
}
bool
ASTSerializer::forIn(ParseNode *loop, ParseNode *head, HandleValue var, HandleValue stmt,
MutableHandleValue dst)
{
RootedValue expr(cx);
bool isForEach = loop->pn_iflags & JSITER_FOREACH;
bool isForOf = loop->pn_iflags & JSITER_FOR_OF;
JS_ASSERT(!isForOf || !isForEach);
return expression(head->pn_kid3, &expr) &&
(isForOf ? builder.forOfStatement(var, expr, stmt, &loop->pn_pos, dst) :
builder.forInStatement(var, expr, stmt, isForEach, &loop->pn_pos, dst));
builder.forInStatement(var, expr, stmt, isForEach, &loop->pn_pos, dst);
}
bool
@ -2139,7 +2148,17 @@ ASTSerializer::statement(ParseNode *pn, MutableHandleValue dst)
: head->pn_kid1->isKind(PNK_LEXICALSCOPE)
? variableDeclaration(head->pn_kid1->pn_expr, true, &var)
: variableDeclaration(head->pn_kid1, false, &var)) &&
forOfOrIn(pn, head, var, stmt, dst);
forIn(pn, head, var, stmt, dst);
}
if (head->isKind(PNK_FOROF)) {
RootedValue var(cx);
return (!head->pn_kid1
? pattern(head->pn_kid2, nullptr, &var)
: head->pn_kid1->isKind(PNK_LEXICALSCOPE)
? variableDeclaration(head->pn_kid1->pn_expr, true, &var)
: variableDeclaration(head->pn_kid1, false, &var)) &&
forOf(pn, head, var, stmt, dst);
}
RootedValue init(cx), test(cx), update(cx);
@ -2169,7 +2188,7 @@ ASTSerializer::statement(ParseNode *pn, MutableHandleValue dst)
RootedValue stmt(cx);
return statement(loop->pn_right, &stmt) && forOfOrIn(loop, head, var, stmt, dst);
return statement(loop->pn_right, &stmt) && forIn(loop, head, var, stmt, dst);
}
case PNK_BREAK:
@ -2262,10 +2281,10 @@ ASTSerializer::comprehensionBlock(ParseNode *pn, MutableHandleValue dst)
ParseNode *in = pn->pn_left;
LOCAL_ASSERT(in && in->isKind(PNK_FORIN));
LOCAL_ASSERT(in && (in->isKind(PNK_FORIN) || in->isKind(PNK_FOROF)));
bool isForEach = pn->pn_iflags & JSITER_FOREACH;
bool isForOf = pn->pn_iflags & JSITER_FOR_OF;
bool isForOf = in->isKind(PNK_FOROF);
RootedValue patt(cx), src(cx);
return pattern(in->pn_kid2, nullptr, &patt) &&

Просмотреть файл

@ -681,8 +681,6 @@ assertError("for each (const [x,y,z] in foo);", SyntaxError);
assertStmt("for (var {a:x,b:y,c:z} = 22 in foo);", forInStmt(varDecl([{ id: axbycz, init: lit(22) }]), ident("foo"), emptyStmt));
assertStmt("for (var [x,y,z] = 22 in foo);", forInStmt(varDecl([{ id: xyz, init: lit(22) }]), ident("foo"), emptyStmt));
assertStmt("for (var {a:x,b:y,c:z} = 22 of foo);", forOfStmt(varDecl([{ id: axbycz, init: lit(22) }]), ident("foo"), emptyStmt));
assertStmt("for (var [x,y,z] = 22 of foo);", forOfStmt(varDecl([{ id: xyz, init: lit(22) }]), ident("foo"), emptyStmt));
assertStmt("for each (var {a:x,b:y,c:z} = 22 in foo);", forEachInStmt(varDecl([{ id: axbycz, init: lit(22) }]), ident("foo"), emptyStmt));
assertStmt("for each (var [x,y,z] = 22 in foo);", forEachInStmt(varDecl([{ id: xyz, init: lit(22) }]), ident("foo"), emptyStmt));
assertError("for (x = 22 in foo);", SyntaxError);