aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings/bindings.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/bindings/bindings.cpp')
-rw-r--r--src/bun.js/bindings/bindings.cpp540
1 files changed, 540 insertions, 0 deletions
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index 8c0df075e..4cb163f88 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -77,6 +77,12 @@
#include "HTTPHeaderNames.h"
#include "JSDOMPromiseDeferred.h"
#include "JavaScriptCore/TestRunnerUtils.h"
+#include "JavaScriptCore/DateInstance.h"
+#include "JavaScriptCore/RegExpObject.h"
+#include "JavaScriptCore/PropertyNameArray.h"
+#include "JavaScriptCore/HashMapImpl.h"
+#include "JavaScriptCore/HashMapImplInlines.h"
+#include "OnigurumaRegExp.h"
template<typename UWSResponse>
static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res)
@@ -122,6 +128,530 @@ static void handlePromise(PromiseType* promise, JSC__JSGlobalObject* globalObjec
}
}
+static bool canPerformFastPropertyEnumerationForObjectAssignBun(Structure* s)
+{
+ if (s->typeInfo().overridesGetOwnPropertySlot())
+ return false;
+ if (s->typeInfo().overridesAnyFormOfGetOwnPropertyNames())
+ return false;
+ // FIXME: Indexed properties can be handled.
+ // https://bugs.webkit.org/show_bug.cgi?id=185358
+ if (hasIndexedProperties(s->indexingType()))
+ return false;
+ if (s->hasGetterSetterProperties())
+ return false;
+ if (s->hasReadOnlyOrGetterSetterPropertiesExcludingProto())
+ return false;
+ if (s->hasCustomGetterSetterProperties())
+ return false;
+ if (s->isUncacheableDictionary())
+ return false;
+ // Cannot perform fast [[Put]] to |target| if the property names of the |source| contain "__proto__".
+ if (s->hasUnderscoreProtoPropertyExcludingOriginalProto())
+ return false;
+ return true;
+}
+
+// adapted from underscorejs [https://underscorejs.org/docs/modules/isEqual.html]
+bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2, Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16>& stack, ThrowScope* scope, bool addToStack)
+{
+ VM& vm = globalObject->vm();
+ if (!v1.isEmpty() && !v2.isEmpty() && JSC::sameValue(globalObject, v1, v2)) {
+ return true;
+ }
+
+ if (v1.isEmpty() || v2.isEmpty())
+ return v1.isEmpty() == v2.isEmpty();
+
+ if (v1.isPrimitive() || v2.isPrimitive())
+ return false;
+
+ RELEASE_ASSERT(v1.isCell());
+ RELEASE_ASSERT(v2.isCell());
+
+ size_t length = stack.size();
+ size_t originalLength = length;
+ for (size_t i = 0; i < length; i++) {
+ auto values = stack.at(i);
+ if (JSC::JSValue::strictEqual(globalObject, values.first, v1)) {
+ return JSC::JSValue::strictEqual(globalObject, values.second, v2);
+ } else if (JSC::JSValue::strictEqual(globalObject, values.second, v2))
+ return false;
+ }
+
+ if (addToStack) {
+ stack.append({ v1, v2 });
+ }
+
+ JSCell* c1 = v1.asCell();
+ JSCell* c2 = v2.asCell();
+ JSC::JSType c1Type = c1->type();
+ JSC::JSType c2Type = c2->type();
+
+ switch (c1Type) {
+ case JSSetType: {
+ if (c2Type != JSSetType) {
+ return false;
+ }
+
+ JSSet* set1 = jsCast<JSSet*>(c1);
+ JSSet* set2 = jsCast<JSSet*>(c2);
+
+ if (set1->size() != set2->size()) {
+ return false;
+ }
+
+ IterationRecord iterationRecord1 = iteratorForIterable(globalObject, v1);
+ bool isEqual = true;
+ while (true) {
+ JSValue next1 = iteratorStep(globalObject, iterationRecord1);
+ if (next1.isFalse()) {
+ break;
+ }
+
+ JSValue nextValue1 = iteratorValue(globalObject, next1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ bool found = false;
+ IterationRecord iterationRecord2 = iteratorForIterable(globalObject, v2);
+ while (true) {
+ JSValue next2 = iteratorStep(globalObject, iterationRecord2);
+ if (UNLIKELY(next2.isFalse())) {
+ break;
+ }
+
+ JSValue nextValue2 = iteratorValue(globalObject, next2);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ // set has unique values, no need to count
+ if (Bun__deepEquals(globalObject, nextValue1, nextValue2, stack, scope, false)) {
+ found = true;
+ if (!nextValue1.isPrimitive()) {
+ stack.append({ nextValue1, nextValue2 });
+ }
+ break;
+ }
+ }
+
+ if (!found) {
+ isEqual = false;
+ break;
+ }
+ }
+
+ if (!isEqual) {
+ return false;
+ }
+
+ break;
+ }
+ case JSMapType: {
+ if (c2Type != JSMapType) {
+ return false;
+ }
+
+ JSMap* map1 = jsCast<JSMap*>(c1);
+ JSMap* map2 = jsCast<JSMap*>(c2);
+ size_t leftSize = map1->size();
+
+ if (leftSize != map2->size()) {
+ return false;
+ }
+
+ IterationRecord iterationRecord1 = iteratorForIterable(globalObject, v1);
+ bool isEqual = true;
+ while (true) {
+ JSValue next1 = iteratorStep(globalObject, iterationRecord1);
+ if (next1.isFalse()) {
+ break;
+ }
+
+ JSValue nextValue1 = iteratorValue(globalObject, next1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (UNLIKELY(!nextValue1.isObject())) {
+ return false;
+ }
+
+ JSObject* nextValueObject1 = asObject(nextValue1);
+
+ JSValue key1 = nextValueObject1->getIndex(globalObject, static_cast<unsigned>(0));
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ JSValue value1 = nextValueObject1->getIndex(globalObject, static_cast<unsigned>(1));
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ bool found = false;
+ IterationRecord iterationRecord2 = iteratorForIterable(globalObject, v2);
+ while (true) {
+ JSValue next2 = iteratorStep(globalObject, iterationRecord2);
+ if (UNLIKELY(next2.isFalse())) {
+ break;
+ }
+
+ JSValue nextValue2 = iteratorValue(globalObject, next2);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (UNLIKELY(!nextValue2.isObject())) {
+ return false;
+ }
+
+ JSObject* nextValueObject2 = asObject(nextValue2);
+
+ JSValue key2 = nextValueObject2->getIndex(globalObject, static_cast<unsigned>(0));
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ JSValue value2 = nextValueObject2->getIndex(globalObject, static_cast<unsigned>(1));
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (Bun__deepEquals(globalObject, key1, key2, stack, scope, false)) {
+ if (Bun__deepEquals(globalObject, nextValue1, nextValue2, stack, scope, false)) {
+ found = true;
+ if (!nextValue1.isPrimitive()) {
+ stack.append({ nextValue1, nextValue2 });
+ }
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ isEqual = false;
+ break;
+ }
+ }
+
+ if (!isEqual) {
+ return false;
+ }
+
+ break;
+ }
+ case ArrayBufferType: {
+ if (c2Type != ArrayBufferType) {
+ return false;
+ }
+
+ JSC::ArrayBuffer* left = jsCast<JSArrayBuffer*>(v1)->impl();
+ JSC::ArrayBuffer* right = jsCast<JSArrayBuffer*>(v2)->impl();
+ size_t byteLength = left->byteLength();
+
+ if (right->byteLength() != byteLength) {
+ return false;
+ }
+
+ if (byteLength == 0)
+ return true;
+
+ if (UNLIKELY(right->isDetached() || left->isDetached())) {
+ return false;
+ }
+
+ const void* vector = left->data();
+ const void* rightVector = right->data();
+ if (UNLIKELY(!vector || !rightVector)) {
+ return false;
+ }
+
+ if (UNLIKELY(vector == rightVector))
+ return true;
+
+ return (memcmp(vector, rightVector, byteLength) == 0);
+ }
+ case JSDateType: {
+ if (c2Type != JSDateType) {
+ return false;
+ }
+
+ JSC::DateInstance* left = jsCast<DateInstance*>(v1);
+ JSC::DateInstance* right = jsCast<DateInstance*>(v2);
+
+ if (left->structureID() == right->structureID()) {
+ return left->internalNumber() == right->internalNumber();
+ }
+ break;
+ }
+ case RegExpObjectType: {
+ if (c2Type != RegExpObjectType) {
+ return false;
+ }
+
+ if (OnigurumaRegEx* left = jsDynamicCast<OnigurumaRegEx*>(v1)) {
+ OnigurumaRegEx* right = jsDynamicCast<OnigurumaRegEx*>(v2);
+ if (UNLIKELY(!right)) {
+ return false;
+ }
+
+ if (!equal(left->patternString(), right->patternString())) {
+ return false;
+ }
+
+ if (!equal(left->flagsString(), right->flagsString())) {
+ return false;
+ }
+
+ return true;
+ } else if (JSC::RegExpObject* left = jsDynamicCast<JSC::RegExpObject*>(v1)) {
+ JSC::RegExpObject* right = jsDynamicCast<JSC::RegExpObject*>(v2);
+
+ if (UNLIKELY(!right)) {
+ return false;
+ }
+
+ return left->regExp()->key() == right->regExp()->key();
+ }
+
+ return false;
+ }
+ case Int8ArrayType:
+ case Uint8ArrayType:
+ case Uint8ClampedArrayType:
+ case Int16ArrayType:
+ case Uint16ArrayType:
+ case Int32ArrayType:
+ case Uint32ArrayType:
+ case Float32ArrayType:
+ case Float64ArrayType:
+ case BigInt64ArrayType:
+ case BigUint64ArrayType: {
+ if (!isTypedArrayType(c2Type)) {
+ return false;
+ }
+
+ JSC::JSArrayBufferView* left = jsCast<JSArrayBufferView*>(v1);
+ JSC::JSArrayBufferView* right = jsCast<JSArrayBufferView*>(v2);
+ size_t byteLength = left->byteLength();
+
+ if (right->byteLength() != byteLength) {
+ return false;
+ }
+
+ if (byteLength == 0)
+ return true;
+
+ if (UNLIKELY(right->isDetached() || left->isDetached())) {
+ return false;
+ }
+
+ const void* vector = left->vector();
+ const void* rightVector = right->vector();
+ if (UNLIKELY(!vector || !rightVector)) {
+ return false;
+ }
+
+ if (UNLIKELY(vector == rightVector))
+ return true;
+
+ return (memcmp(vector, rightVector, byteLength) == 0);
+ }
+
+ case JSFunctionType: {
+ return false;
+ }
+
+ default: {
+ break;
+ }
+ }
+
+ bool v1Array = isArray(globalObject, v1);
+ RETURN_IF_EXCEPTION(*scope, false);
+ bool v2Array = isArray(globalObject, v2);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ JSObject* o1 = v1.getObject();
+ JSObject* o2 = v2.getObject();
+
+ if (v1Array != v2Array)
+ return false;
+
+ if (v1Array && v2Array) {
+ JSC::JSArray* array1 = JSC::jsCast<JSC::JSArray*>(v1);
+ JSC::JSArray* array2 = JSC::jsCast<JSC::JSArray*>(v2);
+ size_t length = array1->length();
+ if (length != array2->length()) {
+ return false;
+ }
+
+ if (array1->canDoFastIndexedAccess() && array2->canDoFastIndexedAccess()) {
+ for (size_t i = 0; i < length; i++) {
+ JSValue left = o1->getIndexQuickly(i);
+ RETURN_IF_EXCEPTION(*scope, false);
+ JSValue right = o2->getIndexQuickly(i);
+ RETURN_IF_EXCEPTION(*scope, false);
+ if (!Bun__deepEquals(globalObject, left, right, stack, scope, true)) {
+ return false;
+ }
+
+ RETURN_IF_EXCEPTION(*scope, false);
+ }
+
+ } else {
+ for (size_t i = 0; i < length; i++) {
+ JSValue left = o1->getIndex(globalObject, i);
+ RETURN_IF_EXCEPTION(*scope, false);
+ JSValue right = o2->getIndex(globalObject, i);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (!Bun__deepEquals(globalObject, left, right, stack, scope, true)) {
+ return false;
+ }
+
+ RETURN_IF_EXCEPTION(*scope, false);
+ }
+ }
+
+ JSC::PropertyNameArray a1(vm, PropertyNameMode::Symbols, PrivateSymbolMode::Include);
+ JSC::PropertyNameArray a2(vm, PropertyNameMode::Symbols, PrivateSymbolMode::Include);
+ JSObject::getOwnPropertyNames(o1, globalObject, a1, DontEnumPropertiesMode::Exclude);
+ JSObject::getOwnPropertyNames(o2, globalObject, a2, DontEnumPropertiesMode::Exclude);
+
+ size_t propertyLength = a1.size();
+ if (propertyLength != a2.size()) {
+ return false;
+ }
+
+ // take a property name from one, try to get it from both
+ for (size_t i = 0; i < propertyLength; i++) {
+ Identifier i1 = a1[i];
+ PropertyName propertyName1 = PropertyName(i1);
+
+ JSValue prop1 = o1->get(globalObject, propertyName1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (UNLIKELY(!prop1)) {
+ return false;
+ }
+
+ JSValue prop2 = o2->getIfPropertyExists(globalObject, propertyName1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (!prop2) {
+ return false;
+ }
+
+ if (!Bun__deepEquals(globalObject, prop1, prop2, stack, scope, true)) {
+ return false;
+ }
+
+ RETURN_IF_EXCEPTION(*scope, false);
+ }
+
+ if (addToStack) {
+ stack.remove(originalLength);
+ }
+
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ return true;
+ }
+
+ JSC::Structure* o1Structure = o1->structure();
+ if (canPerformFastPropertyEnumerationForObjectAssignBun(o1Structure)) {
+ JSC::Structure* o2Structure = o2->structure();
+ if (canPerformFastPropertyEnumerationForObjectAssignBun(o2Structure)) {
+
+ size_t count1 = 0;
+
+ bool result = true;
+ if (o2Structure->maxOffset() != o1Structure->maxOffset()) {
+ return false;
+ }
+
+ o1Structure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool {
+ if (entry.attributes() & PropertyAttribute::DontEnum) {
+ return true;
+ }
+ count1++;
+
+ JSValue right = o2->getDirect(vm, JSC::PropertyName(entry.key()));
+
+ if (!right) {
+ result = false;
+ return false;
+ }
+
+ JSValue left = o1->getDirect(entry.offset());
+ if (left == right || JSC::sameValue(globalObject, left, right)) {
+ return true;
+ }
+
+ if (!Bun__deepEquals(globalObject, left, right, stack, scope, true)) {
+ result = false;
+ return false;
+ }
+
+ return true;
+ });
+
+ if (result && o2Structure->id() != o1Structure->id()) {
+ size_t remain = count1;
+ o2Structure->forEachProperty(vm, [&](const PropertyTableEntry& entry) -> bool {
+ if (entry.attributes() & PropertyAttribute::DontEnum) {
+ return true;
+ }
+
+ if (remain == 0) {
+ result = false;
+ return false;
+ }
+
+ remain--;
+ return true;
+ });
+ }
+
+ if (addToStack) {
+ stack.remove(originalLength);
+ }
+
+ return result;
+ }
+ }
+
+ JSC::PropertyNameArray a1(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include);
+ JSC::PropertyNameArray a2(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include);
+ o1->getPropertyNames(globalObject, a1, DontEnumPropertiesMode::Exclude);
+ o2->getPropertyNames(globalObject, a2, DontEnumPropertiesMode::Exclude);
+
+ size_t propertyLength = a1.size();
+ if (propertyLength != a2.size()) {
+ return false;
+ }
+
+ // take a property name from one, try to get it from both
+ for (size_t i = 0; i < propertyLength; i++) {
+ Identifier i1 = a1[i];
+ PropertyName propertyName1 = PropertyName(i1);
+
+ JSValue prop1 = o1->get(globalObject, propertyName1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (UNLIKELY(!prop1)) {
+ return false;
+ }
+
+ JSValue prop2 = o2->getIfPropertyExists(globalObject, propertyName1);
+ RETURN_IF_EXCEPTION(*scope, false);
+
+ if (!prop2) {
+ return false;
+ }
+
+ if (!Bun__deepEquals(globalObject, prop1, prop2, stack, scope, true)) {
+ return false;
+ }
+
+ RETURN_IF_EXCEPTION(*scope, false);
+ }
+
+ if (addToStack) {
+ stack.remove(originalLength);
+ }
+
+ return true;
+}
+
extern "C" {
void WebCore__FetchHeaders__toUWSResponse(WebCore__FetchHeaders* arg0, bool is_ssl, void* arg2)
@@ -706,6 +1236,16 @@ bool JSC__JSValue__isSameValue(JSC__JSValue JSValue0, JSC__JSValue JSValue1,
return JSC::sameValue(globalObject, left, right);
}
+bool JSC__JSValue__deepEquals(JSC__JSValue JSValue0, JSC__JSValue JSValue1, JSC__JSGlobalObject* globalObject)
+{
+ JSC::JSValue v1 = JSC::JSValue::decode(JSValue0);
+ JSC::JSValue v2 = JSC::JSValue::decode(JSValue1);
+
+ ThrowScope scope = DECLARE_THROW_SCOPE(globalObject->vm());
+ Vector<std::pair<JSC::JSValue, JSC::JSValue>, 16> stack;
+ return Bun__deepEquals(globalObject, v1, v2, stack, &scope, true);
+}
+
// This is the same as the C API version, except it returns a JSValue which may be a *Exception
// We want that so we can return stack traces.
JSC__JSValue JSObjectCallAsFunctionReturnValue(JSContextRef ctx, JSObjectRef object,