diff --git a/BUILD.gn b/BUILD.gn index 163df26075..488a983001 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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" ] } diff --git a/js/deno.d.ts b/js/deno.d.ts index f554135bc5..a54dab851a 100644 --- a/js/deno.d.ts +++ b/js/deno.d.ts @@ -1,10 +1,10 @@ // Copyright 2018 Ryan Dahl // 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; } diff --git a/js/main.ts b/js/main.ts index 027baa3cf0..379b69c97b 100644 --- a/js/main.ts +++ b/js/main.ts @@ -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; -} diff --git a/js/mock_runtime.js b/js/mock_runtime.js index 66a34657ea..934fbeab4d 100644 --- a/js/mock_runtime.js +++ b/js/mock_runtime.js @@ -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"); }; diff --git a/src/binding.cc b/src/binding.cc index 50b029b900..2c4b6f7227 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -139,6 +139,34 @@ void Print(const v8::FunctionCallbackInfo& args) { fflush(stdout); } +static v8::Local ImportBuf(v8::Isolate* isolate, deno_buf buf) { + auto ab = v8::ArrayBuffer::New( + isolate, reinterpret_cast(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 view) { + auto ab = view->Buffer(); + auto contents = ab->Externalize(); + + deno_buf buf; + buf.alloc_ptr = reinterpret_cast(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& args) { v8::Isolate* isolate = args.GetIsolate(); @@ -169,20 +197,21 @@ void Send(const v8::FunctionCallbackInfo& args) { CHECK_EQ(args.Length(), 1); v8::Local ab_v = args[0]; - CHECK(ab_v->IsArrayBuffer()); - auto ab = v8::Local::Cast(ab_v); - auto contents = ab->GetContents(); - - // data is only a valid pointer until the end of this call. - const char* data = - const_cast(reinterpret_cast(contents.Data())); - deno_buf buf{data, contents.ByteLength()}; + CHECK(ab_v->IsArrayBufferView()); + auto buf = ExportBuf(isolate, v8::Local::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 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); } diff --git a/src/deno.h b/src/deno.h index fd2b4766ad..ab214ab031 100644 --- a/src/deno.h +++ b/src/deno.h @@ -3,6 +3,7 @@ #ifndef DENO_H_ #define DENO_H_ #include +#include // 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); diff --git a/src/flatbuffer_builder.cc b/src/flatbuffer_builder.cc new file mode 100644 index 0000000000..8a7a0c6495 --- /dev/null +++ b/src/flatbuffer_builder.cc @@ -0,0 +1,78 @@ +// Copyright 2018 Bert Belder +// All rights reserved. MIT License. + +#include +#include +#include + +#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(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 diff --git a/src/flatbuffer_builder.h b/src/flatbuffer_builder.h new file mode 100644 index 0000000000..1bc973afec --- /dev/null +++ b/src/flatbuffer_builder.h @@ -0,0 +1,64 @@ +// Copyright 2018 Bert Belder +// All rights reserved. MIT License. +#ifndef FLATBUFFER_BUILDER_H_ +#define FLATBUFFER_BUILDER_H_ + +#include +#include +#include + +#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_ diff --git a/src/flatbuffer_builder_test.cc b/src/flatbuffer_builder_test.cc new file mode 100644 index 0000000000..cfa12f0a07 --- /dev/null +++ b/src/flatbuffer_builder_test.cc @@ -0,0 +1,78 @@ +// Copyright 2018 Bert Belder +// All rights reserved. MIT License. + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +#include "deno.h" +#include "flatbuffer_builder.h" + +template +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(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); + } +} diff --git a/src/main.cc b/src/main.cc index a801bc2921..81b8a27103 100644 --- a/src/main.cc +++ b/src/main.cc @@ -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(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(buf.data); - flatbuffers::Verifier verifier(data, buf.len); + flatbuffers::Verifier verifier(buf.data_ptr, buf.data_len); DCHECK(verifier.VerifyBuffer()); - auto base = flatbuffers::GetRoot(buf.data); + auto base = flatbuffers::GetRoot(buf.data_ptr); auto cmd_id = base->cmdId(); auto msg_type = base->msg_type(); const char* msg_type_name = EnumNamesAny()[msg_type]; diff --git a/src/main.rs b/src/main.rs index ece311a4f6..2755a3a9c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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); - }); + }, + ); } diff --git a/src/mock_runtime_test.cc b/src/mock_runtime_test.cc index 67b097e8d3..19b2249501 100644 --- a/src/mock_runtime_test.cc +++ b/src/mock_runtime_test.cc @@ -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(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(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(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(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(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(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(1), buf.len); - EXPECT_EQ(buf.data[0], 42); + EXPECT_EQ(static_cast(1), buf.data_len); + EXPECT_EQ(buf.data_ptr[0], 42); }); EXPECT_FALSE(deno_execute(d, "a.js", "ErrorHandling()")); EXPECT_EQ(count, 1);