|
21 | 21 | 'use strict'; |
22 | 22 |
|
23 | 23 | const { |
| 24 | + ArrayBufferIsView, |
| 25 | + ArrayBufferPrototypeGetByteLength, |
24 | 26 | ArrayFrom, |
25 | 27 | ArrayIsArray, |
26 | 28 | ArrayPrototypeIndexOf, |
27 | 29 | ArrayPrototypeJoin, |
28 | 30 | ArrayPrototypePush, |
29 | 31 | ArrayPrototypeSlice, |
| 32 | + DataViewPrototypeGetBuffer, |
| 33 | + DataViewPrototypeGetByteLength, |
| 34 | + DataViewPrototypeGetByteOffset, |
30 | 35 | Error, |
31 | 36 | FunctionPrototypeCall, |
32 | | - MapPrototypeDelete, |
33 | 37 | MapPrototypeGet, |
| 38 | + MapPrototypeGetSize, |
34 | 39 | MapPrototypeHas, |
35 | | - MapPrototypeSet, |
36 | 40 | NumberIsNaN, |
37 | 41 | ObjectAssign, |
38 | 42 | ObjectIs, |
39 | 43 | ObjectKeys, |
40 | 44 | ObjectPrototypeIsPrototypeOf, |
| 45 | + ObjectPrototypeToString, |
41 | 46 | ReflectApply, |
42 | 47 | ReflectHas, |
43 | 48 | ReflectOwnKeys, |
44 | 49 | RegExpPrototypeExec, |
| 50 | + SafeArrayIterator, |
45 | 51 | SafeMap, |
46 | 52 | SafeSet, |
47 | 53 | SafeWeakSet, |
| 54 | + SetPrototypeGetSize, |
48 | 55 | String, |
49 | 56 | StringPrototypeIndexOf, |
50 | 57 | StringPrototypeSlice, |
51 | 58 | StringPrototypeSplit, |
52 | 59 | SymbolIterator, |
| 60 | + TypedArrayPrototypeGetLength, |
| 61 | + Uint8Array, |
53 | 62 | } = primordials; |
54 | 63 |
|
55 | 64 | const { |
@@ -65,6 +74,8 @@ const AssertionError = require('internal/assert/assertion_error'); |
65 | 74 | const { inspect } = require('internal/util/inspect'); |
66 | 75 | const { Buffer } = require('buffer'); |
67 | 76 | const { |
| 77 | + isArrayBuffer, |
| 78 | + isDataView, |
68 | 79 | isKeyObject, |
69 | 80 | isPromise, |
70 | 81 | isRegExp, |
@@ -73,6 +84,8 @@ const { |
73 | 84 | isDate, |
74 | 85 | isWeakSet, |
75 | 86 | isWeakMap, |
| 87 | + isSharedArrayBuffer, |
| 88 | + isAnyArrayBuffer, |
76 | 89 | } = require('internal/util/types'); |
77 | 90 | const { isError, deprecate, emitExperimentalWarning } = require('internal/util'); |
78 | 91 | const { innerOk } = require('internal/assert/utils'); |
@@ -369,9 +382,161 @@ function isSpecial(obj) { |
369 | 382 | } |
370 | 383 |
|
371 | 384 | const typesToCallDeepStrictEqualWith = [ |
372 | | - isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer, |
| 385 | + isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer, isSharedArrayBuffer, |
373 | 386 | ]; |
374 | 387 |
|
| 388 | +function compareMaps(actual, expected, comparedObjects) { |
| 389 | + if (MapPrototypeGetSize(actual) !== MapPrototypeGetSize(expected)) { |
| 390 | + return false; |
| 391 | + } |
| 392 | + const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual); |
| 393 | + |
| 394 | + comparedObjects ??= new SafeWeakSet(); |
| 395 | + |
| 396 | + for (const { 0: key, 1: val } of safeIterator) { |
| 397 | + if (!MapPrototypeHas(expected, key)) { |
| 398 | + return false; |
| 399 | + } |
| 400 | + if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) { |
| 401 | + return false; |
| 402 | + } |
| 403 | + } |
| 404 | + return true; |
| 405 | +} |
| 406 | + |
| 407 | +function partiallyCompareArrayBuffersOrViews(actual, expected) { |
| 408 | + let actualView, expectedView, expectedViewLength; |
| 409 | + |
| 410 | + if (!ArrayBufferIsView(actual)) { |
| 411 | + let actualViewLength; |
| 412 | + |
| 413 | + if (isArrayBuffer(actual) && isArrayBuffer(expected)) { |
| 414 | + actualViewLength = ArrayBufferPrototypeGetByteLength(actual); |
| 415 | + expectedViewLength = ArrayBufferPrototypeGetByteLength(expected); |
| 416 | + } else if (isSharedArrayBuffer(actual) && isSharedArrayBuffer(expected)) { |
| 417 | + actualViewLength = actual.byteLength; |
| 418 | + expectedViewLength = expected.byteLength; |
| 419 | + } else { |
| 420 | + // Cannot compare ArrayBuffers with SharedArrayBuffers |
| 421 | + return false; |
| 422 | + } |
| 423 | + |
| 424 | + if (expectedViewLength > actualViewLength) { |
| 425 | + return false; |
| 426 | + } |
| 427 | + actualView = new Uint8Array(actual); |
| 428 | + expectedView = new Uint8Array(expected); |
| 429 | + |
| 430 | + } else if (isDataView(actual)) { |
| 431 | + if (!isDataView(expected)) { |
| 432 | + return false; |
| 433 | + } |
| 434 | + const actualByteLength = DataViewPrototypeGetByteLength(actual); |
| 435 | + expectedViewLength = DataViewPrototypeGetByteLength(expected); |
| 436 | + if (expectedViewLength > actualByteLength) { |
| 437 | + return false; |
| 438 | + } |
| 439 | + |
| 440 | + actualView = new Uint8Array( |
| 441 | + DataViewPrototypeGetBuffer(actual), |
| 442 | + DataViewPrototypeGetByteOffset(actual), |
| 443 | + actualByteLength, |
| 444 | + ); |
| 445 | + expectedView = new Uint8Array( |
| 446 | + DataViewPrototypeGetBuffer(expected), |
| 447 | + DataViewPrototypeGetByteOffset(expected), |
| 448 | + expectedViewLength, |
| 449 | + ); |
| 450 | + } else { |
| 451 | + if (ObjectPrototypeToString(actual) !== ObjectPrototypeToString(expected)) { |
| 452 | + return false; |
| 453 | + } |
| 454 | + actualView = actual; |
| 455 | + expectedView = expected; |
| 456 | + expectedViewLength = TypedArrayPrototypeGetLength(expected); |
| 457 | + |
| 458 | + if (expectedViewLength > TypedArrayPrototypeGetLength(actual)) { |
| 459 | + return false; |
| 460 | + } |
| 461 | + } |
| 462 | + |
| 463 | + for (let i = 0; i < expectedViewLength; i++) { |
| 464 | + if (actualView[i] !== expectedView[i]) { |
| 465 | + return false; |
| 466 | + } |
| 467 | + } |
| 468 | + |
| 469 | + return true; |
| 470 | +} |
| 471 | + |
| 472 | +function partiallyCompareSets(actual, expected, comparedObjects) { |
| 473 | + if (SetPrototypeGetSize(expected) > SetPrototypeGetSize(actual)) { |
| 474 | + return false; // `expected` can't be a subset if it has more elements |
| 475 | + } |
| 476 | + |
| 477 | + if (isDeepEqual === undefined) lazyLoadComparison(); |
| 478 | + |
| 479 | + const actualArray = ArrayFrom(FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], actual)); |
| 480 | + const expectedIterator = FunctionPrototypeCall(SafeSet.prototype[SymbolIterator], expected); |
| 481 | + const usedIndices = new SafeSet(); |
| 482 | + |
| 483 | + expectedIteration: for (const expectedItem of expectedIterator) { |
| 484 | + for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) { |
| 485 | + if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) { |
| 486 | + usedIndices.add(actualIdx); |
| 487 | + continue expectedIteration; |
| 488 | + } |
| 489 | + } |
| 490 | + return false; |
| 491 | + } |
| 492 | + |
| 493 | + return true; |
| 494 | +} |
| 495 | + |
| 496 | +function partiallyCompareArrays(actual, expected, comparedObjects) { |
| 497 | + if (expected.length > actual.length) { |
| 498 | + return false; |
| 499 | + } |
| 500 | + |
| 501 | + if (isDeepEqual === undefined) lazyLoadComparison(); |
| 502 | + |
| 503 | + // Create a map to count occurrences of each element in the expected array |
| 504 | + const expectedCounts = new SafeMap(); |
| 505 | + for (const expectedItem of expected) { |
| 506 | + let found = false; |
| 507 | + for (const { 0: key, 1: count } of expectedCounts) { |
| 508 | + if (isDeepStrictEqual(key, expectedItem)) { |
| 509 | + expectedCounts.set(key, count + 1); |
| 510 | + found = true; |
| 511 | + break; |
| 512 | + } |
| 513 | + } |
| 514 | + if (!found) { |
| 515 | + expectedCounts.set(expectedItem, 1); |
| 516 | + } |
| 517 | + } |
| 518 | + |
| 519 | + const safeActual = new SafeArrayIterator(actual); |
| 520 | + |
| 521 | + // Create a map to count occurrences of relevant elements in the actual array |
| 522 | + for (const actualItem of safeActual) { |
| 523 | + for (const { 0: key, 1: count } of expectedCounts) { |
| 524 | + if (isDeepStrictEqual(key, actualItem)) { |
| 525 | + if (count === 1) { |
| 526 | + expectedCounts.delete(key); |
| 527 | + } else { |
| 528 | + expectedCounts.set(key, count - 1); |
| 529 | + } |
| 530 | + break; |
| 531 | + } |
| 532 | + } |
| 533 | + } |
| 534 | + |
| 535 | + const { size } = expectedCounts; |
| 536 | + expectedCounts.clear(); |
| 537 | + return size === 0; |
| 538 | +} |
| 539 | + |
375 | 540 | /** |
376 | 541 | * Compares two objects or values recursively to check if they are equal. |
377 | 542 | * @param {any} actual - The actual value to compare. |
@@ -388,22 +553,16 @@ function compareBranch( |
388 | 553 | ) { |
389 | 554 | // Check for Map object equality |
390 | 555 | 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(); |
| 556 | + return compareMaps(actual, expected, comparedObjects); |
| 557 | + } |
397 | 558 |
|
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; |
| 559 | + if ( |
| 560 | + ArrayBufferIsView(actual) || |
| 561 | + isAnyArrayBuffer(actual) || |
| 562 | + ArrayBufferIsView(expected) || |
| 563 | + isAnyArrayBuffer(expected) |
| 564 | + ) { |
| 565 | + return partiallyCompareArrayBuffersOrViews(actual, expected); |
407 | 566 | } |
408 | 567 |
|
409 | 568 | for (const type of typesToCallDeepStrictEqualWith) { |
@@ -415,68 +574,12 @@ function compareBranch( |
415 | 574 |
|
416 | 575 | // Check for Set object equality |
417 | 576 | 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; |
| 577 | + return partiallyCompareSets(actual, expected, comparedObjects); |
439 | 578 | } |
440 | 579 |
|
441 | 580 | // Check if expected array is a subset of actual array |
442 | 581 | 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; |
| 582 | + return partiallyCompareArrays(actual, expected, comparedObjects); |
480 | 583 | } |
481 | 584 |
|
482 | 585 | // Comparison done when at least one of the values is not an object |
|