aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings/JSBuffer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/bindings/JSBuffer.cpp')
-rw-r--r--src/bun.js/bindings/JSBuffer.cpp444
1 files changed, 443 insertions, 1 deletions
diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp
index 9b3854b95..0dc1ae444 100644
--- a/src/bun.js/bindings/JSBuffer.cpp
+++ b/src/bun.js/bindings/JSBuffer.cpp
@@ -56,6 +56,46 @@
// #include "JavaScriptCore/JSTypedArrayViewPrototype.h"
#include "JavaScriptCore/JSArrayBufferViewInlines.h"
+#include "simdjson.h"
+#include <JavaScriptCore/JSONAtomStringCache.h>
+#include "simdutf.h"
+
+#include "JavaScriptCore/SmallStrings.h"
+#include "JavaScriptCore/VM.h"
+
+namespace JSC {
+
+template<typename CharacterType>
+ALWAYS_INLINE Ref<AtomStringImpl> JSONAtomStringCache::make(Type type, const CharacterType* characters, unsigned length)
+{
+ if (!length)
+ return *static_cast<AtomStringImpl*>(StringImpl::empty());
+
+ auto firstCharacter = characters[0];
+ if (length == 1) {
+ if (firstCharacter <= maxSingleCharacterString)
+ return vm().smallStrings.singleCharacterStringRep(firstCharacter);
+ } else if (length > maxStringLengthForCache)
+ return AtomStringImpl::add(characters, length).releaseNonNull();
+
+ auto lastCharacter = characters[length - 1];
+ auto& slot = cacheSlot(type, firstCharacter, lastCharacter, length);
+ if (!equal(slot.get(), characters, length)) {
+ auto result = AtomStringImpl::add(characters, length);
+ slot = result;
+ return result.releaseNonNull();
+ }
+
+ return *slot;
+}
+
+ALWAYS_INLINE VM& JSONAtomStringCache::vm() const
+{
+ return *bitwise_cast<VM*>(bitwise_cast<uintptr_t>(this) - OBJECT_OFFSETOF(VM, jsonAtomStringCache));
+}
+
+} // namespace JSC
+
JSC_DECLARE_HOST_FUNCTION(constructJSBuffer);
static JSC_DECLARE_HOST_FUNCTION(jsBufferConstructorFunction_alloc);
@@ -82,6 +122,406 @@ static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap64);
static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_toString);
static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_write);
+using namespace simdjson;
+
+static JSValue recursiveParseJSON(JSC::JSGlobalObject* globalObject, bool& stop, simdjson::ondemand::value element)
+{
+ simdjson::ondemand::json_type type;
+ if (UNLIKELY(element.type().get(type))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ switch (type) {
+ case simdjson::ondemand::json_type::array: {
+ JSC::JSArray* array = JSC::JSArray::tryCreate(globalObject->vm(), globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithUndecided));
+ for (auto child : element.get_array()) {
+ simdjson::ondemand::value current;
+
+ if (UNLIKELY(child.get(current))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ array->push(globalObject, recursiveParseJSON(globalObject, stop, current));
+ if (UNLIKELY(stop)) {
+ return JSC::jsUndefined();
+ }
+ }
+ return array;
+ }
+ case simdjson::ondemand::json_type::object: {
+ JSC::JSObject* object = JSC::constructEmptyObject(globalObject);
+ simdjson::ondemand::object elementObject;
+ if (UNLIKELY(element.get_object().get(elementObject))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ for (auto field : elementObject) {
+ simdjson::ondemand::value current;
+ std::string_view keyView;
+
+ if (UNLIKELY(field.unescaped_key(true).get(keyView))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ if (UNLIKELY(field.value().get(current))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ JSC::Identifier identifier;
+ if (simdutf::validate_ascii(keyView.data(), keyView.length())) {
+ identifier = JSC::Identifier::fromString(globalObject->vm(), globalObject->vm().jsonAtomStringCache.makeIdentifier(keyView.data(), keyView.length()));
+ } else {
+ identifier = JSC::Identifier::fromString(globalObject->vm(), WTF::String::fromUTF8(keyView.data(), keyView.length()));
+ }
+
+ object->putDirect(
+ globalObject->vm(),
+ identifier,
+ recursiveParseJSON(globalObject, stop, current));
+
+ if (UNLIKELY(stop)) {
+ return JSC::jsUndefined();
+ }
+ }
+ return object;
+ }
+ case simdjson::ondemand::json_type::number: {
+ double val;
+ if (UNLIKELY(element.get(val))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ return jsNumber(val);
+ }
+ case simdjson::ondemand::json_type::string: {
+ std::string_view str;
+ if (UNLIKELY(element.get(str))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+ return JSC::jsString(globalObject->vm(), WTF::String::fromUTF8(str.data(), str.length()));
+ }
+ case simdjson::ondemand::json_type::boolean: {
+ bool val;
+ if (UNLIKELY(element.get(val))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ return jsBoolean(val);
+ }
+ case simdjson::ondemand::json_type::null: {
+ bool val;
+ if (UNLIKELY(element.is_null().get(val) || !val)) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ return JSC::jsNull();
+ }
+ }
+}
+
+static JSValue recursiveParseJSONDOM(JSC::JSGlobalObject* globalObject, bool& stop, simdjson::dom::element element)
+{
+ simdjson::dom::element_type type;
+ switch (element.type()) {
+ case simdjson::dom::element_type::ARRAY: {
+ JSC::JSArray* array = JSC::JSArray::tryCreate(globalObject->vm(), globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithUndecided));
+ simdjson::dom::array elementArray;
+ if (UNLIKELY(element.get_array().get(elementArray))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ for (auto child : elementArray) {
+ simdjson::dom::element current;
+
+ if (UNLIKELY(child.get(current))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ array->push(globalObject, recursiveParseJSONDOM(globalObject, stop, current));
+ if (UNLIKELY(stop)) {
+ return JSC::jsUndefined();
+ }
+ }
+ return array;
+ }
+ case simdjson::dom::element_type::OBJECT: {
+ JSC::JSObject* object = JSC::constructEmptyObject(globalObject);
+ simdjson::dom::object elementObject;
+ if (UNLIKELY(element.get_object().get(elementObject))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ for (auto field : elementObject) {
+ simdjson::dom::element current = field.value;
+ std::string_view keyView = field.key;
+
+ object->putDirect(
+ globalObject->vm(),
+ JSC::Identifier::fromString(globalObject->vm(), WTF::String::fromUTF8(keyView.data(), keyView.length())),
+ recursiveParseJSONDOM(globalObject, stop, current));
+
+ if (UNLIKELY(stop)) {
+ return JSC::jsUndefined();
+ }
+ }
+ return object;
+ }
+ case simdjson::dom::element_type::INT64:
+ case simdjson::dom::element_type::UINT64:
+ case simdjson::dom::element_type::DOUBLE: {
+ double val;
+ if (UNLIKELY(element.get(val))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ return jsNumber(val);
+ }
+ case simdjson::dom::element_type::STRING: {
+ std::string_view str;
+ if (UNLIKELY(element.get(str))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+ return JSC::jsString(globalObject->vm(), WTF::String::fromUTF8(str.data(), str.length()));
+ }
+ case simdjson::dom::element_type::BOOL: {
+ bool val;
+ if (UNLIKELY(element.get(val))) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ return jsBoolean(val);
+ }
+ case simdjson::dom::element_type::NULL_VALUE: {
+ bool val;
+ if (UNLIKELY(element.get(val) || !val)) {
+ stop = true;
+ return JSC::jsUndefined();
+ }
+
+ return JSC::jsNull();
+ }
+ }
+}
+
+static JSValue parseJSONOnDemand(JSC::JSGlobalObject* globalObject, const char* ptr, size_t byteLength, size_t allocatedLength)
+{
+ JSC::VM& vm = JSC::getVM(globalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+ simdjson::ondemand::document document;
+ static simdjson::ondemand::parser parser = simdjson::ondemand::parser();
+
+ bool stop = false;
+
+ auto err = parser.iterate(ptr, byteLength, allocatedLength).get(document);
+ if (err != simdjson::SUCCESS) {
+ throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ return JSC::jsNull();
+ }
+
+ simdjson::ondemand::value value;
+ err = document.get_value().get(value);
+
+ if (err == simdjson::error_code::SCALAR_DOCUMENT_AS_VALUE) {
+ simdjson::ondemand::json_type type;
+ if (!document.type().get(type)) {
+ switch (type) {
+ case simdjson::ondemand::json_type::number: {
+ double val;
+ if (UNLIKELY(document.get_double().get(val))) {
+
+ throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ return JSC::jsNull();
+ }
+
+ return jsNumber(val);
+ }
+ case simdjson::ondemand::json_type::string: {
+ std::string_view str;
+ if (UNLIKELY(document.get_string().get(str))) {
+
+ throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ return JSC::jsNull();
+ }
+
+ return JSC::jsString(globalObject->vm(), WTF::String::fromUTF8(str.data(), str.length()));
+ }
+
+ case simdjson::ondemand::json_type::boolean: {
+ bool val;
+ if (UNLIKELY(document.get_bool().get(val))) {
+
+ throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ return JSC::jsNull();
+ }
+
+ return jsBoolean(val);
+ }
+
+ case simdjson::ondemand::json_type::null: {
+ bool val;
+ if (UNLIKELY(document.is_null().get(val) || !val)) {
+
+ throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ return JSC::jsNull();
+ }
+
+ return JSC::jsNull();
+ }
+ }
+ }
+ }
+
+ // if (err != simdjson::error_code::SUCCESS) {
+ // throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, makeString("Invalid JSON due to error"_s, err)));
+ // return JSC::jsNull();
+ // }
+
+ auto result = recursiveParseJSON(globalObject, stop, value);
+
+ if (stop) {
+ throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ return JSC::jsNull();
+ }
+
+ return result;
+}
+
+static JSValue parseJSON(JSC::JSGlobalObject* globalObject, const char* ptr, size_t byteLength, size_t allocatedLength)
+{
+ JSC::VM& vm = JSC::getVM(globalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+ dom::parser parser;
+ dom::element document;
+
+ if (parser.parse(ptr, byteLength, allocatedLength).get(document)) {
+ throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ return JSC::jsNull();
+ }
+
+ // if (err == simdjson::error_code::SCALAR_DOCUMENT_AS_VALUE) {
+ // simdjson::ondemand::json_type type;
+ // if (!document.type().get(type)) {
+ // switch (type) {
+ // case simdjson::ondemand::json_type::number: {
+ // double val;
+ // if (UNLIKELY(document.get_double().get(val))) {
+
+ // throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ // return JSC::jsNull();
+ // }
+
+ // return jsNumber(val);
+ // }
+ // case simdjson::ondemand::json_type::string: {
+ // std::string_view str;
+ // if (UNLIKELY(document.get_string().get(str))) {
+
+ // throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ // return JSC::jsNull();
+ // }
+
+ // return JSC::jsString(globalObject->vm(), WTF::String::fromUTF8(str.data(), str.length()));
+ // }
+
+ // case simdjson::ondemand::json_type::boolean: {
+ // bool val;
+ // if (UNLIKELY(document.get_bool().get(val))) {
+
+ // throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ // return JSC::jsNull();
+ // }
+
+ // return jsBoolean(val);
+ // }
+
+ // case simdjson::ondemand::json_type::null: {
+ // bool val;
+ // if (UNLIKELY(document.is_null().get(val) || !val)) {
+
+ // throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ // return JSC::jsNull();
+ // }
+
+ // return JSC::jsNull();
+ // }
+ // }
+ // }
+ // }
+
+ // if (err != simdjson::error_code::SUCCESS) {
+ // throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, makeString("Invalid JSON due to error"_s, err)));
+ // return JSC::jsNull();
+ // }
+ bool stop = false;
+ auto result = recursiveParseJSONDOM(globalObject, stop, document);
+
+ if (stop) {
+ throwException(globalObject, throwScope, JSC::createSyntaxError(globalObject, "Invalid JSON"_s));
+ return JSC::jsNull();
+ }
+
+ return result;
+}
+
+static JSValue parseJSONWithInsufficientPadding(JSC::JSGlobalObject* globalObject, const void* data, size_t byteLength)
+{
+ JSC::VM& vm = JSC::getVM(globalObject);
+
+ if (!byteLength)
+ return JSC::jsNull();
+
+ void* ptr = malloc(byteLength + simdjson::SIMDJSON_PADDING);
+ if (!ptr) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ throwOutOfMemoryError(globalObject, throwScope);
+ return JSC::jsNull();
+ }
+ memcpy(ptr, data, byteLength);
+
+ JSValue result = parseJSONOnDemand(globalObject, reinterpret_cast<const char*>(ptr), byteLength, byteLength + simdjson::SIMDJSON_PADDING);
+ free(ptr);
+
+ return result;
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeJSONParseFunction, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
+{
+ JSC::VM& vm = JSC::getVM(globalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+
+ auto thisValue = callframe->thisValue();
+ if (UNLIKELY(throwScope.exception()) || !thisValue.isObject())
+ return JSValue::encode(JSC::jsUndefined());
+
+ auto* thisObject = thisValue.getObject();
+
+ if (!thisObject || !thisValue.inherits<JSUint8Array>())
+ return JSValue::encode(JSC::jsUndefined());
+
+ auto* buffer = jsCast<JSUint8Array*>(thisObject);
+ JSC::EnsureStillAliveScope ensureStillAliveScope(buffer);
+ auto res = parseJSONWithInsufficientPadding(globalObject, buffer->vector(), buffer->byteLength());
+ RETURN_IF_EXCEPTION(throwScope, JSValue::encode(JSC::jsUndefined()));
+ return JSValue::encode(res);
+}
+
static JSUint8Array* allocBuffer(JSC::JSGlobalObject* lexicalGlobalObject, size_t byteLength)
{
JSC::VM& vm = JSC::getVM(lexicalGlobalObject);
@@ -1902,6 +2342,8 @@ static const HashTableValue JSBufferPrototypeTableValues[]
{ "writeUint32BE"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt32BECodeGenerator, 1 } },
{ "writeUint32LE"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt32LECodeGenerator, 1 } },
{ "writeUint8"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeWriteUInt8CodeGenerator, 1 } },
+ { "json"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeJSONParseFunction, 0 } },
+
};
void JSBufferPrototype::finishCreation(VM& vm, JSC::JSGlobalObject* globalThis)
@@ -2173,4 +2615,4 @@ bool JSBuffer__isBuffer(JSC::JSGlobalObject* lexicalGlobalObject, JSC::EncodedJS
JSValue prototype = cell->getPrototype(vm, lexicalGlobalObject);
return prototype.inherits<JSBufferPrototype>();
-} \ No newline at end of file
+}