Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,21 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2,
if (v1Array != v2Array)
return false;

if constexpr (checkPrototypes) {
// node compares array lengths with [[Get]] and ===. A Proxy's get trap can report a length
// its target does not have, and the own-property walk that compares proxies never reads it.
if (v1Array && (o1->isProxy() || o2->isProxy())) {
JSValue length1 = o1->get(globalObject, vm.propertyNames->length);
RETURN_IF_EXCEPTION(scope, false);
JSValue length2 = o2->get(globalObject, vm.propertyNames->length);
RETURN_IF_EXCEPTION(scope, false);
bool sameLength = JSC::JSValue::strictEqual(globalObject, length1, length2);
RETURN_IF_EXCEPTION(scope, false);
if (!sameLength)
return false;
}
}

if (v1Array && v2Array && !(o1->isProxy() || o2->isProxy())) {
JSC::JSArray* array1 = uncheckedDowncast<JSC::JSArray>(v1);
JSC::JSArray* array2 = uncheckedDowncast<JSC::JSArray>(v2);
Expand Down
45 changes: 45 additions & 0 deletions test/js/node/assert/deep-equal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ function withOwnConstructor(prototype: object | null, constructor: unknown) {
return Object.create(prototype, { constructor: { value: constructor } });
}

function withReportedLength(array: unknown[], length: unknown) {
return new Proxy(array, {
get: (target, key, receiver) => (key === "length" ? length : Reflect.get(target, key, receiver)),
});
}

function seventyProperties<T extends Record<string, unknown>>(object: T): T {
for (let i = 0; i < 70; i++) (object as Record<string, unknown>)["k" + i] = i;
return object;
Expand Down Expand Up @@ -406,6 +412,45 @@ const cases: Case[] = [
loose: true,
looseBug: "reports not equal",
},
{
name: "a Proxy of [1, 2, 3] and [1, 2, 3]",
a: () => new Proxy([1, 2, 3], {}),
b: () => [1, 2, 3],
strict: true,
loose: true,
},
// node reads an array's length with [[Get]], so a Proxy's get trap decides it.
{
name: "a Proxy of [1, 2, 3] whose get trap reports length 7 and [1, 2, 3]",
a: () => withReportedLength([1, 2, 3], 7),
b: () => [1, 2, 3],
strict: false,
loose: false,
looseBug: "reports equal",
},
{
name: "[1, 2, 3] and a Proxy of [1, 2, 3] whose get trap reports length 7",
a: () => [1, 2, 3],
b: () => withReportedLength([1, 2, 3], 7),
strict: false,
loose: false,
looseBug: "reports equal",
},
{
name: "a Proxy of [1, 2, 3] whose get trap reports length '3' and [1, 2, 3]",
a: () => withReportedLength([1, 2, 3], "3"),
b: () => [1, 2, 3],
strict: false,
loose: false,
looseBug: "reports equal",
},
{
name: "two Proxies of [1, 2, 3] whose get traps report length 7",
a: () => withReportedLength([1, 2, 3], 7),
b: () => withReportedLength([1, 2, 3], 7),
strict: true,
loose: true,
},
{
name: "own constructor: Uint8Array on objects with different prototypes",
a: () => withOwnConstructor({ q: 1 }, Uint8Array),
Expand Down
Loading