Skip to content

Commit

Permalink
Bug 1629795 - Part 3: Ignore exceptions in IteratorClose for Throw co…
Browse files Browse the repository at this point in the history
…mpletions. r=arai

Implements the changes from: tc39/ecma262#1408

The spec PR requires to start the non-syntactic `try` block before retrieving
the "return" property and checking whether or not the "return" property is
callable. As part of this change we can also reorder the other byte code
instructions, which enables us to make the code more similar to normal JS code.
The equivalent JS code is documented in the added comments. Furthermore these
changes allow us to remove the manual stack depth fixups.

Depends on D70819

Differential Revision: https://phabricator.services.mozilla.com/D70820
  • Loading branch information
anba committed Apr 15, 2020
1 parent 3657659 commit c478c02
Showing 1 changed file with 69 additions and 74 deletions.
143 changes: 69 additions & 74 deletions js/src/frontend/BytecodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2856,21 +2856,52 @@ BytecodeEmitter::emitIteratorCloseInScope(EmitterScope& currentScope,
".close() on iterators is prohibited in self-hosted code because it "
"can run user-modifiable iteration code");

// Generate inline logic corresponding to IteratorClose (ES 7.4.6).
// Generate inline logic corresponding to IteratorClose (ES2021 7.4.6) and
// AsyncIteratorClose (ES2021 7.4.7). Steps numbers apply to both operations.
//
// Callers need to ensure that the iterator object is at the top of the
// stack.

// For non-Throw completions, we emit the equivalent of:
//
// var returnMethod = GetMethod(iterator, "return");
// if (returnMethod !== undefined) {
// var innerResult = [Await] Call(returnMethod, iterator);
// CheckIsObj(innerResult);
// }
//
// Whereas for Throw completions, we emit:
//
// try {
// var returnMethod = GetMethod(iterator, "return");
// if (returnMethod !== undefined) {
// [Await] Call(returnMethod, iterator);
// }
// } catch {}

Maybe<TryEmitter> tryCatch;

if (completionKind == CompletionKind::Throw) {
tryCatch.emplace(this, TryEmitter::Kind::TryCatch,
TryEmitter::ControlKind::NonSyntactic);

if (!tryCatch->emitTry()) { // ... ITER
return false;
}
}

if (!emit1(JSOP_DUP)) // ... ITER ITER
return false;

// Step 3.
// Steps 1-2 are assertions, step 3 is implicit.

// Step 4.
//
// Get the "return" method.
if (!emitAtomOp(cx->names().return_, JSOP_CALLPROP)) // ... ITER RET
return false;

