Move buffers between V8 and native

* send()/recv() now operate on TypedArrays rather than ArrayBuffers.

* Remove a copy (through ArrayBuffer.slice()) from the send path.

* Remove a copy (through v8::ArrayBuffer::New()) from the return path.

* After moving a buffer from JS to native, the ArrayBuffer object and
  it's views are made inaccessible ('neutered').

* `struct deno_buf` now holds two [ptr, length] tuples, one for the actual
  memory allocation, and one for the logical data contained therein.
  This is necessary because flatbuffers fills it's buffer bottom-up, so
  the serialized blob doesn't start at beginning of the buffer, but
  somewhere in the middle.
This commit is contained in:
Bert Belder 2018-07-09 03:35:34 +02:00
parent bbcd4c8dd3
commit 24b0e91d80
No known key found for this signature in database
GPG key ID: 7A77887B2E2ED461
12 changed files with 438 additions and 70 deletions

View file

@ -76,6 +76,7 @@ executable("mock_runtime_test") {
testonly = true
sources = [
"src/file_util_test.cc",
"src/flatbuffer_builder_test.cc",
"src/from_snapshot.cc",
"src/mock_runtime_test.cc",
]
@ -106,11 +107,16 @@ v8_source_set("deno_bindings") {
"src/deno.h",
"src/file_util.cc",
"src/file_util.h",
"src/flatbuffer_builder.cc",
"src/flatbuffer_builder.h",
"src/internal.h",
]
deps = [
"third_party/v8:v8_monolith",
]
public_deps = [
"build_extra/flatbuffers:flatbuffers",
]
configs = [ ":deno_config" ]
}

4
js/deno.d.ts vendored
View file

@ -1,10 +1,10 @@
// Copyright 2018 Ryan Dahl <ry@tinyclouds.org>
// All rights reserved. MIT License.
type MessageCallback = (msg: ArrayBuffer) => void;
type MessageCallback = (msg: Uint8Array) => void;
interface Deno {
recv(cb: MessageCallback): void;
send(msg: ArrayBuffer): null | ArrayBuffer;
send(msg: ArrayBufferView): null | Uint8Array;
print(x: string): void;
}

View file

