Skip to content

Commit

Permalink
fix(node/assert): port more test cases from node (#16895)
Browse files Browse the repository at this point in the history
Co-authored-by: DonIsaac <22823424+DonIsaac@users.noreply.github.com>
Co-authored-by: Dylan Conway <35280289+dylan-conway@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 6, 2025
1 parent 253faed commit 146ec77
Show file tree
Hide file tree
Showing 25 changed files with 993 additions and 93 deletions.
13 changes: 13 additions & 0 deletions .vscode/launch.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/bun.js/bindings/BunObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -492,12 +492,12 @@ JSC_DEFINE_HOST_FUNCTION(functionBunDeepEquals, (JSGlobalObject * globalObject,

JSC::JSValue arg1 = callFrame->uncheckedArgument(0);
JSC::JSValue arg2 = callFrame->uncheckedArgument(1);
JSC::JSValue arg3 = callFrame->argument(2);
JSC::JSValue strict = callFrame->argument(2);

Vector<std::pair<JSValue, JSValue>, 16> stack;
MarkedArgumentBuffer gcBuffer;

if (arg3.isBoolean() && arg3.asBoolean()) {
if (strict.isBoolean() && strict.asBoolean()) {

bool isEqual = Bun__deepEquals<true, false>(globalObject, arg1, arg2, gcBuffer, stack, &scope, true);
RETURN_IF_EXCEPTION(scope, {});
Expand Down
211 changes: 148 additions & 63 deletions src/bun.js/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,10 @@ template<bool isStrict, bool enableAsymmetricMatchers>
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)
{
VM& vm = globalObject->vm();
if (UNLIKELY(!vm.isSafeToRecurse())) {
throwStackOverflowError(globalObject, *scope);
return false;
}

// need to check this before primitives, asymmetric matchers
// can match against any type of value.
Expand Down Expand Up @@ -754,7 +758,7 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2,
if (v1Array != v2Array)
return false;

if (v1Array && v2Array) {
if (v1Array && v2Array && !(o1->isProxy() || o2->isProxy())) {
JSC::JSArray* array1 = JSC::jsCast<JSC::JSArray*>(v1);
JSC::JSArray* array2 = JSC::jsCast<JSC::JSArray*>(v2);

Expand Down Expand Up @@ -1070,6 +1074,7 @@ std::optional<bool> specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark
foundMatchingKey = true;
break;
}
RETURN_IF_EXCEPTION(*scope, false);
}

if (!foundMatchingKey) {
Expand Down Expand Up @@ -1108,6 +1113,7 @@ std::optional<bool> specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark
foundMatchingKey = true;
break;
}
RETURN_IF_EXCEPTION(*scope, false);
}

if (!foundMatchingKey) {
Expand Down Expand Up @@ -1187,16 +1193,117 @@ std::optional<bool> specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark
return false;
}

// NOTE(@DonIsaac): could `left` ever _not_ be a JSC::ErrorInstance?
if (JSC::ErrorInstance* left = jsDynamicCast<JSC::ErrorInstance*, JSCell>(c1)) {
JSC::ErrorInstance* right = jsDynamicCast<JSC::ErrorInstance*, JSCell>(c2);

if (UNLIKELY(!right)) {
return false;
}

return (
left->sanitizedNameString(globalObject) == right->sanitizedNameString(globalObject) && left->sanitizedMessageString(globalObject) == right->sanitizedMessageString(globalObject));
if (
left->errorType() != right->errorType() || // quick check on ctors (does not handle subclasses)
left->sanitizedNameString(globalObject) != right->sanitizedNameString(globalObject) || // manual `.name` changes (usually in subclasses)
left->sanitizedMessageString(globalObject) != right->sanitizedMessageString(globalObject) // `.message`
) {
return false;
}

if constexpr (isStrict) {
if (left->runtimeTypeForCause() != right->runtimeTypeForCause()) {
return false;
}
}

VM& vm = globalObject->vm();

// `.cause` is non-enumerable, so it must be checked explicitly.
// note that an undefined cause is different than a missing cause in
// strict mode.
const PropertyName cause(vm.propertyNames->cause);
if constexpr (isStrict) {
if (left->hasProperty(globalObject, cause) != right->hasProperty(globalObject, cause)) {
return false;
}
}
auto leftCause = left->get(globalObject, cause);
RETURN_IF_EXCEPTION(*scope, false);
auto rightCause = right->get(globalObject, cause);
RETURN_IF_EXCEPTION(*scope, false);
if (!Bun__deepEquals<isStrict, enableAsymmetricMatchers>(globalObject, leftCause, rightCause, gcBuffer, stack, scope, true)) {
return false;
}
RETURN_IF_EXCEPTION(*scope, false);

// check arbitrary enumerable properties. `.stack` is not checked.
left->materializeErrorInfoIfNeeded(vm);
right->materializeErrorInfoIfNeeded(vm);
JSC::PropertyNameArray a1(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude);
JSC::PropertyNameArray a2(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude);
left->getPropertyNames(globalObject, a1, DontEnumPropertiesMode::Exclude);
RETURN_IF_EXCEPTION(*scope, false);
right->getPropertyNames(globalObject, a2, DontEnumPropertiesMode::Exclude);
RETURN_IF_EXCEPTION(*scope, false);

const size_t propertyArrayLength1 = a1.size();
const size_t propertyArrayLength2 = a2.size();
if constexpr (isStrict) {
if (propertyArrayLength1 != propertyArrayLength2) {
return false;
}
}

// take a property name from one, try to get it from both
size_t i;
for (i = 0; i < propertyArrayLength1; i++) {
Identifier i1 = a1[i];
if (i1 == vm.propertyNames->stack) continue;
PropertyName propertyName1 = PropertyName(i1);

JSValue prop1 = left->get(globalObject, propertyName1);
RETURN_IF_EXCEPTION(*scope, false);

if (UNLIKELY(!prop1)) {
return false;
}

JSValue prop2 = right->getIfPropertyExists(globalObject, propertyName1);
RETURN_IF_EXCEPTION(*scope, false);

if constexpr (!isStrict) {
if (prop1.isUndefined() && prop2.isEmpty()) {
continue;
}
}

if (!prop2) {
return false;
}

if (!Bun__deepEquals<isStrict, enableAsymmetricMatchers>(globalObject, prop1, prop2, gcBuffer, stack, scope, true)) {
return false;
}

RETURN_IF_EXCEPTION(*scope, false);
}

// for the remaining properties in the other object, make sure they are undefined
for (; i < propertyArrayLength2; i++) {
Identifier i2 = a2[i];
if (i2 == vm.propertyNames->stack) continue;
PropertyName propertyName2 = PropertyName(i2);

JSValue prop2 = right->getIfPropertyExists(globalObject, propertyName2);
RETURN_IF_EXCEPTION(*scope, false);

if (!prop2.isUndefined()) {
return false;
}
}

return true;
}
break;
}
case Int8ArrayType:
case Uint8ArrayType:
Expand All @@ -1213,6 +1320,11 @@ std::optional<bool> specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark
if (!isTypedArrayType(static_cast<JSC::JSType>(c2Type)) || c1Type != c2Type) {
return false;
}
auto info = c1->classInfo();
auto info2 = c2->classInfo();
if (!info || !info2) {
return false;
}

JSC::JSArrayBufferView* left = jsCast<JSArrayBufferView*, JSCell>(c1);
JSC::JSArrayBufferView* right = jsCast<JSArrayBufferView*, JSCell>(c2);
Expand Down Expand Up @@ -1373,7 +1485,20 @@ std::optional<bool> specialObjectsDequal(JSC__JSGlobalObject* globalObject, Mark
compareAsNormalValue:
break;
}

// globalThis is only equal to globalThis
// NOTE: Zig::GlobalObject is tagged as GlobalProxyType
case GlobalObjectType: {
if (c1Type != c2Type) return false;
auto* g1 = jsDynamicCast<JSC::JSGlobalObject*, JSCell>(c1);
auto* g2 = jsDynamicCast<JSC::JSGlobalObject*, JSCell>(c2);
return g1->m_globalThis == g2->m_globalThis;
}
case GlobalProxyType: {
if (c1Type != c2Type) return false;
auto* gp1 = jsDynamicCast<JSC::JSGlobalProxy*, JSCell>(c1);
auto* gp2 = jsDynamicCast<JSC::JSGlobalProxy*, JSCell>(c2);
return gp1->target()->m_globalThis == gp2->target()->m_globalThis;
}
default: {
break;
}
Expand Down Expand Up @@ -1528,6 +1653,19 @@ bool Bun__deepMatch(
return true;
}

// anonymous namespace to avoid name collision
namespace {
template<bool isStrict, bool enableAsymmetricMatchers>
inline bool deepEqualsWrapperImpl(JSC__JSValue a, JSC__JSValue b, JSC__JSGlobalObject* global)
{
auto& vm = global->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16> stack;
MarkedArgumentBuffer args;
return Bun__deepEquals<isStrict, enableAsymmetricMatchers>(global, JSC::JSValue::decode(a), JSC::JSValue::decode(b), args, stack, &scope, true);
}
}

extern "C" {

bool WebCore__FetchHeaders__isEmpty(WebCore__FetchHeaders* arg0)
Expand Down Expand Up @@ -2427,35 +2565,6 @@ size_t JSC__VM__heapSize(JSC__VM* arg0)
return arg0->heap.size();
}

// This is very naive!
JSC__JSInternalPromise* JSC__VM__reloadModule(JSC__VM* vm, JSC__JSGlobalObject* arg1,
ZigString arg2)
{
return nullptr;
// JSC::JSMap *map = JSC::jsDynamicCast<JSC::JSMap *>(
// arg1->vm(), arg1->moduleLoader()->getDirect(
// arg1->vm(), JSC::Identifier::fromString(arg1->vm(), "registry"_s)));

// const JSC::Identifier identifier = Zig::toIdentifier(arg2, arg1);
// JSC::JSValue val = JSC::identifierToJSValue(arg1->vm(), identifier);

// if (!map->has(arg1, val)) return nullptr;

// if (JSC::JSObject *registryEntry =
// JSC::jsDynamicCast<JSC::JSObject *>(arg1-> map->get(arg1, val))) {
// auto moduleIdent = JSC::Identifier::fromString(arg1->vm(), "module");
// if (JSC::JSModuleRecord *record = JSC::jsDynamicCast<JSC::JSModuleRecord *>(
// arg1->vm(), registryEntry->getDirect(arg1->vm(), moduleIdent))) {
// registryEntry->putDirect(arg1->vm(), moduleIdent, JSC::jsUndefined());
// JSC::JSModuleRecord::destroy(static_cast<JSC::JSCell *>(record));
// }
// map->remove(arg1, val);
// return JSC__JSModuleLoader__loadAndEvaluateModule(arg1, arg2);
// }

// return nullptr;
}

bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1,
JSC__JSGlobalObject* globalObject)
{
Expand All @@ -2466,50 +2575,26 @@ bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1,

bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
{
ASSERT_NO_PENDING_EXCEPTION(globalObject);
JSValue v1 = JSValue::decode(JSValue0);
JSValue v2 = JSValue::decode(JSValue1);

ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
Vector<std::pair<JSValue, JSValue>, 16> stack;
MarkedArgumentBuffer args;
return Bun__deepEquals<false, false>(globalObject, v1, v2, args, stack, &scope, true);
return deepEqualsWrapperImpl<false, false>(JSValue0, JSValue1, globalObject);
}

bool JSC__JSValue__jestDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
{
JSValue v1 = JSValue::decode(JSValue0);
JSValue v2 = JSValue::decode(JSValue1);

ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
Vector<std::pair<JSValue, JSValue>, 16> stack;
MarkedArgumentBuffer args;
return Bun__deepEquals<false, true>(globalObject, v1, v2, args, stack, &scope, true);
return deepEqualsWrapperImpl<false, true>(JSValue0, JSValue1, globalObject);
}

bool JSC__JSValue__strictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
{
JSValue v1 = JSValue::decode(JSValue0);
JSValue v2 = JSValue::decode(JSValue1);

ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
Vector<std::pair<JSValue, JSValue>, 16> stack;
MarkedArgumentBuffer args;
return Bun__deepEquals<true, false>(globalObject, v1, v2, args, stack, &scope, true);
return deepEqualsWrapperImpl<true, false>(JSValue0, JSValue1, globalObject);
}

bool JSC__JSValue__jestStrictDeepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
{
JSValue v1 = JSValue::decode(JSValue0);
JSValue v2 = JSValue::decode(JSValue1);

ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
Vector<std::pair<JSValue, JSValue>, 16> stack;
MarkedArgumentBuffer args;

return Bun__deepEquals<true, true>(globalObject, v1, v2, args, stack, &scope, true);
return deepEqualsWrapperImpl<true, true>(JSValue0, JSValue1, globalObject);
}

#undef IMPL_DEEP_EQUALS_WRAPPER

bool JSC__JSValue__deepMatch(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject, bool replacePropsWithAsymmetricMatchers)
{
JSValue obj = JSValue::decode(JSValue0);
Expand Down
27 changes: 20 additions & 7 deletions src/bun.js/bindings/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5642,38 +5642,51 @@ pub const JSValue = enum(i64) {
}

/// Object.is()
///
/// This algorithm differs from the IsStrictlyEqual Algorithm by treating all NaN values as equivalent and by differentiating +0𝔽 from -0𝔽.
/// https://tc39.es/ecma262/#sec-samevalue
pub fn isSameValue(this: JSValue, other: JSValue, global: *JSGlobalObject) bool {
return @intFromEnum(this) == @intFromEnum(other) or cppFn("isSameValue", .{ this, other, global });
}

pub fn deepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool {
return cppFn("deepEquals", .{ this, other, global });
pub fn deepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) JSError!bool {
// JSC__JSValue__deepEquals
const result = cppFn("deepEquals", .{ this, other, global });
if (global.hasException()) return error.JSError;
return result;
}

/// same as `JSValue.deepEquals`, but with jest asymmetric matchers enabled
pub fn jestDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bun.JSError!bool {
pub fn jestDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) JSError!bool {
const result = cppFn("jestDeepEquals", .{ this, other, global });
if (global.hasException()) return error.JSError;
return result;
}

pub fn strictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool {
return cppFn("strictDeepEquals", .{ this, other, global });
pub fn strictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) JSError!bool {
// JSC__JSValue__strictDeepEquals
const result = cppFn("strictDeepEquals", .{ this, other, global });
if (global.hasException()) return error.JSError;
return result;
}

/// same as `JSValue.strictDeepEquals`, but with jest asymmetric matchers enabled
pub fn jestStrictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) bool {
return cppFn("jestStrictDeepEquals", .{ this, other, global });
pub fn jestStrictDeepEquals(this: JSValue, other: JSValue, global: *JSGlobalObject) JSError!bool {
// JSC__JSValue__jestStrictDeepEquals
const result = cppFn("jestStrictDeepEquals", .{ this, other, global });
if (global.hasException()) return error.JSError;
return result;
}

/// NOTE: can throw. Check for exceptions.
pub fn deepMatch(this: JSValue, subset: JSValue, global: *JSGlobalObject, replace_props_with_asymmetric_matchers: bool) bool {
// JSC__JSValue__deepMatch
return cppFn("deepMatch", .{ this, subset, global, replace_props_with_asymmetric_matchers });
}

/// same as `JSValue.deepMatch`, but with jest asymmetric matchers enabled
pub fn jestDeepMatch(this: JSValue, subset: JSValue, global: *JSGlobalObject, replace_props_with_asymmetric_matchers: bool) bool {
// JSC__JSValue__jestDeepMatch
return cppFn("jestDeepMatch", .{ this, subset, global, replace_props_with_asymmetric_matchers });
}

Expand Down
Loading

0 comments on commit 146ec77

Please sign in to comment.