From f9e1d0abbf53ba6effd99f1d352cacb3e5b64549 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Tue, 26 Jan 2021 11:39:07 -0500 Subject: [PATCH] Avoid relying on constructor.name for instanceOf error check. An improvement beyond #1174, for non-production environments that enable minification, such as NODE_ENV='staging'. Since graphql-js goes to the trouble of providing a Symbol.toStringTag property for most of the classes it exports, and that string is immune to minification (unlike constructor.name), we should use it for error checking in instanceOf(value, constructor) whenever the constructor provides a tag, falling back to constructor.name and value.constructor.name for constructors that do not define Symbol.toStringTag (as before). Motivating issue/investigation: https://github.com/apollographql/apollo-client/issues/7446#issuecomment-767660631 --- src/jsutils/instanceOf.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/jsutils/instanceOf.js b/src/jsutils/instanceOf.js index e55cd13f73..2d0bf48208 100644 --- a/src/jsutils/instanceOf.js +++ b/src/jsutils/instanceOf.js @@ -1,3 +1,5 @@ +import { SYMBOL_TO_STRING_TAG } from '../polyfills/symbols'; + /** * A replacement for instanceof which includes an error warning when multi-realm * constructors are detected. @@ -21,9 +23,23 @@ export default process.env.NODE_ENV === 'production' return true; } if (value) { - const valueClass = value.constructor; - const className = constructor.name; - if (className && valueClass && valueClass.name === className) { + const classTag = constructor?.prototype?.[SYMBOL_TO_STRING_TAG]; + const className = classTag || constructor.name; + // When the constructor class defines a Symbol.toStringTag + // property, as most classes exported by graphql-js do, use it + // instead of constructor.name and value.constructor.name to + // detect module/realm duplication, since the Symbol.toStringTag + // string is immune to minification. This code runs only when + // process.env.NODE_ENV !== 'production', but minification is + // often enabled in non-production environments like 'staging'. + // In these environments, this error can be thrown mistakenly if + // we rely on constructor.name and value.constructor.name, since + // they could be minified to the same short string, even though + // value is legitimately _not_ instanceof constructor. + const valueName = classTag + ? value[SYMBOL_TO_STRING_TAG] + : value.constructor?.name; + if (typeof className === 'string' && valueName === className) { throw new Error( `Cannot use ${className} "${value}" from another module or realm.