Bug 1899870 - Implement SuppressedError. r=arai

Differential Revision: https://phabricator.services.mozilla.com/D215608
This commit is contained in:
Debadree Chatterjee 2024-07-08 04:37:06 +00:00
Родитель dbbbf89972
Коммит 6a197a7136
23 изменённых файлов: 1099 добавлений и 90 удалений

Просмотреть файл

@ -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).