mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
wip.1
This commit is contained in:
227
src/bun.js/bindings/SnapshotSerializers.cpp
Normal file
227
src/bun.js/bindings/SnapshotSerializers.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
#include "root.h"
|
||||
#include "SnapshotSerializers.h"
|
||||
|
||||
#include <JavaScriptCore/JSArray.h>
|
||||
#include <JavaScriptCore/JSCJSValueInlines.h>
|
||||
#include <JavaScriptCore/ObjectConstructor.h>
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
#include <JavaScriptCore/Exception.h>
|
||||
#include "ErrorCode.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
const ClassInfo SnapshotSerializers::s_info = { "SnapshotSerializers"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(SnapshotSerializers) };
|
||||
|
||||
SnapshotSerializers::SnapshotSerializers(VM& vm, Structure* structure)
|
||||
: Base(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void SnapshotSerializers::finishCreation(VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
|
||||
// Initialize empty arrays
|
||||
m_testCallbacks.set(vm, this, JSC::constructEmptyArray(this->globalObject(), nullptr, 0));
|
||||
m_serializeCallbacks.set(vm, this, JSC::constructEmptyArray(this->globalObject(), nullptr, 0));
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void SnapshotSerializers::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
SnapshotSerializers* thisObject = jsCast<SnapshotSerializers*>(cell);
|
||||
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
||||
Base::visitChildren(thisObject, visitor);
|
||||
|
||||
visitor.append(thisObject->m_testCallbacks);
|
||||
visitor.append(thisObject->m_serializeCallbacks);
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(SnapshotSerializers);
|
||||
|
||||
SnapshotSerializers* SnapshotSerializers::create(VM& vm, Structure* structure)
|
||||
{
|
||||
SnapshotSerializers* serializers = new (NotNull, allocateCell<SnapshotSerializers>(vm)) SnapshotSerializers(vm, structure);
|
||||
serializers->finishCreation(vm);
|
||||
return serializers;
|
||||
}
|
||||
|
||||
bool SnapshotSerializers::addSerializer(JSGlobalObject* globalObject, JSValue testCallback, JSValue serializeCallback)
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
// Check for re-entrancy
|
||||
if (m_isExecuting) {
|
||||
throwTypeError(globalObject, scope, "Cannot add snapshot serializer from within a test or serialize callback"_s);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate that both callbacks are callable
|
||||
if (!testCallback.isCallable()) {
|
||||
throwTypeError(globalObject, scope, "Snapshot serializer test callback must be a function"_s);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!serializeCallback.isCallable()) {
|
||||
throwTypeError(globalObject, scope, "Snapshot serializer serialize callback must be a function"_s);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the arrays
|
||||
JSArray* testCallbacks = m_testCallbacks.get();
|
||||
JSArray* serializeCallbacks = m_serializeCallbacks.get();
|
||||
|
||||
if (!testCallbacks || !serializeCallbacks) {
|
||||
throwOutOfMemoryError(globalObject, scope);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to the end of the arrays (most recent last, we'll iterate in reverse)
|
||||
testCallbacks->push(globalObject, testCallback);
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
|
||||
serializeCallbacks->push(globalObject, serializeCallback);
|
||||
RETURN_IF_EXCEPTION(scope, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JSValue SnapshotSerializers::serialize(JSGlobalObject* globalObject, JSValue value)
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
// Check for re-entrancy
|
||||
if (m_isExecuting) {
|
||||
throwTypeError(globalObject, scope, "Cannot serialize from within a test or serialize callback"_s);
|
||||
return jsNull();
|
||||
}
|
||||
|
||||
// RAII guard to manage m_isExecuting flag
|
||||
class ExecutionGuard {
|
||||
public:
|
||||
ExecutionGuard(bool& flag) : m_flag(flag) { m_flag = true; }
|
||||
~ExecutionGuard() { m_flag = false; }
|
||||
private:
|
||||
bool& m_flag;
|
||||
};
|
||||
ExecutionGuard guard(m_isExecuting);
|
||||
|
||||
JSArray* testCallbacks = m_testCallbacks.get();
|
||||
JSArray* serializeCallbacks = m_serializeCallbacks.get();
|
||||
|
||||
if (!testCallbacks || !serializeCallbacks) {
|
||||
return jsNull();
|
||||
}
|
||||
|
||||
unsigned length = testCallbacks->length();
|
||||
|
||||
// Iterate through serializers in reverse order (most recent to least recent)
|
||||
for (int i = static_cast<int>(length) - 1; i >= 0; i--) {
|
||||
JSValue testCallback = testCallbacks->getIndex(globalObject, static_cast<unsigned>(i));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
if (!testCallback.isCallable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Call the test function with the value
|
||||
auto callData = JSC::getCallData(testCallback);
|
||||
MarkedArgumentBuffer args;
|
||||
args.append(value);
|
||||
ASSERT(!args.hasOverflowed());
|
||||
|
||||
JSValue testResult = call(globalObject, testCallback, callData, jsUndefined(), args);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
// If the test returns truthy, use this serializer
|
||||
if (testResult.toBoolean(globalObject)) {
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
JSValue serializeCallback = serializeCallbacks->getIndex(globalObject, static_cast<unsigned>(i));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
if (!serializeCallback.isCallable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Call the serialize function with the value
|
||||
auto serializeCallData = JSC::getCallData(serializeCallback);
|
||||
MarkedArgumentBuffer serializeArgs;
|
||||
serializeArgs.append(value);
|
||||
ASSERT(!serializeArgs.hasOverflowed());
|
||||
|
||||
JSValue result = call(globalObject, serializeCallback, serializeCallData, jsUndefined(), serializeArgs);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
// Return the serialized result (should be a string or null)
|
||||
RELEASE_AND_RETURN(scope, result);
|
||||
}
|
||||
}
|
||||
|
||||
// No matching serializer found
|
||||
return jsNull();
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
|
||||
using namespace Bun;
|
||||
using namespace JSC;
|
||||
|
||||
// Zig-exported functions
|
||||
|
||||
extern "C" [[ZIG_EXPORT(zero_is_throw)]] JSC::EncodedJSValue SnapshotSerializers__add(
|
||||
Zig::GlobalObject* globalObject,
|
||||
JSC::EncodedJSValue encodedSerializers,
|
||||
JSC::EncodedJSValue encodedTestCallback,
|
||||
JSC::EncodedJSValue encodedSerializeCallback)
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSValue serializersValue = JSValue::decode(encodedSerializers);
|
||||
SnapshotSerializers* serializers = jsDynamicCast<SnapshotSerializers*>(serializersValue);
|
||||
|
||||
if (!serializers) {
|
||||
throwTypeError(globalObject, scope, "Invalid SnapshotSerializers object"_s);
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSValue testCallback = JSValue::decode(encodedTestCallback);
|
||||
JSValue serializeCallback = JSValue::decode(encodedSerializeCallback);
|
||||
|
||||
bool success = serializers->addSerializer(globalObject, testCallback, serializeCallback);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
if (success) {
|
||||
RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined()));
|
||||
}
|
||||
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
extern "C" [[ZIG_EXPORT(zero_is_throw)]] JSC::EncodedJSValue SnapshotSerializers__serialize(
|
||||
Zig::GlobalObject* globalObject,
|
||||
JSC::EncodedJSValue encodedSerializers,
|
||||
JSC::EncodedJSValue encodedValue)
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSValue serializersValue = JSValue::decode(encodedSerializers);
|
||||
SnapshotSerializers* serializers = jsDynamicCast<SnapshotSerializers*>(serializersValue);
|
||||
|
||||
if (!serializers) {
|
||||
throwTypeError(globalObject, scope, "Invalid SnapshotSerializers object"_s);
|
||||
return JSValue::encode(jsNull());
|
||||
}
|
||||
|
||||
JSValue value = JSValue::decode(encodedValue);
|
||||
JSValue result = serializers->serialize(globalObject, value);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
RELEASE_AND_RETURN(scope, JSValue::encode(result));
|
||||
}
|
||||
76
src/bun.js/bindings/SnapshotSerializers.h
Normal file
76
src/bun.js/bindings/SnapshotSerializers.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/JSDestructibleObject.h>
|
||||
#include <JavaScriptCore/JSArray.h>
|
||||
#include <JavaScriptCore/WriteBarrier.h>
|
||||
#include "ZigGlobalObject.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
class SnapshotSerializers final : public JSC::JSDestructibleObject {
|
||||
public:
|
||||
using Base = JSC::JSDestructibleObject;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags;
|
||||
|
||||
static SnapshotSerializers* create(JSC::VM& vm, JSC::Structure* structure);
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
template<typename MyClassT, JSC::SubspaceAccess mode>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
|
||||
return nullptr;
|
||||
return WebCore::subspaceForImpl<MyClassT, WebCore::UseCustomHeapCellType::No>(
|
||||
vm,
|
||||
[](auto& spaces) { return spaces.m_clientSubspaceForSnapshotSerializers.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForSnapshotSerializers = std::forward<decltype(space)>(space); },
|
||||
[](auto& spaces) { return spaces.m_subspaceForSnapshotSerializers.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_subspaceForSnapshotSerializers = std::forward<decltype(space)>(space); });
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
DECLARE_VISIT_CHILDREN;
|
||||
|
||||
// Add a new snapshot serializer
|
||||
// Returns true on success, false if in re-entrant call (and throws)
|
||||
bool addSerializer(JSC::JSGlobalObject* globalObject, JSC::JSValue testCallback, JSC::JSValue serializeCallback);
|
||||
|
||||
// Test a value and serialize if a matching serializer is found
|
||||
// Returns the serialized string or null
|
||||
JSC::JSValue serialize(JSC::JSGlobalObject* globalObject, JSC::JSValue value);
|
||||
|
||||
private:
|
||||
SnapshotSerializers(JSC::VM& vm, JSC::Structure* structure);
|
||||
|
||||
void finishCreation(JSC::VM& vm);
|
||||
|
||||
// Arrays stored in reverse order (most recent first for iteration)
|
||||
JSC::WriteBarrier<JSC::JSArray> m_testCallbacks;
|
||||
JSC::WriteBarrier<JSC::JSArray> m_serializeCallbacks;
|
||||
|
||||
// Re-entrancy guard
|
||||
bool m_isExecuting { false };
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
|
||||
// Exposed to Zig
|
||||
extern "C" {
|
||||
|
||||
[[ZIG_EXPORT(zero_is_throw)]] JSC::EncodedJSValue SnapshotSerializers__add(
|
||||
Zig::GlobalObject* globalObject,
|
||||
JSC::EncodedJSValue encodedSerializers,
|
||||
JSC::EncodedJSValue encodedTestCallback,
|
||||
JSC::EncodedJSValue encodedSerializeCallback);
|
||||
|
||||
[[ZIG_EXPORT(zero_is_throw)]] JSC::EncodedJSValue SnapshotSerializers__serialize(
|
||||
Zig::GlobalObject* globalObject,
|
||||
JSC::EncodedJSValue encodedSerializers,
|
||||
JSC::EncodedJSValue encodedValue);
|
||||
|
||||
} // extern "C"
|
||||
62
src/bun.js/bindings/SnapshotSerializersBindings.zig
Normal file
62
src/bun.js/bindings/SnapshotSerializersBindings.zig
Normal file
@@ -0,0 +1,62 @@
|
||||
// Example Zig bindings for SnapshotSerializers
|
||||
// This shows how to use the exported C++ functions from Zig
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const JSC = bun.JSC;
|
||||
|
||||
// Import the exported C++ functions
|
||||
extern "c" fn SnapshotSerializers__add(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
serializers: JSC.JSValue,
|
||||
testCallback: JSC.JSValue,
|
||||
serializeCallback: JSC.JSValue,
|
||||
) JSC.JSValue;
|
||||
|
||||
extern "c" fn SnapshotSerializers__serialize(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
serializers: JSC.JSValue,
|
||||
value: JSC.JSValue,
|
||||
) JSC.JSValue;
|
||||
|
||||
/// Add a snapshot serializer
|
||||
pub fn addSerializer(
|
||||
global: *JSC.JSGlobalObject,
|
||||
serializers: JSC.JSValue,
|
||||
test_callback: JSC.JSValue,
|
||||
serialize_callback: JSC.JSValue,
|
||||
) JSC.JSValue {
|
||||
return SnapshotSerializers__add(
|
||||
global,
|
||||
serializers,
|
||||
test_callback,
|
||||
serialize_callback,
|
||||
);
|
||||
}
|
||||
|
||||
/// Serialize a value using the registered serializers
|
||||
pub fn serialize(
|
||||
global: *JSC.JSGlobalObject,
|
||||
serializers: JSC.JSValue,
|
||||
value: JSC.JSValue,
|
||||
) JSC.JSValue {
|
||||
return SnapshotSerializers__serialize(
|
||||
global,
|
||||
serializers,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
//
|
||||
// const serializers = SnapshotSerializers.create(vm, structure);
|
||||
//
|
||||
// // Add a serializer for custom objects
|
||||
// const test_fn = JSFunction.create(...); // function that returns true for custom objects
|
||||
// const serialize_fn = JSFunction.create(...); // function that serializes the object
|
||||
//
|
||||
// _ = addSerializer(global, serializers, test_fn, serialize_fn);
|
||||
//
|
||||
// // Use the serializer
|
||||
// const custom_object = ...;
|
||||
// const result = serialize(global, serializers, custom_object);
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSMockFunction;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForAsyncContextFrame;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMockWithImplementationCleanupData;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSnapshotSerializers;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForProcessObject;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForInternalModuleRegistry;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForErrorCodeCache;
|
||||
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSMockFunction;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForAsyncContextFrame;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForMockWithImplementationCleanupData;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForSnapshotSerializers;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForProcessObject;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForInternalModuleRegistry;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForErrorCodeCache;
|
||||
|
||||
@@ -17,6 +17,7 @@ pub const Snapshots = struct {
|
||||
snapshot_dir_path: ?string = null,
|
||||
inline_snapshots_to_write: *std.AutoArrayHashMap(TestRunner.File.ID, std.array_list.Managed(InlineSnapshotToWrite)),
|
||||
last_error_snapshot_name: ?[]const u8 = null,
|
||||
serializers: jsc.Strong.Optional = .empty,
|
||||
|
||||
pub const InlineSnapshotToWrite = struct {
|
||||
line: c_ulong,
|
||||
|
||||
Reference in New Issue
Block a user