// Step 4.
// Step 5.
//
// Do nothing if "return" is undefined or null.
InternalIfEmitter ifReturnMethodIsDefined(this);
Expand All @@ -2880,99 +2911,46 @@ BytecodeEmitter::emitIteratorCloseInScope(EmitterScope& currentScope,
if (!ifReturnMethodIsDefined.emitThenElse()) // ... ITER RET
return false;

if (completionKind == CompletionKind::Throw) {
// 7.4.6 IteratorClose ( iterator, completion )
// ...
// 3. Let return be ? GetMethod(iterator, "return").
// 4. If return is undefined, return Completion(completion).
// 5. Let innerResult be Call(return, iterator, « »).
// 6. If completion.[[Type]] is throw, return Completion(completion).
// 7. If innerResult.[[Type]] is throw, return
// Completion(innerResult).
//
// For CompletionKind::Normal case, JSOP_CALL for step 5 checks if RET
// is callable, and throws if not. Since step 6 doesn't match and
// error handling in step 3 and step 7 can be merged.
//
// For CompletionKind::Throw case, an error thrown by JSOP_CALL for
// step 5 is ignored by try-catch. So we should check if RET is
// callable here, outside of try-catch, and the throw immediately if
// not.
CheckIsCallableKind kind = CheckIsCallableKind::IteratorReturn;
if (!emitCheckIsCallable(kind)) // ... ITER RET
return false;
}

// Steps 5, 8.
// Steps 5.c, 7.
//
// Call "return" if it is not undefined or null, and check that it returns
// an Object.
// Call the "return" method.
if (!emit1(JSOP_SWAP)) // ... RET ITER
return false;

Maybe<TryEmitter> tryCatch;

if (completionKind == CompletionKind::Throw) {
tryCatch.emplace(this, TryEmitter::Kind::TryCatch, TryEmitter::ControlKind::NonSyntactic);

// Mutate stack to balance stack for try-catch.
if (!emit1(JSOP_UNDEFINED)) // ... RET ITER UNDEF
return false;
if (!tryCatch->emitTry()) // ... RET ITER UNDEF
return false;
if (!emitDupAt(2, 2)) // ... RET ITER UNDEF RET ITER
return false;
}

if (!emitCall(JSOP_CALL, 0)) // ... ... RESULT
if (!emitCall(JSOP_CALL, 0)) // ... RESULT
return false;
checkTypeSet(JSOP_CALL);

// 7.4.7 AsyncIteratorClose, step 5.d.
if (iterKind == IteratorKind::Async) {
if (completionKind != CompletionKind::Throw) {
// Await clobbers rval, so save the current rval.
if (!emit1(JSOP_GETRVAL)) // ... ... RESULT RVAL
if (!emit1(JSOP_GETRVAL)) // ... RESULT RVAL
return false;
if (!emit1(JSOP_SWAP)) // ... ... RVAL RESULT
if (!emit1(JSOP_SWAP)) // ... RVAL RESULT
return false;
}
if (!emitAwaitInScope(currentScope)) // ... ... RVAL? RESULT
return false;
}

if (completionKind == CompletionKind::Throw) {
if (!emit1(JSOP_SWAP)) // ... RET ITER RESULT UNDEF
return false;
if (!emit1(JSOP_POP)) // ... RET ITER RESULT
return false;

if (!tryCatch->emitCatch()) // ... RET ITER RESULT EXC
if (!emitAwaitInScope(currentScope)) // ... RVAL? RESULT
return false;

// Just ignore the exception thrown by call and await.
if (!emit1(JSOP_POP)) // ... RET ITER RESULT
return false;

if (!tryCatch->emitEnd()) // ... RET ITER RESULT
return false;

// Restore stack.
if (!emit2(JSOP_UNPICK, 2)) // ... RESULT RET ITER
return false;
if (!emitPopN(2)) // ... RESULT
return false;
} else {
if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) // ... RVAL? RESULT
return false;

if (iterKind == IteratorKind::Async) {
if (completionKind != CompletionKind::Throw) {
if (!emit1(JSOP_SWAP)) // ... RESULT RVAL
return false;
if (!emit1(JSOP_SETRVAL)) // ... RESULT
return false;
}
}

// Step 6 (Handled in caller).

// Step 8.
if (completionKind != CompletionKind::Throw) {
// Check that the "return" result is an object.
if (!emitCheckIsObj(CheckIsObjectKind::IteratorReturn)) { // ... RESULT
return false;
}
}

if (!ifReturnMethodIsDefined.emitElse()) // ... ITER RET
return false;

Expand All @@ -2982,6 +2960,23 @@ BytecodeEmitter::emitIteratorCloseInScope(EmitterScope& currentScope,
if (!ifReturnMethodIsDefined.emitEnd())
return false;

if (completionKind == CompletionKind::Throw) { // ... ITER EXC
if (!tryCatch->emitCatch()) {
return false;
}

// Just ignore the exception thrown by call and await.
if (!emit1(JSOP_POP)) { // ... ITER
return false;
}

if (!tryCatch->emitEnd()) { // ... ITER
return false;
}
}

// Step 9 (Handled in caller).

return emit1(JSOP_POP); // ...
}

Expand Down

0 comments on commit c478c02

Please sign in to comment.