@ -19,7 +19,7 @@ function assignCmdId(): number {
return cmdId;
}
function startMsg(cmdId: number): ArrayBuffer {
function startMsg(cmdId: number): Uint8Array {
const builder = new flatbuffers.Builder();
const msg = fbs.Start.createStart(builder, 0);
fbs.Base.startBase(builder);
@ -27,7 +27,7 @@ function startMsg(cmdId: number): ArrayBuffer {
fbs.Base.addMsg(builder, msg);
fbs.Base.addMsgType(builder, fbs.Any.Start);
builder.finish(fbs.Base.endBase(builder));
return typedArrayToArrayBuffer(builder.asUint8Array());
return builder.asUint8Array();
}
window["denoMain"] = () => {
@ -47,7 +47,7 @@ window["denoMain"] = () => {
}
// Deserialize res into startResMsg.
const bb = new flatbuffers.ByteBuffer(new Uint8Array(res));
const bb = new flatbuffers.ByteBuffer(res);
const base = fbs.Base.getRootAsBase(bb);
assert(base.cmdId() === cmdId);
assert(fbs.Any.StartRes === base.msgType());
@ -69,10 +69,3 @@ window["denoMain"] = () => {
mod.compileAndRun();
*/
};
function typedArrayToArrayBuffer(ta: Uint8Array): ArrayBuffer {
return ta.buffer.slice(
ta.byteOffset,
ta.byteOffset + ta.byteLength
) as ArrayBuffer;
}

View file

@ -7,10 +7,6 @@ function assert(cond) {
if (!cond) throw Error("mock_runtime.js assert failed");
}
global.typedArrayToArrayBuffer = ta => {
return ta.buffer.slice(ta.byteOffset, ta.byteOffset + ta.byteLength);
};
global.CanCallFunction = () => {
deno.print("Hello world from foo");
return "foo";
@ -41,22 +37,20 @@ global.SendByteLength = () => {
};
global.RecvReturnEmpty = () => {
const ui8 = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
const ab = typedArrayToArrayBuffer(ui8);
let r = deno.send(ab);
assert(r == null);
r = deno.send(ab);
assert(r == null);
const m1 = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
const m2 = m1.slice();
const r1 = deno.send(m1);
assert(r1 == null);
const r2 = deno.send(m2);
assert(r2 == null);
};
global.RecvReturnBar = () => {
const ui8 = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
const ab = typedArrayToArrayBuffer(ui8);
const r = deno.send(ab);
assert(r instanceof ArrayBuffer);
const m = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
const r = deno.send(m);
assert(r instanceof Uint8Array);
assert(r.byteLength === 3);
const rui8 = new Uint8Array(r);
const rstr = String.fromCharCode(...rui8);
const rstr = String.fromCharCode(...r);
assert(rstr === "bar");
};
@ -67,6 +61,59 @@ global.DoubleRecvFails = () => {
deno.recv((channel, msg) => assert(false));
};
global.SendRecvSlice = () => {
const abLen = 1024;
let buf = new Uint8Array(abLen);
for (let i = 0; i < 5; i++) {
// Set first and last byte, for verification by the native side.
buf[0] = 100 + i;
buf[buf.length - 1] = 100 - i;
// On the native side, the slice is shortened by 19 bytes.
buf = deno.send(buf);
assert(buf.byteOffset === i * 11);
assert(buf.byteLength === abLen - i * 30 - 19);
assert(buf.buffer.byteLength == abLen);
// Look for values written by the backend.
assert(buf[0] === 200 + i);
assert(buf[buf.length - 1] === 200 - i);
// On the JS side, the start of the slice is moved up by 11 bytes.
buf = buf.subarray(11);
assert(buf.byteOffset === (i + 1) * 11);
assert(buf.byteLength === abLen - (i + 1) * 30);
}
};
global.JSSendArrayBufferViewTypes = () => {
// Test that ArrayBufferView slices are transferred correctly.
// Send Uint8Array.
const ab1 = new ArrayBuffer(4321);
const u8 = new Uint8Array(ab1, 2468, 1000);
u8[0] = 1;
deno.send(u8);
// Send Uint32Array.
const ab2 = new ArrayBuffer(4321);
const u32 = new Uint32Array(ab2, 2468, 1000 / Uint32Array.BYTES_PER_ELEMENT);
u32[0] = 0x02020202;
deno.send(u32);
// Send DataView.
const ab3 = new ArrayBuffer(4321);
const dv = new DataView(ab3, 2468, 1000);
dv.setUint8(0, 3);
deno.send(dv);
};
global.JSSendNeutersBuffer = () => {
// Buffer should be neutered after transferring it to the native side.
const u8 = new Uint8Array([42]);
assert(u8.byteLength === 1);
assert(u8.buffer.byteLength === 1);
assert(u8[0] === 42);
const r = deno.send(u8);
assert(u8.byteLength === 0);
assert(u8.buffer.byteLength === 0);
assert(u8[0] === undefined);
};
// The following join has caused SnapshotBug to segfault when using kKeep.
[].join("");
@ -82,7 +129,7 @@ global.ErrorHandling = () => {
assert(line === 3);
assert(col === 1);
assert(error instanceof Error);
deno.send(typedArrayToArrayBuffer(new Uint8Array([42])));
deno.send(new Uint8Array([42]));
};
eval("\n\n notdefined()\n//# sourceURL=helloworld.js");
};

View file

@ -139,6 +139,34 @@ void Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
fflush(stdout);
}
static v8::Local<v8::Uint8Array> ImportBuf(v8::Isolate* isolate, deno_buf buf) {
auto ab = v8::ArrayBuffer::New(
isolate, reinterpret_cast<void*>(buf.alloc_ptr), buf.alloc_len,
v8::ArrayBufferCreationMode::kInternalized);
auto view =
v8::Uint8Array::New(ab, buf.data_ptr - buf.alloc_ptr, buf.data_len);
return view;
}
static deno_buf ExportBuf(v8::Isolate* isolate,
v8::Local<v8::ArrayBufferView> view) {
auto ab = view->Buffer();
auto contents = ab->Externalize();
deno_buf buf;
buf.alloc_ptr = reinterpret_cast<uint8_t*>(contents.Data());
buf.alloc_len = contents.ByteLength();
buf.data_ptr = buf.alloc_ptr + view->ByteOffset();
buf.data_len = view->ByteLength();
// Prevent JS from modifying buffer contents after exporting.
ab->Neuter();
return buf;
}
static void FreeBuf(deno_buf buf) { free(buf.alloc_ptr); }
// Sets the recv callback.
void Recv(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
@ -169,20 +197,21 @@ void Send(const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(args.Length(), 1);
v8::Local<v8::Value> ab_v = args[0];
CHECK(ab_v->IsArrayBuffer());
auto ab = v8::Local<v8::ArrayBuffer>::Cast(ab_v);
auto contents = ab->GetContents();
// data is only a valid pointer until the end of this call.
const char* data =
const_cast<const char*>(reinterpret_cast<char*>(contents.Data()));
deno_buf buf{data, contents.ByteLength()};
CHECK(ab_v->IsArrayBufferView());
auto buf = ExportBuf(isolate, v8::Local<v8::ArrayBufferView>::Cast(ab_v));
DCHECK_EQ(d->currentArgs, nullptr);
d->currentArgs = &args;
d->cb(d, buf);
// Buffer is only valid until the end of the callback.
// TODO(piscisaureus):
// It's possible that data in the buffer is needed after the callback
// returns, e.g. when the handler offloads work to a thread pool, therefore
// make the callback responsible for releasing the buffer.
FreeBuf(buf);
d->currentArgs = nullptr;
}
@ -303,12 +332,8 @@ int deno_send(Deno* d, deno_buf buf) {
return 0;
}
// TODO(ry) support zero-copy.
auto ab = v8::ArrayBuffer::New(d->isolate, buf.len);
memcpy(ab->GetContents().Data(), buf.data, buf.len);
v8::Local<v8::Value> args[1];
args[0] = ab;
args[0] = deno::ImportBuf(d->isolate, buf);
recv->Call(context->Global(), 1, args);
if (try_catch.HasCaught()) {
@ -320,9 +345,7 @@ int deno_send(Deno* d, deno_buf buf) {
}
void deno_set_response(Deno* d, deno_buf buf) {
// TODO(ry) Support zero-copy.
auto ab = v8::ArrayBuffer::New(d->isolate, buf.len);
memcpy(ab->GetContents().Data(), buf.data, buf.len);
auto ab = deno::ImportBuf(d->isolate, buf);
d->currentArgs->GetReturnValue().Set(ab);
}

View file

@ -3,6 +3,7 @@
#ifndef DENO_H_
#define DENO_H_
#include <stddef.h>
#include <stdint.h>
// Neither Rust nor Go support calling directly into C++ functions, therefore
// the public interface to libdeno is done in C.
#ifdef __cplusplus
@ -11,8 +12,10 @@ extern "C" {
// Data that gets transmitted.
typedef struct {
const char* data;
size_t len;
uint8_t* alloc_ptr; // Start of memory allocation (returned from `malloc()`).
size_t alloc_len; // Length of the memory allocation.
uint8_t* data_ptr; // Start of logical contents (within the allocation).
size_t data_len; // Length of logical contents.
} deno_buf;
struct deno_s;
@ -37,11 +40,17 @@ int deno_execute(Deno* d, const char* js_filename, const char* js_source);
// Routes message to the javascript callback set with deno.recv(). A false
// return value indicates error. Check deno_last_exception() for exception text.
// 0 = fail, 1 = success
// After calling deno_send(), the caller no longer owns `buf` and must not use
// it; deno_send() is responsible for releasing it's memory.
// TODO(piscisaureus) In C++ and/or Rust, use a smart pointer or similar to
// enforce this rule.
int deno_send(Deno* d, deno_buf buf);
// Call this inside a deno_recv_cb to respond synchronously to messages.
// If this is not called during the life time of a deno_recv_cb callback
// the deno.send() call in javascript will return null.
// After calling deno_set_response(), the caller no longer owns `buf` and must
// not access it; deno_set_response() is responsible for releasing it's memory.
void deno_set_response(Deno* d, deno_buf buf);
const char* deno_last_exception(Deno* d);

78
src/flatbuffer_builder.cc Normal file
View file

@ -0,0 +1,78 @@
// Copyright 2018 Bert Belder <bertbelder@gmail.com>
// All rights reserved. MIT License.
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include "deno.h"
#include "flatbuffer_builder.h"
#include "flatbuffers/flatbuffers.h"
namespace deno {
deno_buf FlatBufferBuilder::ExportBuf() {
uint8_t* data_ptr = GetBufferPointer();
size_t data_len = GetSize();
return allocator_.GetAndKeepBuf(data_ptr, data_len);
}
deno_buf FlatBufferBuilder::Allocator::GetAndKeepBuf(uint8_t* data_ptr,
size_t data_len) {
// The builder will typically allocate one chunk of memory with some
// default size. After that, it'll only call allocate() again if the
// initial allocation wasn't big enough, which is then immediately
// followed by deallocate() to release the buffer that was too small.
//
// Therefore we can assume that the `data_ptr` points somewhere inside
// the last allocation, and that we never have to protect earlier
// allocations from being released.
//
// Each builder gets it's own Allocator instance, so multiple builders
// can be exist at the same time without conflicts.
assert(last_alloc_ptr_ != nullptr); // Must have allocated.
assert(keep_alloc_ptr_ == nullptr); // Didn't export any buffer so far.
assert(data_ptr >= last_alloc_ptr_); // Data must be within allocation.
assert(data_ptr + data_len <= last_alloc_ptr_ + last_alloc_len_);
keep_alloc_ptr_ = last_alloc_ptr_;
deno_buf buf;
buf.alloc_ptr = last_alloc_ptr_;
buf.alloc_len = last_alloc_len_;
buf.data_ptr = data_ptr;
buf.data_len = data_len;
return buf;
}
uint8_t* FlatBufferBuilder::Allocator::allocate(size_t size) {
auto ptr = reinterpret_cast<uint8_t*>(malloc(size));
if (ptr == nullptr) {
return nullptr;
}
last_alloc_ptr_ = ptr;
last_alloc_len_ = size;
return ptr;
}
void FlatBufferBuilder::Allocator::deallocate(uint8_t* ptr, size_t size) {
if (ptr == last_alloc_ptr_) {
last_alloc_ptr_ = nullptr;
last_alloc_len_ = 0;
}
if (ptr == keep_alloc_ptr_) {
// This allocation became an exported buffer, so don't free it.
// Clearing keep_alloc_ptr_ makes it possible to export another
// buffer later (after the builder is reset with `Reset()`).
keep_alloc_ptr_ = nullptr;
return;
}
free(ptr);
}
} // namespace deno

64
src/flatbuffer_builder.h Normal file
View file

@ -0,0 +1,64 @@
// Copyright 2018 Bert Belder <bertbelder@gmail.com>
// All rights reserved. MIT License.
#ifndef FLATBUFFER_BUILDER_H_
#define FLATBUFFER_BUILDER_H_
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include "deno.h"
#include "flatbuffers/flatbuffers.h"
namespace deno {
// Wrap the default FlatBufferBuilder class, because the default one can't give
// us a pointer to the output buffer that we own. Nominally,
// FlatBufferBuilder::Release() should do that, but it returns some
// smart-pointer-like object (DetachedBuffer) that frees the buffer when it goes
// out of scope.
//
// This wrapper adds the `ExportBuf` method that returns a deno_buf, which
// is really not owned at all -- the caller is responsible for releasing the
// allocation with free().
//
// The alternative allocator also uses malloc()/free(), rather than
// new/delete[], so that the exported buffer can be later be converted to an
// ArrayBuffer; the (default) V8 ArrayBuffer allocator also uses free().
class FlatBufferBuilder : public flatbuffers::FlatBufferBuilder {
static const size_t kDefaultInitialSize = 1024;
class Allocator : public flatbuffers::Allocator {
uint8_t* keep_alloc_ptr_ = nullptr;
uint8_t* last_alloc_ptr_ = nullptr;
size_t last_alloc_len_ = 0;
public:
deno_buf GetAndKeepBuf(uint8_t* data_ptr, size_t data_len);
protected:
virtual uint8_t* allocate(size_t size);
virtual void deallocate(uint8_t* ptr, size_t size);
};
Allocator allocator_;
public:
explicit FlatBufferBuilder(size_t initial_size = kDefaultInitialSize)
: flatbuffers::FlatBufferBuilder(initial_size, &allocator_) {}
// Export the finalized flatbuffer as a deno_buf structure. The caller takes
// ownership of the underlying memory allocation, which must be released with
// free().
// Afer calling ExportBuf() the FlatBufferBuilder should no longer be used;
// However it can be used again once it is reset with the Reset() method.
deno_buf ExportBuf();
// Don't use these.
flatbuffers::DetachedBuffer Release() = delete;
flatbuffers::DetachedBuffer ReleaseBufferPointer() = delete;
};
} // namespace deno
#endif // FLATBUFFER_BUILDER_H_

View file

@ -0,0 +1,78 @@
// Copyright 2018 Bert Belder <bertbelder@gmail.com>
// All rights reserved. MIT License.
#include <stdint.h>
#include "testing/gtest/include/gtest/gtest.h"
#include "deno.h"
#include "flatbuffer_builder.h"
template <typename T, std::size_t N>
constexpr std::size_t countof(T const (&)[N]) noexcept {
return N;
}
TEST(FlatBufferBuilderTest, ExportBuf) {
const uint32_t nums[] = {1, 2, 3};
const char str[] = "hello mars";
deno_buf nums_buf;
deno_buf str_buf;
// Use scope so builder gets destroyed after building buffers.
{
deno::FlatBufferBuilder builder;
// Build first flatbuffer.
auto nums_fb = builder.CreateVector(nums, countof(nums));
builder.Finish(nums_fb);
nums_buf = builder.ExportBuf();
// Reset builder.
builder.Reset();
// Build second flatbuffer using the same builder.
auto str_fb = builder.CreateString(str);
builder.Finish(str_fb);
str_buf = builder.ExportBuf();
}
// Allocations should be different.
EXPECT_NE(nums_buf.alloc_ptr, str_buf.alloc_ptr);
// Logical buffer data should be contained inside their allocations.
EXPECT_GE(nums_buf.data_ptr, nums_buf.alloc_ptr);
EXPECT_LE(nums_buf.data_ptr + nums_buf.data_len,
nums_buf.alloc_ptr + nums_buf.alloc_len);
EXPECT_GE(str_buf.data_ptr, str_buf.alloc_ptr);
EXPECT_LE(str_buf.data_ptr + str_buf.data_len,
str_buf.alloc_ptr + str_buf.alloc_len);
// Since there is no way to parse these buffers without generating code,
// just check whether the data is contained in the raw content.
// Both the numbers vector and the string start at offset 8 in the flatbuffer.
auto nums_data = reinterpret_cast<uint32_t*>(nums_buf.data_ptr + 8);
for (size_t i = 0; i < countof(nums); i++) {
EXPECT_EQ(nums_data[i], nums[i]);
}
auto str_data = str_buf.data_ptr + 8;
for (size_t i = 0; i < countof(str); i++) {
EXPECT_EQ(str_data[i], str[i]);
}
}
TEST(FlatBufferBuilderTest, CanGrowBuffer) {
static const size_t kSmallInitialSize = 32;
static const char zeroes[1024] = {0};
{
// Create buffer with small initial size.
deno::FlatBufferBuilder builder(kSmallInitialSize);
// Write 1 byte and export buffer.
builder.Finish(builder.CreateVector(zeroes, 1));
auto buf = builder.ExportBuf();
// Exported buffer should have initial size.
EXPECT_EQ(buf.alloc_len, kSmallInitialSize);
}
{
// Create buffer with small initial size.
deno::FlatBufferBuilder builder(kSmallInitialSize);
// Write 1024 bytes and export buffer.
builder.Finish(builder.CreateVector(zeroes, countof(zeroes)));
auto buf = builder.ExportBuf();
// Exported buffer have grown.
EXPECT_GT(buf.alloc_len, kSmallInitialSize);
}
}

View file

@ -11,6 +11,7 @@
#endif
#include "deno.h"
#include "flatbuffer_builder.h"
#include "flatbuffers/flatbuffers.h"
#include "src/handlers.h"
#include "src/msg_generated.h"
@ -23,7 +24,7 @@ static int global_argc;
// Sends StartRes message
void HandleStart(Deno* d, uint32_t cmd_id) {
flatbuffers::FlatBufferBuilder builder;
deno::FlatBufferBuilder builder;
char cwdbuf[1024];
// TODO(piscisaureus): support unicode on windows.
@ -39,9 +40,7 @@ void HandleStart(Deno* d, uint32_t cmd_id) {
auto start_msg = CreateStartRes(builder, start_cwd, start_argv);
auto base = CreateBase(builder, cmd_id, 0, Any_StartRes, start_msg.Union());
builder.Finish(base);
deno_buf bufout{reinterpret_cast<const char*>(builder.GetBufferPointer()),
builder.GetSize()};
deno_set_response(d, bufout);
deno_set_response(d, builder.ExportBuf());
}
void HandleCodeFetch(Deno* d, uint32_t cmd_id, const CodeFetch* msg) {
@ -54,11 +53,10 @@ void HandleCodeFetch(Deno* d, uint32_t cmd_id, const CodeFetch* msg) {
}
void MessagesFromJS(Deno* d, deno_buf buf) {
auto data = reinterpret_cast<const uint8_t*>(buf.data);
flatbuffers::Verifier verifier(data, buf.len);
flatbuffers::Verifier verifier(buf.data_ptr, buf.data_len);
DCHECK(verifier.VerifyBuffer<Base>());
auto base = flatbuffers::GetRoot<Base>(buf.data);
auto base = flatbuffers::GetRoot<Base>(buf.data_ptr);
auto cmd_id = base->cmdId();
auto msg_type = base->msg_type();
const char* msg_type_name = EnumNamesAny()[msg_type];

View file

@ -9,8 +9,10 @@ use std::ptr;
#[repr(C)]
struct deno_buf {
data: *const c_char,
len: c_int, // TODO(ry) should be size_t.
alloc_ptr: *mut u8,
alloc_len: usize,
data_ptr: *mut u8,
data_len: usize,
}
#[repr(C)]
@ -120,9 +122,10 @@ fn main() {
let mut d = Deno::new();
d.execute("deno_main.js", "denoMain();")
.unwrap_or_else(|err| {
d.execute("deno_main.js", "denoMain();").unwrap_or_else(
|err| {
println!("Error {}\n", err);
std::process::exit(1);
});
},
);
}

View file

@ -23,7 +23,17 @@ TEST(MockRuntimeTest, ErrorsCorrectly) {
deno_delete(d);
}
deno_buf strbuf(const char* str) { return deno_buf{str, strlen(str)}; }
deno_buf strbuf(const char* str) {
auto len = strlen(str);
deno_buf buf;
buf.alloc_ptr = reinterpret_cast<uint8_t*>(strdup(str));
buf.alloc_len = len + 1;
buf.data_ptr = buf.alloc_ptr;
buf.data_len = len;
return buf;
}
TEST(MockRuntimeTest, SendSuccess) {
Deno* d = deno_new(nullptr, nullptr);
@ -51,10 +61,10 @@ TEST(MockRuntimeTest, RecvReturnEmpty) {
static int count = 0;
Deno* d = deno_new(nullptr, [](auto _, auto buf) {
count++;
EXPECT_EQ(static_cast<size_t>(3), buf.len);
EXPECT_EQ(buf.data[0], 'a');
EXPECT_EQ(buf.data[1], 'b');
EXPECT_EQ(buf.data[2], 'c');
EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
EXPECT_EQ(buf.data_ptr[0], 'a');
EXPECT_EQ(buf.data_ptr[1], 'b');
EXPECT_EQ(buf.data_ptr[2], 'c');
});
EXPECT_TRUE(deno_execute(d, "a.js", "RecvReturnEmpty()"));
EXPECT_EQ(count, 2);
@ -65,10 +75,10 @@ TEST(MockRuntimeTest, RecvReturnBar) {
static int count = 0;
Deno* d = deno_new(nullptr, [](auto deno, auto buf) {
count++;
EXPECT_EQ(static_cast<size_t>(3), buf.len);
EXPECT_EQ(buf.data[0], 'a');
EXPECT_EQ(buf.data[1], 'b');
EXPECT_EQ(buf.data[2], 'c');
EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
EXPECT_EQ(buf.data_ptr[0], 'a');
EXPECT_EQ(buf.data_ptr[1], 'b');
EXPECT_EQ(buf.data_ptr[2], 'c');
deno_set_response(deno, strbuf("bar"));
});
EXPECT_TRUE(deno_execute(d, "a.js", "RecvReturnBar()"));
@ -82,6 +92,65 @@ TEST(MockRuntimeTest, DoubleRecvFails) {
deno_delete(d);
}
TEST(MockRuntimeTest, SendRecvSlice) {
static int count = 0;
Deno* d = deno_new(nullptr, [](auto deno, auto buf) {
static const size_t alloc_len = 1024;
size_t i = count++;
// Check the size and offset of the slice.
size_t data_offset = buf.data_ptr - buf.alloc_ptr;
EXPECT_EQ(data_offset, i * 11);
EXPECT_EQ(buf.data_len, alloc_len - i * 30);
EXPECT_EQ(buf.alloc_len, alloc_len);
// Check values written by the JS side.
EXPECT_EQ(buf.data_ptr[0], 100 + i);
EXPECT_EQ(buf.data_ptr[buf.data_len - 1], 100 - i);
// Make copy of the backing buffer -- this is currently necessary because
// deno_set_response() takes ownership over the buffer, but we are not given
// ownership of `buf` by our caller.
uint8_t* alloc_ptr = reinterpret_cast<uint8_t*>(malloc(alloc_len));
memcpy(alloc_ptr, buf.alloc_ptr, alloc_len);
// Make a slice that is a bit shorter than the original.
deno_buf buf2{alloc_ptr, alloc_len, alloc_ptr + data_offset,
buf.data_len - 19};
// Place some values into the buffer for the JS side to verify.
buf2.data_ptr[0] = 200 + i;
buf2.data_ptr[buf2.data_len - 1] = 200 - i;
// Send back.
deno_set_response(deno, buf2);
});
EXPECT_TRUE(deno_execute(d, "a.js", "SendRecvSlice()"));
EXPECT_EQ(count, 5);
deno_delete(d);
}
TEST(MockRuntimeTest, JSSendArrayBufferViewTypes) {
static int count = 0;
Deno* d = deno_new(nullptr, [](auto _, auto buf) {
count++;
size_t data_offset = buf.data_ptr - buf.alloc_ptr;
EXPECT_EQ(data_offset, 2468);
EXPECT_EQ(buf.data_len, 1000);
EXPECT_EQ(buf.alloc_len, 4321);
EXPECT_EQ(buf.data_ptr[0], count);
});
EXPECT_TRUE(deno_execute(d, "a.js", "JSSendArrayBufferViewTypes()"));
EXPECT_EQ(count, 3);
deno_delete(d);
}
TEST(MockRuntimeTest, JSSendNeutersBuffer) {
static int count = 0;
Deno* d = deno_new(nullptr, [](auto _, auto buf) {
count++;
EXPECT_EQ(buf.data_len, 1);
EXPECT_EQ(buf.data_ptr[0], 42);
});
EXPECT_TRUE(deno_execute(d, "a.js", "JSSendNeutersBuffer()"));
EXPECT_EQ(count, 1);
deno_delete(d);
}
TEST(MockRuntimeTest, TypedArraySnapshots) {
Deno* d = deno_new(nullptr, nullptr);
EXPECT_TRUE(deno_execute(d, "a.js", "TypedArraySnapshots()"));
@ -98,8 +167,8 @@ TEST(MockRuntimeTest, ErrorHandling) {
static int count = 0;
Deno* d = deno_new(nullptr, [](auto deno, auto buf) {
count++;
EXPECT_EQ(static_cast<size_t>(1), buf.len);
EXPECT_EQ(buf.data[0], 42);
EXPECT_EQ(static_cast<size_t>(1), buf.data_len);
EXPECT_EQ(buf.data_ptr[0], 42);
});
EXPECT_FALSE(deno_execute(d, "a.js", "ErrorHandling()"));
EXPECT_EQ(count, 1);