From e7a040ee55c42da73473d618cd73875aa05e9870 Mon Sep 17 00:00:00 2001 From: Alexandre ABRIOUX Date: Fri, 19 Apr 2024 11:44:51 +0200 Subject: [PATCH 1/5] test(core/axios): add failing test for un-writable error stack --- test/unit/core/Axios.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/unit/core/Axios.js diff --git a/test/unit/core/Axios.js b/test/unit/core/Axios.js new file mode 100644 index 0000000000..b92c53a12c --- /dev/null +++ b/test/unit/core/Axios.js @@ -0,0 +1,20 @@ +import Axios from "../../../lib/core/Axios.js"; +import assert from "assert"; + + +describe('Axios', function () { + it('should support errors with un-writable stack', async function () { + const axios = new Axios({}); + // mock axios._request to return an Error with an un-writable stack property + axios._request = () => { + const mockError = new Error("test-error"); + Object.defineProperty(mockError, "stack", {value: {}, writable: false}); + throw mockError; + } + try { + await axios.request("test-url", {}) + } catch (e) { + assert.strictEqual(e.message, "test-error") + } + }) +}); From 94cdd54989485f6f5d2361360ba6fe88361b4d70 Mon Sep 17 00:00:00 2001 From: Alexandre ABRIOUX Date: Fri, 19 Apr 2024 11:45:00 +0200 Subject: [PATCH 2/5] fix(core/axios): handle un-writable error stack --- lib/core/Axios.js | 2 +- lib/utils.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/core/Axios.js b/lib/core/Axios.js index 2713364a0a..ad448e31a6 100644 --- a/lib/core/Axios.js +++ b/lib/core/Axios.js @@ -50,7 +50,7 @@ class Axios { if (!err.stack) { err.stack = stack; // match without the 2 top stack lines - } else if (stack && !String(err.stack).endsWith(stack.replace(/^.+\n.+\n/, ''))) { + } else if (stack && utils.isWritable(err, "stack") && !String(err.stack).endsWith(stack.replace(/^.+\n.+\n/, ''))) { err.stack += '\n' + stack } } diff --git a/lib/utils.js b/lib/utils.js index a386b77fbc..c6d7e9a8f3 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -668,6 +668,11 @@ const isAsyncFn = kindOfTest('AsyncFunction'); const isThenable = (thing) => thing && (isObject(thing) || isFunction(thing)) && isFunction(thing.then) && isFunction(thing.catch); +const isWritable = (obj, property) => { + const desc = Object.getOwnPropertyDescriptor(obj, property) || {} + return Boolean(desc.writable) +} + export default { isArray, isArrayBuffer, @@ -719,5 +724,6 @@ export default { isSpecCompliantForm, toJSONObject, isAsyncFn, - isThenable + isThenable, + isWritable }; From 34dbaaf56237b4c57ad612b11ca05f9387760d12 Mon Sep 17 00:00:00 2001 From: Alexandre ABRIOUX Date: Fri, 3 May 2024 20:15:54 +0200 Subject: [PATCH 3/5] fix(core/axios): handle un-writable error stack with a try..catch --- lib/core/Axios.js | 8 ++++++-- lib/utils.js | 8 +------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/core/Axios.js b/lib/core/Axios.js index ad448e31a6..f04b060d49 100644 --- a/lib/core/Axios.js +++ b/lib/core/Axios.js @@ -50,8 +50,12 @@ class Axios { if (!err.stack) { err.stack = stack; // match without the 2 top stack lines - } else if (stack && utils.isWritable(err, "stack") && !String(err.stack).endsWith(stack.replace(/^.+\n.+\n/, ''))) { - err.stack += '\n' + stack + } else if (stack && !String(err.stack).endsWith(stack.replace(/^.+\n.+\n/, ''))) { + try { + err.stack += '\n' + stack + } catch (e) { + // ignore the case where "stack" is an un-writable property + } } } diff --git a/lib/utils.js b/lib/utils.js index c6d7e9a8f3..a386b77fbc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -668,11 +668,6 @@ const isAsyncFn = kindOfTest('AsyncFunction'); const isThenable = (thing) => thing && (isObject(thing) || isFunction(thing)) && isFunction(thing.then) && isFunction(thing.catch); -const isWritable = (obj, property) => { - const desc = Object.getOwnPropertyDescriptor(obj, property) || {} - return Boolean(desc.writable) -} - export default { isArray, isArrayBuffer, @@ -724,6 +719,5 @@ export default { isSpecCompliantForm, toJSONObject, isAsyncFn, - isThenable, - isWritable + isThenable }; From 35478f210cb2a1ef46685b656eb33180bd1b3885 Mon Sep 17 00:00:00 2001 From: Alexandre ABRIOUX Date: Mon, 6 May 2024 12:00:54 +0200 Subject: [PATCH 4/5] chore: also handle undefined and unwritable stack property --- lib/core/Axios.js | 15 +++++++-------- test/unit/core/Axios.js | 39 ++++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/lib/core/Axios.js b/lib/core/Axios.js index f04b060d49..2765bbbda3 100644 --- a/lib/core/Axios.js +++ b/lib/core/Axios.js @@ -46,16 +46,15 @@ class Axios { // slice off the Error: ... line const stack = dummy.stack ? dummy.stack.replace(/^.+\n/, '') : ''; - - if (!err.stack) { - err.stack = stack; - // match without the 2 top stack lines - } else if (stack && !String(err.stack).endsWith(stack.replace(/^.+\n.+\n/, ''))) { - try { + try { + if (!err.stack) { + err.stack = stack; + // match without the 2 top stack lines + } else if (stack && !String(err.stack).endsWith(stack.replace(/^.+\n.+\n/, ''))) { err.stack += '\n' + stack - } catch (e) { - // ignore the case where "stack" is an un-writable property } + } catch (e) { + // ignore the case where "stack" is an un-writable property } } diff --git a/test/unit/core/Axios.js b/test/unit/core/Axios.js index b92c53a12c..f93d8c2de2 100644 --- a/test/unit/core/Axios.js +++ b/test/unit/core/Axios.js @@ -1,20 +1,29 @@ import Axios from "../../../lib/core/Axios.js"; import assert from "assert"; - describe('Axios', function () { - it('should support errors with un-writable stack', async function () { - const axios = new Axios({}); - // mock axios._request to return an Error with an un-writable stack property - axios._request = () => { - const mockError = new Error("test-error"); - Object.defineProperty(mockError, "stack", {value: {}, writable: false}); - throw mockError; - } - try { - await axios.request("test-url", {}) - } catch (e) { - assert.strictEqual(e.message, "test-error") - } - }) + describe("handle unwritable error stack", function () { + async function testUnwritableErrorStack(initialStackValue) { + const axios = new Axios({}); + // mock axios._request to return an Error with an un-writable stack property + axios._request = () => { + const mockError = new Error("test-error"); + Object.defineProperty(mockError, "stack", {value: initialStackValue, writable: false}); + throw mockError; + } + try { + await axios.request("test-url", {}) + } catch (e) { + assert.strictEqual(e.message, "test-error") + } + } + + it('should support errors with a defined but un-writable stack', async function () { + await testUnwritableErrorStack({}) + }); + + it('should support errors with an undefined and un-writable stack', async function () { + await testUnwritableErrorStack(undefined) + }); + }) }); From 473424b68225e8980da31a331c33c57d6f9d1a26 Mon Sep 17 00:00:00 2001 From: Alexandre ABRIOUX Date: Mon, 6 May 2024 14:36:37 +0200 Subject: [PATCH 5/5] test: add getter/setter test --- test/unit/core/Axios.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/test/unit/core/Axios.js b/test/unit/core/Axios.js index f93d8c2de2..8958ad1d0b 100644 --- a/test/unit/core/Axios.js +++ b/test/unit/core/Axios.js @@ -2,13 +2,13 @@ import Axios from "../../../lib/core/Axios.js"; import assert from "assert"; describe('Axios', function () { - describe("handle unwritable error stack", function () { - async function testUnwritableErrorStack(initialStackValue) { + describe("handle un-writable error stack", function () { + async function testUnwritableErrorStack(stackAttributes) { const axios = new Axios({}); // mock axios._request to return an Error with an un-writable stack property axios._request = () => { const mockError = new Error("test-error"); - Object.defineProperty(mockError, "stack", {value: initialStackValue, writable: false}); + Object.defineProperty(mockError, "stack", stackAttributes); throw mockError; } try { @@ -19,11 +19,29 @@ describe('Axios', function () { } it('should support errors with a defined but un-writable stack', async function () { - await testUnwritableErrorStack({}) + await testUnwritableErrorStack({value: {}, writable: false}) }); it('should support errors with an undefined and un-writable stack', async function () { - await testUnwritableErrorStack(undefined) + await testUnwritableErrorStack({value: undefined, writable: false}) + }); + + it('should support errors with a custom getter/setter for the stack property', async function () { + await testUnwritableErrorStack({ + get: () => ({}), + set: () => { + throw new Error('read-only'); + } + }) + }); + + it('should support errors with a custom getter/setter for the stack property (null case)', async function () { + await testUnwritableErrorStack({ + get: () => null, + set: () => { + throw new Error('read-only'); + } + }) }); }) });