mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-09 01:08:39 +09:00
Bug 1204028: Evaluate LHS reference before RHS in destructuring
Issue #73 [Depends on] Bug 1147371: Implement IteratorClose
This commit is contained in:
parent
078b9f3edc
commit
2f27d8433c
11 changed files with 1743 additions and 97 deletions
|
|
@ -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<PropertyAccess>().isSuper()) {
|
||||
if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression()))
|
||||
return false;
|
||||
*emitted = 2;
|
||||
} else {
|
||||
if (!emitTree(target->pn_expr))
|
||||
return false;
|
||||
*emitted = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PNK_ELEM: {
|
||||
if (target->as<PropertyByValue>().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<PropertyAccess>().isSuper()) {
|
||||
if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression()))
|
||||
return false;
|
||||
if (!emit2(JSOP_PICK, 2))
|
||||
return false;
|
||||
if (target->as<PropertyAccess>().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<PropertyByValue>().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
|
||||
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 (!emitIteratorNext(pattern)) // ... OBJ ITER RESULT
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ ITER *LREF RESULT RESULT
|
||||
return false;
|
||||
if (!emit1(JSOP_DUP)) // ... OBJ ITER 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ namespace jit {
|
|||
_(JSOP_DUP2) \
|
||||
_(JSOP_SWAP) \
|
||||
_(JSOP_PICK) \
|
||||
_(JSOP_UNPICK) \
|
||||
_(JSOP_GOTO) \
|
||||
_(JSOP_IFEQ) \
|
||||
_(JSOP_IFNE) \
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -142,6 +142,9 @@ class MBasicBlock : public TempObject, public InlineListNode<MBasicBlock>
|
|||
// 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);
|
||||
|
||||
|
|
|
|||
727
js/src/tests/ecma_6/Destructuring/order-super.js
Normal file
727
js/src/tests/ecma_6/Destructuring/order-super.js
Normal file
|
|
@ -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);
|
||||
745
js/src/tests/ecma_6/Destructuring/order.js
Normal file
745
js/src/tests/ecma_6/Destructuring/order.js
Normal file
|
|
@ -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);
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue