Skip to content
Merged
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
48 changes: 14 additions & 34 deletions src/jsc/bindings/JSBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2675,42 +2675,22 @@ JSC_DEFINE_HOST_FUNCTION(jsBufferConstructorFunction_byteLength, (JSGlobalObject
return jsBufferConstructorFunction_byteLengthBody(lexicalGlobalObject, callFrame);
}

class JSBufferConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static constexpr unsigned StructureFlags = Base::StructureFlags | HasStaticPropertyTable;

~JSBufferConstructor() = default;

static void destroy(JSC::JSCell* cell)
{
static_cast<JSBufferConstructor*>(cell)->JSBufferConstructor::~JSBufferConstructor();
}

static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
JSValue prototype = globalObject->m_typedArrayUint8.constructorInitializedOnMainThread(globalObject);
return Bun::createClassStructure(vm, globalObject, prototype, JSC::TypeInfo(prototype.asCell()->type(), StructureFlags), info());
}

DECLARE_INFO;

static JSBufferConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype)
{
JSBufferConstructor* constructor = new (NotNull, JSC::allocateCell<JSBufferConstructor>(vm)) JSBufferConstructor(vm, globalObject, structure);
constructor->finishCreation(vm, globalObject, prototype);
return constructor;
}

private:
JSBufferConstructor(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
: Base(vm, structure, callJSBuffer, constructJSBuffer)

{
}
JSC::Structure* JSBufferConstructor::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
JSValue prototype = globalObject->m_typedArrayUint8.constructorInitializedOnMainThread(globalObject);
return Bun::createClassStructure(vm, globalObject, prototype, JSC::TypeInfo(prototype.asCell()->type(), StructureFlags), info());
}

void finishCreation(JSC::VM&, JSGlobalObject*, JSC::JSObject* prototype);
JSBufferConstructor* JSBufferConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype)
{
JSBufferConstructor* constructor = new (NotNull, JSC::allocateCell<JSBufferConstructor>(vm)) JSBufferConstructor(vm, structure);
constructor->finishCreation(vm, globalObject, prototype);
return constructor;
}

JSBufferConstructor::JSBufferConstructor(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure, callJSBuffer, constructJSBuffer)
{
}

JSC_DEFINE_HOST_FUNCTION(jsBufferConstructorFunction_isEncoding, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
Expand Down
25 changes: 25 additions & 0 deletions src/jsc/bindings/JSBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include "root.h"

#include <JavaScriptCore/InternalFunction.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/ThrowScope.h>
#include <wtf/NeverDestroyed.h>
Expand Down Expand Up @@ -65,6 +66,30 @@ JSC::JSUint8Array* createBuffer(JSC::JSGlobalObject* lexicalGlobalObject, const
JSC::JSUint8Array* createBuffer(JSC::JSGlobalObject* lexicalGlobalObject, Ref<JSC::ArrayBuffer>&& backingStore);
JSC::JSUint8Array* createEmptyBuffer(JSC::JSGlobalObject* lexicalGlobalObject);

class JSBufferConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::HasStaticPropertyTable;

~JSBufferConstructor() = default;

static void destroy(JSC::JSCell* cell)
{
static_cast<JSBufferConstructor*>(cell)->JSBufferConstructor::~JSBufferConstructor();
}

static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*);

DECLARE_INFO;

static JSBufferConstructor* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*, JSC::JSObject* prototype);

private:
JSBufferConstructor(JSC::VM&, JSC::Structure*);

void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSC::JSObject* prototype);
};

JSC_DECLARE_HOST_FUNCTION(constructSlowBuffer);
JSC::JSObject* createBufferPrototype(JSC::VM&, JSC::JSGlobalObject*);
JSC::Structure* createBufferStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype);
Expand Down
123 changes: 113 additions & 10 deletions src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,24 @@
#include "JavaScriptCore/ProxyObject.h"
#include "JavaScriptCore/Microtask.h"
#include "JavaScriptCore/MicrotaskQueue.h"
#include "JavaScriptCore/ArrayConstructor.h"
#include "JavaScriptCore/BigIntConstructor.h"
#include "JavaScriptCore/BooleanConstructor.h"
#include "JavaScriptCore/DateConstructor.h"
#include "JavaScriptCore/ErrorConstructor.h"
#include "JavaScriptCore/JSArrayBufferConstructor.h"
#include "JavaScriptCore/JSTypedArrayConstructors.h"
#include "JavaScriptCore/MapConstructor.h"
#include "JavaScriptCore/NumberConstructor.h"
#include "JavaScriptCore/ObjectConstructor.h"
#include "JavaScriptCore/ObjectPrototypeInlines.h"
#include "JavaScriptCore/RegExpConstructor.h"
#include "JavaScriptCore/SetConstructor.h"
#include "JavaScriptCore/StringConstructor.h"
#include "JavaScriptCore/SymbolConstructor.h"
#include "JavaScriptCore/WeakMapConstructor.h"
#include "JavaScriptCore/WeakSetConstructor.h"
#include "JSBuffer.h"
#include "JavaScriptCore/ParserError.h"
#include "JavaScriptCore/ScriptExecutable.h"
#include "JavaScriptCore/StackFrame.h"
Expand Down Expand Up @@ -735,6 +752,48 @@ static bool nonIndexOwnPropertiesEqual(JSC::JSGlobalObject* globalObject, Marked
return true;
}

// node's wellKnownConstructors set (lib/internal/util/comparisons.js), matched for any realm.
static bool isWellKnownConstructor(JSValue value)
{
if (!value.isCell())
return false;
JSCell* cell = value.asCell();
if (cell->type() != InternalFunctionType && cell->type() != JSFunctionType)
return false;
const ClassInfo* info = cell->classInfo();
return info == JSC::ObjectConstructor::info()
|| info == JSC::ArrayConstructor::info()
|| info == JSC::FunctionConstructor::info()
|| info == JSC::RegExpConstructor::info()
|| info == JSC::JSPromiseConstructor::info()
|| info == JSC::StringConstructor::info()
|| info == JSC::SymbolConstructor::info()
|| info == JSC::BigIntConstructor::info()
|| info == JSC::BooleanConstructor::info()
|| info == JSC::NumberConstructor::info()
|| info == JSC::DateConstructor::info()
|| info == JSC::ErrorConstructor::info()
|| info == JSC::MapConstructor::info()
|| info == JSC::SetConstructor::info()
|| info == JSC::WeakMapConstructor::info()
|| info == JSC::WeakSetConstructor::info()
|| info == JSC::JSArrayBufferConstructor::info()
|| info == JSC::JSInt8ArrayConstructor::info()
|| info == JSC::JSInt16ArrayConstructor::info()
|| info == JSC::JSInt32ArrayConstructor::info()
|| info == JSC::JSUint8ArrayConstructor::info()
|| info == JSC::JSUint8ClampedArrayConstructor::info()
|| info == JSC::JSUint16ArrayConstructor::info()
|| info == JSC::JSUint32ArrayConstructor::info()
|| info == JSC::JSFloat16ArrayConstructor::info()
|| info == JSC::JSFloat32ArrayConstructor::info()
|| info == JSC::JSFloat64ArrayConstructor::info()
|| info == JSC::JSBigInt64ArrayConstructor::info()
|| info == JSC::JSBigUint64ArrayConstructor::info()
|| info == JSC::JSDataViewConstructor::info()
|| info == WebCore::JSBufferConstructor::info();
}

template<bool isStrict, bool enableAsymmetricMatchers, bool checkPrototypes, bool skipPrototypeIdentity>
bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2, MarkedArgumentBuffer& gcBuffer, Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16>& stack, ThrowScope& scope, bool addToStack)
{
Expand Down Expand Up @@ -815,19 +874,63 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2,
ASSERT(c1);
ASSERT(c2);

// Node's deepStrictEqual compares [[Prototype]]s with ===. Only the
// node:assert/node:util entry point does this; Bun.deepEquals and
// expect() keep their prototype-blind semantics.
if constexpr (checkPrototypes && !skipPrototypeIdentity) {
// node's objectComparisonStart (lib/internal/util/comparisons.js): the constructor /
// [[Prototype]] rule in strict mode, then equal Object.prototype.toString tags in every mode.
Comment thread
dylan-conway marked this conversation as resolved.
Resolved
if constexpr (checkPrototypes) {
JSObject* protoCheck1 = v1.getObject();
JSObject* protoCheck2 = v2.getObject();
if (protoCheck1 && protoCheck2) {
JSValue proto1 = protoCheck1->getPrototype(globalObject);
if constexpr (!skipPrototypeIdentity) {
const auto& constructorName = vm.propertyNames->constructor;
PropertySlot slot1(protoCheck1, PropertySlot::InternalMethodType::Get);
bool hasConstructor1 = protoCheck1->getPropertySlot(globalObject, constructorName, slot1);
RETURN_IF_EXCEPTION(scope, false);
JSValue constructor1 = hasConstructor1 ? slot1.getValue(globalObject, constructorName) : jsUndefined();
RETURN_IF_EXCEPTION(scope, false);
bool compareConstructors = isWellKnownConstructor(constructor1);
if (!compareConstructors && !constructor1.isUndefined()) {
if (slot1.isTaintedByOpaqueObject()) {
PropertySlot ownSlot(protoCheck1, PropertySlot::InternalMethodType::GetOwnProperty);
bool hasOwnConstructor = protoCheck1->methodTable()->getOwnPropertySlot(protoCheck1, globalObject, constructorName, ownSlot);
RETURN_IF_EXCEPTION(scope, false);
compareConstructors = !hasOwnConstructor;
} else {
compareConstructors = slot1.slotBase() != protoCheck1;
}
}
if (compareConstructors) {
// Same mono-proto structure means the same prototype chain, so a data slot found on it is val2.constructor too.
bool inheritedFromSharedChain = slot1.isCacheableValue() && slot1.slotBase() != protoCheck1
&& protoCheck1->structureID() == protoCheck2->structureID() && !protoCheck1->structure()->hasPolyProto();
if (!inheritedFromSharedChain) {
JSValue constructor2 = protoCheck2->get(globalObject, constructorName);
RETURN_IF_EXCEPTION(scope, false);
bool sameConstructor = JSC::JSValue::strictEqual(globalObject, constructor1, constructor2);
RETURN_IF_EXCEPTION(scope, false);
if (!sameConstructor) {
return false;
}
}
} else {
JSValue proto1 = protoCheck1->getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, false);
JSValue proto2 = protoCheck2->getPrototype(globalObject);
RETURN_IF_EXCEPTION(scope, false);
if (proto1 != proto2) {
return false;
}
}
}
JSString* tag1 = objectPrototypeToString(globalObject, protoCheck1);
RETURN_IF_EXCEPTION(scope, false);
JSValue proto2 = protoCheck2->getPrototype(globalObject);
JSString* tag2 = objectPrototypeToString(globalObject, protoCheck2);
RETURN_IF_EXCEPTION(scope, false);
if (proto1 != proto2) {
return false;
if (tag1 != tag2) {
bool sameTag = tag1->equal(globalObject, tag2);
RETURN_IF_EXCEPTION(scope, false);
if (!sameTag) {
return false;
}
}
}
}
Expand Down Expand Up @@ -952,7 +1055,7 @@ bool Bun__deepEquals(JSC::JSGlobalObject* globalObject, JSValue v1, JSValue v2,
return true;
}

if constexpr (isStrict && !skipPrototypeIdentity) {
if constexpr (isStrict && !checkPrototypes && !skipPrototypeIdentity) {
if (!equal(JSObject::calculatedClassName(o1), JSObject::calculatedClassName(o2))) {
return false;
}
Expand Down Expand Up @@ -1625,7 +1728,7 @@ static std::optional<bool> specialObjectsDequalSlow(const DeepEqualsMode& mode,
}
}

if (!mode.skipPrototypeIdentity) {
if (!mode.checkPrototypes && !mode.skipPrototypeIdentity) {
if (!equal(JSObject::calculatedClassName(c1->getObject()), JSObject::calculatedClassName(c2->getObject()))) {
return false;
}
Expand Down
17 changes: 17 additions & 0 deletions test/js/bun/bun-object/deep-equals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,23 @@ describe("Bun.deepEquals strict mode", () => {
expect(Bun.deepEquals(new Foo(), { a: 1 }, true)).toBe(false);
});

it("ignores class names when the fourth argument is true", () => {
class Foo {
a = 1;
}
class Bar {
a = 1;
}
class S extends String {}
// The fourth argument is not in the public types.
const deepEquals = Bun.deepEquals as (a: unknown, b: unknown, strict?: boolean, skipPrototype?: boolean) => boolean;
expect(deepEquals(new Foo(), new Bar(), true)).toBe(false);
expect(deepEquals(new Foo(), new Bar(), true, true)).toBe(true);
expect(deepEquals(new Foo(), { a: 2 }, true, true)).toBe(false);
expect(deepEquals(new String("a"), new S("a"), true)).toBe(false);
expect(deepEquals(new String("a"), new S("a"), true, true)).toBe(true);
});

it("is symmetric", () => {
const a = { entries: [1, 2] };
const b = { entries: [1, 2], extra: undefined };
Expand Down
Loading