From 811d74a1a72892853bb8926711ec4ae8a3dbec83 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Fri, 26 Dec 2025 11:04:30 +0700 Subject: [PATCH 1/3] assert,util: improve comparison performance This makes sure that the toStringTag symbol is used, if available instead of calculating the toString() value each time, if not needed (the type checks make a brand check, so there is no need to check the toStringTag, if non is defined on the object). --- lib/internal/util/comparisons.js | 262 ++++++++++++++++--------------- 1 file changed, 138 insertions(+), 124 deletions(-) diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js index 3b800b48b7c5d5..a5e1af8304eb6f 100644 --- a/lib/internal/util/comparisons.js +++ b/lib/internal/util/comparisons.js @@ -41,6 +41,7 @@ const { StringPrototypeValueOf, Symbol, SymbolPrototypeValueOf, + SymbolToStringTag, TypedArrayPrototypeGetByteLength: getByteLength, TypedArrayPrototypeGetSymbolToStringTag, Uint16Array, @@ -264,6 +265,17 @@ function innerDeepEqual(val1, val2, mode, memos) { return objectComparisonStart(val1, val2, mode, memos); } +function hasUnequalTag(val1, val2) { + return val1[SymbolToStringTag] !== val2[SymbolToStringTag]; +} + +function slowHasUnequalTag(val1Tag, val1, val2) { + if (val1[SymbolToStringTag]) { + return val1[SymbolToStringTag] !== val2[SymbolToStringTag]; + } + return val1Tag !== ObjectPrototypeToString(val2); +} + function objectComparisonStart(val1, val2, mode, memos) { if (mode === kStrict) { if (wellKnownConstructors.has(val1.constructor) || @@ -276,16 +288,10 @@ function objectComparisonStart(val1, val2, mode, memos) { } } - const val1Tag = ObjectPrototypeToString(val1); - const val2Tag = ObjectPrototypeToString(val2); - - if (val1Tag !== val2Tag) { - return false; - } - if (ArrayIsArray(val1)) { if (!ArrayIsArray(val2) || - (val1.length !== val2.length && (mode !== kPartial || val1.length < val2.length))) { + (val1.length !== val2.length && (mode !== kPartial || val1.length < val2.length)) || + hasUnequalTag(val1, val2)) { return false; } @@ -296,124 +302,136 @@ function objectComparisonStart(val1, val2, mode, memos) { return false; } return keyCheck(val1, val2, mode, memos, kIsArray, keys2); - } else if (val1Tag === '[object Object]') { - return keyCheck(val1, val2, mode, memos, kNoIterator); - } else if (isDate(val1)) { - if (!isDate(val2)) { - return false; - } - const time1 = DatePrototypeGetTime(val1); - const time2 = DatePrototypeGetTime(val2); - // eslint-disable-next-line no-self-compare - if (time1 !== time2 && (time1 === time1 || time2 === time2)) { - return false; - } - } else if (isRegExp(val1)) { - if (!isRegExp(val2) || !areSimilarRegExps(val1, val2)) { - return false; - } - } else if (isArrayBufferView(val1)) { - if (TypedArrayPrototypeGetSymbolToStringTag(val1) !== - TypedArrayPrototypeGetSymbolToStringTag(val2)) { - return false; - } - if (mode === kPartial && val1.byteLength !== val2.byteLength) { - if (!isPartialArrayBufferView(val1, val2)) { + } else { + let val1Tag; + if (val1[SymbolToStringTag] === undefined && + (val1Tag = ObjectPrototypeToString(val1)) === '[object Object]') { + if (slowHasUnequalTag(val1Tag, val1, val2)) { return false; } - } else if (mode === kLoose && - (isFloat32Array(val1) || isFloat64Array(val1) || isFloat16Array(val1))) { - if (!areSimilarFloatArrays(val1, val2)) { + return keyCheck(val1, val2, mode, memos, kNoIterator); + } else if (isSet(val1)) { + if (!isSet(val2) || + (val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size)) || + hasUnequalTag(val1, val2)) { return false; } - } else if (!areSimilarTypedArrays(val1, val2)) { - return false; - } - // Buffer.compare returns true, so val1.length === val2.length. If they both - // only contain numeric keys, we don't need to exam further than checking - // the symbols. - const filter = mode !== kLoose ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS; - const keys2 = getOwnNonIndexProperties(val2, filter); - if (mode !== kPartial && - keys2.length !== getOwnNonIndexProperties(val1, filter).length) { - return false; - } - return keyCheck(val1, val2, mode, memos, kNoIterator, keys2); - } else if (isSet(val1)) { - if (!isSet(val2) || - (val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size))) { - return false; - } - return keyCheck(val1, val2, mode, memos, kIsSet); - } else if (isMap(val1)) { - if (!isMap(val2) || - (val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size))) { - return false; - } - return keyCheck(val1, val2, mode, memos, kIsMap); - } else if (isAnyArrayBuffer(val1)) { - if (!isAnyArrayBuffer(val2)) { - return false; - } - if (mode !== kPartial || val1.byteLength === val2.byteLength) { - if (!areEqualArrayBuffers(val1, val2)) { + return keyCheck(val1, val2, mode, memos, kIsSet); + } else if (isMap(val1)) { + if (!isMap(val2) || + (val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size)) || + hasUnequalTag(val1, val2)) { return false; } - } else if (!isPartialUint8Array(new Uint8Array(val1), new Uint8Array(val2))) { - return false; - } - } else if (isError(val1)) { - // Do not compare the stack as it might differ even though the error itself - // is otherwise identical. - if (!isError(val2) || - !isEnumerableOrIdentical(val1, val2, 'message', mode, memos) || - !isEnumerableOrIdentical(val1, val2, 'name', mode, memos) || - !isEnumerableOrIdentical(val1, val2, 'cause', mode, memos) || - !isEnumerableOrIdentical(val1, val2, 'errors', mode, memos)) { - return false; - } - const hasOwnVal2Cause = hasOwn(val2, 'cause'); - if ((hasOwnVal2Cause !== hasOwn(val1, 'cause') && (mode !== kPartial || hasOwnVal2Cause))) { - return false; - } - } else if (isBoxedPrimitive(val1)) { - if (!isEqualBoxedPrimitive(val1, val2)) { - return false; - } - } else if (ArrayIsArray(val2) || - isArrayBufferView(val2) || - isSet(val2) || - isMap(val2) || - isDate(val2) || - isRegExp(val2) || - isAnyArrayBuffer(val2) || - isBoxedPrimitive(val2) || - isNativeError(val2) || - val2 instanceof Error) { - return false; - } else if (isURL(val1)) { - if (!isURL(val2) || val1.href !== val2.href) { - return false; - } - } else if (isKeyObject(val1)) { - if (!isKeyObject(val2) || !val1.equals(val2)) { + return keyCheck(val1, val2, mode, memos, kIsMap); + } else if (isArrayBufferView(val1)) { + if (TypedArrayPrototypeGetSymbolToStringTag(val1) !== + TypedArrayPrototypeGetSymbolToStringTag(val2)) { + return false; + } + if (mode === kPartial && val1.byteLength !== val2.byteLength) { + if (!isPartialArrayBufferView(val1, val2)) { + return false; + } + } else if (mode === kLoose && + (isFloat32Array(val1) || isFloat64Array(val1) || isFloat16Array(val1))) { + if (!areSimilarFloatArrays(val1, val2)) { + return false; + } + } else if (!areSimilarTypedArrays(val1, val2)) { + return false; + } + // Buffer.compare returns true, so val1.length === val2.length. If they both + // only contain numeric keys, we don't need to exam further than checking + // the symbols. + const filter = mode !== kLoose ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS; + const keys2 = getOwnNonIndexProperties(val2, filter); + if (mode !== kPartial && + keys2.length !== getOwnNonIndexProperties(val1, filter).length) { + return false; + } + return keyCheck(val1, val2, mode, memos, kNoIterator, keys2); + } else if (isDate(val1)) { + if (!isDate(val2)) { + return false; + } + const time1 = DatePrototypeGetTime(val1); + const time2 = DatePrototypeGetTime(val2); + // eslint-disable-next-line no-self-compare + if (time1 !== time2 && (time1 === time1 || time2 === time2)) { + return false; + } + } else if (isRegExp(val1)) { + if (!isRegExp(val2) || !areSimilarRegExps(val1, val2) || hasUnequalTag(val1, val2)) { + return false; + } + } else if (isAnyArrayBuffer(val1)) { + if (!isAnyArrayBuffer(val2) || hasUnequalTag(val1, val2)) { + return false; + } + if (mode !== kPartial || val1.byteLength === val2.byteLength) { + if (!areEqualArrayBuffers(val1, val2)) { + return false; + } + } else if (!isPartialUint8Array(new Uint8Array(val1), new Uint8Array(val2))) { + return false; + } + } else if (slowHasUnequalTag(val1Tag ?? ObjectPrototypeToString(val1), val1, val2) || + ArrayIsArray(val2) || + isArrayBufferView(val2) || + isSet(val2) || + isMap(val2) || + isDate(val2) || + isRegExp(val2) || + isAnyArrayBuffer(val2)) { return false; - } - } else if (isCryptoKey(val1)) { - if (kKeyObject === undefined) { - kKeyObject = require('internal/crypto/util').kKeyObject; - ({ kExtractable, kAlgorithm, kKeyUsages } = require('internal/crypto/keys')); - } - if (!isCryptoKey(val2) || - val1[kExtractable] !== val2[kExtractable] || - !innerDeepEqual(val1[kAlgorithm], val2[kAlgorithm], mode, memos) || - !innerDeepEqual(val1[kKeyUsages], val2[kKeyUsages], mode, memos) || - !innerDeepEqual(val1[kKeyObject], val2[kKeyObject], mode, memos) - ) { + } else if (isError(val1)) { + // Do not compare the stack as it might differ even though the error itself + // is otherwise identical. + if (!isError(val2) || + !isEnumerableOrIdentical(val1, val2, 'message', mode, memos) || + !isEnumerableOrIdentical(val1, val2, 'name', mode, memos) || + !isEnumerableOrIdentical(val1, val2, 'cause', mode, memos) || + !isEnumerableOrIdentical(val1, val2, 'errors', mode, memos)) { + return false; + } + const hasOwnVal2Cause = hasOwn(val2, 'cause'); + if ((hasOwnVal2Cause !== hasOwn(val1, 'cause') && (mode !== kPartial || hasOwnVal2Cause))) { + return false; + } + } else if (isBoxedPrimitive(val1)) { + if (!isEqualBoxedPrimitive(val1, val2)) { + return false; + } + } else if (isURL(val1)) { + if (!isURL(val2) || val1.href !== val2.href) { + return false; + } + } else if (isKeyObject(val1)) { + if (!isKeyObject(val2) || !val1.equals(val2)) { + return false; + } + } else if (isCryptoKey(val1)) { + if (kKeyObject === undefined) { + kKeyObject = require('internal/crypto/util').kKeyObject; + ({ kExtractable, kAlgorithm, kKeyUsages } = require('internal/crypto/keys')); + } + if (!isCryptoKey(val2) || + val1[kExtractable] !== val2[kExtractable] || + !innerDeepEqual(val1[kAlgorithm], val2[kAlgorithm], mode, memos) || + !innerDeepEqual(val1[kKeyUsages], val2[kKeyUsages], mode, memos) || + !innerDeepEqual(val1[kKeyObject], val2[kKeyObject], mode, memos) + ) { + return false; + } + } else if (isBoxedPrimitive(val2) || + isNativeError(val2) || + val2 instanceof Error || + isWeakMap(val1) || + isWeakSet(val1) || + isPromise(val1)) { return false; } - } else if (isWeakMap(val1) || isWeakSet(val1) || isPromise(val1)) { - return false; } return keyCheck(val1, val2, mode, memos, kNoIterator); @@ -879,10 +897,8 @@ function partialSparseArrayEquiv(a, b, mode, memos, startA, startB) { let aPos = startA; const keysA = ObjectKeys(a); const keysB = ObjectKeys(b); - const keysBLength = keysB.length; - const keysALength = keysA.length; - const lenA = keysALength - startA; - const lenB = keysBLength - startB; + const lenA = keysA.length - startA; + const lenB = keysB.length - startB; if (lenA < lenB) { return false; } @@ -890,7 +906,7 @@ function partialSparseArrayEquiv(a, b, mode, memos, startA, startB) { const keyB = keysB[startB + i]; while (!innerDeepEqual(a[keysA[aPos]], b[keyB], mode, memos)) { aPos++; - if (aPos > keysALength - lenB + i) { + if (aPos > keysA.length - lenB + i) { return false; } } @@ -922,8 +938,6 @@ function partialArrayEquiv(a, b, mode, memos) { } function sparseArrayEquiv(a, b, mode, memos, i) { - // TODO(BridgeAR): Use internal method to only get index properties. The - // same applies to the partial implementation. const keysA = ObjectKeys(a); const keysB = ObjectKeys(b); if (keysA.length !== keysB.length) { From 7fc5aa093eeff5c96728ea93b6e5e108d93c4476 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Fri, 26 Dec 2025 11:27:46 +0700 Subject: [PATCH 2/3] fixup! linter --- lib/internal/util/comparisons.js | 242 +++++++++++++++---------------- 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js index a5e1af8304eb6f..ebd012f7c74898 100644 --- a/lib/internal/util/comparisons.js +++ b/lib/internal/util/comparisons.js @@ -302,136 +302,136 @@ function objectComparisonStart(val1, val2, mode, memos) { return false; } return keyCheck(val1, val2, mode, memos, kIsArray, keys2); - } else { - let val1Tag; - if (val1[SymbolToStringTag] === undefined && - (val1Tag = ObjectPrototypeToString(val1)) === '[object Object]') { - if (slowHasUnequalTag(val1Tag, val1, val2)) { - return false; - } - return keyCheck(val1, val2, mode, memos, kNoIterator); - } else if (isSet(val1)) { - if (!isSet(val2) || - (val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size)) || - hasUnequalTag(val1, val2)) { - return false; - } - return keyCheck(val1, val2, mode, memos, kIsSet); - } else if (isMap(val1)) { - if (!isMap(val2) || - (val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size)) || - hasUnequalTag(val1, val2)) { - return false; - } - return keyCheck(val1, val2, mode, memos, kIsMap); - } else if (isArrayBufferView(val1)) { - if (TypedArrayPrototypeGetSymbolToStringTag(val1) !== - TypedArrayPrototypeGetSymbolToStringTag(val2)) { - return false; - } - if (mode === kPartial && val1.byteLength !== val2.byteLength) { - if (!isPartialArrayBufferView(val1, val2)) { - return false; - } - } else if (mode === kLoose && - (isFloat32Array(val1) || isFloat64Array(val1) || isFloat16Array(val1))) { - if (!areSimilarFloatArrays(val1, val2)) { - return false; - } - } else if (!areSimilarTypedArrays(val1, val2)) { - return false; - } - // Buffer.compare returns true, so val1.length === val2.length. If they both - // only contain numeric keys, we don't need to exam further than checking - // the symbols. - const filter = mode !== kLoose ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS; - const keys2 = getOwnNonIndexProperties(val2, filter); - if (mode !== kPartial && - keys2.length !== getOwnNonIndexProperties(val1, filter).length) { - return false; - } - return keyCheck(val1, val2, mode, memos, kNoIterator, keys2); - } else if (isDate(val1)) { - if (!isDate(val2)) { - return false; - } - const time1 = DatePrototypeGetTime(val1); - const time2 = DatePrototypeGetTime(val2); - // eslint-disable-next-line no-self-compare - if (time1 !== time2 && (time1 === time1 || time2 === time2)) { - return false; - } - } else if (isRegExp(val1)) { - if (!isRegExp(val2) || !areSimilarRegExps(val1, val2) || hasUnequalTag(val1, val2)) { - return false; - } - } else if (isAnyArrayBuffer(val1)) { - if (!isAnyArrayBuffer(val2) || hasUnequalTag(val1, val2)) { - return false; - } - if (mode !== kPartial || val1.byteLength === val2.byteLength) { - if (!areEqualArrayBuffers(val1, val2)) { - return false; - } - } else if (!isPartialUint8Array(new Uint8Array(val1), new Uint8Array(val2))) { - return false; - } - } else if (slowHasUnequalTag(val1Tag ?? ObjectPrototypeToString(val1), val1, val2) || - ArrayIsArray(val2) || - isArrayBufferView(val2) || - isSet(val2) || - isMap(val2) || - isDate(val2) || - isRegExp(val2) || - isAnyArrayBuffer(val2)) { + } + + let val1Tag; + if (val1[SymbolToStringTag] === undefined && + (val1Tag = ObjectPrototypeToString(val1)) === '[object Object]') { + if (slowHasUnequalTag(val1Tag, val1, val2)) { return false; - } else if (isError(val1)) { - // Do not compare the stack as it might differ even though the error itself - // is otherwise identical. - if (!isError(val2) || - !isEnumerableOrIdentical(val1, val2, 'message', mode, memos) || - !isEnumerableOrIdentical(val1, val2, 'name', mode, memos) || - !isEnumerableOrIdentical(val1, val2, 'cause', mode, memos) || - !isEnumerableOrIdentical(val1, val2, 'errors', mode, memos)) { - return false; - } - const hasOwnVal2Cause = hasOwn(val2, 'cause'); - if ((hasOwnVal2Cause !== hasOwn(val1, 'cause') && (mode !== kPartial || hasOwnVal2Cause))) { - return false; - } - } else if (isBoxedPrimitive(val1)) { - if (!isEqualBoxedPrimitive(val1, val2)) { - return false; - } - } else if (isURL(val1)) { - if (!isURL(val2) || val1.href !== val2.href) { + } + return keyCheck(val1, val2, mode, memos, kNoIterator); + } else if (isSet(val1)) { + if (!isSet(val2) || + (val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size)) || + hasUnequalTag(val1, val2)) { + return false; + } + return keyCheck(val1, val2, mode, memos, kIsSet); + } else if (isMap(val1)) { + if (!isMap(val2) || + (val1.size !== val2.size && (mode !== kPartial || val1.size < val2.size)) || + hasUnequalTag(val1, val2)) { + return false; + } + return keyCheck(val1, val2, mode, memos, kIsMap); + } else if (isArrayBufferView(val1)) { + if (TypedArrayPrototypeGetSymbolToStringTag(val1) !== + TypedArrayPrototypeGetSymbolToStringTag(val2)) { + return false; + } + if (mode === kPartial && val1.byteLength !== val2.byteLength) { + if (!isPartialArrayBufferView(val1, val2)) { return false; } - } else if (isKeyObject(val1)) { - if (!isKeyObject(val2) || !val1.equals(val2)) { + } else if (mode === kLoose && + (isFloat32Array(val1) || isFloat64Array(val1) || isFloat16Array(val1))) { + if (!areSimilarFloatArrays(val1, val2)) { return false; } - } else if (isCryptoKey(val1)) { - if (kKeyObject === undefined) { - kKeyObject = require('internal/crypto/util').kKeyObject; - ({ kExtractable, kAlgorithm, kKeyUsages } = require('internal/crypto/keys')); - } - if (!isCryptoKey(val2) || - val1[kExtractable] !== val2[kExtractable] || - !innerDeepEqual(val1[kAlgorithm], val2[kAlgorithm], mode, memos) || - !innerDeepEqual(val1[kKeyUsages], val2[kKeyUsages], mode, memos) || - !innerDeepEqual(val1[kKeyObject], val2[kKeyObject], mode, memos) - ) { + } else if (!areSimilarTypedArrays(val1, val2)) { + return false; + } + // Buffer.compare returns true, so val1.length === val2.length. If they both + // only contain numeric keys, we don't need to exam further than checking + // the symbols. + const filter = mode !== kLoose ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS; + const keys2 = getOwnNonIndexProperties(val2, filter); + if (mode !== kPartial && + keys2.length !== getOwnNonIndexProperties(val1, filter).length) { + return false; + } + return keyCheck(val1, val2, mode, memos, kNoIterator, keys2); + } else if (isDate(val1)) { + if (!isDate(val2)) { + return false; + } + const time1 = DatePrototypeGetTime(val1); + const time2 = DatePrototypeGetTime(val2); + // eslint-disable-next-line no-self-compare + if (time1 !== time2 && (time1 === time1 || time2 === time2)) { + return false; + } + } else if (isRegExp(val1)) { + if (!isRegExp(val2) || !areSimilarRegExps(val1, val2) || hasUnequalTag(val1, val2)) { + return false; + } + } else if (isAnyArrayBuffer(val1)) { + if (!isAnyArrayBuffer(val2) || hasUnequalTag(val1, val2)) { + return false; + } + if (mode !== kPartial || val1.byteLength === val2.byteLength) { + if (!areEqualArrayBuffers(val1, val2)) { return false; } - } else if (isBoxedPrimitive(val2) || - isNativeError(val2) || - val2 instanceof Error || - isWeakMap(val1) || - isWeakSet(val1) || - isPromise(val1)) { + } else if (!isPartialUint8Array(new Uint8Array(val1), new Uint8Array(val2))) { + return false; + } + } else if (slowHasUnequalTag(val1Tag ?? ObjectPrototypeToString(val1), val1, val2) || + ArrayIsArray(val2) || + isArrayBufferView(val2) || + isSet(val2) || + isMap(val2) || + isDate(val2) || + isRegExp(val2) || + isAnyArrayBuffer(val2)) { + return false; + } else if (isError(val1)) { + // Do not compare the stack as it might differ even though the error itself + // is otherwise identical. + if (!isError(val2) || + !isEnumerableOrIdentical(val1, val2, 'message', mode, memos) || + !isEnumerableOrIdentical(val1, val2, 'name', mode, memos) || + !isEnumerableOrIdentical(val1, val2, 'cause', mode, memos) || + !isEnumerableOrIdentical(val1, val2, 'errors', mode, memos)) { return false; } + const hasOwnVal2Cause = hasOwn(val2, 'cause'); + if ((hasOwnVal2Cause !== hasOwn(val1, 'cause') && (mode !== kPartial || hasOwnVal2Cause))) { + return false; + } + } else if (isBoxedPrimitive(val1)) { + if (!isEqualBoxedPrimitive(val1, val2)) { + return false; + } + } else if (isURL(val1)) { + if (!isURL(val2) || val1.href !== val2.href) { + return false; + } + } else if (isKeyObject(val1)) { + if (!isKeyObject(val2) || !val1.equals(val2)) { + return false; + } + } else if (isCryptoKey(val1)) { + if (kKeyObject === undefined) { + kKeyObject = require('internal/crypto/util').kKeyObject; + ({ kExtractable, kAlgorithm, kKeyUsages } = require('internal/crypto/keys')); + } + if (!isCryptoKey(val2) || + val1[kExtractable] !== val2[kExtractable] || + !innerDeepEqual(val1[kAlgorithm], val2[kAlgorithm], mode, memos) || + !innerDeepEqual(val1[kKeyUsages], val2[kKeyUsages], mode, memos) || + !innerDeepEqual(val1[kKeyObject], val2[kKeyObject], mode, memos) + ) { + return false; + } + } else if (isBoxedPrimitive(val2) || + isNativeError(val2) || + val2 instanceof Error || + isWeakMap(val1) || + isWeakSet(val1) || + isPromise(val1)) { + return false; } return keyCheck(val1, val2, mode, memos, kNoIterator); From 1e76b295e4d002914153233b423a6fbed778d23f Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Sat, 27 Dec 2025 12:46:14 +0700 Subject: [PATCH 3/3] fixup! error serdes uses Symbol.toStringTag to recreate errors This is a workaround for errors recreated by error serdes. --- lib/internal/util/comparisons.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js index ebd012f7c74898..bee24b8ef72dca 100644 --- a/lib/internal/util/comparisons.js +++ b/lib/internal/util/comparisons.js @@ -270,7 +270,7 @@ function hasUnequalTag(val1, val2) { } function slowHasUnequalTag(val1Tag, val1, val2) { - if (val1[SymbolToStringTag]) { + if (val1[SymbolToStringTag] !== undefined && val2[SymbolToStringTag] !== undefined) { return val1[SymbolToStringTag] !== val2[SymbolToStringTag]; } return val1Tag !== ObjectPrototypeToString(val2);