diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 5a16a56a4a7eb8..6c80aa7bf4f2b3 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -23,6 +23,11 @@ const { customInspectSymbol, } = require('internal/util'); const { inspect } = require('internal/util/inspect'); +const { + codes: { + ERR_INVALID_THIS, + } +} = require('internal/errors'); const kAborted = Symbol('kAborted'); @@ -37,13 +42,21 @@ function customInspect(self, obj, depth, options) { return `${self.constructor.name} ${inspect(obj, opts)}`; } +function validateAbortSignal(obj) { + if (obj?.[kAborted] === undefined) + throw new ERR_INVALID_THIS('AbortSignal'); +} + class AbortSignal extends EventTarget { constructor() { // eslint-disable-next-line no-restricted-syntax throw new TypeError('Illegal constructor'); } - get aborted() { return !!this[kAborted]; } + get aborted() { + validateAbortSignal(this); + return !!this[kAborted]; + } [customInspectSymbol](depth, options) { return customInspect(this, { @@ -89,13 +102,26 @@ function abortSignal(signal) { // initializers for now: // https://bugs.chromium.org/p/v8/issues/detail?id=10704 const kSignal = Symbol('signal'); + +function validateAbortController(obj) { + if (obj?.[kSignal] === undefined) + throw new ERR_INVALID_THIS('AbortController'); +} + class AbortController { constructor() { this[kSignal] = createAbortSignal(); } - get signal() { return this[kSignal]; } - abort() { abortSignal(this[kSignal]); } + get signal() { + validateAbortController(this); + return this[kSignal]; + } + + abort() { + validateAbortController(this); + abortSignal(this[kSignal]); + } [customInspectSymbol](depth, options) { return customInspect(this, { diff --git a/test/parallel/test-abortcontroller.js b/test/parallel/test-abortcontroller.js index 2b36da332e44aa..d26ec8641a671e 100644 --- a/test/parallel/test-abortcontroller.js +++ b/test/parallel/test-abortcontroller.js @@ -72,3 +72,63 @@ const { ok, strictEqual, throws } = require('assert'); const signal = AbortSignal.abort(); ok(signal.aborted); } + +{ + // Test that AbortController properties and methods validate the receiver + const acSignalGet = Object.getOwnPropertyDescriptor( + AbortController.prototype, + 'signal' + ).get; + const acAbort = AbortController.prototype.abort; + + const goodController = new AbortController(); + ok(acSignalGet.call(goodController)); + acAbort.call(goodController); + + const badAbortControllers = [ + null, + undefined, + 0, + NaN, + true, + 'AbortController', + Object.create(AbortController.prototype) + ]; + for (const badController of badAbortControllers) { + throws( + () => acSignalGet.call(badController), + { code: 'ERR_INVALID_THIS', name: 'TypeError' } + ); + throws( + () => acAbort.call(badController), + { code: 'ERR_INVALID_THIS', name: 'TypeError' } + ); + } +} + +{ + // Test that AbortSignal properties validate the receiver + const signalAbortedGet = Object.getOwnPropertyDescriptor( + AbortSignal.prototype, + 'aborted' + ).get; + + const goodSignal = new AbortController().signal; + strictEqual(signalAbortedGet.call(goodSignal), false); + + const badAbortSignals = [ + null, + undefined, + 0, + NaN, + true, + 'AbortSignal', + Object.create(AbortSignal.prototype) + ]; + for (const badSignal of badAbortSignals) { + throws( + () => signalAbortedGet.call(badSignal), + { code: 'ERR_INVALID_THIS', name: 'TypeError' } + ); + } +}