summaryrefslogtreecommitdiff
path: root/libdeno
diff options
context:
space:
mode:
Diffstat (limited to 'libdeno')
-rw-r--r--libdeno/binding.cc93
-rw-r--r--libdeno/deno.h42
-rw-r--r--libdeno/internal.h2
-rw-r--r--libdeno/libdeno_test.cc134
-rw-r--r--libdeno/libdeno_test.js37
5 files changed, 185 insertions, 123 deletions
diff --git a/libdeno/binding.cc b/libdeno/binding.cc
index b3c59c3ec..a790573a7 100644
--- a/libdeno/binding.cc
+++ b/libdeno/binding.cc
@@ -25,6 +25,36 @@ Deno* FromIsolate(v8::Isolate* isolate) {
return static_cast<Deno*>(isolate->GetData(0));
}
+void LazilyCreateDataMap(Deno* d) {
+ if (d->async_data_map.IsEmpty()) {
+ v8::HandleScope handle_scope(d->isolate);
+ // It's important for security reasons that async_data_map is not exposed to
+ // the VM.
+ auto async_data_map = v8::Map::New(d->isolate);
+ d->async_data_map.Reset(d->isolate, async_data_map);
+ }
+ DCHECK(!d->async_data_map.IsEmpty());
+}
+
+void AddDataRef(Deno* d, int32_t req_id, v8::Local<v8::Value> data_v) {
+ LazilyCreateDataMap(d);
+ auto async_data_map = d->async_data_map.Get(d->isolate);
+ auto context = d->context.Get(d->isolate);
+ auto req_id_v = v8::Integer::New(d->isolate, req_id);
+ auto r = async_data_map->Set(context, req_id_v, data_v);
+ CHECK(!r.IsEmpty());
+}
+
+void DeleteDataRef(Deno* d, int32_t req_id) {
+ LazilyCreateDataMap(d);
+ auto context = d->context.Get(d->isolate);
+ // Delete persistent reference to data ArrayBuffer.
+ auto async_data_map = d->async_data_map.Get(d->isolate);
+ auto req_id_v = v8::Integer::New(d->isolate, req_id);
+ auto maybe_deleted = async_data_map->Delete(context, req_id_v);
+ DCHECK(maybe_deleted.IsJust());
+}
+
// Extracts a C string from a v8::V8 Utf8Value.
const char* ToCString(const v8::String::Utf8Value& value) {
return *value ? *value : "<string conversion failed>";
@@ -214,17 +244,40 @@ void Send(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Locker locker(d->isolate);
v8::EscapableHandleScope handle_scope(isolate);
- CHECK_EQ(args.Length(), 1);
- v8::Local<v8::Value> ab_v = args[0];
- CHECK(ab_v->IsArrayBufferView());
- auto buf = GetContents(isolate, v8::Local<v8::ArrayBufferView>::Cast(ab_v));
+ CHECK_EQ(d->currentArgs, nullptr); // libdeno.send re-entry forbidden.
+ int32_t req_id = d->next_req_id++;
+
+ v8::Local<v8::Value> control_v = args[0];
+ CHECK(control_v->IsArrayBufferView());
+ deno_buf control =
+ GetContents(isolate, v8::Local<v8::ArrayBufferView>::Cast(control_v));
+ deno_buf data = {nullptr, 0u, nullptr, 0u};
+ v8::Local<v8::Value> data_v;
+ if (args.Length() == 2) {
+ if (args[1]->IsArrayBufferView()) {
+ data_v = args[1];
+ data = GetContents(isolate, v8::Local<v8::ArrayBufferView>::Cast(data_v));
+ }
+ } else {
+ CHECK_EQ(args.Length(), 1);
+ }
DCHECK_EQ(d->currentArgs, nullptr);
d->currentArgs = &args;
- d->cb(d, buf);
+ d->cb(d, req_id, control, data);
- d->currentArgs = nullptr;
+ if (d->currentArgs == nullptr) {
+ // This indicates that deno_repond() was called already.
+ } else {
+ // Asynchronous.
+ d->currentArgs = nullptr;
+ // If the data ArrayBuffer was given, we must maintain a strong reference
+ // to it until deno_respond is called.
+ if (!data_v.IsEmpty()) {
+ AddDataRef(d, req_id, data_v);
+ }
+ }
}
// Sets the global error handler.
@@ -358,6 +411,7 @@ void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context,
}
void AddIsolate(Deno* d, v8::Isolate* isolate) {
+ d->next_req_id = 0;
d->isolate = isolate;
// Leaving this code here because it will probably be useful later on, but
// disabling it now as I haven't got tests for the desired behavior.
@@ -400,7 +454,17 @@ int deno_execute(Deno* d, const char* js_filename, const char* js_source) {
return deno::Execute(context, js_filename, js_source) ? 1 : 0;
}
-int deno_send(Deno* d, deno_buf buf) {
+int deno_respond(Deno* d, int32_t req_id, deno_buf buf) {
+ if (d->currentArgs != nullptr) {
+ // Synchronous response.
+ auto ab = deno::ImportBuf(d->isolate, buf);
+ d->currentArgs->GetReturnValue().Set(ab);
+ d->currentArgs = nullptr;
+ return 0;
+ }
+
+ // Asynchronous response.
+
v8::Locker locker(d->isolate);
v8::Isolate::Scope isolate_scope(d->isolate);
v8::HandleScope handle_scope(d->isolate);
@@ -410,10 +474,12 @@ int deno_send(Deno* d, deno_buf buf) {
v8::TryCatch try_catch(d->isolate);
+ deno::DeleteDataRef(d, req_id);
+
auto recv = d->recv.Get(d->isolate);
if (recv.IsEmpty()) {
d->last_exception = "libdeno.recv has not been called.";
- return 0;
+ return 1;
}
v8::Local<v8::Value> args[1];
@@ -422,17 +488,10 @@ int deno_send(Deno* d, deno_buf buf) {
if (try_catch.HasCaught()) {
deno::HandleException(context, try_catch.Exception());
- return 0;
+ return 1;
}
- return 1;
-}
-
-void deno_set_response(Deno* d, deno_buf buf) {
- // printf("deno_set_response: ");
- // hexdump(buf.data_ptr, buf.data_len);
- auto ab = deno::ImportBuf(d->isolate, buf);
- d->currentArgs->GetReturnValue().Set(ab);
+ return 0;
}
void deno_delete(Deno* d) {
diff --git a/libdeno/deno.h b/libdeno/deno.h
index 90edca3d9..b0248b7ad 100644
--- a/libdeno/deno.h
+++ b/libdeno/deno.h
@@ -20,9 +20,11 @@ typedef struct {
struct deno_s;
typedef struct deno_s Deno;
-// A callback to receive a message from deno.send javascript call.
-// buf is valid only for the lifetime of the call.
-typedef void (*deno_recv_cb)(Deno* d, deno_buf buf);
+// A callback to receive a message from a libdeno.send() javascript call.
+// control_buf is valid for only for the lifetime of this callback.
+// data_buf is valid until deno_respond() is called.
+typedef void (*deno_recv_cb)(Deno* d, int32_t req_id, deno_buf control_buf,
+ deno_buf data_buf);
void deno_init();
const char* deno_v8_version();
@@ -39,21 +41,25 @@ void* deno_get_data(Deno*);
// 0 = fail, 1 = success
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);
+// deno_respond sends up to one message back for every deno_recv_cb made.
+//
+// If this is called during deno_recv_cb, the issuing libdeno.send() in
+// javascript will synchronously return the specified buf as an ArrayBuffer (or
+// null if buf is empty).
+//
+// If this is called after deno_recv_cb has returned, the deno_respond
+// will call into the JS callback specified by libdeno.recv().
+//
+// (Ideally, but not currently: After calling deno_respond(), the caller no
+// longer owns `buf` and must not use it; deno_respond() is responsible for
+// releasing its memory.)
+//
+// Calling this function more than once with the same req_id will result in
+// an error.
+//
+// A non-zero return value, means a JS exception was encountered during the
+// libdeno.recv() callback. Check deno_last_exception() for exception text.
+int deno_respond(Deno* d, int32_t req_id, deno_buf buf);
const char* deno_last_exception(Deno* d);
diff --git a/libdeno/internal.h b/libdeno/internal.h
index 93fdea5db..dd377b36c 100644
--- a/libdeno/internal.h
+++ b/libdeno/internal.h
@@ -15,7 +15,9 @@ struct deno_s {
v8::Persistent<v8::Function> recv;
v8::Persistent<v8::Function> global_error_handler;
v8::Persistent<v8::Context> context;
+ v8::Persistent<v8::Map> async_data_map;
deno_recv_cb cb;
+ int32_t next_req_id;
void* data;
};
}
diff --git a/libdeno/libdeno_test.cc b/libdeno/libdeno_test.cc
index c8b0adfd0..5177ab4a8 100644
--- a/libdeno/libdeno_test.cc
+++ b/libdeno/libdeno_test.cc
@@ -45,34 +45,17 @@ deno_buf StrBufNullAllocPtr(const char* str) {
return buf;
}
-TEST(LibDenoTest, SendSuccess) {
- Deno* d = deno_new(nullptr, nullptr);
- EXPECT_TRUE(deno_execute(d, "a.js", "SendSuccess()"));
- EXPECT_TRUE(deno_send(d, strbuf("abc")));
- deno_delete(d);
-}
-
-TEST(LibDenoTest, SendWrongByteLength) {
- Deno* d = deno_new(nullptr, nullptr);
- EXPECT_TRUE(deno_execute(d, "a.js", "SendWrongByteLength()"));
- // deno_send the wrong sized message, it should throw.
- EXPECT_FALSE(deno_send(d, strbuf("abcd")));
- std::string exception = deno_last_exception(d);
- EXPECT_GT(exception.length(), 1u);
- EXPECT_NE(exception.find("assert"), std::string::npos);
- deno_delete(d);
-}
-
-TEST(LibDenoTest, SendNoCallback) {
- Deno* d = deno_new(nullptr, nullptr);
- // We didn't call deno.recv() in JS, should fail.
- EXPECT_FALSE(deno_send(d, strbuf("abc")));
- deno_delete(d);
+void assert_null(deno_buf b) {
+ EXPECT_EQ(b.alloc_ptr, nullptr);
+ EXPECT_EQ(b.alloc_len, 0u);
+ EXPECT_EQ(b.data_ptr, nullptr);
+ EXPECT_EQ(b.data_len, 0u);
}
TEST(LibDenoTest, RecvReturnEmpty) {
static int count = 0;
- Deno* d = deno_new(nullptr, [](auto _, auto buf) {
+ Deno* d = deno_new(nullptr, [](auto _, int req_id, auto buf, auto data_buf) {
+ assert_null(data_buf);
count++;
EXPECT_EQ(static_cast<size_t>(3), buf.data_len);
EXPECT_EQ(buf.data_ptr[0], 'a');
@@ -86,14 +69,16 @@ TEST(LibDenoTest, RecvReturnEmpty) {
TEST(LibDenoTest, RecvReturnBar) {
static int count = 0;
- Deno* d = deno_new(nullptr, [](auto deno, auto buf) {
- count++;
- 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"));
- });
+ Deno* d =
+ deno_new(nullptr, [](auto deno, int req_id, auto buf, auto data_buf) {
+ assert_null(data_buf);
+ count++;
+ 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_respond(deno, req_id, strbuf("bar"));
+ });
EXPECT_TRUE(deno_execute(d, "a.js", "RecvReturnBar()"));
EXPECT_EQ(count, 1);
deno_delete(d);
@@ -107,31 +92,33 @@ TEST(LibDenoTest, DoubleRecvFails) {
TEST(LibDenoTest, 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);
- });
+ Deno* d =
+ deno_new(nullptr, [](auto deno, int req_id, auto buf, auto data_buf) {
+ assert_null(data_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_respond() 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_respond(deno, req_id, buf2);
+ });
EXPECT_TRUE(deno_execute(d, "a.js", "SendRecvSlice()"));
EXPECT_EQ(count, 5);
deno_delete(d);
@@ -139,7 +126,8 @@ TEST(LibDenoTest, SendRecvSlice) {
TEST(LibDenoTest, JSSendArrayBufferViewTypes) {
static int count = 0;
- Deno* d = deno_new(nullptr, [](auto _, auto buf) {
+ Deno* d = deno_new(nullptr, [](auto _, int req_id, auto buf, auto data_buf) {
+ assert_null(data_buf);
count++;
size_t data_offset = buf.data_ptr - buf.alloc_ptr;
EXPECT_EQ(data_offset, 2468u);
@@ -166,7 +154,8 @@ TEST(LibDenoTest, SnapshotBug) {
TEST(LibDenoTest, GlobalErrorHandling) {
static int count = 0;
- Deno* d = deno_new(nullptr, [](auto _, auto buf) {
+ Deno* d = deno_new(nullptr, [](auto _, int req_id, auto buf, auto data_buf) {
+ assert_null(data_buf);
count++;
EXPECT_EQ(static_cast<size_t>(1), buf.data_len);
EXPECT_EQ(buf.data_ptr[0], 42);
@@ -182,14 +171,25 @@ TEST(LibDenoTest, DoubleGlobalErrorHandlingFails) {
deno_delete(d);
}
-TEST(LibDenoTest, SendNullAllocPtr) {
+TEST(LibDenoTest, DataBuf) {
static int count = 0;
- Deno* d = deno_new(nullptr, [](auto _, auto buf) { count++; });
- EXPECT_TRUE(deno_execute(d, "a.js", "SendNullAllocPtr()"));
- deno_buf buf = StrBufNullAllocPtr("abcd");
- EXPECT_EQ(buf.alloc_ptr, nullptr);
- EXPECT_EQ(buf.data_len, 4u);
- EXPECT_TRUE(deno_send(d, buf));
- EXPECT_EQ(count, 0);
+ static deno_buf data_buf_copy;
+ Deno* d = deno_new(nullptr,
+ [](auto _, int req_id, deno_buf buf, deno_buf data_buf) {
+ count++;
+ data_buf.data_ptr[0] = 4;
+ data_buf.data_ptr[1] = 2;
+ data_buf_copy = data_buf;
+ EXPECT_EQ(2u, buf.data_len);
+ EXPECT_EQ(2u, data_buf.data_len);
+ EXPECT_EQ(buf.data_ptr[0], 1);
+ EXPECT_EQ(buf.data_ptr[1], 2);
+ });
+ EXPECT_TRUE(deno_execute(d, "a.js", "DataBuf()"));
+ EXPECT_EQ(count, 1);
+ // data_buf was subsequently changed in JS, let's check that our copy reflects
+ // that.
+ EXPECT_EQ(data_buf_copy.data_ptr[0], 9);
+ EXPECT_EQ(data_buf_copy.data_ptr[1], 8);
deno_delete(d);
}
diff --git a/libdeno/libdeno_test.js b/libdeno/libdeno_test.js
index e0d1d7252..1b5137f0a 100644
--- a/libdeno/libdeno_test.js
+++ b/libdeno/libdeno_test.js
@@ -25,18 +25,6 @@ global.TypedArraySnapshots = () => {
assert(snapshotted[3] === 7);
};
-global.SendSuccess = () => {
- libdeno.recv(msg => {
- libdeno.print("SendSuccess: ok");
- });
-};
-
-global.SendWrongByteLength = () => {
- libdeno.recv(msg => {
- assert(msg.byteLength === 3);
- });
-};
-
global.RecvReturnEmpty = () => {
const m1 = new Uint8Array("abc".split("").map(c => c.charCodeAt(0)));
const m2 = m1.slice();
@@ -128,13 +116,20 @@ global.DoubleGlobalErrorHandlingFails = () => {
libdeno.setGlobalErrorHandler((message, source, line, col, error) => {});
};
-global.SendNullAllocPtr = () => {
- libdeno.recv(msg => {
- assert(msg instanceof Uint8Array);
- assert(msg.byteLength === 4);
- assert(msg[0] === "a".charCodeAt(0));
- assert(msg[1] === "b".charCodeAt(0));
- assert(msg[2] === "c".charCodeAt(0));
- assert(msg[3] === "d".charCodeAt(0));
- });
+// Allocate this buf at the top level to avoid GC.
+const dataBuf = new Uint8Array([3, 4]);
+
+global.DataBuf = () => {
+ const a = new Uint8Array([1, 2]);
+ const b = dataBuf;
+ // The second parameter of send should modified by the
+ // privileged side.
+ const r = libdeno.send(a, b);
+ assert(r == null);
+ // b is different.
+ assert(b[0] === 4);
+ assert(b[1] === 2);
+ // Now we modify it again.
+ b[0] = 9;
+ b[1] = 8;
};