Skip to content

Commit b079afc

Browse files
committed
assert: make partialDeepStrictEqual work with ArrayBuffers
Fixes: #56097
1 parent 3f9c6c0 commit b079afc

File tree

3 files changed

+346
-77
lines changed

3 files changed

+346
-77
lines changed

lib/assert.js

+172-74
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@
2121
'use strict';
2222

2323
const {
24+
ArrayBufferIsView,
25+
ArrayBufferPrototypeGetByteLength,
2426
ArrayFrom,
2527
ArrayIsArray,
2628
ArrayPrototypeIndexOf,
2729
ArrayPrototypeJoin,
2830
ArrayPrototypePush,
2931
ArrayPrototypeSlice,
32+
DataViewPrototypeGetBuffer,
33+
DataViewPrototypeGetByteLength,
34+
DataViewPrototypeGetByteOffset,
3035
Error,
3136
FunctionPrototypeCall,
3237
MapPrototypeDelete,
@@ -38,6 +43,7 @@ const {
3843
ObjectIs,
3944
ObjectKeys,
4045
ObjectPrototypeIsPrototypeOf,
46+
ObjectPrototypeToString,
4147
ReflectApply,
4248
ReflectHas,
4349
ReflectOwnKeys,
@@ -50,6 +56,8 @@ const {
5056
StringPrototypeSlice,
5157
StringPrototypeSplit,
5258
SymbolIterator,
59+
TypedArrayPrototypeGetLength,
60+
Uint8Array,
5361
} = primordials;
5462

5563
const {
@@ -65,6 +73,8 @@ const AssertionError = require('internal/assert/assertion_error');
6573
const { inspect } = require('internal/util/inspect');
6674
const { Buffer } = require('buffer');
6775
const {
76+
isArrayBuffer,
77+
isDataView,
6878
isKeyObject,
6979
isPromise,
7080
isRegExp,
@@ -73,6 +83,8 @@ const {
7383
isDate,
7484
isWeakSet,
7585
isWeakMap,
86+
isSharedArrayBuffer,
87+
isAnyArrayBuffer,
7688
} = require('internal/util/types');
7789
const { isError, deprecate, emitExperimentalWarning } = require('internal/util');
7890
const { innerOk } = require('internal/assert/utils');
@@ -369,9 +381,157 @@ function isSpecial(obj) {
369381
}
370382

371383
const typesToCallDeepStrictEqualWith = [
372-
isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer,
384+
isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer, isSharedArrayBuffer,
373385
];
374386

387+
function compareMaps(actual, expected, comparedObjects) {
388+
if (actual.size !== expected.size) {
389+
return false;
390+
}
391+
const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual);
392+
393+
comparedObjects ??= new SafeWeakSet();
394+
395+
for (const { 0: key, 1: val } of safeIterator) {
396+
if (!MapPrototypeHas(expected, key)) {
397+
return false;
398+
}
399+
if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) {
400+
return false;
401+
}
402+
}
403+
return true;
404+
}
405+
406+
function compareArrayBuffers(actual, expected) {
407+
let actualView, expectedView, expectedViewLength;
408+
409+
if (!ArrayBufferIsView(actual)) {
410+
let actualViewLength;
411+
412+
if (isArrayBuffer(actual) && isArrayBuffer(expected)) {
413+
actualViewLength = ArrayBufferPrototypeGetByteLength(actual);
414+
expectedViewLength = ArrayBufferPrototypeGetByteLength(expected);
415+
} else if (isSharedArrayBuffer(actual) && isSharedArrayBuffer(expected)) {
416+
actualViewLength = actual.byteLength;
417+
expectedViewLength = expected.byteLength;
418+
} else {
419+
// Cannot compare ArrayBuffers with SharedArrayBuffers
420+
return false;
421+
}
422+
423+
if (expectedViewLength > actualViewLength) {
424+
return false;
425+
}
426+
actualView = new Uint8Array(actual);
427+
expectedView = new Uint8Array(expected);
428+
429+
} else if (isDataView(actual)) {
430+
if (!isDataView(expected)) {
431+
return false;
432+
}
433+
const actualByteLength = DataViewPrototypeGetByteLength(actual);
434+
expectedViewLength = DataViewPrototypeGetByteLength(expected);
435+
if (expectedViewLength > actualByteLength) {
436+
return false;
437+
}
438+
439+
actualView = new Uint8Array(
440+
DataViewPrototypeGetBuffer(actual),
441+
DataViewPrototypeGetByteOffset(actual),
442+
actualByteLength,
443+
);
444+
expectedView = new Uint8Array(
445+
DataViewPrototypeGetBuffer(expected),
446+
DataViewPrototypeGetByteOffset(expected),
447+
expectedViewLength,
448+
);
449+
} else {
450+
if (ObjectPrototypeToString(actual) !== ObjectPrototypeToString(expected)) {
451+
return false;
452+
}
453+
actualView = actual;
454+
expectedView = expected;
455+
expectedViewLength = TypedArrayPrototypeGetLength(expected);
456+
457+
if (expectedViewLength > TypedArrayPrototypeGetLength(actual)) {
458+
return false;
459+
}
460+
}
461+
462+
for (let i = 0; i < expectedViewLength; i++) {
463+
if (actualView[i] !== expectedView[i]) {
464+
return false;
465+
}
466+
}
467+
468+
return true;
469+
}
470+
471+
function compareSets(actual, expected, comparedObjects) {
472+
if (expected.size > actual.size) {
473+
return false; // `expected` can't be a subset if it has more elements
474+
}
475+
476+
if (isDeepEqual === undefined) lazyLoadComparison();
477+
478+
const actualArray = ArrayFrom(FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], actual));
479+
const expectedIterator = FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], expected);
480+
const usedIndices = new SafeSet();
481+
482+
expectedIteration: for (const expectedItem of expectedIterator) {
483+
for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) {
484+
if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) {
485+
usedIndices.add(actualIdx);
486+
continue expectedIteration;
487+
}
488+
}
489+
return false;
490+
}
491+
492+
return true;
493+
}
494+
495+
function compareArrays(actual, expected, comparedObjects) {
496+
if (expected.length > actual.length) {
497+
return false;
498+
}
499+
500+
if (isDeepEqual === undefined) lazyLoadComparison();
501+
502+
// Create a map to count occurrences of each element in the expected array
503+
const expectedCounts = new SafeMap();
504+
for (const expectedItem of expected) {
505+
let found = false;
506+
for (const { 0: key, 1: count } of expectedCounts) {
507+
if (isDeepStrictEqual(key, expectedItem)) {
508+
MapPrototypeSet(expectedCounts, key, count + 1);
509+
found = true;
510+
break;
511+
}
512+
}
513+
if (!found) {
514+
MapPrototypeSet(expectedCounts, expectedItem, 1);
515+
}
516+
}
517+
518+
// Create a map to count occurrences of relevant elements in the actual array
519+
for (const actualItem of actual) {
520+
for (const { 0: key, 1: count } of expectedCounts) {
521+
if (isDeepStrictEqual(key, actualItem)) {
522+
if (count === 1) {
523+
MapPrototypeDelete(expectedCounts, key);
524+
} else {
525+
MapPrototypeSet(expectedCounts, key, count - 1);
526+
}
527+
break;
528+
}
529+
}
530+
}
531+
532+
return !expectedCounts.size;
533+
}
534+
375535
/**
376536
* Compares two objects or values recursively to check if they are equal.
377537
* @param {any} actual - The actual value to compare.
@@ -388,22 +548,16 @@ function compareBranch(
388548
) {
389549
// Check for Map object equality
390550
if (isMap(actual) && isMap(expected)) {
391-
if (actual.size !== expected.size) {
392-
return false;
393-
}
394-
const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual);
395-
396-
comparedObjects ??= new SafeWeakSet();
551+
return compareMaps(actual, expected, comparedObjects);
552+
}
397553

398-
for (const { 0: key, 1: val } of safeIterator) {
399-
if (!MapPrototypeHas(expected, key)) {
400-
return false;
401-
}
402-
if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) {
403-
return false;
404-
}
405-
}
406-
return true;
554+
if (
555+
ArrayBufferIsView(actual) ||
556+
isAnyArrayBuffer(actual) ||
557+
ArrayBufferIsView(expected) ||
558+
isAnyArrayBuffer(expected)
559+
) {
560+
return compareArrayBuffers(actual, expected);
407561
}
408562

409563
for (const type of typesToCallDeepStrictEqualWith) {
@@ -415,68 +569,12 @@ function compareBranch(
415569

416570
// Check for Set object equality
417571
if (isSet(actual) && isSet(expected)) {
418-
if (expected.size > actual.size) {
419-
return false; // `expected` can't be a subset if it has more elements
420-
}
421-
422-
if (isDeepEqual === undefined) lazyLoadComparison();
423-
424-
const actualArray = ArrayFrom(FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], actual));
425-
const expectedIterator = FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], expected);
426-
const usedIndices = new SafeSet();
427-
428-
expectedIteration: for (const expectedItem of expectedIterator) {
429-
for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) {
430-
if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) {
431-
usedIndices.add(actualIdx);
432-
continue expectedIteration;
433-
}
434-
}
435-
return false;
436-
}
437-
438-
return true;
572+
return compareSets(actual, expected, comparedObjects);
439573
}
440574

441575
// Check if expected array is a subset of actual array
442576
if (ArrayIsArray(actual) && ArrayIsArray(expected)) {
443-
if (expected.length > actual.length) {
444-
return false;
445-
}
446-
447-
if (isDeepEqual === undefined) lazyLoadComparison();
448-
449-
// Create a map to count occurrences of each element in the expected array
450-
const expectedCounts = new SafeMap();
451-
for (const expectedItem of expected) {
452-
let found = false;
453-
for (const { 0: key, 1: count } of expectedCounts) {
454-
if (isDeepStrictEqual(key, expectedItem)) {
455-
MapPrototypeSet(expectedCounts, key, count + 1);
456-
found = true;
457-
break;
458-
}
459-
}
460-
if (!found) {
461-
MapPrototypeSet(expectedCounts, expectedItem, 1);
462-
}
463-
}
464-
465-
// Create a map to count occurrences of relevant elements in the actual array
466-
for (const actualItem of actual) {
467-
for (const { 0: key, 1: count } of expectedCounts) {
468-
if (isDeepStrictEqual(key, actualItem)) {
469-
if (count === 1) {
470-
MapPrototypeDelete(expectedCounts, key);
471-
} else {
472-
MapPrototypeSet(expectedCounts, key, count - 1);
473-
}
474-
break;
475-
}
476-
}
477-
}
478-
479-
return !expectedCounts.size;
577+
return compareArrays(actual, expected, comparedObjects);
480578
}
481579

482580
// Comparison done when at least one of the values is not an object

0 commit comments

Comments
 (0)