mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-31 11:37:30 +09:00
Bug 1346862 - Fix IteratorClose due to non-local jumps being catchable by try statements inside for-of
Issue #74
This commit is contained in:
parent
5bf58e6044
commit
8af5664922
7 changed files with 113 additions and 18 deletions
|
|
@ -2065,7 +2065,11 @@ class ForOfLoopControl : public LoopControl
|
||||||
|
|
||||||
bool emitIteratorClose(BytecodeEmitter* bce,
|
bool emitIteratorClose(BytecodeEmitter* bce,
|
||||||
CompletionKind completionKind = CompletionKind::Normal) {
|
CompletionKind completionKind = CompletionKind::Normal) {
|
||||||
return bce->emitIteratorClose(completionKind, allowSelfHosted_);
|
ptrdiff_t start = bce->offset();
|
||||||
|
if (!bce->emitIteratorClose(completionKind, allowSelfHosted_))
|
||||||
|
return false;
|
||||||
|
ptrdiff_t end = bce->offset();
|
||||||
|
return bce->tryNoteList.append(JSTRY_FOR_OF_ITERCLOSE, 0, start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool emitPrepareForNonLocalJump(BytecodeEmitter* bce, bool isTarget) {
|
bool emitPrepareForNonLocalJump(BytecodeEmitter* bce, bool isTarget) {
|
||||||
|
|
@ -2614,17 +2618,6 @@ BytecodeEmitter::emitUint32Operand(JSOp op, uint32_t operand)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
|
||||||
BytecodeEmitter::flushPops(int* npops)
|
|
||||||
{
|
|
||||||
MOZ_ASSERT(*npops != 0);
|
|
||||||
if (!emitUint16Operand(JSOP_POPN, *npops))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
*npops = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class NonLocalExitControl
|
class NonLocalExitControl
|
||||||
|
|
@ -2718,8 +2711,9 @@ NonLocalExitControl::prepareForNonLocalJump(BytecodeEmitter::NestableControl* ta
|
||||||
bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue;
|
bool emitIteratorCloseAtTarget = emitIteratorClose && kind_ != Continue;
|
||||||
|
|
||||||
auto flushPops = [&npops](BytecodeEmitter* bce) {
|
auto flushPops = [&npops](BytecodeEmitter* bce) {
|
||||||
if (npops && !bce->flushPops(&npops))
|
if (npops && !bce->emitUint16Operand(JSOP_POPN, npops))
|
||||||
return false;
|
return false;
|
||||||
|
npops = 0;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -452,8 +452,6 @@ struct MOZ_STACK_CLASS BytecodeEmitter
|
||||||
|
|
||||||
JSOp strictifySetNameOp(JSOp op);
|
JSOp strictifySetNameOp(JSOp op);
|
||||||
|
|
||||||
MOZ_MUST_USE bool flushPops(int* npops);
|
|
||||||
|
|
||||||
MOZ_MUST_USE bool emitCheck(ptrdiff_t delta, ptrdiff_t* offset);
|
MOZ_MUST_USE bool emitCheck(ptrdiff_t delta, ptrdiff_t* offset);
|
||||||
|
|
||||||
// Emit one bytecode.
|
// Emit one bytecode.
|
||||||
|
|
|
||||||
|
|
@ -436,6 +436,8 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx
|
||||||
if (!script->hasTrynotes())
|
if (!script->hasTrynotes())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
bool inForOfIterClose = false;
|
||||||
|
|
||||||
for (TryNoteIterIon tni(cx, frame); !tni.done(); ++tni) {
|
for (TryNoteIterIon tni(cx, frame); !tni.done(); ++tni) {
|
||||||
JSTryNote* tn = *tni;
|
JSTryNote* tn = *tni;
|
||||||
|
|
||||||
|
|
@ -447,12 +449,23 @@ HandleExceptionIon(JSContext* cx, const InlineFrameIterator& frame, ResumeFromEx
|
||||||
CloseLiveIteratorIon(cx, frame, tn);
|
CloseLiveIteratorIon(cx, frame, tn);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case JSTRY_FOR_OF_ITERCLOSE:
|
||||||
|
inForOfIterClose = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case JSTRY_FOR_OF:
|
case JSTRY_FOR_OF:
|
||||||
|
inForOfIterClose = false;
|
||||||
|
break;
|
||||||
|
|
||||||
case JSTRY_LOOP:
|
case JSTRY_LOOP:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case JSTRY_CATCH:
|
case JSTRY_CATCH:
|
||||||
if (cx->isExceptionPending()) {
|
if (cx->isExceptionPending()) {
|
||||||
|
// See corresponding comment in ProcessTryNotes.
|
||||||
|
if (inForOfIterClose)
|
||||||
|
break;
|
||||||
|
|
||||||
// Ion can compile try-catch, but bailing out to catch
|
// Ion can compile try-catch, but bailing out to catch
|
||||||
// exceptions is slow. Reset the warm-up counter so that if we
|
// exceptions is slow. Reset the warm-up counter so that if we
|
||||||
// catch many exceptions we won't Ion-compile the script.
|
// catch many exceptions we won't Ion-compile the script.
|
||||||
|
|
@ -583,6 +596,7 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen
|
||||||
ResumeFromException* rfe, jsbytecode** pc)
|
ResumeFromException* rfe, jsbytecode** pc)
|
||||||
{
|
{
|
||||||
RootedScript script(cx, frame.baselineFrame()->script());
|
RootedScript script(cx, frame.baselineFrame()->script());
|
||||||
|
bool inForOfIterClose = false;
|
||||||
|
|
||||||
for (TryNoteIterBaseline tni(cx, frame.baselineFrame(), *pc); !tni.done(); ++tni) {
|
for (TryNoteIterBaseline tni(cx, frame.baselineFrame(), *pc); !tni.done(); ++tni) {
|
||||||
JSTryNote* tn = *tni;
|
JSTryNote* tn = *tni;
|
||||||
|
|
@ -593,7 +607,11 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen
|
||||||
// If we're closing a legacy generator, we have to skip catch
|
// If we're closing a legacy generator, we have to skip catch
|
||||||
// blocks.
|
// blocks.
|
||||||
if (cx->isClosingGenerator())
|
if (cx->isClosingGenerator())
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
|
// See corresponding comment in ProcessTryNotes.
|
||||||
|
if (inForOfIterClose)
|
||||||
|
break;
|
||||||
|
|
||||||
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
|
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
|
||||||
|
|
||||||
|
|
@ -609,6 +627,10 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen
|
||||||
}
|
}
|
||||||
|
|
||||||
case JSTRY_FINALLY: {
|
case JSTRY_FINALLY: {
|
||||||
|
// See corresponding comment in ProcessTryNotes.
|
||||||
|
if (inForOfIterClose)
|
||||||
|
break;
|
||||||
|
|
||||||
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
|
SettleOnTryNote(cx, tn, frame, ei, rfe, pc);
|
||||||
rfe->kind = ResumeFromException::RESUME_FINALLY;
|
rfe->kind = ResumeFromException::RESUME_FINALLY;
|
||||||
rfe->target = script->baselineScript()->nativeCodeForPC(script, *pc);
|
rfe->target = script->baselineScript()->nativeCodeForPC(script, *pc);
|
||||||
|
|
@ -652,7 +674,14 @@ ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, Environmen
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case JSTRY_FOR_OF_ITERCLOSE:
|
||||||
|
inForOfIterClose = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case JSTRY_FOR_OF:
|
case JSTRY_FOR_OF:
|
||||||
|
inForOfIterClose = false;
|
||||||
|
break;
|
||||||
|
|
||||||
case JSTRY_LOOP:
|
case JSTRY_LOOP:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ enum JSTryNoteKind {
|
||||||
JSTRY_FOR_IN,
|
JSTRY_FOR_IN,
|
||||||
JSTRY_FOR_OF,
|
JSTRY_FOR_OF,
|
||||||
JSTRY_LOOP,
|
JSTRY_LOOP,
|
||||||
|
JSTRY_FOR_OF_ITERCLOSE,
|
||||||
JSTRY_DESTRUCTURING_ITERCLOSE
|
JSTRY_DESTRUCTURING_ITERCLOSE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2599,6 +2599,8 @@ TryNoteName(JSTryNoteKind kind)
|
||||||
return "for-of";
|
return "for-of";
|
||||||
case JSTRY_LOOP:
|
case JSTRY_LOOP:
|
||||||
return "loop";
|
return "loop";
|
||||||
|
case JSTRY_FOR_OF_ITERCLOSE:
|
||||||
|
return "for-of-iterclose";
|
||||||
case JSTRY_DESTRUCTURING_ITERCLOSE:
|
case JSTRY_DESTRUCTURING_ITERCLOSE:
|
||||||
return "dstr-iterclose";
|
return "dstr-iterclose";
|
||||||
}
|
}
|
||||||
|
|
@ -2612,14 +2614,14 @@ TryNotes(JSContext* cx, HandleScript script, Sprinter* sp)
|
||||||
if (!script->hasTrynotes())
|
if (!script->hasTrynotes())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (sp->put("\nException table:\nkind stack start end\n") < 0)
|
if (sp->put("\nException table:\nkind stack start end\n") < 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
JSTryNote* tn = script->trynotes()->vector;
|
JSTryNote* tn = script->trynotes()->vector;
|
||||||
JSTryNote* tnlimit = tn + script->trynotes()->length;
|
JSTryNote* tnlimit = tn + script->trynotes()->length;
|
||||||
do {
|
do {
|
||||||
uint32_t startOff = script->pcToOffset(script->main()) + tn->start;
|
uint32_t startOff = script->pcToOffset(script->main()) + tn->start;
|
||||||
if (!sp->jsprintf(" %-14s %6u %8u %8u\n",
|
if (!sp->jsprintf(" %-16s %6u %8u %8u\n",
|
||||||
TryNoteName(static_cast<JSTryNoteKind>(tn->kind)),
|
TryNoteName(static_cast<JSTryNoteKind>(tn->kind)),
|
||||||
tn->stackDepth, startOff, startOff + tn->length))
|
tn->stackDepth, startOff, startOff + tn->length))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
function test() {
|
||||||
|
var returnCalled = 0;
|
||||||
|
var returnCalledExpected = 0;
|
||||||
|
var catchEntered = 0;
|
||||||
|
var finallyEntered = 0;
|
||||||
|
var finallyEnteredExpected = 0;
|
||||||
|
var iterable = {};
|
||||||
|
iterable[Symbol.iterator] = makeIterator({
|
||||||
|
ret: function() {
|
||||||
|
returnCalled++;
|
||||||
|
throw 42;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// inner try cannot catch IteratorClose throwing
|
||||||
|
assertThrowsValue(function() {
|
||||||
|
for (var x of iterable) {
|
||||||
|
try {
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
catchEntered++;
|
||||||
|
} finally {
|
||||||
|
finallyEntered++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 42);
|
||||||
|
assertEq(returnCalled, ++returnCalledExpected);
|
||||||
|
assertEq(catchEntered, 0);
|
||||||
|
assertEq(finallyEntered, ++finallyEnteredExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
test();
|
||||||
|
|
||||||
|
if (typeof reportCompare === "function")
|
||||||
|
reportCompare(0, 0);
|
||||||
|
|
@ -1156,6 +1156,7 @@ enum HandleErrorContinuation
|
||||||
static HandleErrorContinuation
|
static HandleErrorContinuation
|
||||||
ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs)
|
ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs)
|
||||||
{
|
{
|
||||||
|
bool inForOfIterClose = false;
|
||||||
for (TryNoteIterInterpreter tni(cx, regs); !tni.done(); ++tni) {
|
for (TryNoteIterInterpreter tni(cx, regs); !tni.done(); ++tni) {
|
||||||
JSTryNote* tn = *tni;
|
JSTryNote* tn = *tni;
|
||||||
|
|
||||||
|
|
@ -1164,10 +1165,38 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs)
|
||||||
/* Catch cannot intercept the closing of a generator. */
|
/* Catch cannot intercept the closing of a generator. */
|
||||||
if (cx->isClosingGenerator())
|
if (cx->isClosingGenerator())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// If IteratorClose due to abnormal completion threw inside a
|
||||||
|
// for-of loop, it is not catchable by try statements inside of
|
||||||
|
// the for-of loop.
|
||||||
|
//
|
||||||
|
// This is handled by this weirdness in the exception handler
|
||||||
|
// instead of in bytecode because it is hard to do so in bytecode:
|
||||||
|
//
|
||||||
|
// 1. IteratorClose emitted due to abnormal completion (break,
|
||||||
|
// throw, return) are emitted inline, at the source location of
|
||||||
|
// the break, throw, or return statement. For example:
|
||||||
|
//
|
||||||
|
// for (x of iter) {
|
||||||
|
// try { return; } catch (e) { }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// From the try-note nesting's perspective, the IteratorClose
|
||||||
|
// resulting from |return| is covered by the inner try, when it
|
||||||
|
// should not be.
|
||||||
|
//
|
||||||
|
// 2. Try-catch notes cannot be disjoint. That is, we can't have
|
||||||
|
// multiple notes with disjoint pc ranges jumping to the same
|
||||||
|
// catch block.
|
||||||
|
if (inForOfIterClose)
|
||||||
|
break;
|
||||||
SettleOnTryNote(cx, tn, ei, regs);
|
SettleOnTryNote(cx, tn, ei, regs);
|
||||||
return CatchContinuation;
|
return CatchContinuation;
|
||||||
|
|
||||||
case JSTRY_FINALLY:
|
case JSTRY_FINALLY:
|
||||||
|
// See note above.
|
||||||
|
if (inForOfIterClose)
|
||||||
|
break;
|
||||||
SettleOnTryNote(cx, tn, ei, regs);
|
SettleOnTryNote(cx, tn, ei, regs);
|
||||||
return FinallyContinuation;
|
return FinallyContinuation;
|
||||||
|
|
||||||
|
|
@ -1206,7 +1235,14 @@ ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case JSTRY_FOR_OF_ITERCLOSE:
|
||||||
|
inForOfIterClose = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case JSTRY_FOR_OF:
|
case JSTRY_FOR_OF:
|
||||||
|
inForOfIterClose = false;
|
||||||
|
break;
|
||||||
|
|
||||||
case JSTRY_LOOP:
|
case JSTRY_LOOP:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue