Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add structured cloning to Deno.core #9458

Merged
merged 10 commits into from
Feb 16, 2021
112 changes: 97 additions & 15 deletions core/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 46,12 @@ lazy_static! {
v8::ExternalReference {
function: decode.map_fn_to()
},
v8::ExternalReference {
function: serialize.map_fn_to()
},
v8::ExternalReference {
function: deserialize.map_fn_to()
},
v8::ExternalReference {
function: get_promise_details.map_fn_to()
},
Expand Down Expand Up @@ -124,6 130,8 @@ pub fn initialize_context<'s>(
set_func(scope, core_val, "evalContext", eval_context);
set_func(scope, core_val, "encode", encode);
set_func(scope, core_val, "decode", decode);
set_func(scope, core_val, "serialize", serialize);
set_func(scope, core_val, "deserialize", deserialize);
set_func(scope, core_val, "getPromiseDetails", get_promise_details);
set_func(scope, core_val, "getProxyDetails", get_proxy_details);

Expand Down Expand Up @@ -427,9 435,7 @@ fn eval_context(
let source = match v8::Local::<v8::String>::try_from(args.get(0)) {
Ok(s) => s,
Err(_) => {
let msg = v8::String::new(scope, "Invalid argument").unwrap();
let exception = v8::Exception::type_error(scope, msg);
scope.throw_exception(exception);
throw_type_error(scope, "Invalid argument");
return;
}
};
Expand Down Expand Up @@ -550,9 556,7 @@ fn encode(
let text = match v8::Local::<v8::String>::try_from(args.get(0)) {
Ok(s) => s,
Err(_) => {
let msg = v8::String::new(scope, "Invalid argument").unwrap();
let exception = v8::Exception::type_error(scope, msg);
scope.throw_exception(exception);
throw_type_error(scope, "Invalid argument");
return;
}
};
Expand Down Expand Up @@ -583,9 587,7 @@ fn decode(
let view = match v8::Local::<v8::ArrayBufferView>::try_from(args.get(0)) {
Ok(view) => view,
Err(_) => {
let msg = v8::String::new(scope, "Invalid argument").unwrap();
let exception = v8::Exception::type_error(scope, msg);
scope.throw_exception(exception);
throw_type_error(scope, "Invalid argument");
return;
}
};
Expand Down Expand Up @@ -625,6 627,90 @@ fn decode(
};
}

struct SerializeDeserialize {}

impl v8::ValueSerializerImpl for SerializeDeserialize {
#[allow(unused_variables)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What unused variables?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is required by v8::ValueSerializerImpl but is never used in core/bindings.rs (yet I think)

fn throw_data_clone_error<'s>(
&mut self,
scope: &mut v8::HandleScope<'s>,
message: v8::Local<'s, v8::String>,
) {
let error = v8::Exception::error(scope, message);
scope.throw_exception(error);
}
}

impl v8::ValueDeserializerImpl for SerializeDeserialize {}

fn serialize(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) {
let serialize_deserialize = Box::new(SerializeDeserialize {});
let mut value_serializer =
v8::ValueSerializer::new(scope, serialize_deserialize);
match value_serializer.write_value(scope.get_current_context(), args.get(0)) {
Some(true) => {
let vector = value_serializer.release();
let buf = {
let buf_len = vector.len();
let backing_store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(
vector.into_boxed_slice(),
);
let backing_store_shared = backing_store.make_shared();
let ab =
v8::ArrayBuffer::with_backing_store(scope, &backing_store_shared);
v8::Uint8Array::new(scope, ab, 0, buf_len)
.expect("Failed to create UintArray8")
};

rv.set(buf.into());
}
_ => {
throw_type_error(scope, "Invalid argument");
}
}
}

fn deserialize(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) {
let view = match v8::Local::<v8::ArrayBufferView>::try_from(args.get(0)) {
Ok(view) => view,
Err(_) => {
throw_type_error(scope, "Invalid argument");
return;
}
};

let backing_store = view.buffer(scope).unwrap().get_backing_store();
let buf = unsafe {
get_backing_store_slice(
&backing_store,
view.byte_offset(),
view.byte_length(),
)
};

let serialize_deserialize = Box::new(SerializeDeserialize {});
let mut value_deserializer =
v8::ValueDeserializer::new(scope, serialize_deserialize, buf);
let value = value_deserializer.read_value(scope.get_current_context());

match value {
Some(deserialized) => rv.set(deserialized),
None => {
let msg = v8::String::new(scope, "string too long").unwrap();
let exception = v8::Exception::range_error(scope, msg);
scope.throw_exception(exception);
}
};
}

fn queue_microtask(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
Expand All @@ -633,9 719,7 @@ fn queue_microtask(
match v8::Local::<v8::Function>::try_from(args.get(0)) {
Ok(f) => scope.enqueue_microtask(f),
Err(_) => {
let msg = v8::String::new(scope, "Invalid argument").unwrap();
let exception = v8::Exception::type_error(scope, msg);
scope.throw_exception(exception);
throw_type_error(scope, "Invalid argument");
}
};
}
Expand Down Expand Up @@ -725,9 809,7 @@ fn get_promise_details(
let promise = match v8::Local::<v8::Promise>::try_from(args.get(0)) {
Ok(val) => val,
Err(_) => {
let msg = v8::String::new(scope, "Invalid argument").unwrap();
let exception = v8::Exception::type_error(scope, msg);
scope.throw_exception(exception);
throw_type_error(scope, "Invalid argument");
return;
}
};
Expand Down
16 changes: 16 additions & 0 deletions core/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2038,6 2038,22 @@ pub mod tests {
});
}

#[test]
fn test_serialize_deserialize() {
run_in_task(|mut cx| {
let (mut runtime, _dispatch_count) = setup(Mode::Async);
runtime
.execute(
"serialize_deserialize_test.js",
include_str!("serialize_deserialize_test.js"),
)
.unwrap();
if let Poll::Ready(Err(_)) = runtime.poll_event_loop(&mut cx) {
unreachable!();
}
});
}

#[test]
fn will_snapshot() {
let snapshot = {
Expand Down
69 changes: 69 additions & 0 deletions core/serialize_deserialize_test.js
Original file line number Diff line number Diff line change
@@ -0,0 1,69 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
"use strict";

function assert(cond) {
if (!cond) {
throw Error("assert");
}
}

function assertArrayEquals(a1, a2) {
if (a1.length !== a2.length) throw Error("assert");

for (const index in a1) {
if (a1[index] !== a2[index]) {
throw Error("assert");
}
}
}

function main() {
const emptyString = "";
const emptyStringSerialized = [34, 0];
assertArrayEquals(Deno.core.serialize(emptyString), emptyStringSerialized);
assert(
Deno.core.deserialize(new Uint8Array(emptyStringSerialized)) ===
emptyString,
);

const primitiveValueArray = ["test", "a", null, undefined];
// deno-fmt-ignore
const primitiveValueArraySerialized = [
65, 4, 34, 4, 116, 101, 115, 116,
34, 1, 97, 48, 95, 36, 0, 4,
];
assertArrayEquals(
Deno.core.serialize(primitiveValueArray),
primitiveValueArraySerialized,
);

assertArrayEquals(
Deno.core.deserialize(
new Uint8Array(primitiveValueArraySerialized),
),
primitiveValueArray,
);

const circularObject = { test: null, test2: "dd", test3: "aa" };
circularObject.test = circularObject;
// deno-fmt-ignore
const circularObjectSerialized = [
111, 34, 4, 116, 101, 115, 116, 94,
0, 34, 5, 116, 101, 115, 116, 50,
34, 2, 100, 100, 34, 5, 116, 101,
115, 116, 51, 34, 2, 97, 97, 123,
3,
];

assertArrayEquals(
Deno.core.serialize(circularObject),
circularObjectSerialized,
);

const deserializedCircularObject = Deno.core.deserialize(
new Uint8Array(circularObjectSerialized),
);
assert(deserializedCircularObject.test == deserializedCircularObject);
}

main();