зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1899870 - Implement SuppressedError. r=arai
Differential Revision: https://phabricator.services.mozilla.com/D215608
This commit is contained in:
Родитель
dbbbf89972
Коммит
6a197a7136
|
@ -62,24 +62,37 @@ enum ErrorArgumentsType {
|
||||||
* using the generalized error reporting mechanism. (One side effect of this
|
* using the generalized error reporting mechanism. (One side effect of this
|
||||||
* type is to not prepend 'Error:' to warning messages.) This value can go away
|
* type is to not prepend 'Error:' to warning messages.) This value can go away
|
||||||
* if we ever decide to use an entirely separate mechanism for warnings.
|
* if we ever decide to use an entirely separate mechanism for warnings.
|
||||||
|
*
|
||||||
|
* The errors and warnings are arranged in alphabetically within their
|
||||||
|
* respective categories as defined in the comments below.
|
||||||
*/
|
*/
|
||||||
enum JSExnType {
|
enum JSExnType {
|
||||||
|
// Generic Errors
|
||||||
JSEXN_ERR,
|
JSEXN_ERR,
|
||||||
JSEXN_FIRST = JSEXN_ERR,
|
JSEXN_FIRST = JSEXN_ERR,
|
||||||
|
// Internal Errors
|
||||||
JSEXN_INTERNALERR,
|
JSEXN_INTERNALERR,
|
||||||
|
// ECMAScript Errors
|
||||||
JSEXN_AGGREGATEERR,
|
JSEXN_AGGREGATEERR,
|
||||||
JSEXN_EVALERR,
|
JSEXN_EVALERR,
|
||||||
JSEXN_RANGEERR,
|
JSEXN_RANGEERR,
|
||||||
JSEXN_REFERENCEERR,
|
JSEXN_REFERENCEERR,
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
JSEXN_SUPPRESSEDERR,
|
||||||
|
#endif
|
||||||
JSEXN_SYNTAXERR,
|
JSEXN_SYNTAXERR,
|
||||||
JSEXN_TYPEERR,
|
JSEXN_TYPEERR,
|
||||||
JSEXN_URIERR,
|
JSEXN_URIERR,
|
||||||
|
// Debugger Errors
|
||||||
JSEXN_DEBUGGEEWOULDRUN,
|
JSEXN_DEBUGGEEWOULDRUN,
|
||||||
|
// WASM Errors
|
||||||
JSEXN_WASMCOMPILEERROR,
|
JSEXN_WASMCOMPILEERROR,
|
||||||
JSEXN_WASMLINKERROR,
|
JSEXN_WASMLINKERROR,
|
||||||
JSEXN_WASMRUNTIMEERROR,
|
JSEXN_WASMRUNTIMEERROR,
|
||||||
JSEXN_ERROR_LIMIT,
|
JSEXN_ERROR_LIMIT,
|
||||||
|
// Warnings
|
||||||
JSEXN_WARN = JSEXN_ERROR_LIMIT,
|
JSEXN_WARN = JSEXN_ERROR_LIMIT,
|
||||||
|
// Error Notes
|
||||||
JSEXN_NOTE,
|
JSEXN_NOTE,
|
||||||
JSEXN_LIMIT
|
JSEXN_LIMIT
|
||||||
};
|
};
|
||||||
|
|
|
@ -86,6 +86,8 @@
|
||||||
REAL(EvalError, ERROR_CLASP(JSEXN_EVALERR)) \
|
REAL(EvalError, ERROR_CLASP(JSEXN_EVALERR)) \
|
||||||
REAL(RangeError, ERROR_CLASP(JSEXN_RANGEERR)) \
|
REAL(RangeError, ERROR_CLASP(JSEXN_RANGEERR)) \
|
||||||
REAL(ReferenceError, ERROR_CLASP(JSEXN_REFERENCEERR)) \
|
REAL(ReferenceError, ERROR_CLASP(JSEXN_REFERENCEERR)) \
|
||||||
|
IF_EXPLICIT_RESOURCE_MANAGEMENT( \
|
||||||
|
REAL(SuppressedError, ERROR_CLASP(JSEXN_SUPPRESSEDERR))) \
|
||||||
REAL(SyntaxError, ERROR_CLASP(JSEXN_SYNTAXERR)) \
|
REAL(SyntaxError, ERROR_CLASP(JSEXN_SYNTAXERR)) \
|
||||||
REAL(TypeError, ERROR_CLASP(JSEXN_TYPEERR)) \
|
REAL(TypeError, ERROR_CLASP(JSEXN_TYPEERR)) \
|
||||||
REAL(URIError, ERROR_CLASP(JSEXN_URIERR)) \
|
REAL(URIError, ERROR_CLASP(JSEXN_URIERR)) \
|
||||||
|
|
|
@ -959,4 +959,9 @@ MSG_DEF(JSMSG_TEMPORAL_PARSER_MONTH_DAY_CALENDAR_NOT_ISO8601, 0, JSEXN_RANGEERR,
|
||||||
MSG_DEF(JSMSG_TEMPORAL_PARSER_YEAR_MONTH_CALENDAR_NOT_ISO8601, 0, JSEXN_RANGEERR, "Year-Month formats only support the \"iso8601\" calendar")
|
MSG_DEF(JSMSG_TEMPORAL_PARSER_YEAR_MONTH_CALENDAR_NOT_ISO8601, 0, JSEXN_RANGEERR, "Year-Month formats only support the \"iso8601\" calendar")
|
||||||
MSG_DEF(JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE, 0, JSEXN_RANGEERR, "time zone offset must not contain seconds precision")
|
MSG_DEF(JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE, 0, JSEXN_RANGEERR, "time zone offset must not contain seconds precision")
|
||||||
|
|
||||||
|
// Explicit Resource Management
|
||||||
|
|
||||||
|
// TODO: Improve the messaging for suppressed errors (Bug 1906150)
|
||||||
|
MSG_DEF(JSMSG_ERROR_WAS_SUPPRESSED, 0, IF_EXPLICIT_RESOURCE_MANAGEMENT(JSEXN_SUPPRESSEDERR, JSEXN_INTERNALERR), "An error is suppressed because another error happened while disposing an object")
|
||||||
|
|
||||||
//clang-format on
|
//clang-format on
|
||||||
|
|
|
@ -38,7 +38,12 @@ bool ForOfLoopControl::emitBeginCodeNeedingIteratorClose(BytecodeEmitter* bce) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
|
bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
|
||||||
if (!tryCatch_->emitCatch(TryEmitter::ExceptionStack::Yes)) {
|
if (!tryCatch_->emitCatch(TryEmitter::ExceptionStack::Yes
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
,
|
||||||
|
TryEmitter::ForForOfIteratorClose::Yes
|
||||||
|
#endif
|
||||||
|
)) {
|
||||||
// [stack] ITER ... EXCEPTION STACK
|
// [stack] ITER ... EXCEPTION STACK
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -48,15 +53,7 @@ bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
|
||||||
// [stack] ITER ... EXCEPTION STACK ITER
|
// [stack] ITER ... EXCEPTION STACK ITER
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
|
||||||
// Explicit Resource Management Proposal
|
|
||||||
// https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset
|
|
||||||
// Step 9.i.i.1 Set result to
|
|
||||||
// Completion(DisposeResources(iterationEnv.[[DisposeCapability]], result)).
|
|
||||||
if (!bce->innermostEmitterScope()->prepareForForOfIteratorCloseOnThrow()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (!emitIteratorCloseInInnermostScopeWithTryNote(bce,
|
if (!emitIteratorCloseInInnermostScopeWithTryNote(bce,
|
||||||
CompletionKind::Throw)) {
|
CompletionKind::Throw)) {
|
||||||
return false; // ITER ... EXCEPTION STACK
|
return false; // ITER ... EXCEPTION STACK
|
||||||
|
|
|
@ -9,9 +9,12 @@
|
||||||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||||
|
|
||||||
#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
|
#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
|
||||||
#include "frontend/IfEmitter.h" // BytecodeEmitter
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
#include "frontend/SharedContext.h" // StatementKind
|
# include "frontend/EmitterScope.h" // EmitterScope
|
||||||
#include "vm/Opcodes.h" // JSOp
|
#endif
|
||||||
|
#include "frontend/IfEmitter.h" // BytecodeEmitter
|
||||||
|
#include "frontend/SharedContext.h" // StatementKind
|
||||||
|
#include "vm/Opcodes.h" // JSOp
|
||||||
|
|
||||||
using namespace js;
|
using namespace js;
|
||||||
using namespace js::frontend;
|
using namespace js::frontend;
|
||||||
|
@ -94,7 +97,12 @@ bool TryEmitter::emitTryEnd() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TryEmitter::emitCatch(ExceptionStack stack) {
|
bool TryEmitter::emitCatch(ExceptionStack stack
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
,
|
||||||
|
ForForOfIteratorClose forForOfIteratorClose
|
||||||
|
#endif
|
||||||
|
) {
|
||||||
MOZ_ASSERT(state_ == State::Try);
|
MOZ_ASSERT(state_ == State::Try);
|
||||||
if (!emitTryEnd()) {
|
if (!emitTryEnd()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -115,6 +123,18 @@ bool TryEmitter::emitCatch(ExceptionStack stack) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
// Explicit Resource Management Proposal
|
||||||
|
// https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset
|
||||||
|
// Step 9.i.i.1 Set result to
|
||||||
|
// Completion(DisposeResources(iterationEnv.[[DisposeCapability]], result)).
|
||||||
|
if (forForOfIteratorClose == ForForOfIteratorClose::Yes) {
|
||||||
|
if (!bce_->innermostEmitterScope()->prepareForForOfIteratorCloseOnThrow()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (stack == ExceptionStack::No) {
|
if (stack == ExceptionStack::No) {
|
||||||
if (!bce_->emit1(JSOp::Exception)) {
|
if (!bce_->emit1(JSOp::Exception)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -216,7 +216,23 @@ class MOZ_STACK_CLASS TryEmitter {
|
||||||
Yes,
|
Yes,
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] bool emitCatch(ExceptionStack stack = ExceptionStack::No);
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
enum class ForForOfIteratorClose : bool {
|
||||||
|
No,
|
||||||
|
/**
|
||||||
|
* Emit additional code for the ForOfIteratorClose operation.
|
||||||
|
*/
|
||||||
|
Yes,
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[[nodiscard]] bool emitCatch(
|
||||||
|
ExceptionStack stack = ExceptionStack::No
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
,
|
||||||
|
ForForOfIteratorClose forForOfIteratorClose = ForForOfIteratorClose::No
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
// If `finallyPos` is specified, it's an offset of the finally block's
|
// If `finallyPos` is specified, it's an offset of the finally block's
|
||||||
// "{" character in the source code text, to improve line:column number in
|
// "{" character in the source code text, to improve line:column number in
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include "frontend/BytecodeEmitter.h"
|
#include "frontend/BytecodeEmitter.h"
|
||||||
#include "frontend/EmitterScope.h"
|
#include "frontend/EmitterScope.h"
|
||||||
|
#include "vm/DisposeJumpKind.h"
|
||||||
|
|
||||||
using namespace js;
|
using namespace js;
|
||||||
using namespace js::frontend;
|
using namespace js::frontend;
|
||||||
|
@ -36,7 +37,8 @@ bool UsingEmitter::prepareForAssignment(Kind kind) {
|
||||||
|
|
||||||
bool UsingEmitter::prepareForForOfLoopIteration() {
|
bool UsingEmitter::prepareForForOfLoopIteration() {
|
||||||
MOZ_ASSERT(bce_->innermostEmitterScopeNoCheck()->hasDisposables());
|
MOZ_ASSERT(bce_->innermostEmitterScopeNoCheck()->hasDisposables());
|
||||||
if (!bce_->emit1(JSOp::DisposeDisposables)) {
|
if (!bce_->emit2(JSOp::DisposeDisposables,
|
||||||
|
uint8_t(DisposeJumpKind::JumpOnError))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -44,7 +46,8 @@ bool UsingEmitter::prepareForForOfLoopIteration() {
|
||||||
|
|
||||||
bool UsingEmitter::prepareForForOfIteratorCloseOnThrow() {
|
bool UsingEmitter::prepareForForOfIteratorCloseOnThrow() {
|
||||||
MOZ_ASSERT(bce_->innermostEmitterScopeNoCheck()->hasDisposables());
|
MOZ_ASSERT(bce_->innermostEmitterScopeNoCheck()->hasDisposables());
|
||||||
if (!bce_->emit1(JSOp::DisposeDisposables)) {
|
if (!bce_->emit2(JSOp::DisposeDisposables,
|
||||||
|
uint8_t(DisposeJumpKind::NoJumpOnError))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -52,7 +55,8 @@ bool UsingEmitter::prepareForForOfIteratorCloseOnThrow() {
|
||||||
|
|
||||||
bool UsingEmitter::emitNonLocalJump(EmitterScope* present) {
|
bool UsingEmitter::emitNonLocalJump(EmitterScope* present) {
|
||||||
MOZ_ASSERT(present->hasDisposables());
|
MOZ_ASSERT(present->hasDisposables());
|
||||||
if (!bce_->emit1(JSOp::DisposeDisposables)) {
|
if (!bce_->emit2(JSOp::DisposeDisposables,
|
||||||
|
uint8_t(DisposeJumpKind::JumpOnError))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -67,7 +71,8 @@ bool UsingEmitter::emitEnd() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bce_->emit1(JSOp::DisposeDisposables)) {
|
if (!bce_->emit2(JSOp::DisposeDisposables,
|
||||||
|
uint8_t(DisposeJumpKind::JumpOnError))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,3 +94,45 @@ if (typeof assertArrayEq === 'undefined') {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof assertSuppressionChain === 'undefined' && typeof globalThis.SuppressedError !== 'undefined') {
|
||||||
|
|
||||||
|
function errorChainVerificationHelper(err, suppressions, verifier) {
|
||||||
|
let i = 0;
|
||||||
|
while (err instanceof SuppressedError) {
|
||||||
|
assertEq(verifier(err.error, suppressions[i]), true);
|
||||||
|
err = err.suppressed;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
assertEq(verifier(err, suppressions[i]), true);
|
||||||
|
assertEq(i, suppressions.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var assertSuppressionChain = function assertSuppressionChain(fn, suppressions) {
|
||||||
|
let caught = false;
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
} catch (err) {
|
||||||
|
caught = true;
|
||||||
|
errorChainVerificationHelper(err, suppressions, function(err, suppression) {
|
||||||
|
return err === suppression;
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
assertEq(caught, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var assertSuppressionChainErrorMessages = function assertSuppressionChainErrorMessages(fn, suppressions) {
|
||||||
|
let caught = false;
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
} catch (err) {
|
||||||
|
caught = true;
|
||||||
|
errorChainVerificationHelper(err, suppressions, function(err, suppression) {
|
||||||
|
return err instanceof suppression.ctor && err.message === suppression.message;
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
assertEq(caught, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
// |jit-test| skip-if: !getBuildConfiguration("explicit-resource-management")
|
||||||
|
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
|
{
|
||||||
|
const values = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2")];
|
||||||
|
async function* gen() {
|
||||||
|
using d = {
|
||||||
|
value: "d",
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
values.push(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield await Promise.resolve("a");
|
||||||
|
yield await Promise.resolve("b");
|
||||||
|
using c = {
|
||||||
|
value: "c",
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
values.push(this.value);
|
||||||
|
throw errorsToThrow[0]; // This error will suppress the error thrown below.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw errorsToThrow[1]; // This error will be suppressed during disposal.
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testDisposeWithThrowAndPendingException() {
|
||||||
|
let x = gen();
|
||||||
|
values.push((await x.next()).value);
|
||||||
|
values.push((await x.next()).value);
|
||||||
|
await x.next();
|
||||||
|
}
|
||||||
|
let e = null;
|
||||||
|
testDisposeWithThrowAndPendingException().catch((err) => { e = err; });
|
||||||
|
drainJobQueue();
|
||||||
|
assertSuppressionChain(() => { throw e; }, errorsToThrow);
|
||||||
|
assertArrayEq(values, ["a", "b", "c", "d"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const values = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2")];
|
||||||
|
async function* gen() {
|
||||||
|
using c = {
|
||||||
|
value: "c",
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
values.push(this.value);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield await Promise.resolve("a");
|
||||||
|
yield await Promise.resolve("b");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
async function testDisposeWithThrowAndForcedThrowInAsyncGenerator() {
|
||||||
|
let x = gen();
|
||||||
|
values.push((await x.next()).value);
|
||||||
|
await x.throw(errorsToThrow[1]);
|
||||||
|
}
|
||||||
|
let e = null;
|
||||||
|
testDisposeWithThrowAndForcedThrowInAsyncGenerator().catch((err) => { e = err; });
|
||||||
|
drainJobQueue();
|
||||||
|
assertSuppressionChain(() => { throw e; }, errorsToThrow);
|
||||||
|
assertArrayEq(values, ["a", "c"]);
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// |jit-test| skip-if: !getBuildConfiguration("explicit-resource-management")
|
||||||
|
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
const g1 = newGlobal({ newCompartment: true });
|
||||||
|
const g2 = newGlobal({ newCompartment: true });
|
||||||
|
function testDifferentGlobalErrors() {
|
||||||
|
const g1Error = g1.evaluate(`new Error("g1")`);
|
||||||
|
const g2Error = g2.evaluate(`new Error("g2")`);
|
||||||
|
using x = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw g1Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using y = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
throw g2Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("g");
|
||||||
|
}
|
||||||
|
assertSuppressionChainErrorMessages(testDifferentGlobalErrors, [
|
||||||
|
{ctor: g1.evaluate('Error'), message: 'g1'},
|
||||||
|
{ctor: g2.evaluate('Error'), message: 'g2'},
|
||||||
|
{ctor: Error, message: 'g'},
|
||||||
|
]);
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// |jit-test| skip-if: !getBuildConfiguration("explicit-resource-management")
|
||||||
|
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
|
{
|
||||||
|
const values = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2")];
|
||||||
|
function* gen() {
|
||||||
|
using d = {
|
||||||
|
value: "d",
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
values.push(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield "a";
|
||||||
|
yield "b";
|
||||||
|
using c = {
|
||||||
|
value: "c",
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
values.push(this.value);
|
||||||
|
throw errorsToThrow[0]; // This error will suppress the error thrown below.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw errorsToThrow[1]; // This error will be suppressed during disposal.
|
||||||
|
}
|
||||||
|
assertSuppressionChain(() => {
|
||||||
|
let x = gen();
|
||||||
|
values.push(x.next().value);
|
||||||
|
values.push(x.next().value);
|
||||||
|
x.next();
|
||||||
|
}, errorsToThrow);
|
||||||
|
|
||||||
|
assertArrayEq(values, ["a", "b", "c", "d"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const values = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2")];
|
||||||
|
function* gen() {
|
||||||
|
using c = {
|
||||||
|
value: "c",
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
values.push(this.value);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield "a";
|
||||||
|
yield "b";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSuppressionChain(() => {
|
||||||
|
let x = gen();
|
||||||
|
values.push(x.next().value);
|
||||||
|
x.throw(errorsToThrow[1]); // This error will be suppressed during disposal.
|
||||||
|
}, errorsToThrow);
|
||||||
|
|
||||||
|
assertArrayEq(values, ["a", "c"]);
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
// |jit-test| skip-if: !getBuildConfiguration("explicit-resource-management")
|
||||||
|
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2")];
|
||||||
|
function testDisposedWithThrowInOrdinaryLoop() {
|
||||||
|
const disposables = [
|
||||||
|
{
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
using x = disposables[i];
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChain(testDisposedWithThrowInOrdinaryLoop, errorsToThrow);
|
||||||
|
assertArrayEq(disposed, [1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2")];
|
||||||
|
function testDisposedWithThrowInLoopRequiringIteratorClose() {
|
||||||
|
const disposables = [
|
||||||
|
{
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
for (const d of disposables) {
|
||||||
|
using x = d;
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChain(testDisposedWithThrowInLoopRequiringIteratorClose, errorsToThrow);
|
||||||
|
assertArrayEq(disposed, [1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const values = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2")];
|
||||||
|
function testDisposedWithThrowInLoopWithCustomIterable() {
|
||||||
|
const disposables = [
|
||||||
|
{
|
||||||
|
val: "a",
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
values.push(this.val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: "b",
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
values.push(this.val);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const iterable = {
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
let i = 0;
|
||||||
|
return {
|
||||||
|
next() {
|
||||||
|
if (i === disposables.length) {
|
||||||
|
return { done: true };
|
||||||
|
}
|
||||||
|
return { value: disposables[i++], done: false };
|
||||||
|
},
|
||||||
|
return() {
|
||||||
|
values.push("return");
|
||||||
|
return { done: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (using d of iterable) {
|
||||||
|
if (d.val === "b") {
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChain(testDisposedWithThrowInLoopWithCustomIterable, errorsToThrow);
|
||||||
|
assertArrayEq(values, ["a", "b", "return"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2"), new Error("test3"), new Error("test4")];
|
||||||
|
function testDisposeWithThrowInForOfLoop() {
|
||||||
|
const d1 = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const d2 = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (using d of [d1, d2]) {
|
||||||
|
throw errorsToThrow[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChain(testDisposeWithThrowInForOfLoop, [errorsToThrow[0], errorsToThrow[2]]);
|
||||||
|
assertArrayEq(disposed, [1]);
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
// |jit-test| skip-if: !getBuildConfiguration("explicit-resource-management")
|
||||||
|
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
const throwObject = { message: 'Hello world' };
|
||||||
|
function testNonErrorObjectThrowsDuringDispose() {
|
||||||
|
using x = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using y = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
throw "test";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using z = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
throw null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw throwObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSuppressionChain(testNonErrorObjectThrowsDuringDispose, [
|
||||||
|
1, "test", null, throwObject
|
||||||
|
]);
|
||||||
|
assertArrayEq(disposed, [2, 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
class SubError extends Error {
|
||||||
|
constructor(num) {
|
||||||
|
super();
|
||||||
|
this.name = 'SubError';
|
||||||
|
this.ident = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const errorsToThrow = [new SubError(1), new SubError(2)];
|
||||||
|
function testCustomErrorObjectThrowsDuringDispose() {
|
||||||
|
using x = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using y = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChain(testCustomErrorObjectThrowsDuringDispose, errorsToThrow);
|
||||||
|
assertArrayEq(disposed, [2, 1]);
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
// |jit-test| skip-if: !getBuildConfiguration("explicit-resource-management")
|
||||||
|
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
function testUndefinedAccessSuppressedErrorWithThrowInDispose() {
|
||||||
|
using x = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
undefined.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChainErrorMessages(testUndefinedAccessSuppressedErrorWithThrowInDispose, [{ctor: TypeError, message: "can't access property \"x\" of undefined"}]);
|
||||||
|
assertArrayEq(disposed, [1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
function testReferenceErrorSuppressedErrorWithThrowInDispose() {
|
||||||
|
using x = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
y.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChainErrorMessages(testReferenceErrorSuppressedErrorWithThrowInDispose, [{ctor: ReferenceError, message: "y is not defined"}]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
function testMultipleRuntimeErrorsWithThrowsDuringDispose() {
|
||||||
|
using x = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
undefined.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using y = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
a.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using z = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
this[Symbol.dispose]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChainErrorMessages(testMultipleRuntimeErrorsWithThrowsDuringDispose, [
|
||||||
|
{ctor: TypeError, message: "can't access property \"x\" of undefined"},
|
||||||
|
{ctor: ReferenceError, message: "a is not defined"},
|
||||||
|
{ctor: InternalError, message: "too much recursion"},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
function testMultipleRuntimeErrorsWithThrowsDuringDisposeAndOutsideDispose() {
|
||||||
|
using x = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
undefined.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using y = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
a.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using z = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
this[Symbol.dispose]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
null.x;
|
||||||
|
}
|
||||||
|
assertSuppressionChainErrorMessages(testMultipleRuntimeErrorsWithThrowsDuringDisposeAndOutsideDispose, [
|
||||||
|
{ctor: TypeError, message: "can't access property \"x\" of undefined"},
|
||||||
|
{ctor: ReferenceError, message: "a is not defined"},
|
||||||
|
{ctor: InternalError, message: "too much recursion"},
|
||||||
|
{ctor: TypeError, message: "can't access property \"x\" of null"},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const values = [];
|
||||||
|
function* gen() {
|
||||||
|
using d = {
|
||||||
|
value: "d",
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
values.push(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yield "a";
|
||||||
|
yield "b";
|
||||||
|
using c = {
|
||||||
|
value: "c",
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
values.push(this.value);
|
||||||
|
a.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
null.x;
|
||||||
|
}
|
||||||
|
function testRuntimeErrorsWithGenerators() {
|
||||||
|
let x = gen();
|
||||||
|
values.push(x.next().value);
|
||||||
|
values.push(x.next().value);
|
||||||
|
x.next();
|
||||||
|
}
|
||||||
|
assertSuppressionChainErrorMessages(testRuntimeErrorsWithGenerators, [
|
||||||
|
{ctor: ReferenceError, message: "a is not defined"},
|
||||||
|
{ctor: TypeError, message: "can't access property \"x\" of null"}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
const d = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
null.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function testRuntimeErrorWithLoops() {
|
||||||
|
for (using x of [d]) {
|
||||||
|
y.a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChainErrorMessages(testRuntimeErrorWithLoops, [
|
||||||
|
{ctor: TypeError, message: "can't access property \"x\" of null"},
|
||||||
|
{ctor: ReferenceError, message: "y is not defined"},
|
||||||
|
]);
|
||||||
|
}
|
|
@ -0,0 +1,243 @@
|
||||||
|
// |jit-test| skip-if: !getBuildConfiguration("explicit-resource-management")
|
||||||
|
|
||||||
|
load(libdir + "asserts.js");
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
function testExceptionBeforeDispose() {
|
||||||
|
throw new Error("test");
|
||||||
|
using d = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
assertThrowsInstanceOf(testExceptionOutsideDispose, Error);
|
||||||
|
assertEq(disposed.length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
function testExceptionOutsideDispose() {
|
||||||
|
using d = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
throw new Error("test");
|
||||||
|
}
|
||||||
|
assertThrowsInstanceOf(testExceptionOutsideDispose, Error);
|
||||||
|
assertArrayEq(disposed, [1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
function testExceptionInsideDispose() {
|
||||||
|
using d = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw new Error("test");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
assertThrowsInstanceOf(testExceptionInsideDispose, Error);
|
||||||
|
assertArrayEq(disposed, [1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2")];
|
||||||
|
function testExceptionInsideAndOutsideDispose() {
|
||||||
|
using d = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
assertSuppressionChain(testExceptionInsideAndOutsideDispose, errorsToThrow);
|
||||||
|
assertArrayEq(disposed, [1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2"), new Error("test3")];
|
||||||
|
function testMultipleDisposeWithException() {
|
||||||
|
using d1 = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using d2 = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using d3 = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(3);
|
||||||
|
throw errorsToThrow[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChain(testMultipleDisposeWithException, errorsToThrow);
|
||||||
|
assertArrayEq(disposed, [3, 2, 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2"), new Error("test3"), new Error("test4")];
|
||||||
|
function testMultipleDisposeWithThrowsAndOutsideThrow() {
|
||||||
|
using d1 = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using d2 = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using d3 = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(3);
|
||||||
|
throw errorsToThrow[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw errorsToThrow[3];
|
||||||
|
}
|
||||||
|
assertSuppressionChain(testMultipleDisposeWithThrowsAndOutsideThrow, errorsToThrow);
|
||||||
|
assertArrayEq(disposed, [3, 2, 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const disposed = [];
|
||||||
|
const errorsToThrow = [new Error("test1"), new Error("test2"), new Error("test3")];
|
||||||
|
function testDisposeWithThrowInAnInnerScope() {
|
||||||
|
using d1 = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
{
|
||||||
|
using d2 = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let a = 0, b = () => a;
|
||||||
|
throw errorsToThrow[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSuppressionChain(testDisposeWithThrowInAnInnerScope, errorsToThrow);
|
||||||
|
assertArrayEq(disposed, [2, 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let disposed = [];
|
||||||
|
const errorsToThrow = [new Error('test1'), new Error('test2'), new Error('test3')];
|
||||||
|
function testDisposeWithThrowInSwitchCase(cs) {
|
||||||
|
switch (cs) {
|
||||||
|
case "only_1_dispose":
|
||||||
|
using x = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "2_dispose":
|
||||||
|
using y = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using z = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "dispose_with_outside":
|
||||||
|
using a = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using b = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
throw errorsToThrow[2];
|
||||||
|
break;
|
||||||
|
case 'fallthrough':
|
||||||
|
using aa = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(1);
|
||||||
|
throw errorsToThrow[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
case 'fall':
|
||||||
|
using bb = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
disposed.push(2);
|
||||||
|
throw errorsToThrow[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw errorsToThrow[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThrowsInstanceOf(() => testDisposeWithThrowInSwitchCase('only_1_dispose'), Error);
|
||||||
|
assertArrayEq(disposed, [1]);
|
||||||
|
disposed = [];
|
||||||
|
assertSuppressionChain(() => testDisposeWithThrowInSwitchCase('2_dispose'), [errorsToThrow[0], errorsToThrow[1]]);
|
||||||
|
assertArrayEq(disposed, [2,1]);
|
||||||
|
disposed = [];
|
||||||
|
assertSuppressionChain(() => testDisposeWithThrowInSwitchCase('dispose_with_outside'), errorsToThrow);
|
||||||
|
assertArrayEq(disposed, [2,1]);
|
||||||
|
disposed = [];
|
||||||
|
assertSuppressionChain(() => testDisposeWithThrowInSwitchCase('fallthrough'), errorsToThrow);
|
||||||
|
assertArrayEq(disposed, [2,1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
globalThis.disposedModule = [];
|
||||||
|
globalThis.errorsToThrowModule = [new Error('test1'), new Error('test2'), new Error('test3')];
|
||||||
|
const m = parseModule(`
|
||||||
|
using x = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
globalThis.disposedModule.push(1);
|
||||||
|
throw globalThis.errorsToThrowModule[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using y = {
|
||||||
|
[Symbol.dispose]() {
|
||||||
|
globalThis.disposedModule.push(2);
|
||||||
|
throw globalThis.errorsToThrowModule[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw globalThis.errorsToThrowModule[2];
|
||||||
|
`);
|
||||||
|
moduleLink(m);
|
||||||
|
let e = null;
|
||||||
|
moduleEvaluate(m).catch((err) => { e = err; });
|
||||||
|
drainJobQueue();
|
||||||
|
assertSuppressionChain(() => { throw e; }, globalThis.errorsToThrowModule);
|
||||||
|
assertArrayEq(globalThis.disposedModule, [2, 1]);
|
||||||
|
}
|
|
@ -77,6 +77,9 @@ static_assert(
|
||||||
JSProto_Error + int(JSEXN_EVALERR) == JSProto_EvalError &&
|
JSProto_Error + int(JSEXN_EVALERR) == JSProto_EvalError &&
|
||||||
JSProto_Error + int(JSEXN_RANGEERR) == JSProto_RangeError &&
|
JSProto_Error + int(JSEXN_RANGEERR) == JSProto_RangeError &&
|
||||||
JSProto_Error + int(JSEXN_REFERENCEERR) == JSProto_ReferenceError &&
|
JSProto_Error + int(JSEXN_REFERENCEERR) == JSProto_ReferenceError &&
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
JSProto_Error + int(JSEXN_SUPPRESSEDERR) == JSProto_SuppressedError &&
|
||||||
|
#endif
|
||||||
JSProto_Error + int(JSEXN_SYNTAXERR) == JSProto_SyntaxError &&
|
JSProto_Error + int(JSEXN_SYNTAXERR) == JSProto_SyntaxError &&
|
||||||
JSProto_Error + int(JSEXN_TYPEERR) == JSProto_TypeError &&
|
JSProto_Error + int(JSEXN_TYPEERR) == JSProto_TypeError &&
|
||||||
JSProto_Error + int(JSEXN_URIERR) == JSProto_URIError &&
|
JSProto_Error + int(JSEXN_URIERR) == JSProto_URIError &&
|
||||||
|
|
|
@ -175,6 +175,7 @@
|
||||||
MACRO_(enumerate, "enumerate") \
|
MACRO_(enumerate, "enumerate") \
|
||||||
MACRO_(era, "era") \
|
MACRO_(era, "era") \
|
||||||
MACRO_(eraYear, "eraYear") \
|
MACRO_(eraYear, "eraYear") \
|
||||||
|
IF_EXPLICIT_RESOURCE_MANAGEMENT(MACRO_(error, "error")) \
|
||||||
MACRO_(errors, "errors") \
|
MACRO_(errors, "errors") \
|
||||||
MACRO_(ErrorToStringWithTrailingNewline, "ErrorToStringWithTrailingNewline") \
|
MACRO_(ErrorToStringWithTrailingNewline, "ErrorToStringWithTrailingNewline") \
|
||||||
MACRO_(escape, "escape") \
|
MACRO_(escape, "escape") \
|
||||||
|
@ -542,6 +543,7 @@
|
||||||
MACRO_(StructType, "StructType") \
|
MACRO_(StructType, "StructType") \
|
||||||
MACRO_(style, "style") \
|
MACRO_(style, "style") \
|
||||||
MACRO_(super, "super") \
|
MACRO_(super, "super") \
|
||||||
|
IF_EXPLICIT_RESOURCE_MANAGEMENT(MACRO_(suppressed, "suppressed")) \
|
||||||
MACRO_(switch_, "switch") \
|
MACRO_(switch_, "switch") \
|
||||||
MACRO_(symmetricDifference, "symmetricDifference") \
|
MACRO_(symmetricDifference, "symmetricDifference") \
|
||||||
MACRO_(target, "target") \
|
MACRO_(target, "target") \
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||||
|
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
#ifndef vm_DisposeJumpKind_h
|
||||||
|
#define vm_DisposeJumpKind_h
|
||||||
|
|
||||||
|
#include <stdint.h> // uint8_t
|
||||||
|
|
||||||
|
namespace js {
|
||||||
|
|
||||||
|
enum class DisposeJumpKind : uint8_t {
|
||||||
|
/*
|
||||||
|
* Jump out of interpreter Loop to error handling code
|
||||||
|
* if there was an exception during the Dispose Operation.
|
||||||
|
*/
|
||||||
|
JumpOnError,
|
||||||
|
/*
|
||||||
|
* Do not jump out of the interpreter loop to error handling
|
||||||
|
* even if there are errors pending.
|
||||||
|
*/
|
||||||
|
NoJumpOnError,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace js
|
||||||
|
|
||||||
|
#endif /* vm_DisposeJumpKind_h */
|
|
@ -59,12 +59,10 @@
|
||||||
|
|
||||||
using namespace js;
|
using namespace js;
|
||||||
|
|
||||||
#define IMPLEMENT_ERROR_PROTO_CLASS(name) \
|
#define IMPLEMENT_ERROR_PROTO_CLASS(name) \
|
||||||
{ \
|
{#name ".prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_##name), \
|
||||||
#name ".prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_##name), \
|
JS_NULL_CLASS_OPS, \
|
||||||
JS_NULL_CLASS_OPS, \
|
&ErrorObject::classSpecs[JSProto_##name - JSProto_Error]}
|
||||||
&ErrorObject::classSpecs[JSProto_##name - JSProto_Error] \
|
|
||||||
}
|
|
||||||
|
|
||||||
const JSClass ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = {
|
const JSClass ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = {
|
||||||
IMPLEMENT_ERROR_PROTO_CLASS(Error),
|
IMPLEMENT_ERROR_PROTO_CLASS(Error),
|
||||||
|
@ -74,6 +72,9 @@ const JSClass ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = {
|
||||||
IMPLEMENT_ERROR_PROTO_CLASS(EvalError),
|
IMPLEMENT_ERROR_PROTO_CLASS(EvalError),
|
||||||
IMPLEMENT_ERROR_PROTO_CLASS(RangeError),
|
IMPLEMENT_ERROR_PROTO_CLASS(RangeError),
|
||||||
IMPLEMENT_ERROR_PROTO_CLASS(ReferenceError),
|
IMPLEMENT_ERROR_PROTO_CLASS(ReferenceError),
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
IMPLEMENT_ERROR_PROTO_CLASS(SuppressedError),
|
||||||
|
#endif
|
||||||
IMPLEMENT_ERROR_PROTO_CLASS(SyntaxError),
|
IMPLEMENT_ERROR_PROTO_CLASS(SyntaxError),
|
||||||
IMPLEMENT_ERROR_PROTO_CLASS(TypeError),
|
IMPLEMENT_ERROR_PROTO_CLASS(TypeError),
|
||||||
IMPLEMENT_ERROR_PROTO_CLASS(URIError),
|
IMPLEMENT_ERROR_PROTO_CLASS(URIError),
|
||||||
|
@ -109,6 +110,9 @@ IMPLEMENT_NATIVE_ERROR_PROPERTIES(AggregateError)
|
||||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(EvalError)
|
IMPLEMENT_NATIVE_ERROR_PROPERTIES(EvalError)
|
||||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(RangeError)
|
IMPLEMENT_NATIVE_ERROR_PROPERTIES(RangeError)
|
||||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(ReferenceError)
|
IMPLEMENT_NATIVE_ERROR_PROPERTIES(ReferenceError)
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
IMPLEMENT_NATIVE_ERROR_PROPERTIES(SuppressedError)
|
||||||
|
#endif
|
||||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(SyntaxError)
|
IMPLEMENT_NATIVE_ERROR_PROPERTIES(SyntaxError)
|
||||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(TypeError)
|
IMPLEMENT_NATIVE_ERROR_PROPERTIES(TypeError)
|
||||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(URIError)
|
IMPLEMENT_NATIVE_ERROR_PROPERTIES(URIError)
|
||||||
|
@ -117,18 +121,25 @@ IMPLEMENT_NATIVE_ERROR_PROPERTIES(CompileError)
|
||||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(LinkError)
|
IMPLEMENT_NATIVE_ERROR_PROPERTIES(LinkError)
|
||||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(RuntimeError)
|
IMPLEMENT_NATIVE_ERROR_PROPERTIES(RuntimeError)
|
||||||
|
|
||||||
#define IMPLEMENT_NATIVE_ERROR_SPEC(name) \
|
#define IMPLEMENT_NATIVE_ERROR_SPEC(name) \
|
||||||
{ \
|
{ErrorObject::createConstructor, \
|
||||||
ErrorObject::createConstructor, ErrorObject::createProto, nullptr, \
|
ErrorObject::createProto, \
|
||||||
nullptr, nullptr, name##_properties, nullptr, JSProto_Error \
|
nullptr, \
|
||||||
}
|
nullptr, \
|
||||||
|
nullptr, \
|
||||||
|
name##_properties, \
|
||||||
|
nullptr, \
|
||||||
|
JSProto_Error}
|
||||||
|
|
||||||
#define IMPLEMENT_NONGLOBAL_ERROR_SPEC(name) \
|
#define IMPLEMENT_NONGLOBAL_ERROR_SPEC(name) \
|
||||||
{ \
|
{ErrorObject::createConstructor, \
|
||||||
ErrorObject::createConstructor, ErrorObject::createProto, nullptr, \
|
ErrorObject::createProto, \
|
||||||
nullptr, nullptr, name##_properties, nullptr, \
|
nullptr, \
|
||||||
JSProto_Error | ClassSpec::DontDefineConstructor \
|
nullptr, \
|
||||||
}
|
nullptr, \
|
||||||
|
name##_properties, \
|
||||||
|
nullptr, \
|
||||||
|
JSProto_Error | ClassSpec::DontDefineConstructor}
|
||||||
|
|
||||||
const ClassSpec ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = {
|
const ClassSpec ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = {
|
||||||
{ErrorObject::createConstructor, ErrorObject::createProto, nullptr, nullptr,
|
{ErrorObject::createConstructor, ErrorObject::createProto, nullptr, nullptr,
|
||||||
|
@ -139,6 +150,9 @@ const ClassSpec ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = {
|
||||||
IMPLEMENT_NATIVE_ERROR_SPEC(EvalError),
|
IMPLEMENT_NATIVE_ERROR_SPEC(EvalError),
|
||||||
IMPLEMENT_NATIVE_ERROR_SPEC(RangeError),
|
IMPLEMENT_NATIVE_ERROR_SPEC(RangeError),
|
||||||
IMPLEMENT_NATIVE_ERROR_SPEC(ReferenceError),
|
IMPLEMENT_NATIVE_ERROR_SPEC(ReferenceError),
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
IMPLEMENT_NATIVE_ERROR_SPEC(SuppressedError),
|
||||||
|
#endif
|
||||||
IMPLEMENT_NATIVE_ERROR_SPEC(SyntaxError),
|
IMPLEMENT_NATIVE_ERROR_SPEC(SyntaxError),
|
||||||
IMPLEMENT_NATIVE_ERROR_SPEC(TypeError),
|
IMPLEMENT_NATIVE_ERROR_SPEC(TypeError),
|
||||||
IMPLEMENT_NATIVE_ERROR_SPEC(URIError),
|
IMPLEMENT_NATIVE_ERROR_SPEC(URIError),
|
||||||
|
@ -148,15 +162,13 @@ const ClassSpec ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = {
|
||||||
IMPLEMENT_NONGLOBAL_ERROR_SPEC(LinkError),
|
IMPLEMENT_NONGLOBAL_ERROR_SPEC(LinkError),
|
||||||
IMPLEMENT_NONGLOBAL_ERROR_SPEC(RuntimeError)};
|
IMPLEMENT_NONGLOBAL_ERROR_SPEC(RuntimeError)};
|
||||||
|
|
||||||
#define IMPLEMENT_ERROR_CLASS_CORE(name, reserved_slots) \
|
#define IMPLEMENT_ERROR_CLASS_CORE(name, reserved_slots) \
|
||||||
{ \
|
{#name, \
|
||||||
#name, \
|
JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
|
||||||
JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
|
JSCLASS_HAS_RESERVED_SLOTS(reserved_slots) | \
|
||||||
JSCLASS_HAS_RESERVED_SLOTS(reserved_slots) | \
|
JSCLASS_BACKGROUND_FINALIZE, \
|
||||||
JSCLASS_BACKGROUND_FINALIZE, \
|
&ErrorObjectClassOps, \
|
||||||
&ErrorObjectClassOps, \
|
&ErrorObject::classSpecs[JSProto_##name - JSProto_Error]}
|
||||||
&ErrorObject::classSpecs[JSProto_##name - JSProto_Error] \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define IMPLEMENT_ERROR_CLASS(name) \
|
#define IMPLEMENT_ERROR_CLASS(name) \
|
||||||
IMPLEMENT_ERROR_CLASS_CORE(name, ErrorObject::RESERVED_SLOTS)
|
IMPLEMENT_ERROR_CLASS_CORE(name, ErrorObject::RESERVED_SLOTS)
|
||||||
|
@ -187,6 +199,9 @@ const JSClass ErrorObject::classes[JSEXN_ERROR_LIMIT] = {
|
||||||
IMPLEMENT_ERROR_CLASS_MAYBE_WASM_TRAP(InternalError),
|
IMPLEMENT_ERROR_CLASS_MAYBE_WASM_TRAP(InternalError),
|
||||||
IMPLEMENT_ERROR_CLASS(AggregateError), IMPLEMENT_ERROR_CLASS(EvalError),
|
IMPLEMENT_ERROR_CLASS(AggregateError), IMPLEMENT_ERROR_CLASS(EvalError),
|
||||||
IMPLEMENT_ERROR_CLASS(RangeError), IMPLEMENT_ERROR_CLASS(ReferenceError),
|
IMPLEMENT_ERROR_CLASS(RangeError), IMPLEMENT_ERROR_CLASS(ReferenceError),
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
IMPLEMENT_ERROR_CLASS(SuppressedError),
|
||||||
|
#endif
|
||||||
IMPLEMENT_ERROR_CLASS(SyntaxError), IMPLEMENT_ERROR_CLASS(TypeError),
|
IMPLEMENT_ERROR_CLASS(SyntaxError), IMPLEMENT_ERROR_CLASS(TypeError),
|
||||||
IMPLEMENT_ERROR_CLASS(URIError),
|
IMPLEMENT_ERROR_CLASS(URIError),
|
||||||
// These Error subclasses are not accessible via the global object:
|
// These Error subclasses are not accessible via the global object:
|
||||||
|
@ -293,6 +308,11 @@ static bool Error(JSContext* cx, unsigned argc, Value* vp) {
|
||||||
MOZ_ASSERT(exnType != JSEXN_AGGREGATEERR,
|
MOZ_ASSERT(exnType != JSEXN_AGGREGATEERR,
|
||||||
"AggregateError has its own constructor function");
|
"AggregateError has its own constructor function");
|
||||||
|
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
MOZ_ASSERT(exnType != JSEXN_SUPPRESSEDERR,
|
||||||
|
"SuppressedError has its own constuctor function");
|
||||||
|
#endif
|
||||||
|
|
||||||
JSProtoKey protoKey =
|
JSProtoKey protoKey =
|
||||||
JSCLASS_CACHED_PROTO_KEY(&ErrorObject::classes[exnType]);
|
JSCLASS_CACHED_PROTO_KEY(&ErrorObject::classes[exnType]);
|
||||||
|
|
||||||
|
@ -358,6 +378,60 @@ static bool AggregateError(JSContext* cx, unsigned argc, Value* vp) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
// Explicit Resource Management Proposal
|
||||||
|
// SuppressedError ( error, suppressed, message )
|
||||||
|
// https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-suppressederror
|
||||||
|
static bool SuppressedError(JSContext* cx, unsigned argc, Value* vp) {
|
||||||
|
CallArgs args = CallArgsFromVp(argc, vp);
|
||||||
|
|
||||||
|
mozilla::DebugOnly<JSExnType> exnType =
|
||||||
|
JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
|
||||||
|
|
||||||
|
MOZ_ASSERT(exnType == JSEXN_SUPPRESSEDERR);
|
||||||
|
|
||||||
|
// Step 1. If NewTarget is undefined, let newTarget be the active function
|
||||||
|
// object; else let newTarget be NewTarget.
|
||||||
|
// Step 2. Let O be ? OrdinaryCreateFromConstructor(newTarget,
|
||||||
|
// "%SuppressedError.prototype%", « [[ErrorData]] »).
|
||||||
|
JS::Rooted<JSObject*> proto(cx);
|
||||||
|
|
||||||
|
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_SuppressedError,
|
||||||
|
&proto)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3. If message is not undefined, then
|
||||||
|
// Step 3.a. Let messageString be ? ToString(message).
|
||||||
|
// Step 3.b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message",
|
||||||
|
// messageString).
|
||||||
|
JS::Rooted<ErrorObject*> obj(
|
||||||
|
cx, CreateErrorObject(cx, args, 2, JSEXN_SUPPRESSEDERR, proto));
|
||||||
|
|
||||||
|
if (!obj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4. Perform CreateNonEnumerableDataPropertyOrThrow(O, "error", error).
|
||||||
|
JS::Rooted<JS::Value> errorVal(cx, args.get(0));
|
||||||
|
if (!NativeDefineDataProperty(cx, obj, cx->names().error, errorVal, 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5. Perform CreateNonEnumerableDataPropertyOrThrow(O, "suppressed",
|
||||||
|
// suppressed).
|
||||||
|
JS::Rooted<JS::Value> suppressedVal(cx, args.get(1));
|
||||||
|
if (!NativeDefineDataProperty(cx, obj, cx->names().suppressed, suppressedVal,
|
||||||
|
0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6. Return O.
|
||||||
|
args.rval().setObject(*obj);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
JSObject* ErrorObject::createProto(JSContext* cx, JSProtoKey key) {
|
JSObject* ErrorObject::createProto(JSContext* cx, JSProtoKey key) {
|
||||||
JSExnType type = ExnTypeFromProtoKey(key);
|
JSExnType type = ExnTypeFromProtoKey(key);
|
||||||
|
@ -397,7 +471,14 @@ JSObject* ErrorObject::createConstructor(JSContext* cx, JSProtoKey key) {
|
||||||
if (type == JSEXN_AGGREGATEERR) {
|
if (type == JSEXN_AGGREGATEERR) {
|
||||||
native = AggregateError;
|
native = AggregateError;
|
||||||
nargs = 2;
|
nargs = 2;
|
||||||
} else {
|
}
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
else if (type == JSEXN_SUPPRESSEDERR) {
|
||||||
|
native = SuppressedError;
|
||||||
|
nargs = 3;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else {
|
||||||
native = Error;
|
native = Error;
|
||||||
nargs = 1;
|
nargs = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,6 +139,9 @@ bool GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key) {
|
||||||
case JSProto_Error:
|
case JSProto_Error:
|
||||||
case JSProto_InternalError:
|
case JSProto_InternalError:
|
||||||
case JSProto_AggregateError:
|
case JSProto_AggregateError:
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
case JSProto_SuppressedError:
|
||||||
|
#endif
|
||||||
case JSProto_EvalError:
|
case JSProto_EvalError:
|
||||||
case JSProto_RangeError:
|
case JSProto_RangeError:
|
||||||
case JSProto_ReferenceError:
|
case JSProto_ReferenceError:
|
||||||
|
|
|
@ -45,7 +45,11 @@
|
||||||
#include "vm/AsyncFunction.h"
|
#include "vm/AsyncFunction.h"
|
||||||
#include "vm/AsyncIteration.h"
|
#include "vm/AsyncIteration.h"
|
||||||
#include "vm/BigIntType.h"
|
#include "vm/BigIntType.h"
|
||||||
#include "vm/BytecodeUtil.h" // JSDVG_SEARCH_STACK
|
#include "vm/BytecodeUtil.h" // JSDVG_SEARCH_STACK
|
||||||
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
|
# include "vm/DisposeJumpKind.h"
|
||||||
|
# include "vm/ErrorObject.h"
|
||||||
|
#endif
|
||||||
#include "vm/EqualityOperations.h" // js::StrictlyEqual
|
#include "vm/EqualityOperations.h" // js::StrictlyEqual
|
||||||
#include "vm/GeneratorObject.h"
|
#include "vm/GeneratorObject.h"
|
||||||
#include "vm/Iteration.h"
|
#include "vm/Iteration.h"
|
||||||
|
@ -1745,6 +1749,49 @@ bool js::CreateDisposableResource(JSContext* cx, JS::Handle<JS::Value> obj,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Explicit Resource Management Proposal
|
||||||
|
// https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-disposeresources
|
||||||
|
// Steps 3.e.iii.1.c-e.
|
||||||
|
ErrorObject* js::CreateSuppressedError(JSContext* cx,
|
||||||
|
JS::Handle<JS::Value> error,
|
||||||
|
JS::Handle<JS::Value> suppressed) {
|
||||||
|
// Step 3.e.iii.1.c. Let error be a newly created SuppressedError object.
|
||||||
|
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
|
||||||
|
JSMSG_ERROR_WAS_SUPPRESSED);
|
||||||
|
|
||||||
|
JS::Rooted<JS::Value> thrownSuppressed(cx);
|
||||||
|
|
||||||
|
if (!cx->getPendingException(&thrownSuppressed)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
cx->clearPendingException();
|
||||||
|
|
||||||
|
JS::Rooted<ErrorObject*> errorObj(
|
||||||
|
cx, &thrownSuppressed.toObject().as<ErrorObject>());
|
||||||
|
|
||||||
|
// Step 3.e.iii.1.d. Perform
|
||||||
|
// CreateNonEnumerableDataPropertyOrThrow(error, "error", result).
|
||||||
|
if (!NativeDefineDataProperty(cx, errorObj, cx->names().error, error, 0)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3.e.iii.1.e. Perform
|
||||||
|
// CreateNonEnumerableDataPropertyOrThrow(error, "suppressed",
|
||||||
|
// suppressed).
|
||||||
|
if (!NativeDefineDataProperty(cx, errorObj, cx->names().suppressed,
|
||||||
|
suppressed, 0)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Improve the capturing of stack and error messages (Bug 1906150)
|
||||||
|
|
||||||
|
return errorObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit Resource Management Proposal
|
||||||
|
// DisposeResources ( disposeCapability, completion )
|
||||||
|
// https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-disposeresources
|
||||||
bool js::DisposeDisposablesOnScopeLeave(JSContext* cx,
|
bool js::DisposeDisposablesOnScopeLeave(JSContext* cx,
|
||||||
JS::Handle<JSObject*> env) {
|
JS::Handle<JSObject*> env) {
|
||||||
if (!env->is<LexicalEnvironmentObject>() &&
|
if (!env->is<LexicalEnvironmentObject>() &&
|
||||||
|
@ -1765,13 +1812,8 @@ bool js::DisposeDisposablesOnScopeLeave(JSContext* cx,
|
||||||
|
|
||||||
uint32_t index = disposables->length();
|
uint32_t index = disposables->length();
|
||||||
|
|
||||||
|
// hadError and latestException correspond to the completion value.
|
||||||
bool hadError = false;
|
bool hadError = false;
|
||||||
|
|
||||||
// Explicit Resource Management Proposal
|
|
||||||
// DisposeResources ( disposeCapability, completion )
|
|
||||||
// https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-disposeresources
|
|
||||||
// Step 1. For each element resource of
|
|
||||||
// disposeCapability.[[DisposableResourceStack]], in reverse list order, do
|
|
||||||
JS::Rooted<JS::Value> latestException(cx);
|
JS::Rooted<JS::Value> latestException(cx);
|
||||||
|
|
||||||
if (cx->isExceptionPending()) {
|
if (cx->isExceptionPending()) {
|
||||||
|
@ -1782,67 +1824,83 @@ bool js::DisposeDisposablesOnScopeLeave(JSContext* cx,
|
||||||
cx->clearPendingException();
|
cx->clearPendingException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 3. For each element resource of
|
||||||
|
// disposeCapability.[[DisposableResourceStack]], in reverse list order, do
|
||||||
while (index) {
|
while (index) {
|
||||||
--index;
|
--index;
|
||||||
Value val = disposables->get(index);
|
Value val = disposables->get(index);
|
||||||
|
|
||||||
MOZ_ASSERT(val.isObject());
|
MOZ_ASSERT(val.isObject());
|
||||||
|
|
||||||
JS::Rooted<JSObject*> obj(cx, &val.toObject());
|
JS::Rooted<DisposableRecordObject*> resource(
|
||||||
JS::Rooted<DisposableRecordObject*> record(
|
cx, &val.toObject().as<DisposableRecordObject>());
|
||||||
cx, &obj->as<DisposableRecordObject>());
|
|
||||||
|
|
||||||
// DisposeResources ( disposeCapability, completion )
|
// Step 3.a. Let value be resource.[[ResourceValue]].
|
||||||
// Step 1.a. Let result be
|
JS::Rooted<JS::Value> value(cx, resource->getObject());
|
||||||
// Completion(Dispose(resource.[[ResourceValue]], resource.[[Hint]],
|
|
||||||
// resource.[[DisposeMethod]])).
|
// Step 3.b. Let hint be resource.[[Hint]].
|
||||||
//
|
// TODO: Implementation of async-dispose, implicitly sync-dispose for now
|
||||||
// Dispose ( V, hint, method )
|
// (Bug 1906534).
|
||||||
// https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-dispose
|
// Step 3.c. Let method be resource.[[DisposeMethod]].
|
||||||
// Step 1. If method is undefined, let result be undefined.
|
JS::Rooted<JS::Value> method(cx, resource->getMethod());
|
||||||
if (record->getObject().isUndefined()) {
|
|
||||||
|
// Step 3.e. If method is not undefined, then
|
||||||
|
if (method.isUndefined()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
JS::Rooted<JS::Value> disposeProp(cx, record->getMethod());
|
// Step 3.e.i. Let result be Completion(Call(method, value)).
|
||||||
JS::Rooted<JSObject*> recordedObj(cx, &record->getObject().toObject());
|
|
||||||
|
|
||||||
// Dispose ( V, hint, method )
|
|
||||||
// https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-dispose
|
|
||||||
// Step 2. Else, let result be ? Call(method, V).
|
|
||||||
JS::Rooted<JS::Value> rval(cx);
|
JS::Rooted<JS::Value> rval(cx);
|
||||||
if (!Call(cx, disposeProp, recordedObj, &rval)) {
|
if (!Call(cx, method, value, &rval)) {
|
||||||
// Step 1.b. If result is a throw completion, then
|
// Step 3.e.iii. If result is a throw completion, then
|
||||||
// TODO: Suppressed Error Object and subsequent steps in the spec need
|
if (hadError) {
|
||||||
// to be implemented (Bug 1899870). For now, we just keep track of the
|
// Step 3.e.iii.1.a. Set result to result.[[Value]].
|
||||||
// latest exception and continue with the disposal.
|
JS::Rooted<JS::Value> result(cx);
|
||||||
hadError = true;
|
if (!cx->getPendingException(&result)) {
|
||||||
if (cx->isExceptionPending()) {
|
|
||||||
if (!cx->getPendingException(&latestException)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
cx->clearPendingException();
|
cx->clearPendingException();
|
||||||
|
|
||||||
|
// Step 3.e.iii.1.b. Let suppressed be completion.[[Value]].
|
||||||
|
JS::Rooted<JS::Value> suppressed(cx, latestException);
|
||||||
|
|
||||||
|
// Steps 3.e.iii.1.c-e.
|
||||||
|
ErrorObject* errorObj = CreateSuppressedError(cx, result, suppressed);
|
||||||
|
if (!errorObj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Step 3.e.iii.1.f. Set completion to ThrowCompletion(error).
|
||||||
|
latestException.set(ObjectValue(*errorObj));
|
||||||
|
} else {
|
||||||
|
// Step 3.e.iii.2. Else,
|
||||||
|
// Step 3.e.iii.2.a. Set completion to result.
|
||||||
|
hadError = true;
|
||||||
|
if (cx->isExceptionPending()) {
|
||||||
|
if (!cx->getPendingException(&latestException)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cx->clearPendingException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisposeResources ( disposeCapability, completion )
|
// Step 6. Set disposeCapability.[[DisposableResourceStack]] to a new empty
|
||||||
// Step 3. Set disposeCapability.[[DisposableResourceStack]] to
|
// List.
|
||||||
// a new empty List.
|
|
||||||
if (env->is<LexicalEnvironmentObject>()) {
|
if (env->is<LexicalEnvironmentObject>()) {
|
||||||
env->as<LexicalEnvironmentObject>().clearDisposables();
|
env->as<LexicalEnvironmentObject>().clearDisposables();
|
||||||
} else {
|
} else {
|
||||||
env->as<ModuleEnvironmentObject>().clearDisposables();
|
env->as<ModuleEnvironmentObject>().clearDisposables();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Return ? completion.
|
// Step 7. Return ? completion.
|
||||||
if (hadError) {
|
if (hadError) {
|
||||||
cx->clearPendingException();
|
|
||||||
cx->setPendingException(latestException, ShouldCaptureStack::Maybe);
|
cx->setPendingException(latestException, ShouldCaptureStack::Maybe);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Step 7. Return ? completion.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -1856,7 +1914,8 @@ bool MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER js::Interpret(JSContext* cx,
|
||||||
*/
|
*/
|
||||||
#define INTERPRETER_LOOP()
|
#define INTERPRETER_LOOP()
|
||||||
#define CASE(OP) label_##OP:
|
#define CASE(OP) label_##OP:
|
||||||
#define DEFAULT() label_default:
|
#define DEFAULT() \
|
||||||
|
label_default:
|
||||||
#define DISPATCH_TO(OP) goto* addresses[(OP)]
|
#define DISPATCH_TO(OP) goto* addresses[(OP)]
|
||||||
|
|
||||||
#define LABEL(X) (&&label_##X)
|
#define LABEL(X) (&&label_##X)
|
||||||
|
@ -2225,9 +2284,21 @@ bool MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER js::Interpret(JSContext* cx,
|
||||||
CASE(DisposeDisposables) {
|
CASE(DisposeDisposables) {
|
||||||
ReservedRooted<JSObject*> env(&rootObject0,
|
ReservedRooted<JSObject*> env(&rootObject0,
|
||||||
REGS.fp()->environmentChain());
|
REGS.fp()->environmentChain());
|
||||||
|
DisposeJumpKind jumpKind = DisposeJumpKind(GET_UINT8(REGS.pc));
|
||||||
if (!DisposeDisposablesOnScopeLeave(cx, env)) {
|
bool ok = DisposeDisposablesOnScopeLeave(cx, env);
|
||||||
goto error;
|
if (jumpKind == DisposeJumpKind::JumpOnError) {
|
||||||
|
if (!ok) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MOZ_ASSERT(jumpKind == DisposeJumpKind::NoJumpOnError);
|
||||||
|
// The NoJumpOnError mode for this bytecode is used
|
||||||
|
// in the special case of For-of iterator close when there
|
||||||
|
// is an exception during the loop. Hence, if we reach this
|
||||||
|
// point in the execution we must have an exception
|
||||||
|
// pending and the bytecode following this must handle the
|
||||||
|
// exception.
|
||||||
|
MOZ_ASSERT(!ok, "NoJumpOnError used without a pending exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
END_CASE(DisposeDisposables)
|
END_CASE(DisposeDisposables)
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind
|
#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind
|
||||||
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||||
# include "vm/DisposableRecord.h"
|
# include "vm/DisposableRecord.h"
|
||||||
|
# include "vm/ErrorObject.h"
|
||||||
# include "vm/UsingHint.h"
|
# include "vm/UsingHint.h"
|
||||||
#endif
|
#endif
|
||||||
#include "vm/Stack.h"
|
#include "vm/Stack.h"
|
||||||
|
@ -648,6 +649,9 @@ bool DisposeDisposablesOnScopeLeave(JSContext* cx, JS::Handle<JSObject*> env);
|
||||||
bool GetDisposeMethod(JSContext* cx, JS::Handle<JS::Value> obj, UsingHint hint,
|
bool GetDisposeMethod(JSContext* cx, JS::Handle<JS::Value> obj, UsingHint hint,
|
||||||
JS::MutableHandle<JS::Value> disposeMethod);
|
JS::MutableHandle<JS::Value> disposeMethod);
|
||||||
|
|
||||||
|
ErrorObject* CreateSuppressedError(JSContext* cx, JS::Handle<JS::Value> error,
|
||||||
|
JS::Handle<JS::Value> suppressed);
|
||||||
|
|
||||||
bool CreateDisposableResource(JSContext* cx, JS::Handle<JS::Value> objVal,
|
bool CreateDisposableResource(JSContext* cx, JS::Handle<JS::Value> objVal,
|
||||||
UsingHint hint,
|
UsingHint hint,
|
||||||
JS::MutableHandle<JS::Value> result);
|
JS::MutableHandle<JS::Value> result);
|
||||||
|
|
|
@ -3422,10 +3422,10 @@
|
||||||
*
|
*
|
||||||
* Category: Variables and scopes
|
* Category: Variables and scopes
|
||||||
* Type: Entering and leaving environments
|
* Type: Entering and leaving environments
|
||||||
* Operands:
|
* Operands: DisposeJumpKind jumpKind
|
||||||
* Stack: =>
|
* Stack: =>
|
||||||
*/ \
|
*/ \
|
||||||
IF_EXPLICIT_RESOURCE_MANAGEMENT(MACRO(DisposeDisposables, dispose_disposables, NULL, 1, 0, 0, JOF_BYTE)) \
|
IF_EXPLICIT_RESOURCE_MANAGEMENT(MACRO(DisposeDisposables, dispose_disposables, NULL, 2, 0, 0, JOF_UINT8)) \
|
||||||
/*
|
/*
|
||||||
* Push the current VariableEnvironment (the environment on the environment
|
* Push the current VariableEnvironment (the environment on the environment
|
||||||
* chain designated to receive new variables).
|
* chain designated to receive new variables).
|
||||||
|
|
Загрузка…
Ссылка в новой задаче