зеркало из 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
|
||||
* 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.
|
||||
*
|
||||
* The errors and warnings are arranged in alphabetically within their
|
||||
* respective categories as defined in the comments below.
|
||||
*/
|
||||
enum JSExnType {
|
||||
// Generic Errors
|
||||
JSEXN_ERR,
|
||||
JSEXN_FIRST = JSEXN_ERR,
|
||||
// Internal Errors
|
||||
JSEXN_INTERNALERR,
|
||||
// ECMAScript Errors
|
||||
JSEXN_AGGREGATEERR,
|
||||
JSEXN_EVALERR,
|
||||
JSEXN_RANGEERR,
|
||||
JSEXN_REFERENCEERR,
|
||||
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||
JSEXN_SUPPRESSEDERR,
|
||||
#endif
|
||||
JSEXN_SYNTAXERR,
|
||||
JSEXN_TYPEERR,
|
||||
JSEXN_URIERR,
|
||||
// Debugger Errors
|
||||
JSEXN_DEBUGGEEWOULDRUN,
|
||||
// WASM Errors
|
||||
JSEXN_WASMCOMPILEERROR,
|
||||
JSEXN_WASMLINKERROR,
|
||||
JSEXN_WASMRUNTIMEERROR,
|
||||
JSEXN_ERROR_LIMIT,
|
||||
// Warnings
|
||||
JSEXN_WARN = JSEXN_ERROR_LIMIT,
|
||||
// Error Notes
|
||||
JSEXN_NOTE,
|
||||
JSEXN_LIMIT
|
||||
};
|
||||
|
|
|
@ -86,6 +86,8 @@
|
|||
REAL(EvalError, ERROR_CLASP(JSEXN_EVALERR)) \
|
||||
REAL(RangeError, ERROR_CLASP(JSEXN_RANGEERR)) \
|
||||
REAL(ReferenceError, ERROR_CLASP(JSEXN_REFERENCEERR)) \
|
||||
IF_EXPLICIT_RESOURCE_MANAGEMENT( \
|
||||
REAL(SuppressedError, ERROR_CLASP(JSEXN_SUPPRESSEDERR))) \
|
||||
REAL(SyntaxError, ERROR_CLASP(JSEXN_SYNTAXERR)) \
|
||||
REAL(TypeError, ERROR_CLASP(JSEXN_TYPEERR)) \
|
||||
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_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
|
||||
|
|
|
@ -38,7 +38,12 @@ bool ForOfLoopControl::emitBeginCodeNeedingIteratorClose(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
|
||||
return false;
|
||||
}
|
||||
|
@ -48,15 +53,7 @@ bool ForOfLoopControl::emitEndCodeNeedingIteratorClose(BytecodeEmitter* bce) {
|
|||
// [stack] ITER ... EXCEPTION STACK ITER
|
||||
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,
|
||||
CompletionKind::Throw)) {
|
||||
return false; // ITER ... EXCEPTION STACK
|
||||
|
|
|
@ -9,9 +9,12 @@
|
|||
#include "mozilla/Assertions.h" // MOZ_ASSERT
|
||||
|
||||
#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
|
||||
#include "frontend/IfEmitter.h" // BytecodeEmitter
|
||||
#include "frontend/SharedContext.h" // StatementKind
|
||||
#include "vm/Opcodes.h" // JSOp
|
||||
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||
# include "frontend/EmitterScope.h" // EmitterScope
|
||||
#endif
|
||||
#include "frontend/IfEmitter.h" // BytecodeEmitter
|
||||
#include "frontend/SharedContext.h" // StatementKind
|
||||
#include "vm/Opcodes.h" // JSOp
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
@ -94,7 +97,12 @@ bool TryEmitter::emitTryEnd() {
|
|||
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);
|
||||
if (!emitTryEnd()) {
|
||||
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 (!bce_->emit1(JSOp::Exception)) {
|
||||
return false;
|
||||
|
|
|
@ -216,7 +216,23 @@ class MOZ_STACK_CLASS TryEmitter {
|
|||
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
|
||||
// "{" character in the source code text, to improve line:column number in
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "frontend/BytecodeEmitter.h"
|
||||
#include "frontend/EmitterScope.h"
|
||||
#include "vm/DisposeJumpKind.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::frontend;
|
||||
|
@ -36,7 +37,8 @@ bool UsingEmitter::prepareForAssignment(Kind kind) {
|
|||
|
||||
bool UsingEmitter::prepareForForOfLoopIteration() {
|
||||
MOZ_ASSERT(bce_->innermostEmitterScopeNoCheck()->hasDisposables());
|
||||
if (!bce_->emit1(JSOp::DisposeDisposables)) {
|
||||
if (!bce_->emit2(JSOp::DisposeDisposables,
|
||||
uint8_t(DisposeJumpKind::JumpOnError))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -44,7 +46,8 @@ bool UsingEmitter::prepareForForOfLoopIteration() {
|
|||
|
||||
bool UsingEmitter::prepareForForOfIteratorCloseOnThrow() {
|
||||
MOZ_ASSERT(bce_->innermostEmitterScopeNoCheck()->hasDisposables());
|
||||
if (!bce_->emit1(JSOp::DisposeDisposables)) {
|
||||
if (!bce_->emit2(JSOp::DisposeDisposables,
|
||||
uint8_t(DisposeJumpKind::NoJumpOnError))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -52,7 +55,8 @@ bool UsingEmitter::prepareForForOfIteratorCloseOnThrow() {
|
|||
|
||||
bool UsingEmitter::emitNonLocalJump(EmitterScope* present) {
|
||||
MOZ_ASSERT(present->hasDisposables());
|
||||
if (!bce_->emit1(JSOp::DisposeDisposables)) {
|
||||
if (!bce_->emit2(JSOp::DisposeDisposables,
|
||||
uint8_t(DisposeJumpKind::JumpOnError))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -67,7 +71,8 @@ bool UsingEmitter::emitEnd() {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!bce_->emit1(JSOp::DisposeDisposables)) {
|
||||
if (!bce_->emit2(JSOp::DisposeDisposables,
|
||||
uint8_t(DisposeJumpKind::JumpOnError))) {
|
||||
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_RANGEERR) == JSProto_RangeError &&
|
||||
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_TYPEERR) == JSProto_TypeError &&
|
||||
JSProto_Error + int(JSEXN_URIERR) == JSProto_URIError &&
|
||||
|
|
|
@ -175,6 +175,7 @@
|
|||
MACRO_(enumerate, "enumerate") \
|
||||
MACRO_(era, "era") \
|
||||
MACRO_(eraYear, "eraYear") \
|
||||
IF_EXPLICIT_RESOURCE_MANAGEMENT(MACRO_(error, "error")) \
|
||||
MACRO_(errors, "errors") \
|
||||
MACRO_(ErrorToStringWithTrailingNewline, "ErrorToStringWithTrailingNewline") \
|
||||
MACRO_(escape, "escape") \
|
||||
|
@ -542,6 +543,7 @@
|
|||
MACRO_(StructType, "StructType") \
|
||||
MACRO_(style, "style") \
|
||||
MACRO_(super, "super") \
|
||||
IF_EXPLICIT_RESOURCE_MANAGEMENT(MACRO_(suppressed, "suppressed")) \
|
||||
MACRO_(switch_, "switch") \
|
||||
MACRO_(symmetricDifference, "symmetricDifference") \
|
||||
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;
|
||||
|
||||
#define IMPLEMENT_ERROR_PROTO_CLASS(name) \
|
||||
{ \
|
||||
#name ".prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_##name), \
|
||||
JS_NULL_CLASS_OPS, \
|
||||
&ErrorObject::classSpecs[JSProto_##name - JSProto_Error] \
|
||||
}
|
||||
#define IMPLEMENT_ERROR_PROTO_CLASS(name) \
|
||||
{#name ".prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_##name), \
|
||||
JS_NULL_CLASS_OPS, \
|
||||
&ErrorObject::classSpecs[JSProto_##name - JSProto_Error]}
|
||||
|
||||
const JSClass ErrorObject::protoClasses[JSEXN_ERROR_LIMIT] = {
|
||||
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(RangeError),
|
||||
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(TypeError),
|
||||
IMPLEMENT_ERROR_PROTO_CLASS(URIError),
|
||||
|
@ -109,6 +110,9 @@ IMPLEMENT_NATIVE_ERROR_PROPERTIES(AggregateError)
|
|||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(EvalError)
|
||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(RangeError)
|
||||
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(TypeError)
|
||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(URIError)
|
||||
|
@ -117,18 +121,25 @@ IMPLEMENT_NATIVE_ERROR_PROPERTIES(CompileError)
|
|||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(LinkError)
|
||||
IMPLEMENT_NATIVE_ERROR_PROPERTIES(RuntimeError)
|
||||
|
||||
#define IMPLEMENT_NATIVE_ERROR_SPEC(name) \
|
||||
{ \
|
||||
ErrorObject::createConstructor, ErrorObject::createProto, nullptr, \
|
||||
nullptr, nullptr, name##_properties, nullptr, JSProto_Error \
|
||||
}
|
||||
#define IMPLEMENT_NATIVE_ERROR_SPEC(name) \
|
||||
{ErrorObject::createConstructor, \
|
||||
ErrorObject::createProto, \
|
||||
nullptr, \
|
||||
nullptr, \
|
||||
nullptr, \
|
||||
name##_properties, \
|
||||
nullptr, \
|
||||
JSProto_Error}
|
||||
|
||||
#define IMPLEMENT_NONGLOBAL_ERROR_SPEC(name) \
|
||||
{ \
|
||||
ErrorObject::createConstructor, ErrorObject::createProto, nullptr, \
|
||||
nullptr, nullptr, name##_properties, nullptr, \
|
||||
JSProto_Error | ClassSpec::DontDefineConstructor \
|
||||
}
|
||||
#define IMPLEMENT_NONGLOBAL_ERROR_SPEC(name) \
|
||||
{ErrorObject::createConstructor, \
|
||||
ErrorObject::createProto, \
|
||||
nullptr, \
|
||||
nullptr, \
|
||||
nullptr, \
|
||||
name##_properties, \
|
||||
nullptr, \
|
||||
JSProto_Error | ClassSpec::DontDefineConstructor}
|
||||
|
||||
const ClassSpec ErrorObject::classSpecs[JSEXN_ERROR_LIMIT] = {
|
||||
{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(RangeError),
|
||||
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(TypeError),
|
||||
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(RuntimeError)};
|
||||
|
||||
#define IMPLEMENT_ERROR_CLASS_CORE(name, reserved_slots) \
|
||||
{ \
|
||||
#name, \
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
|
||||
JSCLASS_HAS_RESERVED_SLOTS(reserved_slots) | \
|
||||
JSCLASS_BACKGROUND_FINALIZE, \
|
||||
&ErrorObjectClassOps, \
|
||||
&ErrorObject::classSpecs[JSProto_##name - JSProto_Error] \
|
||||
}
|
||||
#define IMPLEMENT_ERROR_CLASS_CORE(name, reserved_slots) \
|
||||
{#name, \
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_##name) | \
|
||||
JSCLASS_HAS_RESERVED_SLOTS(reserved_slots) | \
|
||||
JSCLASS_BACKGROUND_FINALIZE, \
|
||||
&ErrorObjectClassOps, \
|
||||
&ErrorObject::classSpecs[JSProto_##name - JSProto_Error]}
|
||||
|
||||
#define IMPLEMENT_ERROR_CLASS(name) \
|
||||
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(AggregateError), IMPLEMENT_ERROR_CLASS(EvalError),
|
||||
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(URIError),
|
||||
// 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,
|
||||
"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 =
|
||||
JSCLASS_CACHED_PROTO_KEY(&ErrorObject::classes[exnType]);
|
||||
|
||||
|
@ -358,6 +378,60 @@ static bool AggregateError(JSContext* cx, unsigned argc, Value* vp) {
|
|||
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 */
|
||||
JSObject* ErrorObject::createProto(JSContext* cx, JSProtoKey key) {
|
||||
JSExnType type = ExnTypeFromProtoKey(key);
|
||||
|
@ -397,7 +471,14 @@ JSObject* ErrorObject::createConstructor(JSContext* cx, JSProtoKey key) {
|
|||
if (type == JSEXN_AGGREGATEERR) {
|
||||
native = AggregateError;
|
||||
nargs = 2;
|
||||
} else {
|
||||
}
|
||||
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||
else if (type == JSEXN_SUPPRESSEDERR) {
|
||||
native = SuppressedError;
|
||||
nargs = 3;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
native = Error;
|
||||
nargs = 1;
|
||||
}
|
||||
|
|
|
@ -139,6 +139,9 @@ bool GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key) {
|
|||
case JSProto_Error:
|
||||
case JSProto_InternalError:
|
||||
case JSProto_AggregateError:
|
||||
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||
case JSProto_SuppressedError:
|
||||
#endif
|
||||
case JSProto_EvalError:
|
||||
case JSProto_RangeError:
|
||||
case JSProto_ReferenceError:
|
||||
|
|
|
@ -45,7 +45,11 @@
|
|||
#include "vm/AsyncFunction.h"
|
||||
#include "vm/AsyncIteration.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/GeneratorObject.h"
|
||||
#include "vm/Iteration.h"
|
||||
|
@ -1745,6 +1749,49 @@ bool js::CreateDisposableResource(JSContext* cx, JS::Handle<JS::Value> obj,
|
|||
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,
|
||||
JS::Handle<JSObject*> env) {
|
||||
if (!env->is<LexicalEnvironmentObject>() &&
|
||||
|
@ -1765,13 +1812,8 @@ bool js::DisposeDisposablesOnScopeLeave(JSContext* cx,
|
|||
|
||||
uint32_t index = disposables->length();
|
||||
|
||||
// hadError and latestException correspond to the completion value.
|
||||
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);
|
||||
|
||||
if (cx->isExceptionPending()) {
|
||||
|
@ -1782,67 +1824,83 @@ bool js::DisposeDisposablesOnScopeLeave(JSContext* cx,
|
|||
cx->clearPendingException();
|
||||
}
|
||||
|
||||
// Step 3. For each element resource of
|
||||
// disposeCapability.[[DisposableResourceStack]], in reverse list order, do
|
||||
while (index) {
|
||||
--index;
|
||||
Value val = disposables->get(index);
|
||||
|
||||
MOZ_ASSERT(val.isObject());
|
||||
|
||||
JS::Rooted<JSObject*> obj(cx, &val.toObject());
|
||||
JS::Rooted<DisposableRecordObject*> record(
|
||||
cx, &obj->as<DisposableRecordObject>());
|
||||
JS::Rooted<DisposableRecordObject*> resource(
|
||||
cx, &val.toObject().as<DisposableRecordObject>());
|
||||
|
||||
// DisposeResources ( disposeCapability, completion )
|
||||
// Step 1.a. Let result be
|
||||
// Completion(Dispose(resource.[[ResourceValue]], resource.[[Hint]],
|
||||
// resource.[[DisposeMethod]])).
|
||||
//
|
||||
// Dispose ( V, hint, method )
|
||||
// https://arai-a.github.io/ecma262-compare/?pr=3000&id=sec-dispose
|
||||
// Step 1. If method is undefined, let result be undefined.
|
||||
if (record->getObject().isUndefined()) {
|
||||
// Step 3.a. Let value be resource.[[ResourceValue]].
|
||||
JS::Rooted<JS::Value> value(cx, resource->getObject());
|
||||
|
||||
// Step 3.b. Let hint be resource.[[Hint]].
|
||||
// TODO: Implementation of async-dispose, implicitly sync-dispose for now
|
||||
// (Bug 1906534).
|
||||
// Step 3.c. Let method be resource.[[DisposeMethod]].
|
||||
JS::Rooted<JS::Value> method(cx, resource->getMethod());
|
||||
|
||||
// Step 3.e. If method is not undefined, then
|
||||
if (method.isUndefined()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> disposeProp(cx, record->getMethod());
|
||||
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).
|
||||
// Step 3.e.i. Let result be Completion(Call(method, value)).
|
||||
JS::Rooted<JS::Value> rval(cx);
|
||||
if (!Call(cx, disposeProp, recordedObj, &rval)) {
|
||||
// Step 1.b. If result is a throw completion, then
|
||||
// TODO: Suppressed Error Object and subsequent steps in the spec need
|
||||
// to be implemented (Bug 1899870). For now, we just keep track of the
|
||||
// latest exception and continue with the disposal.
|
||||
hadError = true;
|
||||
if (cx->isExceptionPending()) {
|
||||
if (!cx->getPendingException(&latestException)) {
|
||||
if (!Call(cx, method, value, &rval)) {
|
||||
// Step 3.e.iii. If result is a throw completion, then
|
||||
if (hadError) {
|
||||
// Step 3.e.iii.1.a. Set result to result.[[Value]].
|
||||
JS::Rooted<JS::Value> result(cx);
|
||||
if (!cx->getPendingException(&result)) {
|
||||
return false;
|
||||
}
|
||||
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 3. Set disposeCapability.[[DisposableResourceStack]] to
|
||||
// a new empty List.
|
||||
// Step 6. Set disposeCapability.[[DisposableResourceStack]] to a new empty
|
||||
// List.
|
||||
if (env->is<LexicalEnvironmentObject>()) {
|
||||
env->as<LexicalEnvironmentObject>().clearDisposables();
|
||||
} else {
|
||||
env->as<ModuleEnvironmentObject>().clearDisposables();
|
||||
}
|
||||
|
||||
// 4. Return ? completion.
|
||||
// Step 7. Return ? completion.
|
||||
if (hadError) {
|
||||
cx->clearPendingException();
|
||||
cx->setPendingException(latestException, ShouldCaptureStack::Maybe);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7. Return ? completion.
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
@ -1856,7 +1914,8 @@ bool MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER js::Interpret(JSContext* cx,
|
|||
*/
|
||||
#define INTERPRETER_LOOP()
|
||||
#define CASE(OP) label_##OP:
|
||||
#define DEFAULT() label_default:
|
||||
#define DEFAULT() \
|
||||
label_default:
|
||||
#define DISPATCH_TO(OP) goto* addresses[(OP)]
|
||||
|
||||
#define LABEL(X) (&&label_##X)
|
||||
|
@ -2225,9 +2284,21 @@ bool MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER js::Interpret(JSContext* cx,
|
|||
CASE(DisposeDisposables) {
|
||||
ReservedRooted<JSObject*> env(&rootObject0,
|
||||
REGS.fp()->environmentChain());
|
||||
|
||||
if (!DisposeDisposablesOnScopeLeave(cx, env)) {
|
||||
goto error;
|
||||
DisposeJumpKind jumpKind = DisposeJumpKind(GET_UINT8(REGS.pc));
|
||||
bool ok = DisposeDisposablesOnScopeLeave(cx, env);
|
||||
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)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "vm/CheckIsObjectKind.h" // CheckIsObjectKind
|
||||
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
|
||||
# include "vm/DisposableRecord.h"
|
||||
# include "vm/ErrorObject.h"
|
||||
# include "vm/UsingHint.h"
|
||||
#endif
|
||||
#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,
|
||||
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,
|
||||
UsingHint hint,
|
||||
JS::MutableHandle<JS::Value> result);
|
||||
|
|
|
@ -3422,10 +3422,10 @@
|
|||
*
|
||||
* Category: Variables and scopes
|
||||
* Type: Entering and leaving environments
|
||||
* Operands:
|
||||
* Operands: DisposeJumpKind jumpKind
|
||||
* 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
|
||||
* chain designated to receive new variables).
|
||||
|
|
Загрузка…
Ссылка в новой задаче