From 3ee73ca14cec9ac99ebee938d76650ad11aa98da Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Tue, 20 Mar 2018 10:27:23 +0100 Subject: Bug 1322314 - Disallow emitting ParseNode twice Issue #73 [Depends on] Bug 1147371: Implement IteratorClose --- js/src/frontend/BytecodeEmitter.cpp | 29 ++++------------------------- js/src/frontend/BytecodeEmitter.h | 4 +--- js/src/frontend/ParseNode.h | 4 +--- 3 files changed, 6 insertions(+), 31 deletions(-) (limited to 'js') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index acf734794..ee26d0c43 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -6975,14 +6975,13 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) RootedFunction fun(cx, funbox->function()); RootedAtom name(cx, fun->explicitName()); MOZ_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); - MOZ_ASSERT_IF(pn->isOp(JSOP_FUNWITHPROTO), needsProto); /* * Set the |wasEmitted| flag in the funbox once the function has been * emitted. Function definitions that need hoisting to the top of the * function will be seen by emitFunction in two places. */ - if (funbox->wasEmitted && pn->functionIsHoisted()) { + if (funbox->wasEmitted) { // Annex B block-scoped functions are hoisted like any other // block-scoped function to the top of their scope. When their // definitions are seen for the second time, we need to emit the @@ -7111,7 +7110,7 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) } if (needsProto) { - MOZ_ASSERT(pn->getOp() == JSOP_FUNWITHPROTO || pn->getOp() == JSOP_LAMBDA); + MOZ_ASSERT(pn->getOp() == JSOP_LAMBDA); pn->setOp(JSOP_FUNWITHPROTO); } @@ -10047,15 +10046,6 @@ CGConstList::finish(ConstArray* array) array->vector[i] = list[i]; } -bool -CGObjectList::isAdded(ObjectBox* objbox) -{ - // An objbox added to CGObjectList as non-first element has non-null - // emitLink member. The first element has null emitLink. - // Check for firstbox to cover the first element. - return objbox->emitLink || objbox == firstbox; -} - /* * Find the index of the given object for code generator. * @@ -10067,15 +10057,9 @@ CGObjectList::isAdded(ObjectBox* objbox) unsigned CGObjectList::add(ObjectBox* objbox) { - if (isAdded(objbox)) - return indexOf(objbox->object); - + MOZ_ASSERT(!objbox->emitLink); objbox->emitLink = lastbox; lastbox = objbox; - - // See the comment in CGObjectList::isAdded. - if (!firstbox) - firstbox = objbox; return length++; } @@ -10102,12 +10086,7 @@ CGObjectList::finish(ObjectArray* array) MOZ_ASSERT(!*cursor); MOZ_ASSERT(objbox->object->isTenured()); *cursor = objbox->object; - - ObjectBox* tmp = objbox->emitLink; - // Clear emitLink for CGObjectList::isAdded. - objbox->emitLink = nullptr; - objbox = tmp; - } while (objbox != nullptr); + } while ((objbox = objbox->emitLink) != nullptr); MOZ_ASSERT(cursor == array->vector); } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 9a2ddb568..4b3750dd5 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -43,12 +43,10 @@ class CGConstList { struct CGObjectList { uint32_t length; /* number of emitted so far objects */ - ObjectBox* firstbox; /* first emitted object */ ObjectBox* lastbox; /* last emitted object */ - CGObjectList() : length(0), firstbox(nullptr), lastbox(nullptr) {} + CGObjectList() : length(0), lastbox(nullptr) {} - bool isAdded(ObjectBox* objbox); unsigned add(ObjectBox* objbox); unsigned indexOf(JSObject* obj); void finish(ObjectArray* array); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index ff26279af..c58dab431 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -649,14 +649,12 @@ class ParseNode MOZ_ASSERT(pn_arity == PN_CODE && getKind() == PNK_FUNCTION); MOZ_ASSERT(isOp(JSOP_LAMBDA) || // lambda, genexpr isOp(JSOP_LAMBDA_ARROW) || // arrow function - isOp(JSOP_FUNWITHPROTO) || // already emitted lambda with needsProto isOp(JSOP_DEFFUN) || // non-body-level function statement isOp(JSOP_NOP) || // body-level function stmt in global code isOp(JSOP_GETLOCAL) || // body-level function stmt in function code isOp(JSOP_GETARG) || // body-level function redeclaring formal isOp(JSOP_INITLEXICAL)); // block-level function stmt - return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && - !isOp(JSOP_FUNWITHPROTO) && !isOp(JSOP_DEFFUN); + return !isOp(JSOP_LAMBDA) && !isOp(JSOP_LAMBDA_ARROW) && !isOp(JSOP_DEFFUN); } /* -- cgit v1.2.3 From caa2a53c402c7b509e9939e9aefe595dc0dbe516 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Tue, 20 Mar 2018 10:46:22 +0100 Subject: Bug 1322314 - Do not emit ParseNode twice in BytecodeEmitter::emitDestructuringOpsArray Issue #73 [Depends on] Bug 1147371: Implement IteratorClose --- js/src/frontend/BytecodeEmitter.cpp | 257 ++++++++++----------- js/src/frontend/BytecodeEmitter.h | 1 - .../ecma_6/Destructuring/array-default-class.js | 25 ++ 3 files changed, 144 insertions(+), 139 deletions(-) create mode 100644 js/src/tests/ecma_6/Destructuring/array-default-class.js (limited to 'js') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index ee26d0c43..a68fe8538 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4462,13 +4462,6 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla return true; } -bool -BytecodeEmitter::emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav) -{ - TDZCheckCache tdzCache(this); - return emitDestructuringLHS(target, flav); -} - bool BytecodeEmitter::emitIteratorNext(ParseNode* pn, bool allowSelfHosted) { @@ -4739,7 +4732,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // // let x, y; // let a, b, c, d; - // let tmp, done, iter, result; // stack values + // let iter, result, done, value; // stack values // // iter = x[Symbol.iterator](); // @@ -4747,115 +4740,113 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // result = iter.next(); // done = result.done; // - // if (done) { - // a = undefined; - // - // result = undefined; - // done = true; - // } else { - // a = result.value; + // if (done) + // value = undefined; + // else + // value = result.value; // - // // Do next element's .next() and .done access here - // result = iter.next(); - // done = result.done; - // } + // a = value; // // // ==== emitted by loop for b ==== // if (done) { - // b = undefined; - // - // result = undefined; - // done = true; + // value = undefined; // } else { - // b = result.value; - // // result = iter.next(); // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // + // b = value; + // // // ==== emitted by loop for elision ==== // if (done) { - // result = undefined - // done = true + // value = undefined; // } else { - // result.value; - // // result = iter.next(); // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // // // ==== emitted by loop for c ==== // if (done) { - // c = y; + // value = undefined; // } else { - // tmp = result.value; - // if (tmp === undefined) - // tmp = y; - // c = tmp; - // - // // Don't do next element's .next() and .done access if - // // this is the last non-spread element. + // result = iter.next(); + // done = result.done; + // if (done) + // value = undefined; + // else + // value = result.value; // } // + // if (value === undefined) + // value = y; + // + // c = value; + // // // ==== emitted by loop for d ==== - // if (done) { - // // Assing empty array when completed - // d = []; - // } else { - // d = [...iter]; - // } + // if (done) + // value = []; + // else + // value = [...iter]; + // + // d = value; - /* - * Use an iterator to destructure the RHS, instead of index lookup. We - * must leave the *original* value on the stack. - */ + // Use an iterator to destructure the RHS, instead of index lookup. We + // must leave the *original* value on the stack. if (!emit1(JSOP_DUP)) // ... OBJ OBJ return false; - if (!emitIterator()) // ... OBJ? ITER + if (!emitIterator()) // ... OBJ ITER return false; - bool needToPopIterator = true; for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { bool isHead = member == pattern->pn_head; + bool hasNext = !!member->pn_next; + if (member->isKind(PNK_SPREAD)) { IfThenElseEmitter ifThenElse(this); if (!isHead) { // If spread is not the first element of the pattern, // iterator can already be completed. - if (!ifThenElse.emitIfElse()) // ... OBJ? ITER + // ... OBJ ITER DONE + if (!ifThenElse.emitIfElse()) // ... OBJ ITER return false; - if (!emit1(JSOP_POP)) // ... OBJ? - return false; - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ARRAY + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER ARRAY return false; - if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ? - return false; - - if (!ifThenElse.emitElse()) // ... OBJ? ITER + if (!ifThenElse.emitElse()) // ... OBJ ITER return false; } // If iterator is not completed, create a new array with the rest // of the iterator. - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ? ITER ARRAY + if (!emit1(JSOP_DUP)) // ... OBJ ITER return false; - if (!emitNumberOp(0)) // ... OBJ? ITER ARRAY INDEX + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER ITER ARRAY return false; - if (!emitSpread()) // ... OBJ? ARRAY INDEX + if (!emitNumberOp(0)) // ... OBJ ITER ITER ARRAY INDEX return false; - if (!emit1(JSOP_POP)) // ... OBJ? ARRAY + if (!emitSpread()) // ... OBJ ITER ARRAY INDEX return false; - if (!emitDestructuringLHSInBranch(member, flav)) // ... OBJ? + if (!emit1(JSOP_POP)) // ... OBJ ITER ARRAY return false; if (!isHead) { if (!ifThenElse.emitEnd()) return false; - MOZ_ASSERT(ifThenElse.popped() == 1); + MOZ_ASSERT(ifThenElse.pushed() == 1); } - needToPopIterator = false; - MOZ_ASSERT(!member->pn_next); + + if (!emitDestructuringLHS(member, flav)) // ... OBJ ITER + return false; + + MOZ_ASSERT(!hasNext); break; } @@ -4867,110 +4858,100 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav } bool isElision = subpattern->isKind(PNK_ELISION); - bool hasNextNonSpread = member->pn_next && !member->pn_next->isKind(PNK_SPREAD); - bool hasNextSpread = member->pn_next && member->pn_next->isKind(PNK_SPREAD); MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD)); - auto emitNext = [pattern](ExclusiveContext* cx, BytecodeEmitter* bce) { - if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER ITER - return false; - if (!bce->emitIteratorNext(pattern)) // ... OBJ? ITER RESULT + IfThenElseEmitter ifAlreadyDone(this); + if (!isHead) { + // If this element is not the first element of the pattern, + // iterator can already be completed. + // ... OBJ ITER DONE + if (hasNext) { + if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE DONE + return false; + } + if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE return false; - if (!bce->emit1(JSOP_DUP)) // ... OBJ? ITER RESULT RESULT + + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE UNDEF return false; - if (!bce->emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ? ITER RESULT DONE? + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE UNDEF return false; - return true; - }; - if (isHead) { - if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE? + if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE return false; + + if (hasNext) { + if (!emit1(JSOP_POP)) // ... OBJ ITER + return false; + } } - IfThenElseEmitter ifThenElse(this); - if (!ifThenElse.emitIfElse()) // ... OBJ? ITER RESULT + if (!emit1(JSOP_DUP)) // ... OBJ ITER ITER return false; - - if (!emit1(JSOP_POP)) // ... OBJ? ITER + if (!emitIteratorNext(pattern)) // ... OBJ ITER RESULT return false; - if (pndefault) { - // Emit only pndefault tree here, as undefined check in emitDefault - // should always be true. - if (!emitInitializerInBranch(pndefault, subpattern)) // ... OBJ? ITER VALUE + if (!emit1(JSOP_DUP)) // ... OBJ ITER RESULT RESULT + return false; + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER RESULT DONE + return false; + + if (hasNext) { + if (!emit1(JSOP_DUP)) // ... OBJ ITER RESULT DONE DONE return false; - } else { - if (!isElision) { - if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER UNDEFINED - return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) - return false; - } } - if (!isElision) { - if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER - return false; - } else if (pndefault) { - if (!emit1(JSOP_POP)) // ... OBJ? ITER + + IfThenElseEmitter ifDone(this); + if (!ifDone.emitIfElse()) // ... OBJ ITER RESULT ?DONE + return false; + + if (hasNext) { + if (!emit1(JSOP_SWAP)) // ... OBJ ITER ?DONE RESULT return false; } + if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE + return false; + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE UNDEF + return false; + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE UNDEF + return false; - // Setup next element's result when the iterator is done. - if (hasNextNonSpread) { - if (!emit1(JSOP_UNDEFINED)) // ... OBJ? ITER RESULT - return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) - return false; - if (!emit1(JSOP_TRUE)) // ... OBJ? ITER RESULT DONE? - return false; - } else if (hasNextSpread) { - if (!emit1(JSOP_TRUE)) // ... OBJ? ITER DONE? + if (!ifDone.emitElse()) // ... OBJ ITER RESULT ?DONE + return false; + + if (hasNext) { + if (!emit1(JSOP_SWAP)) // ... OBJ ITER ?DONE RESULT return false; } - - if (!ifThenElse.emitElse()) // ... OBJ? ITER RESULT + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE VALUE return false; - if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ? ITER VALUE + if (!ifDone.emitEnd()) return false; + MOZ_ASSERT(ifDone.pushed() == 0); - if (pndefault) { - if (!emitDefault(pndefault, subpattern)) // ... OBJ? ITER VALUE + if (!isHead) { + if (!ifAlreadyDone.emitEnd()) return false; + MOZ_ASSERT(ifAlreadyDone.pushed() == 1); } - if (!isElision) { - if (!emitDestructuringLHSInBranch(subpattern, flav)) // ... OBJ? ITER - return false; - } else { - if (!emit1(JSOP_POP)) // ... OBJ? ITER + if (pndefault) { + if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE VALUE return false; } - // Setup next element's result when the iterator is not done. - if (hasNextNonSpread) { - if (!emitNext(cx, this)) // ... OBJ? ITER RESULT DONE? + if (!isElision) { + if (!emitDestructuringLHS(subpattern, flav)) // ... OBJ ITER ?DONE return false; - } else if (hasNextSpread) { - if (!emit1(JSOP_FALSE)) // ... OBJ? ITER DONE? + } else { + if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE return false; } - - if (!ifThenElse.emitEnd()) - return false; - if (hasNextNonSpread) - MOZ_ASSERT(ifThenElse.pushed() == 1); - else if (hasNextSpread) - MOZ_ASSERT(ifThenElse.pushed() == 0); - else - MOZ_ASSERT(ifThenElse.popped() == 1); } - if (needToPopIterator) { - if (!emit1(JSOP_POP)) // ... OBJ? - return false; - } + if (!emit1(JSOP_POP)) // ... OBJ + return false; return true; } @@ -6118,7 +6099,7 @@ BytecodeEmitter::emitSpread(bool allowSelfHosted) return false; if (!emit1(JSOP_DUP)) // ITER ARR I RESULT RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE? + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER ARR I RESULT DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) // ITER ARR I RESULT @@ -6307,7 +6288,7 @@ BytecodeEmitter::emitForOf(ParseNode* forOfLoop, EmitterScope* headLexicalEmitte return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE? + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; if (!emitBackwardJump(JSOP_IFEQ, top, &beq, &breakTarget)) @@ -6803,7 +6784,7 @@ BytecodeEmitter::emitComprehensionForOf(ParseNode* pn) return false; if (!emit1(JSOP_DUP)) // ITER RESULT RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE? + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ITER RESULT DONE return false; JumpList beq; diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 4b3750dd5..f09b529ed 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -646,7 +646,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter // the stack and emits code to destructure a single lhs expression (either a // name or a compound []/{} expression). MOZ_MUST_USE bool emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav); - MOZ_MUST_USE bool emitDestructuringLHSInBranch(ParseNode* target, DestructuringFlavor flav); // emitDestructuringOps assumes the to-be-destructured value has been // pushed on the stack and emits code to destructure each part of a [] or diff --git a/js/src/tests/ecma_6/Destructuring/array-default-class.js b/js/src/tests/ecma_6/Destructuring/array-default-class.js new file mode 100644 index 000000000..5aa9c579b --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/array-default-class.js @@ -0,0 +1,25 @@ +var BUGNUMBER = 1322314; +var summary = "Function in computed property in class expression in array destructuring default"; + +print(BUGNUMBER + ": " + summary); + +function* g([ + a = class E { + [ (function() { return "foo"; })() ]() { + return 10; + } + } +]) { + yield a; +} + +let C = [...g([])][0]; +let x = new C(); +assertEq(x.foo(), 10); + +C = [...g([undefined])][0]; +x = new C(); +assertEq(x.foo(), 10); + +if (typeof reportCompare === "function") + reportCompare(0, 0); -- cgit v1.2.3 From 2a57d73c3b5304be3f9be51018a1bbee79f007e2 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Tue, 20 Mar 2018 12:40:00 +0100 Subject: Bug 1204028: Evaluate LHS reference before RHS in destructuring Issue #73 [Depends on] Bug 1147371: Implement IteratorClose --- js/src/frontend/BytecodeEmitter.cpp | 277 ++++++--- js/src/frontend/BytecodeEmitter.h | 18 +- js/src/jit/BaselineCompiler.cpp | 30 +- js/src/jit/BaselineCompiler.h | 1 + js/src/jit/IonBuilder.cpp | 5 + js/src/jit/MIRGraph.cpp | 13 + js/src/jit/MIRGraph.h | 3 + js/src/tests/ecma_6/Destructuring/order-super.js | 727 ++++++++++++++++++++++ js/src/tests/ecma_6/Destructuring/order.js | 745 +++++++++++++++++++++++ js/src/vm/Interpreter.cpp | 11 +- js/src/vm/Opcodes.h | 10 +- 11 files changed, 1743 insertions(+), 97 deletions(-) create mode 100644 js/src/tests/ecma_6/Destructuring/order-super.js create mode 100644 js/src/tests/ecma_6/Destructuring/order.js (limited to 'js') diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index a68fe8538..a4cfeb753 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4325,7 +4325,69 @@ BytecodeEmitter::emitDestructuringDeclsWithEmitter(ParseNode* pattern, NameEmitt } bool -BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav) +BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, size_t* emitted) +{ + *emitted = 0; + + if (target->isKind(PNK_SPREAD)) + target = target->pn_kid; + else if (target->isKind(PNK_ASSIGN)) + target = target->pn_left; + + // No need to recur into PNK_ARRAY and PNK_OBJECT subpatterns here, since + // emitSetOrInitializeDestructuring does the recursion when setting or + // initializing value. Getting reference doesn't recur. + if (target->isKind(PNK_NAME) || target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) + return true; + +#ifdef DEBUG + int depth = stackDepth; +#endif + + switch (target->getKind()) { + case PNK_DOT: { + if (target->as().isSuper()) { + if (!emitSuperPropLHS(&target->as().expression())) + return false; + *emitted = 2; + } else { + if (!emitTree(target->pn_expr)) + return false; + *emitted = 1; + } + break; + } + + case PNK_ELEM: { + if (target->as().isSuper()) { + if (!emitSuperElemOperands(target, EmitElemOption::Ref)) + return false; + *emitted = 3; + } else { + if (!emitElemOperands(target, EmitElemOption::Ref)) + return false; + *emitted = 2; + } + break; + } + + case PNK_CALL: + MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget " + "rejects function calls as assignment " + "targets in destructuring assignments"); + break; + + default: + MOZ_CRASH("emitDestructuringLHSRef: bad lhs kind"); + } + + MOZ_ASSERT(stackDepth == depth + int(*emitted)); + + return true; +} + +bool +BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav) { // Now emit the lvalue opcode sequence. If the lvalue is a nested // destructuring initialiser-form, call ourselves to handle it, then pop @@ -4401,44 +4463,28 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla } case PNK_DOT: { - // See the (PNK_NAME, JSOP_SETNAME) case above. - // - // In `a.x = b`, `a` is evaluated first, then `b`, then a - // JSOP_SETPROP instruction. - // - // In `[a.x] = [b]`, per spec, `b` is evaluated before `a`. Then we - // need a property set -- but the operands are on the stack in the - // wrong order for JSOP_SETPROP, so we have to add a JSOP_SWAP. + // The reference is already pushed by emitDestructuringLHSRef. JSOp setOp; - if (target->as().isSuper()) { - if (!emitSuperPropLHS(&target->as().expression())) - return false; - if (!emit2(JSOP_PICK, 2)) - return false; + if (target->as().isSuper()) setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER; - } else { - if (!emitTree(target->pn_expr)) - return false; - if (!emit1(JSOP_SWAP)) - return false; + else setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP; - } if (!emitAtomOp(target, setOp)) return false; break; } case PNK_ELEM: { - // See the comment at `case PNK_DOT:` above. This case, - // `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP - // is emitted by emitElemOperands. + // The reference is already pushed by emitDestructuringLHSRef. if (target->as().isSuper()) { JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER; - if (!emitSuperElemOp(target, setOp)) + // emitDestructuringLHSRef already did emitSuperElemOperands + // part of emitSuperElemOp. Perform remaining part here. + if (!emitElemOpBase(setOp)) return false; } else { JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; - if (!emitElemOp(target, setOp)) + if (!emitElemOpBase(setOp)) return false; } break; @@ -4451,7 +4497,7 @@ BytecodeEmitter::emitDestructuringLHS(ParseNode* target, DestructuringFlavor fla break; default: - MOZ_CRASH("emitDestructuringLHS: bad lhs kind"); + MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind"); } // Pop the assigned value. @@ -4732,11 +4778,13 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // // let x, y; // let a, b, c, d; - // let iter, result, done, value; // stack values + // let iter, lref, result, done, value; // stack values // // iter = x[Symbol.iterator](); // // // ==== emitted by loop for a ==== + // lref = GetReference(a); + // // result = iter.next(); // done = result.done; // @@ -4745,9 +4793,11 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // else // value = result.value; // - // a = value; + // SetOrInitialize(lref, value); // // // ==== emitted by loop for b ==== + // lref = GetReference(b); + // // if (done) { // value = undefined; // } else { @@ -4759,7 +4809,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // value = result.value; // } // - // b = value; + // SetOrInitialize(lref, value); // // // ==== emitted by loop for elision ==== // if (done) { @@ -4774,6 +4824,8 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // } // // // ==== emitted by loop for c ==== + // lref = GetReference(c); + // // if (done) { // value = undefined; // } else { @@ -4788,15 +4840,17 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav // if (value === undefined) // value = y; // - // c = value; + // SetOrInitialize(lref, value); // // // ==== emitted by loop for d ==== + // lref = GetReference(d); + // // if (done) // value = []; // else // value = [...iter]; // - // d = value; + // SetOrInitialize(lref, value); // Use an iterator to destructure the RHS, instead of index lookup. We // must leave the *original* value on the stack. @@ -4810,31 +4864,40 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav bool hasNext = !!member->pn_next; if (member->isKind(PNK_SPREAD)) { + size_t emitted = 0; + if (!emitDestructuringLHSRef(member, &emitted)) // ... OBJ ITER ?DONE *LREF + return false; + IfThenElseEmitter ifThenElse(this); if (!isHead) { // If spread is not the first element of the pattern, // iterator can already be completed. - // ... OBJ ITER DONE - if (!ifThenElse.emitIfElse()) // ... OBJ ITER + // ... OBJ ITER DONE *LREF + if (emitted) { + if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE + return false; + } + + if (!ifThenElse.emitIfElse()) // ... OBJ ITER *LREF return false; - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER ARRAY + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ARRAY return false; - if (!ifThenElse.emitElse()) // ... OBJ ITER + if (!ifThenElse.emitElse()) // ... OBJ ITER *LREF return false; } // If iterator is not completed, create a new array with the rest // of the iterator. - if (!emit1(JSOP_DUP)) // ... OBJ ITER + if (!emitDupAt(emitted)) // ... OBJ ITER *LREF ITER return false; - if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER ITER ARRAY + if (!emitUint32Operand(JSOP_NEWARRAY, 0)) // ... OBJ ITER *LREF ITER ARRAY return false; - if (!emitNumberOp(0)) // ... OBJ ITER ITER ARRAY INDEX + if (!emitNumberOp(0)) // ... OBJ ITER *LREF ITER ARRAY INDEX return false; - if (!emitSpread()) // ... OBJ ITER ARRAY INDEX + if (!emitSpread()) // ... OBJ ITER *LREF ARRAY INDEX return false; - if (!emit1(JSOP_POP)) // ... OBJ ITER ARRAY + if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF ARRAY return false; if (!isHead) { @@ -4843,7 +4906,7 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav MOZ_ASSERT(ifThenElse.pushed() == 1); } - if (!emitDestructuringLHS(member, flav)) // ... OBJ ITER + if (!emitSetOrInitializeDestructuring(member, flav)) // ... OBJ ITER return false; MOZ_ASSERT(!hasNext); @@ -4861,69 +4924,91 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav MOZ_ASSERT(!subpattern->isKind(PNK_SPREAD)); + size_t emitted = 0; + if (!isElision) { + if (!emitDestructuringLHSRef(subpattern, &emitted)) // ... OBJ ITER ?DONE *LREF + return false; + } + IfThenElseEmitter ifAlreadyDone(this); if (!isHead) { // If this element is not the first element of the pattern, // iterator can already be completed. - // ... OBJ ITER DONE - if (hasNext) { - if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE DONE - return false; + // ... OBJ ITER DONE *LREF + if (emitted) { + if (hasNext) { + if (!emitDupAt(emitted)) // ... OBJ ITER DONE *LREF DONE + return false; + } else { + if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE + return false; + } + } else { + if (hasNext) { + // The position of LREF in the following stack comment + // isn't accurate for the operation, but it's equivalent + // since LREF is nothing + if (!emit1(JSOP_DUP)) // ... OBJ ITER DONE *LREF DONE + return false; + } } - if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE + if (!ifAlreadyDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF return false; - if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE UNDEF + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE UNDEF + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF return false; - if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE + if (!ifAlreadyDone.emitElse()) // ... OBJ ITER ?DONE *LREF return false; if (hasNext) { - if (!emit1(JSOP_POP)) // ... OBJ ITER + if (emitted) { + if (!emit2(JSOP_PICK, emitted)) // ... OBJ ITER *LREF DONE + return false; + } + if (!emit1(JSOP_POP)) // ... OBJ ITER *LREF return false; } } - if (!emit1(JSOP_DUP)) // ... OBJ ITER ITER - return false; - if (!emitIteratorNext(pattern)) // ... OBJ ITER RESULT + if (emitted) { + if (!emitDupAt(emitted)) // ... OBJ ITER *LREF ITER + return false; + } else { + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF ITER + return false; + } + if (!emitIteratorNext(pattern)) // ... OBJ ITER *LREF RESULT return false; - if (!emit1(JSOP_DUP)) // ... OBJ ITER RESULT RESULT + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT RESULT return false; - if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER RESULT DONE + if (!emitAtomOp(cx->names().done, JSOP_GETPROP)) // ... OBJ ITER *LREF RESULT DONE return false; if (hasNext) { - if (!emit1(JSOP_DUP)) // ... OBJ ITER RESULT DONE DONE + if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT DONE DONE + return false; + if (!emit2(JSOP_UNPICK, emitted + 2)) // ... OBJ ITER DONE *LREF RESULT DONE return false; } IfThenElseEmitter ifDone(this); - if (!ifDone.emitIfElse()) // ... OBJ ITER RESULT ?DONE + if (!ifDone.emitIfElse()) // ... OBJ ITER ?DONE *LREF RESULT return false; - if (hasNext) { - if (!emit1(JSOP_SWAP)) // ... OBJ ITER ?DONE RESULT - return false; - } - if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE + if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE *LREF return false; - if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE UNDEF + if (!emit1(JSOP_UNDEFINED)) // ... OBJ ITER ?DONE *LREF UNDEF return false; - if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE UNDEF + if (!emit1(JSOP_NOP_DESTRUCTURING)) // ... OBJ ITER ?DONE *LREF UNDEF return false; - if (!ifDone.emitElse()) // ... OBJ ITER RESULT ?DONE + if (!ifDone.emitElse()) // ... OBJ ITER ?DONE *LREF RESULT return false; - if (hasNext) { - if (!emit1(JSOP_SWAP)) // ... OBJ ITER ?DONE RESULT - return false; - } - if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE VALUE + if (!emitAtomOp(cx->names().value, JSOP_GETPROP)) // ... OBJ ITER ?DONE *LREF VALUE return false; if (!ifDone.emitEnd()) @@ -4937,13 +5022,16 @@ BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlav } if (pndefault) { - if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE VALUE + if (!emitDefault(pndefault, subpattern)) // ... OBJ ITER ?DONE *LREF VALUE return false; } if (!isElision) { - if (!emitDestructuringLHS(subpattern, flav)) // ... OBJ ITER ?DONE + if (!emitSetOrInitializeDestructuring(subpattern, + flav)) // ... OBJ ITER ?DONE + { return false; + } } else { if (!emit1(JSOP_POP)) // ... OBJ ITER ?DONE return false; @@ -4975,27 +5063,43 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla return false; for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) { - // Duplicate the value being destructured to use as a reference base. - if (!emit1(JSOP_DUP)) // ... RHS RHS + ParseNode* subpattern; + if (member->isKind(PNK_MUTATEPROTO)) + subpattern = member->pn_kid; + else + subpattern = member->pn_right; + ParseNode* lhs = subpattern; + if (lhs->isKind(PNK_ASSIGN)) + lhs = lhs->pn_left; + + size_t emitted; + if (!emitDestructuringLHSRef(lhs, &emitted)) // ... RHS *LREF return false; + // Duplicate the value being destructured to use as a reference base. + if (emitted) { + if (!emitDupAt(emitted)) // ... RHS *LREF RHS + return false; + } else { + if (!emit1(JSOP_DUP)) // ... RHS RHS + return false; + } + // Now push the property name currently being matched, which is the // current property name "label" on the left of a colon in the object // initialiser. bool needsGetElem = true; - ParseNode* subpattern; if (member->isKind(PNK_MUTATEPROTO)) { - if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS PROP + if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) // ... RHS *LREF PROP return false; needsGetElem = false; - subpattern = member->pn_kid; } else { MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND)); ParseNode* key = member->pn_left; if (key->isKind(PNK_NUMBER)) { - if (!emitNumberOp(key->pn_dval)) // ... RHS RHS KEY + if (!emitNumberOp(key->pn_dval)) // ... RHS *LREF RHS KEY return false; } else if (key->isKind(PNK_OBJECT_PROPERTY_NAME) || key->isKind(PNK_STRING)) { PropertyName* name = key->pn_atom->asPropertyName(); @@ -5005,33 +5109,30 @@ BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFla // as indexes for simplification of downstream analysis. jsid id = NameToId(name); if (id != IdToTypeId(id)) { - if (!emitTree(key)) // ... RHS RHS KEY + if (!emitTree(key)) // ... RHS *LREF RHS KEY return false; } else { - if (!emitAtomOp(name, JSOP_GETPROP)) // ...RHS PROP + if (!emitAtomOp(name, JSOP_GETPROP)) // ... RHS *LREF PROP return false; needsGetElem = false; } } else { - if (!emitComputedPropertyName(key)) // ... RHS RHS KEY + if (!emitComputedPropertyName(key)) // ... RHS *LREF RHS KEY return false; } - - subpattern = member->pn_right; } // Get the property value if not done already. - if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS PROP + if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) // ... RHS *LREF PROP return false; if (subpattern->isKind(PNK_ASSIGN)) { - if (!emitDefault(subpattern->pn_right, subpattern->pn_left)) + if (!emitDefault(subpattern->pn_right, lhs)) // ... RHS *LREF VALUE return false; - subpattern = subpattern->pn_left; } - // Destructure PROP per this member's subpattern. - if (!emitDestructuringLHS(subpattern, flav)) + // Destructure PROP per this member's lhs. + if (!emitSetOrInitializeDestructuring(subpattern, flav)) // ... RHS return false; } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index f09b529ed..066c06672 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -609,7 +609,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM // opcode onto the stack in the right order. In the case of SETELEM, the // value to be assigned must already be pushed. - enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign }; + enum class EmitElemOption { Get, Set, Call, IncDec, CompoundAssign, Ref }; MOZ_MUST_USE bool emitElemOperands(ParseNode* pn, EmitElemOption opts); MOZ_MUST_USE bool emitElemOpBase(JSOp op); @@ -642,10 +642,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter DestructuringAssignment }; - // emitDestructuringLHS assumes the to-be-destructured value has been pushed on - // the stack and emits code to destructure a single lhs expression (either a - // name or a compound []/{} expression). - MOZ_MUST_USE bool emitDestructuringLHS(ParseNode* target, DestructuringFlavor flav); + // emitDestructuringLHSRef emits the lhs expression's reference. + // If the lhs expression is object property |OBJ.prop|, it emits |OBJ|. + // If it's object element |OBJ[ELEM]|, it emits |OBJ| and |ELEM|. + // If there's nothing to evaluate for the reference, it emits nothing. + // |emitted| parameter receives the number of values pushed onto the stack. + MOZ_MUST_USE bool emitDestructuringLHSRef(ParseNode* target, size_t* emitted); + + // emitSetOrInitializeDestructuring assumes the lhs expression's reference + // and the to-be-destructured value has been pushed on the stack. It emits + // code to destructure a single lhs expression (either a name or a compound + // []/{} expression). + MOZ_MUST_USE bool emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav); // emitDestructuringOps assumes the to-be-destructured value has been // pushed on the stack and emits code to destructure each part of a [] or diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 4dcc10b61..2f0d41707 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1147,7 +1147,7 @@ BaselineCompiler::emit_JSOP_PICK() // after : A B D E C // First, move value at -(amount + 1) into R0. - int depth = -(GET_INT8(pc) + 1); + int32_t depth = -(GET_INT8(pc) + 1); masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0); // Move the other values down. @@ -1165,6 +1165,34 @@ BaselineCompiler::emit_JSOP_PICK() return true; } +bool +BaselineCompiler::emit_JSOP_UNPICK() +{ + frame.syncStack(0); + + // Pick takes the top of the stack value and moves it under the nth value. + // For instance, unpick 2: + // before: A B C D E + // after : A B E C D + + // First, move value at -1 into R0. + masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); + + // Move the other values up. + int32_t depth = -(GET_INT8(pc) + 1); + for (int32_t i = -1; i > depth; i--) { + Address source = frame.addressOfStackValue(frame.peek(i - 1)); + Address dest = frame.addressOfStackValue(frame.peek(i)); + masm.loadValue(source, R1); + masm.storeValue(R1, dest); + } + + // Store R0 under the nth value. + Address dest = frame.addressOfStackValue(frame.peek(depth)); + masm.storeValue(R0, dest); + return true; +} + bool BaselineCompiler::emit_JSOP_GOTO() { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 77f4dd005..60dac0966 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -42,6 +42,7 @@ namespace jit { _(JSOP_DUP2) \ _(JSOP_SWAP) \ _(JSOP_PICK) \ + _(JSOP_UNPICK) \ _(JSOP_GOTO) \ _(JSOP_IFEQ) \ _(JSOP_IFNE) \ diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 2e7784ff4..c4df415a4 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1563,6 +1563,7 @@ IonBuilder::traverseBytecode() case JSOP_DUP: case JSOP_DUP2: case JSOP_PICK: + case JSOP_UNPICK: case JSOP_SWAP: case JSOP_SETARG: case JSOP_SETLOCAL: @@ -2017,6 +2018,10 @@ IonBuilder::inspectOpcode(JSOp op) current->pick(-GET_INT8(pc)); return true; + case JSOP_UNPICK: + current->unpick(-GET_INT8(pc)); + return true; + case JSOP_GETALIASEDVAR: return jsop_getaliasedvar(EnvironmentCoordinate(pc)); diff --git a/js/src/jit/MIRGraph.cpp b/js/src/jit/MIRGraph.cpp index 3a363a5bf..d6e0fa8ff 100644 --- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -789,6 +789,19 @@ MBasicBlock::pick(int32_t depth) swapAt(depth); } +void +MBasicBlock::unpick(int32_t depth) +{ + // unpick take the top of the stack element and move it under the depth-th + // element; + // unpick(-2): + // A B C D E + // A B C E D [ swapAt(-1) ] + // A B E C D [ swapAt(-2) ] + for (int32_t n = -1; n >= depth; n--) + swapAt(n); +} + void MBasicBlock::swapAt(int32_t depth) { diff --git a/js/src/jit/MIRGraph.h b/js/src/jit/MIRGraph.h index b986218f4..705d70fa1 100644 --- a/js/src/jit/MIRGraph.h +++ b/js/src/jit/MIRGraph.h @@ -142,6 +142,9 @@ class MBasicBlock : public TempObject, public InlineListNode // Move the definition to the top of the stack. void pick(int32_t depth); + // Move the top of the stack definition under the depth-th stack value. + void unpick(int32_t depth); + // Exchange 2 stack slots at the defined depth void swapAt(int32_t depth); diff --git a/js/src/tests/ecma_6/Destructuring/order-super.js b/js/src/tests/ecma_6/Destructuring/order-super.js new file mode 100644 index 000000000..afe11e2d9 --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/order-super.js @@ -0,0 +1,727 @@ +var BUGNUMBER = 1204028; +var summary = "Destructuring should evaluate lhs reference before rhs in super property"; + +if (typeof assertEq === "undefined") { + assertEq = function(a, b) { + if (a !== b) + throw new Error(`expected ${b}, got ${a}\n${new Error().stack}`); + }; +} + +print(BUGNUMBER + ": " + summary); + +let logs = []; +function log(x) { + logs.push(x); +} + +let unwrapMap = new Map(); +function unwrap(maybeWrapped) { + if (unwrapMap.has(maybeWrapped)) + return unwrapMap.get(maybeWrapped); + return maybeWrapped; +} +function ToString(name) { + if (name == Symbol.iterator) + return "@@iterator"; + return String(name); +} +function logger(obj, prefix=[]) { + let wrapped = new Proxy(obj, { + get(that, name) { + if (name == "return") { + // FIXME: Bug 1147371. + // We ignore IteratorClose for now. + return obj[name]; + } + + let names = prefix.concat(ToString(name)); + log("rhs get " + names.join("::")); + let v = obj[name]; + if (typeof v === "object" || typeof v === "function") + return logger(v, names); + return v; + }, + apply(that, thisArg, args) { + let names = prefix.slice(); + log("rhs call " + names.join("::")); + let v = obj.apply(unwrap(thisArg), args); + if (typeof v === "object" || typeof v === "function") { + names[names.length - 1] += "()"; + return logger(v, names); + } + return v; + } + }); + unwrapMap.set(wrapped, obj); + return wrapped; +} + +class C1 { + constructor() { + this.clear(); + } + clear() { + this.values = {}; + } +} +for (let name of [ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "0", "1", "length" +]) { + Object.defineProperty(C1.prototype, name, { + set: function(value) { + log("lhs set " + name); + this.values[name] = value; + } + }); +} +class C2 extends C1 { + constructor() { + super(); + + let clear = () => { + logs = []; + this.clear(); + }; + + // Array. + + clear(); + [ + super.a + ] = logger(["A"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + clear(); + [ + super[ (log("lhs before name a"), "a") ] + ] = logger(["A"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + // Array rest. + + clear(); + [ + ...super.a + ] = logger(["A", "B", "C"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); + assertEq(this.values.a.join(","), "A,B,C"); + + clear(); + [ + ...super[ (log("lhs before name a"), "a") ] + ] = logger(["A", "B", "C"]);; + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); + assertEq(this.values.a.join(","), "A,B,C"); + + // Array combined. + + clear(); + [ + super.a, + super[ (log("lhs before name b"), "b") ], + ...super[ (log("lhs before name c"), "c") ] + ] = logger(["A", "B", "C"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "lhs before name b", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set b", + + "lhs before name c", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set c", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b, "B"); + assertEq(this.values.c.join(","), "C"); + + // Object. + + clear(); + ({ + a: super.a + } = logger({a: "A"})); + assertEq(logs.join(","), + [ + "rhs get a", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + clear(); + ({ + a: super[ (log("lhs before name a"), "a") ] + } = logger({a: "A"})); + assertEq(logs.join(","), + [ + "lhs before name a", + "rhs get a", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + // Object combined. + + clear(); + ({ + a: super.a, + b: super[ (log("lhs before name b"), "b") ] + } = logger({a: "A", b: "B"})); + assertEq(logs.join(","), + [ + "rhs get a", + "lhs set a", + + "lhs before name b", + "rhs get b", + "lhs set b", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b, "B"); + + // == Nested == + + // Array -> Array + + clear(); + [ + [ + super[ (log("lhs before name a"), "a") ], + ...super[ (log("lhs before name b"), "b") ] + ] + ] = logger([["A", "B"]]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name a", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set a", + + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "lhs set b", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b.length, 1); + assertEq(this.values.b[0], "B"); + + // Array rest -> Array + + clear(); + [ + ...[ + super[ (log("lhs before name a"), "a") ], + ...super[ (log("lhs before name b"), "b") ] + ] + ] = logger(["A", "B"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before name a", + "lhs set a", + + "lhs before name b", + "lhs set b", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b.join(","), "B"); + + // Array -> Object + clear(); + [ + { + a: super[ (log("lhs before name a"), "a") ] + } + ] = logger([{a: "A"}]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before name a", + "rhs get @@iterator()::next()::value::a", + "lhs set a", + ].join(",")); + assertEq(this.values.a, "A"); + + // Array rest -> Object + clear(); + [ + ...{ + 0: super[ (log("lhs before name 0"), "0") ], + 1: super[ (log("lhs before name 1"), "1") ], + length: super[ (log("lhs before name length"), "length") ], + } + ] = logger(["A", "B"]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before name 0", + "lhs set 0", + + "lhs before name 1", + "lhs set 1", + + "lhs before name length", + "lhs set length", + ].join(",")); + assertEq(this.values["0"], "A"); + assertEq(this.values["1"], "B"); + assertEq(this.values.length, 2); + + // Object -> Array + clear(); + ({ + a: [ + super[ (log("lhs before name b"), "b") ] + ] + } = logger({a: ["B"]})); + assertEq(logs.join(","), + [ + "rhs get a", + "rhs get a::@@iterator", + "rhs call a::@@iterator", + + "lhs before name b", + "rhs get a::@@iterator()::next", + "rhs call a::@@iterator()::next", + "rhs get a::@@iterator()::next()::done", + "rhs get a::@@iterator()::next()::value", + "lhs set b", + ].join(",")); + assertEq(this.values.b, "B"); + + // Object -> Object + clear(); + ({ + a: { + b: super[ (log("lhs before name b"), "b") ] + } + } = logger({a: {b: "B"}})); + assertEq(logs.join(","), + [ + "rhs get a", + "lhs before name b", + "rhs get a::b", + "lhs set b", + ].join(",")); + assertEq(this.values.b, "B"); + + // All combined + + clear(); + [ + super[ (log("lhs before name a"), "a") ], + [ + super[ (log("lhs before name b"), "b") ], + { + c: super[ (log("lhs before name c"), "c") ], + d: { + e: super[ (log("lhs before name e"), "e") ], + f: [ + super[ (log("lhs before name g"), "g") ] + ] + } + } + ], + { + h: super[ (log("lhs before name h"), "h") ], + i: [ + super[ (log("lhs before name j"), "j") ], + { + k: [ + super[ (log("lhs before name l"), "l") ] + ] + } + ] + }, + ...[ + super[ (log("lhs before name m"), "m") ], + [ + super[ (log("lhs before name n"), "n") ], + { + o: super[ (log("lhs before name o"), "o") ], + p: { + q: super[ (log("lhs before name q"), "q") ], + r: [ + super[ (log("lhs before name s"), "s") ] + ] + } + } + ], + ...{ + 0: super[ (log("lhs before name t"), "t") ], + 1: [ + super[ (log("lhs before name u"), "u") ], + { + v: super[ (log("lhs before name v"), "v") ], + w: { + x: super[ (log("lhs before name x"), "x") ], + y: [ + super[ (log("lhs before name z"), "z") ] + ] + } + } + ], + length: super[ (log("lhs before name length"), "length") ], + } + ] + ] = logger(["A", + ["B", {c: "C", d: {e: "E", f: ["G"]}}], + {h: "H", i: ["J", {k: ["L"]}]}, + "M", + ["N", {o: "O", p: {q: "Q", r: ["S"]}}], + "T", ["U", {v: "V", w: {x: "X", y: ["Z"]}}]]); + assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set b", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before name c", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::c", + "lhs set c", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d", + + "lhs before name e", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::e", + "lhs set e", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + + "lhs before name g", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::value", + "lhs set g", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before name h", + "rhs get @@iterator()::next()::value::h", + "lhs set h", + + "rhs get @@iterator()::next()::value::i", + "rhs get @@iterator()::next()::value::i::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator", + + "lhs before name j", + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + "lhs set j", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + + "lhs before name l", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::value", + "lhs set l", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before name m", + "lhs set m", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name n", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set n", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before name o", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::o", + "lhs set o", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p", + + "lhs before name q", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::q", + "lhs set q", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + + "lhs before name s", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::value", + "lhs set s", + + "lhs before name t", + "lhs set t", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before name u", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set u", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before name v", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::v", + "lhs set v", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w", + + "lhs before name x", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::x", + "lhs set x", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + + "lhs before name z", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::value", + "lhs set z", + + "lhs before name length", + "lhs set length", + ].join(",")); + assertEq(this.values.a, "A"); + assertEq(this.values.b, "B"); + assertEq(this.values.c, "C"); + assertEq(this.values.e, "E"); + assertEq(this.values.g, "G"); + assertEq(this.values.h, "H"); + assertEq(this.values.j, "J"); + assertEq(this.values.l, "L"); + assertEq(this.values.m, "M"); + assertEq(this.values.n, "N"); + assertEq(this.values.o, "O"); + assertEq(this.values.q, "Q"); + assertEq(this.values.s, "S"); + assertEq(this.values.t, "T"); + assertEq(this.values.u, "U"); + assertEq(this.values.v, "V"); + assertEq(this.values.x, "X"); + assertEq(this.values.z, "Z"); + assertEq(this.values.length, 2); + } +} + +new C2(); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Destructuring/order.js b/js/src/tests/ecma_6/Destructuring/order.js new file mode 100644 index 000000000..4eb48cd2b --- /dev/null +++ b/js/src/tests/ecma_6/Destructuring/order.js @@ -0,0 +1,745 @@ +var BUGNUMBER = 1204028; +var summary = "Destructuring should evaluate lhs reference before rhs"; + +print(BUGNUMBER + ": " + summary); + +let storage = { + clear() { + this.values = {}; + } +}; +storage.clear(); +let obj = new Proxy(storage, { + set(that, name, value) { + log("lhs set " + name); + storage.values[name] = value; + } +}); + +let logs = []; +function log(x) { + logs.push(x); +} + +function clear() { + logs = []; + storage.clear(); +} + +let unwrapMap = new Map(); +function unwrap(maybeWrapped) { + if (unwrapMap.has(maybeWrapped)) + return unwrapMap.get(maybeWrapped); + return maybeWrapped; +} +function ToString(name) { + if (name == Symbol.iterator) + return "@@iterator"; + return String(name); +} +function logger(obj, prefix=[]) { + let wrapped = new Proxy(obj, { + get(that, name) { + if (name == "return") { + // FIXME: Bug 1147371. + // We ignore IteratorClose for now. + return obj[name]; + } + + let names = prefix.concat(ToString(name)); + log("rhs get " + names.join("::")); + let v = obj[name]; + if (typeof v === "object" || typeof v === "function") + return logger(v, names); + return v; + }, + apply(that, thisArg, args) { + let names = prefix.slice(); + log("rhs call " + names.join("::")); + let v = obj.apply(unwrap(thisArg), args); + if (typeof v === "object" || typeof v === "function") { + names[names.length - 1] += "()"; + return logger(v, names); + } + return v; + } + }); + unwrapMap.set(wrapped, obj); + return wrapped; +} + +// Array. + +clear(); +[ + ( log("lhs before obj a"), obj ).a +] = logger(["A"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +clear(); +[ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] +] = logger(["A"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +// Array rest. + +clear(); +[ + ...( log("lhs before obj a"), obj ).a +] = logger(["A", "B", "C"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); +assertEq(storage.values.a.join(","), "A,B,C"); + +clear(); +[ + ...( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] +] = logger(["A", "B", "C"]);; +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set a", + ].join(",")); +assertEq(storage.values.a.join(","), "A,B,C"); + +// Array combined. + +clear(); +[ + ( log("lhs before obj a"), obj ).a, + ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ], + ...( log("lhs before obj c"), obj )[ (log("lhs before name c"), "c") ] +] = logger(["A", "B", "C"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set b", + + "lhs before obj c", + "lhs before name c", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "lhs set c", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b, "B"); +assertEq(storage.values.c.join(","), "C"); + +// Object. + +clear(); +({ + a: ( log("lhs before obj a"), obj ).a +} = logger({a: "A"})); +assertEq(logs.join(","), + [ + "lhs before obj a", + "rhs get a", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +clear(); +({ + a: ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] +} = logger({a: "A"})); +assertEq(logs.join(","), + [ + "lhs before obj a", + "lhs before name a", + "rhs get a", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +// Object combined. + +clear(); +({ + a: ( log("lhs before obj a"), obj ).a, + b: ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] +} = logger({a: "A", b: "B"})); +assertEq(logs.join(","), + [ + "lhs before obj a", + "rhs get a", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "rhs get b", + "lhs set b", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b, "B"); + +// == Nested == + +// Array -> Array + +clear(); +[ + [ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ], + ...( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + ] +] = logger([["A", "B"]]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "lhs set b", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b.length, 1); +assertEq(storage.values.b[0], "B"); + +// Array rest -> Array + +clear(); +[ + ...[ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ], + ...( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + ] +] = logger(["A", "B"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before obj a", + "lhs before name a", + "lhs set a", + + "lhs before obj b", + "lhs before name b", + "lhs set b", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b.join(","), "B"); + +// Array -> Object +clear(); +[ + { + a: ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ] + } +] = logger([{a: "A"}]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next()::value::a", + "lhs set a", + ].join(",")); +assertEq(storage.values.a, "A"); + +// Array rest -> Object +clear(); +[ + ...{ + 0: ( log("lhs before obj 0"), obj )[ (log("lhs before name 0"), "0") ], + 1: ( log("lhs before obj 1"), obj )[ (log("lhs before name 1"), "1") ], + length: ( log("lhs before obj length"), obj )[ (log("lhs before name length"), "length") ], + } +] = logger(["A", "B"]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before obj 0", + "lhs before name 0", + "lhs set 0", + + "lhs before obj 1", + "lhs before name 1", + "lhs set 1", + + "lhs before obj length", + "lhs before name length", + "lhs set length", + ].join(",")); +assertEq(storage.values["0"], "A"); +assertEq(storage.values["1"], "B"); +assertEq(storage.values.length, 2); + +// Object -> Array +clear(); +({ + a: [ + ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + ] +} = logger({a: ["B"]})); +assertEq(logs.join(","), + [ + "rhs get a", + "rhs get a::@@iterator", + "rhs call a::@@iterator", + + "lhs before obj b", + "lhs before name b", + "rhs get a::@@iterator()::next", + "rhs call a::@@iterator()::next", + "rhs get a::@@iterator()::next()::done", + "rhs get a::@@iterator()::next()::value", + "lhs set b", + ].join(",")); +assertEq(storage.values.b, "B"); + +// Object -> Object +clear(); +({ + a: { + b: ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ] + } +} = logger({a: {b: "B"}})); +assertEq(logs.join(","), + [ + "rhs get a", + "lhs before obj b", + "lhs before name b", + "rhs get a::b", + "lhs set b", + ].join(",")); +assertEq(storage.values.b, "B"); + +// All combined + +clear(); +[ + ( log("lhs before obj a"), obj )[ (log("lhs before name a"), "a") ], + [ + ( log("lhs before obj b"), obj )[ (log("lhs before name b"), "b") ], + { + c: ( log("lhs before obj c"), obj )[ (log("lhs before name c"), "c") ], + d: { + e: ( log("lhs before obj e"), obj )[ (log("lhs before name e"), "e") ], + f: [ + ( log("lhs before obj g"), obj )[ (log("lhs before name g"), "g") ] + ] + } + } + ], + { + h: ( log("lhs before obj h"), obj )[ (log("lhs before name h"), "h") ], + i: [ + ( log("lhs before obj j"), obj )[ (log("lhs before name j"), "j") ], + { + k: [ + ( log("lhs before obj l"), obj )[ (log("lhs before name l"), "l") ] + ] + } + ] + }, + ...[ + ( log("lhs before obj m"), obj )[ (log("lhs before name m"), "m") ], + [ + ( log("lhs before obj n"), obj )[ (log("lhs before name n"), "n") ], + { + o: ( log("lhs before obj o"), obj )[ (log("lhs before name o"), "o") ], + p: { + q: ( log("lhs before obj q"), obj )[ (log("lhs before name q"), "q") ], + r: [ + ( log("lhs before obj s"), obj )[ (log("lhs before name s"), "s") ] + ] + } + } + ], + ...{ + 0: ( log("lhs before obj t"), obj )[ (log("lhs before name t"), "t") ], + 1: [ + ( log("lhs before obj u"), obj )[ (log("lhs before name u"), "u") ], + { + v: ( log("lhs before obj v"), obj )[ (log("lhs before name v"), "v") ], + w: { + x: ( log("lhs before obj x"), obj )[ (log("lhs before name x"), "x") ], + y: [ + ( log("lhs before obj z"), obj )[ (log("lhs before name z"), "z") ] + ] + } + } + ], + length: ( log("lhs before obj length"), obj )[ (log("lhs before name length"), "length") ], + } + ] +] = logger(["A", + ["B", {c: "C", d: {e: "E", f: ["G"]}}], + {h: "H", i: ["J", {k: ["L"]}]}, + "M", + ["N", {o: "O", p: {q: "Q", r: ["S"]}}], + "T", ["U", {v: "V", w: {x: "X", y: ["Z"]}}]]); +assertEq(logs.join(","), + [ + "rhs get @@iterator", + "rhs call @@iterator", + + "lhs before obj a", + "lhs before name a", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "lhs set a", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj b", + "lhs before name b", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set b", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before obj c", + "lhs before name c", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::c", + "lhs set c", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d", + + "lhs before obj e", + "lhs before name e", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::e", + "lhs set e", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator", + + "lhs before obj g", + "lhs before name g", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::d::f::@@iterator()::next()::value", + "lhs set g", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + + "lhs before obj h", + "lhs before name h", + "rhs get @@iterator()::next()::value::h", + "lhs set h", + + "rhs get @@iterator()::next()::value::i", + "rhs get @@iterator()::next()::value::i::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator", + + "lhs before obj j", + "lhs before name j", + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + "lhs set j", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value", + + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator", + + "lhs before obj l", + "lhs before name l", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs call @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::i::@@iterator()::next()::value::k::@@iterator()::next()::value", + "lhs set l", + + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + "rhs get @@iterator()::next()::value", + "rhs get @@iterator()::next", + "rhs call @@iterator()::next", + "rhs get @@iterator()::next()::done", + + "lhs before obj m", + "lhs before name m", + "lhs set m", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj n", + "lhs before name n", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set n", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before obj o", + "lhs before name o", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::o", + "lhs set o", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p", + + "lhs before obj q", + "lhs before name q", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::q", + "lhs set q", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator", + + "lhs before obj s", + "lhs before name s", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::p::r::@@iterator()::next()::value", + "lhs set s", + + "lhs before obj t", + "lhs before name t", + "lhs set t", + + "rhs get @@iterator()::next()::value::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator", + + "lhs before obj u", + "lhs before name u", + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + "lhs set u", + + "rhs get @@iterator()::next()::value::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value", + + "lhs before obj v", + "lhs before name v", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::v", + "lhs set v", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w", + + "lhs before obj x", + "lhs before name x", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::x", + "lhs set x", + + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator", + + "lhs before obj z", + "lhs before name z", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs call @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::done", + "rhs get @@iterator()::next()::value::@@iterator()::next()::value::w::y::@@iterator()::next()::value", + "lhs set z", + + "lhs before obj length", + "lhs before name length", + "lhs set length", + ].join(",")); +assertEq(storage.values.a, "A"); +assertEq(storage.values.b, "B"); +assertEq(storage.values.c, "C"); +assertEq(storage.values.e, "E"); +assertEq(storage.values.g, "G"); +assertEq(storage.values.h, "H"); +assertEq(storage.values.j, "J"); +assertEq(storage.values.l, "L"); +assertEq(storage.values.m, "M"); +assertEq(storage.values.n, "N"); +assertEq(storage.values.o, "O"); +assertEq(storage.values.q, "Q"); +assertEq(storage.values.s, "S"); +assertEq(storage.values.t, "T"); +assertEq(storage.values.u, "U"); +assertEq(storage.values.v, "V"); +assertEq(storage.values.x, "X"); +assertEq(storage.values.z, "Z"); +assertEq(storage.values.length, 2); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 9cba1f4dc..8ae9c43b0 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1860,7 +1860,6 @@ CASE(EnableInterruptsPseudoOpcode) /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) -CASE(JSOP_UNUSED183) CASE(JSOP_UNUSED187) CASE(JSOP_UNUSED192) CASE(JSOP_UNUSED209) @@ -2193,6 +2192,16 @@ CASE(JSOP_PICK) } END_CASE(JSOP_PICK) +CASE(JSOP_UNPICK) +{ + int i = GET_UINT8(REGS.pc); + MOZ_ASSERT(REGS.stackDepth() >= unsigned(i) + 1); + Value lval = REGS.sp[-1]; + memmove(REGS.sp - i, REGS.sp - (i + 1), sizeof(Value) * i); + REGS.sp[-(i + 1)] = lval; +} +END_CASE(JSOP_UNPICK) + CASE(JSOP_BINDGNAME) CASE(JSOP_BINDNAME) { diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index f6636004d..b59b9388c 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1880,8 +1880,14 @@ * Stack: fun, name => fun */ \ macro(JSOP_SETFUNNAME, 182,"setfunname", NULL, 2, 2, 1, JOF_UINT8) \ - macro(JSOP_UNUSED183, 183,"unused183", NULL, 1, 0, 0, JOF_BYTE) \ - \ + /* + * Moves the top of the stack value under the nth element of the stack. + * Category: Operators + * Type: Stack Operations + * Operands: uint8_t n + * Stack: v[n], v[n-1], ..., v[1], v[0] => v[0], v[n], v[n-1], ..., v[1] + */ \ + macro(JSOP_UNPICK, 183,"unpick", NULL, 2, 0, 0, JOF_UINT8) \ /* * Pops the top of stack value, pushes property of it onto the stack. * -- cgit v1.2.3