From 0731b5f64311b168ac941ce3e547bb1a32766783 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 | 43 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/lib/test.js b/lib/test.js index 596f83a5..926da137 100644 --- a/lib/test.js +++ b/lib/test.js @@ -542,10 +542,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 9186d052..a39d3a4a 100644 --- a/test/throws.js +++ b/test/throws.js @@ -176,10 +176,19 @@ tap.test('failures', function (tt) { ' at Test. ($TEST/throws.js:$LINE:$COL)', ' [... stack stripped ...]', ' ...', + '# non-extensible throw match', + 'ok 15 error is non-extensible', + 'ok 16 non-extensible error matches', + 'ok 17 errorWithMessage is non-extensible', + 'ok 18 non-extensible error with message matches', + '# frozen `message` property', + 'ok 19 error is non-writable', + 'ok 20 error is non-configurable', + 'ok 21 non-writable error matches', '', - '1..14', - '# tests 14', - '# pass 4', + '1..21', + '# tests 21', + '# pass 11', '# fail 10', '' ]); @@ -221,4 +230,32 @@ tap.test('failures', function (tt) { t['throws'](function () { throw actual; }, TypeError, 'throws actual'); 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(); + }); });