From 0cd7a2cb2e231bd87412170f05020fd910e6d3e4 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 15 Jan 2023 13:49:01 -0800 Subject: [PATCH] [Fix] `throws`: avoid crashing on a nonconfigurable or nonextensible `expected` --- lib/test.js | 10 ++++++---- test/throws.js | 53 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/lib/test.js b/lib/test.js index d2b19806..f6a0e6b2 100644 --- a/lib/test.js +++ b/lib/test.js @@ -630,10 +630,12 @@ Test.prototype['throws'] = function (fn, expected, msg, extra) { fn(); } catch (err) { caught = { error: err }; - if (Object(err) === err && (!isEnumerable(err, 'message') || !has(err, 'message'))) { - var message = err.message; - delete err.message; - err.message = message; + if (Object(err) === err && 'message' in err && (!isEnumerable(err, 'message') || !has(err, 'message'))) { + try { + var message = err.message; + delete err.message; + err.message = message; + } catch (e) { /**/ } } } diff --git a/test/throws.js b/test/throws.js index c1665030..5fff9e87 100644 --- a/test/throws.js +++ b/test/throws.js @@ -112,11 +112,28 @@ tap.test('failures', function (tt) { ' at Test. ($TEST/throws.js:$LINE:$COL)', ' [... stack stripped ...]', ' ...', + '# non-extensible throw match', + 'ok 37 error is non-extensible', + 'ok 38 non-extensible error matches', + 'ok 39 errorWithMessage is non-extensible', + 'not ok 40 non-extensible error with message matches', + ' ---', + ' operator: throws', + ' expected: |-', + ' { foo: 1 }', + ' actual: |-', + ' { message: \'abc\' }', + ' at: Test. ($TEST/throws.js:$LINE:$COL)', + ' ...', + '# frozen `message` property', + 'ok 41 error is non-writable', + 'ok 42 error is non-configurable', + 'ok 43 non-writable error matches', '', - '1..36', - '# tests 36', - '# pass 33', - '# fail 3', + '1..43', + '# tests 43', + '# pass 39', + '# fail 4', '' ]); })); @@ -304,4 +321,32 @@ tap.test('failures', function (tt) { // AssertionError [ERR_ASSERTION] t.end(); }); + + test('non-extensible throw match', { skip: !Object.seal }, function (t) { + var error = { foo: 1 }; + Object.seal(error); + t.throws(function () { error.x = 1; }, TypeError, 'error is non-extensible'); + + t.throws(function () { throw error; }, error, 'non-extensible error matches'); + + var errorWithMessage = { message: 'abc' }; + Object.seal(errorWithMessage); + t.throws(function () { errorWithMessage.x = 1; }, TypeError, 'errorWithMessage is non-extensible'); + + t.throws(function () { throw errorWithMessage; }, error, 'non-extensible error with message matches'); + + t.end(); + }); + + test('frozen `message` property', { skip: !Object.defineProperty }, function (t) { + var error = { message: 'abc' }; + Object.defineProperty(error, 'message', { configurable: false, enumerable: false, writable: false }); + + t.throws(function () { error.message = 'def'; }, TypeError, 'error is non-writable'); + t.throws(function () { delete error.message; }, TypeError, 'error is non-configurable'); + + t.throws(function () { throw error; }, { message: 'abc' }, 'non-writable error matches'); + + t.end(); + }); });