Skip to content

Commit

Permalink
Response on invalid context.
Browse files Browse the repository at this point in the history
  • Loading branch information
Gustaf Sjöberg committed Feb 24, 2016
1 parent ed0d3f4 commit b78e697
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 84 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ erl_crash.dump
/priv

/c_src/test
/c_src/include
/c_src/env.mk
/.erlang.mk
8 changes: 6 additions & 2 deletions c_src/erlang_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
void* data = AllocateUninitialized(length);
return data == NULL ? data : memset(data, 0, length);
}
virtual void* AllocateUninitialized(size_t length) { return malloc(length); }
virtual void Free(void* data, size_t) { free(data); }
virtual void* AllocateUninitialized(size_t length) {
return malloc(length);
}
virtual void Free(void* data, size_t) {
free(data);
}
};

size_t PacketLength() {
Expand Down
1 change: 1 addition & 0 deletions c_src/packet.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
const uint8_t OP_OK = 0;
const uint8_t OP_ERROR = 1;
const uint8_t OP_TIMEOUT = 2;
const uint8_t OP_INVALID_CONTEXT = 3;

const uint8_t OP_EVAL = 1;
const uint8_t OP_CALL = 2;
Expand Down
6 changes: 4 additions & 2 deletions c_src/report.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ void Report(Isolate* isolate, Local<Value> response, uint8_t op) {

Local<Value> input;

TRACE("REPORTING STUFF.");

if (response->IsUndefined()) {
TRACE("Undefined");
input = String::NewFromUtf8(isolate, "undefined");
} else if (response->IsString()) {
input = response;
} else {
input = JSONStringify(isolate, response);
}
Expand All @@ -48,6 +48,8 @@ void Report(Isolate* isolate, Local<Value> response, uint8_t op) {

cout << *utf8;
cout.flush();

TRACE("Flushed");
}

void ReportOK(Isolate* isolate, Local<Value> response) {
Expand Down
175 changes: 97 additions & 78 deletions c_src/vm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,15 @@ bool VM::CreateContext(uint32_t ref) {

bool VM::DestroyContext(uint32_t ref) {
Persistent<Context> pcontext (isolate, contexts[ref]);
pcontext.Reset();
contexts.erase(ref);
return true;
if (pcontext.IsEmpty()) {
Local<String> tt = String::NewFromUtf8(isolate, "empty contextz");
Report(isolate, tt, OP_INVALID_CONTEXT);
return false;
} else {
pcontext.Reset();
contexts.erase(ref);
return true;
}
}

Isolate* VM::GetIsolate() {
Expand All @@ -97,53 +103,60 @@ void VM::Eval(Packet* packet) {
HandleScope handle_scope(isolate);
TryCatch try_catch(isolate);

Local<Context> context = Local<Context>::New(isolate, contexts[packet->ref]);
Context::Scope context_scope(context);

string input = packet->data;

Local<String> json_data = String::NewFromUtf8(isolate, input.c_str());
Local<Object> instructions = JSON::Parse(json_data)->ToObject();

Local<String> source_key = String::NewFromUtf8(isolate, "source");
Local<String> source = instructions->Get(source_key)->ToString();

Local<Script> script = Script::Compile(source);
Local<Context> context = Local<Context>::New(isolate,
contexts[packet->ref]);

if (script.IsEmpty()) {
assert(try_catch.HasCaught());
ReportException(isolate, &try_catch);
if (context.IsEmpty()) {
Local<String> tt = String::NewFromUtf8(isolate, "empty contextz");
Report(isolate, tt, OP_INVALID_CONTEXT);
} else {
pthread_t t;
void *res;
struct TimeoutHandlerArgs args = {
this,
(long)1
};
Context::Scope context_scope(context);

pthread_create(&t, NULL, TimeoutHandler, &args);
string input = packet->data;

Local<Value> result = script->Run();
Local<String> json_data = String::NewFromUtf8(isolate, input.c_str());
Local<Object> instructions = JSON::Parse(json_data)->ToObject();

pthread_cancel(t);
pthread_join(t, &res);
Local<String> source_key = String::NewFromUtf8(isolate, "source");
Local<String> source = instructions->Get(source_key)->ToString();

// FTRACE("Join: %x\n", res);
Local<Script> script = Script::Compile(source);

if (result.IsEmpty()) {
if (script.IsEmpty()) {
assert(try_catch.HasCaught());
if (try_catch.Message().IsEmpty() && try_catch.StackTrace().IsEmpty()) {
TRACE("It's a timeout!\n");
Local<String> tt = String::NewFromUtf8(isolate, "timeout");
Report(isolate, tt, OP_TIMEOUT);
ReportException(isolate, &try_catch);
} else {
pthread_t t;
void *res;
struct TimeoutHandlerArgs args = {
this,
(long)1
};

pthread_create(&t, NULL, TimeoutHandler, &args);

Local<Value> result = script->Run();

pthread_cancel(t);
pthread_join(t, &res);

// FTRACE("Join: %x\n", res);

if (result.IsEmpty()) {
assert(try_catch.HasCaught());
if (try_catch.Message().IsEmpty() && try_catch.StackTrace().IsEmpty()) {
TRACE("It's a timeout!\n");
Local<String> tt = String::NewFromUtf8(isolate, "timeout");
Report(isolate, tt, OP_TIMEOUT);
} else {
TRACE("It's a regular error\n");
ReportException(isolate, &try_catch);
}
FTRACE("Replacing context: %i\n", packet->ref);
// vm.CreateContext(packet->ref);
} else {
TRACE("It's a regular error\n");
ReportException(isolate, &try_catch);
ReportOK(isolate, result);
}
FTRACE("Replacing context: %i\n", packet->ref);
// vm.CreateContext(packet->ref);
} else {
ReportOK(isolate, result);
}
}
}
Expand All @@ -154,55 +167,61 @@ void VM::Call(Packet* packet) {

string input = packet->data;

Local<Context> context = Local<Context>::New(isolate, contexts[packet->ref]);

Context::Scope context_scope(context);
Local<Context> context = Local<Context>::New(isolate,
contexts[packet->ref]);

Local<String> json_data = String::NewFromUtf8(isolate, input.c_str());
Local<Object> instructions = JSON::Parse(json_data)->ToObject();
if (context.IsEmpty()) {
Local<Value> tt = String::NewFromUtf8(isolate, "empty contextz");
Report(isolate, tt, OP_INVALID_CONTEXT);
} else {
Context::Scope context_scope(context);

Local<Object> global = context->Global();
Local<String> json_data = String::NewFromUtf8(isolate, input.c_str());
Local<Object> instructions = JSON::Parse(json_data)->ToObject();

Local<String> function_key = String::NewFromUtf8(isolate, "function");
Local<String> function_name = instructions->Get(function_key)->ToString();
Local<Object> global = context->Global();

Local<String> args_key = String::NewFromUtf8(isolate, "args");
Local<Value> args_value = instructions->Get(args_key);
Local<Array> raw_args = Local<Array>::Cast(args_value);
Local<String> function_key = String::NewFromUtf8(isolate, "function");
Local<String> function_name = instructions->Get(function_key)->ToString();

int len = raw_args->Length();
Local<Value> *args = new Local<Value>[len];
Local<String> args_key = String::NewFromUtf8(isolate, "args");
Local<Value> args_value = instructions->Get(args_key);
Local<Array> raw_args = Local<Array>::Cast(args_value);

for (int i = 0; i < len; i++) {
args[i] = raw_args->Get(i);
}
int len = raw_args->Length();
Local<Value> *args = new Local<Value>[len];

// we cannot simply retrieve the function from the global scope as the
// name can be something like `lol.flop['hi']`. wrapping the call in a
// temporary function is much simpler than attempting to split the name
// and check all the individual parts.
Local<String> prefix = String::NewFromUtf8(isolate,
"function __call() { return ");
Local<String> suffix = String::NewFromUtf8(isolate,
".apply(null, arguments); }");
Local<String> source = String::Concat(String::Concat(prefix,
function_name), suffix);
Local<Script> script = Script::Compile(source);
Local<Value> eval_result = script->Run();

if (eval_result.IsEmpty()) {
assert(try_catch.HasCaught());
ReportException(isolate, &try_catch);
} else {
Local<String> fn = String::NewFromUtf8(isolate, "__call");
Local<Function> function = Local<Function>::Cast(global->Get(fn));
Local<Value> result = function->Call(global, len, args);
for (int i = 0; i < len; i++) {
args[i] = raw_args->Get(i);
}

if (result.IsEmpty()) {
// we cannot simply retrieve the function from the global scope as the
// name can be something like `lol.flop['hi']`. wrapping the call in a
// temporary function is much simpler than attempting to split the name
// and check all the individual parts.
Local<String> prefix = String::NewFromUtf8(isolate,
"function __call() { return ");
Local<String> suffix = String::NewFromUtf8(isolate,
".apply(null, arguments); }");
Local<String> source = String::Concat(String::Concat(prefix,
function_name), suffix);
Local<Script> script = Script::Compile(source);
Local<Value> eval_result = script->Run();

if (eval_result.IsEmpty()) {
assert(try_catch.HasCaught());
ReportException(isolate, &try_catch);
} else {
ReportOK(isolate, result);
Local<String> fn = String::NewFromUtf8(isolate, "__call");
Local<Function> function = Local<Function>::Cast(global->Get(fn));
Local<Value> result = function->Call(global, len, args);

if (result.IsEmpty()) {
assert(try_catch.HasCaught());
ReportException(isolate, &try_catch);
} else {
ReportOK(isolate, result);
}
}
}
}
10 changes: 8 additions & 2 deletions src/erlang_v8_vm.erl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
-define(OP_OK, 0).
-define(OP_ERROR, 1).
-define(OP_TIMEOUT, 2).
-define(OP_INVALID_CONTEXT, 3).

-record(state, {
initial_source = [],
Expand Down Expand Up @@ -205,21 +206,26 @@ send_to_port(Port, Op, Ref, Source, Timeout, _MaxSourceSize) ->
receive_port_data(Port, Timeout).

receive_port_data(Port, _Timeout) ->
io:format(standard_error, "Waiting~n", []),
receive
{Port, {data, <<_:8, _:32, "">>}} ->
{ok, undefined};
{Port, {data, <<?OP_OK:8, _:32, "undefined">>}} ->
{ok, undefined};
{Port, {data, <<?OP_OK:8, _:32, Response/binary>>}} ->
case catch jsx:decode(Response, [return_maps]) of
{'EXIT', _F} ->
{ok, undefined};
{ok, Response};
R ->
{ok, R}
end;
{Port, {data, <<?OP_ERROR:8, _:32, Response/binary>>}} ->
#{ <<"error">> := Reason } = jsx:decode(Response, [return_maps]),
{call_error, Reason};
{Port, {data, <<?OP_TIMEOUT:8, _:32, _Response/binary>>}} ->
{Port, {data, <<?OP_TIMEOUT:8, _:32, _/binary>>}} ->
{call_error, timeout};
{Port, {data, <<?OP_INVALID_CONTEXT:8, _:32, _/binary>>}} ->
{call_error, invalid_context};
{Port, Error} ->
%% TODO: we should probably special case here.
{error, Error}
Expand Down
5 changes: 5 additions & 0 deletions test/port_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ contexts(_Config) ->
{ok, <<"no">>} = erlang_v8:eval(P, Context1, <<"erlang_v8">>),
{ok, <<"yes">>} = erlang_v8:eval(P, Context2, <<"erlang_v8">>),

ok = erlang_v8_vm:destroy_context(P, Context1),
ok = erlang_v8_vm:destroy_context(P, Context2),

{error, invalid_context} = erlang_v8:eval(P, Context1, <<"erlang_v8">>),

erlang_v8:stop_vm(P),

ok.
Expand Down

0 comments on commit b78e697

Please sign in to comment.