├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README ├── c_src ├── erlv8.cc ├── erlv8.hh ├── erlv8_call.cc ├── erlv8_delete.cc ├── erlv8_equals.cc ├── erlv8_extern.cc ├── erlv8_gc.cc ├── erlv8_get.cc ├── erlv8_instantiate.cc ├── erlv8_internal.cc ├── erlv8_list.cc ├── erlv8_proplist.cc ├── erlv8_result.cc ├── erlv8_script.cc ├── erlv8_set.cc ├── erlv8_stop.cc ├── erlv8_string.cc ├── erlv8_taint.cc ├── erlv8_term.cc └── erlv8_unknown.cc ├── include └── erlv8.hrl ├── rebar ├── rebar.config ├── src ├── erlv8.app.src ├── erlv8.erl ├── erlv8_app.erl ├── erlv8_array.erl ├── erlv8_context.erl ├── erlv8_extern.erl ├── erlv8_fun.erl ├── erlv8_fun_invocation.erl ├── erlv8_nif.erl ├── erlv8_object.erl ├── erlv8_sup.erl ├── erlv8_utils.erl ├── erlv8_value.erl ├── erlv8_vm.erl ├── gen_server2.erl ├── priority_queue.erl └── supervisor2.erl └── test └── erlv8_tests.erl /.gitignore: -------------------------------------------------------------------------------- 1 | ebin/*.app 2 | ebin/*.beam 3 | priv/*.so 4 | c_src/*.o 5 | .eunit 6 | deps/proper 7 | deps/proper_stdlib 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/v8"] 2 | path = deps/v8 3 | url = git://github.com/v8/v8.git 4 | branch = trunk 5 | [submodule "deps/zeromq2"] 6 | path = deps/zeromq2 7 | url = git://github.com/zeromq/zeromq2-x.git 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Yurii Rashkovskii. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | X64=$(shell file -L `which epmd` | grep x86_64 | wc -l | xargs echo) 2 | X64L=$(shell file -L `which epmd` | grep x86-64 | wc -l | xargs echo) 3 | OSX=$(shell uname | grep Darwin | wc -l | xargs echo) 4 | LINUX=$(shell uname | grep Linux | wc -l | xargs echo) 5 | 6 | V8ENV=GYPFLAGS="-f make" 7 | ifeq ($(X64),1) 8 | V8FLAGS=arch=x64 9 | else 10 | V8FLAGS= 11 | endif 12 | 13 | ifeq ($(X64L),1) 14 | V8FLAGS=arch=x64 15 | V8ENV=CCFLAGS=-fPIC 16 | endif 17 | 18 | ifeq ($(LINUX),1) 19 | ZMQ_FLAGS=--with-pic 20 | else 21 | ZMQ_FLAGS= 22 | endif 23 | 24 | 25 | all: compile 26 | 27 | sh: 28 | @erl -pa ebin/ deps/*/ebin/ -s reloader -eval "d:err()" 29 | 30 | 31 | deps/v8/.git/config: 32 | @git submodule init 33 | @git submodule update 34 | 35 | deps/zeromq2/.git/HEAD: 36 | @git submodule init 37 | @git submodule update 38 | 39 | deps/v8/libv8.a: deps/v8/.git/config 40 | cd deps/v8 && $(V8ENV) scons $(V8FLAGS) 41 | 42 | deps/zeromq2/src/.libs/libzmq.a: deps/zeromq2/.git/HEAD 43 | @cd deps/zeromq2 && ./autogen.sh && ./configure $(ZMQ_FLAGS) && make 44 | 45 | dependencies: deps/v8/libv8.a deps/zeromq2/src/.libs/libzmq.a 46 | @./rebar get-deps 47 | 48 | test: compile 49 | @./rebar eunit skip_deps=true 50 | 51 | dbg-test: compile 52 | @USE_GDB=true ./rebar eunit skip_deps=true 53 | 54 | compile: dependencies fast 55 | 56 | fast: 57 | @EXTRA_CFLAGS= ./rebar compile 58 | 59 | debug: dependencies 60 | @EXTRA_CFLAGS="-g3 -O0 -DERLV8_DEBUG" ./rebar compile 61 | 62 | clean: 63 | -rm c_src/*.o 64 | 65 | analyze: 66 | clang --analyze -Xanalyzer "-Ideps/v8/include/" -Xanalyzer "-I/usr/local//Cellar/erlang/R15B/lib/erlang/usr/include" -Xanalyzer "-Ideps/zeromq2/include/" c_src/*.cc 67 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Quick Instructions 2 | ================== 3 | 4 | In order to build erlv8, make sure you have g++, erlang & scons installed system-wide. Then just type: 5 | 6 | $ make 7 | 8 | After a couple of minutes (V8 takes quite some time to build), it should be over. 9 | 10 | Good luck! 11 | -------------------------------------------------------------------------------- /c_src/erlv8.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | typedef TickHandlerResolution (*TickHandler)(VM *, char *, ERL_NIF_TERM, ERL_NIF_TERM, ERL_NIF_TERM, int, const ERL_NIF_TERM*); 4 | 5 | struct ErlV8TickHandler { 6 | const char * name; 7 | TickHandler handler; 8 | }; 9 | 10 | static ErlV8TickHandler tick_handlers[] = 11 | { 12 | {"stop", StopTickHandler}, 13 | {"result", ResultTickHandler}, 14 | {"call", CallTickHandler}, 15 | {"inst", InstantiateTickHandler}, 16 | {"delete", DeleteTickHandler}, 17 | {"taint", TaintTickHandler}, 18 | {"equals", EqualsTickHandler}, 19 | {"strict_equals", StrictEqualsTickHandler}, 20 | {"get", GetTickHandler}, 21 | {"get_proto", GetProtoTickHandler}, 22 | {"get_hidden", GetHiddenTickHandler}, 23 | {"set", SetTickHandler}, 24 | {"set_proto", SetProtoTickHandler}, 25 | {"set_hidden", SetHiddenTickHandler}, 26 | {"set_accessor", SetAccessorTickHandler}, 27 | {"proplist", ProplistTickHandler}, 28 | {"list", ListTickHandler}, 29 | {"script", ScriptTickHandler}, 30 | {"gc", GCTickHandler}, 31 | {"to_string", ToStringTickHandler}, 32 | {"to_detail_string", ToDetailStringTickHandler}, 33 | {"extern_proto", ExternProtoTickHandler}, 34 | {"externalize", ExternalizeTickHandler}, 35 | {"internal_count", InternalCountTickHandler}, 36 | {"set_internal", SetInternalTickHandler}, 37 | {"set_internal_extern", SetInternalTickHandler}, 38 | {"get_internal", GetInternalTickHandler}, 39 | {NULL, UnknownTickHandler} 40 | }; 41 | 42 | 43 | VM::VM() { 44 | env = enif_alloc_env(); 45 | mutex = enif_mutex_create((char*)"erlv8_vm_mutex"); 46 | isolate = v8::Isolate::New(); 47 | v8::Isolate::Scope iscope(isolate); 48 | v8::Locker locker(isolate); 49 | v8::HandleScope handle_scope; 50 | 51 | // Moved into the VM object since we have a own isolate for each VM 52 | global_template = v8::Persistent::New(v8::ObjectTemplate::New()); 53 | external_template = v8::Persistent::New(v8::ObjectTemplate::New()); 54 | empty_constructor = v8::Persistent::New(v8::FunctionTemplate::New(EmptyFun)); 55 | string__erlv8__ = v8::Persistent::New(v8::String::New("__erlv8__")); 56 | 57 | context = v8::Context::New(NULL, global_template); 58 | v8::Context::Scope context_scope(context); 59 | tid = enif_thread_self(); 60 | 61 | context->Global()->SetHiddenValue(string__erlv8__,v8::External::New(this)); 62 | 63 | ctx_res_t *ptr = (ctx_res_t *)enif_alloc_resource(ctx_resource, sizeof(ctx_res_t)); 64 | ptr->ctx = v8::Persistent::New(context); 65 | ERL_NIF_TERM resource_term = enif_make_resource(env, ptr); 66 | enif_release_resource(ptr); 67 | 68 | context->Global()->SetHiddenValue(v8::String::New("__erlv8__ctx__"),term_to_external(resource_term)); 69 | 70 | v8::Local tmp = external_template->NewInstance(); 71 | external_proto_num = v8::Persistent::New(tmp); 72 | external_proto_atom = v8::Persistent::New(external_template->NewInstance()); 73 | external_proto_bin = v8::Persistent::New(external_template->NewInstance()); 74 | external_proto_ref = v8::Persistent::New(external_template->NewInstance()); 75 | external_proto_fun = v8::Persistent::New(external_template->NewInstance()); 76 | external_proto_port = v8::Persistent::New(external_template->NewInstance()); 77 | external_proto_pid = v8::Persistent::New(external_template->NewInstance()); 78 | external_proto_tuple = v8::Persistent::New(external_template->NewInstance()); 79 | external_proto_list = v8::Persistent::New(external_template->NewInstance()); 80 | 81 | push_socket = zmq_socket(zmq_context, ZMQ_PUSH); 82 | ticker_push_socket = zmq_socket(zmq_context, ZMQ_PUSH); 83 | pull_socket = zmq_socket(zmq_context, ZMQ_PULL); 84 | 85 | char socket_id[64]; 86 | sprintf(socket_id, "inproc://tick-publisher-%ld", (long int) this); 87 | 88 | char ticker_socket_id[64]; 89 | sprintf(ticker_socket_id, "inproc://tick-publisher-ticker-%ld", (long int) this); 90 | 91 | zmq_bind(push_socket, socket_id); 92 | zmq_bind(ticker_push_socket, ticker_socket_id); 93 | zmq_connect(pull_socket, socket_id); 94 | zmq_connect(pull_socket, ticker_socket_id); 95 | }; 96 | 97 | VM::~VM() { 98 | // v8::Isolate::Scope iscope(isolate); 99 | // v8::Locker locker(isolate); 100 | // v8::HandleScope handle_scope; 101 | isolate->Enter(); 102 | 103 | TRACE("(%p) VM::~VM - 1\n", isolate); 104 | enif_mutex_destroy(mutex); 105 | TRACE("(%p) VM::~VM - 2\n", isolate); 106 | TRACE("(%p) VM::~VM - 3\n", isolate); 107 | external_proto_bin.Dispose(); 108 | external_proto_bin.Clear(); 109 | TRACE("(%p) VM::~VM - 4\n", isolate); 110 | external_proto_ref.Dispose(); 111 | external_proto_ref.Clear(); 112 | external_proto_fun.Dispose(); 113 | external_proto_fun.Clear(); 114 | external_proto_port.Dispose(); 115 | external_proto_port.Clear(); 116 | external_proto_pid.Dispose(); 117 | external_proto_pid.Dispose(); 118 | external_proto_tuple.Clear(); 119 | external_proto_list.Dispose(); 120 | external_proto_list.Clear(); 121 | TRACE("(%p) VM::~VM - 4\n", isolate); 122 | global_template.Dispose(); 123 | global_template.Clear(); 124 | TRACE("(%p) VM::~VM - 5\n", isolate); 125 | external_template.Dispose(); 126 | external_template.Clear(); 127 | TRACE("(%p) VM::~VM - 6\n", isolate); 128 | empty_constructor.Dispose(); 129 | empty_constructor.Clear(); 130 | TRACE("(%p) VM::~VM - 7\n", isolate); 131 | string__erlv8__.Dispose(); 132 | string__erlv8__.Clear(); 133 | TRACE("(%p) VM::~VM - 8\n", isolate); 134 | external_proto_num.Dispose(); 135 | external_proto_num.Clear(); 136 | TRACE("(%p) VM::~VM - 9\n", isolate); 137 | external_proto_atom.Dispose(); 138 | external_proto_atom.Clear(); 139 | TRACE("(%p) VM::~VM - 10\n", isolate); 140 | enif_free_env(env); 141 | TRACE("(%p) VM::~VM - 11\n", isolate); 142 | context.Dispose(); 143 | context.Clear(); 144 | while (v8::Isolate::GetCurrent() == isolate) { 145 | isolate->Exit(); 146 | } 147 | // this should dispoe everything created in the isolate: 148 | // http://markmail.org/message/mcn27ibuijhgkehl 149 | isolate->Dispose(); 150 | 151 | zmq_close(push_socket); 152 | zmq_close(ticker_push_socket); 153 | zmq_close(pull_socket); 154 | }; 155 | 156 | void VM::run() { 157 | v8::Isolate::Scope iscope(isolate); 158 | v8::Locker locker(isolate); 159 | v8::HandleScope handle_scope; // the very top level handle scope 160 | ticker(0); 161 | }; 162 | 163 | void VM::terminate() { 164 | TRACE("(%p) VM::terminate - 1\n", isolate); 165 | v8::V8::TerminateExecution(isolate); 166 | } 167 | 168 | v8::Handle VM::ticker(ERL_NIF_TERM ref0) { 169 | TRACE("(%p) VM::ticker - 0\n", isolate); 170 | LHCS(isolate, context); 171 | isolate->Enter(); 172 | TRACE("(%p) VM::ticker - 1\n", isolate); 173 | char name[MAX_ATOM_LEN]; 174 | unsigned len; 175 | 176 | ErlNifEnv * ref_env = enif_alloc_env(); 177 | ERL_NIF_TERM ref; 178 | TRACE("(%p) VM::ticker - 2\n", isolate); 179 | if ((unsigned long) ref0 == 0) { 180 | ref = ref0; 181 | DEBUG(server, enif_make_atom(env, "current_ticker"), enif_make_atom(env, "top")); 182 | } else { 183 | ref = enif_make_copy(ref_env, ref0); 184 | DEBUG(server, enif_make_atom(env, "current_ticker"), enif_make_copy(env, ref)); 185 | } 186 | TRACE("(%p) VM::ticker - 3\n", isolate); 187 | 188 | zmq_msg_t msg; 189 | Tick tick_s; 190 | ERL_NIF_TERM tick, tick_ref; 191 | while (1) { 192 | { 193 | isolate->Exit(); 194 | TRACE("(%p) VM::ticker - 3.1\n", isolate); 195 | v8::Unlocker unlocker(isolate); 196 | TRACE("(%p) VM::ticker - 3.2\n", isolate); 197 | zmq_msg_init (&msg); 198 | TRACE("(%p) VM::ticker - 3.3\n", isolate); 199 | zmq_recv (pull_socket, &msg, 0); 200 | TRACE("(%p) VM::ticker - 3.4\n", isolate); 201 | memcpy(&tick_s, zmq_msg_data(&msg), sizeof(Tick)); 202 | TRACE("(%p) VM::ticker - 3.5\n", isolate); 203 | tick = enif_make_copy(env, tick_s.tick); 204 | TRACE("(%p) VM::ticker - 3.6\n", isolate); 205 | tick_ref = enif_make_copy(env, tick_s.ref); 206 | TRACE("(%p) VM::ticker - 3.7\n", isolate); 207 | enif_free_env(tick_s.env); 208 | TRACE("(%p) VM::ticker - 3.8\n", isolate); 209 | zmq_msg_close(&msg); 210 | TRACE("(%p) VM::ticker - 3.9\n", isolate); 211 | } 212 | isolate->Enter(); 213 | TRACE("(%p) VM::ticker - 4\n", isolate); 214 | DEBUG(server, 215 | enif_make_tuple2(env, 216 | enif_make_atom(env, "last_tick"), 217 | (unsigned long) ref == 0 ? enif_make_atom(env,"top") : enif_make_copy(env, ref)), 218 | enif_make_copy(env, tick)); 219 | 220 | if (enif_is_tuple(env, tick)) { // should be always true, just a sanity check 221 | TRACE("(%p) VM::ticker - 5\n", isolate); 222 | ERL_NIF_TERM *array; 223 | int arity; 224 | enif_get_tuple(env,tick,&arity,(const ERL_NIF_TERM **)&array); 225 | 226 | enif_get_atom_length(env, array[0], &len, ERL_NIF_LATIN1); 227 | enif_get_atom(env,array[0],(char *)&name,len + 1, ERL_NIF_LATIN1); 228 | 229 | // lookup the matrix 230 | unsigned int i = 0; 231 | bool stop_flag = false; 232 | TRACE("(%p) VM::ticker - 6 (%s)\n", isolate, name); 233 | while (!stop_flag) { 234 | if ((!tick_handlers[i].name) || 235 | (!strcmp(name,tick_handlers[i].name))) { // handler has been located 236 | TRACE("(%p) VM::ticker - 7\n", isolate); 237 | TickHandlerResolution resolution = (tick_handlers[i].handler(this, name, tick, tick_ref, ref, arity, array)); 238 | TRACE("(%p) VM::ticker - 8\n", isolate); 239 | 240 | switch (resolution.type) { 241 | case DONE: 242 | stop_flag = true; 243 | break; 244 | case NEXT: 245 | break; 246 | case RETURN: 247 | TRACE("(%p) VM::ticker - 9\n", isolate); 248 | enif_free_env(ref_env); 249 | TRACE("(%p) VM::ticker - 10\n", isolate); 250 | enif_clear_env(env); 251 | TRACE("(%p) VM::ticker - 11\n", isolate); 252 | zmq_msg_t tick_msg; 253 | int e; 254 | TRACE("(%p) VM::ticker - 12\n", isolate); 255 | while (!pop_ticks.empty()) { 256 | TRACE("(%p) VM::ticker - 12.1\n", isolate); 257 | Tick newtick = pop_ticks.front(); 258 | TRACE("(%p) VM::ticker - 12.2\n", isolate); 259 | pop_ticks.pop(); 260 | TRACE("(%p) VM::ticker - 12.3\n", isolate); 261 | zmq_msg_init_size(&tick_msg, sizeof(Tick)); 262 | TRACE("(%p) VM::ticker - 12.4\n", isolate); 263 | memcpy(zmq_msg_data(&tick_msg), &newtick, sizeof(Tick)); 264 | TRACE("(%p) VM::ticker - 12.5\n", isolate); 265 | do { 266 | e = zmq_send(ticker_push_socket, &tick_msg, ZMQ_NOBLOCK); 267 | TRACE("(%p) VM::ticker - 12.6\n", isolate); 268 | } while (e == EAGAIN); 269 | zmq_msg_close(&tick_msg); 270 | } 271 | TRACE("(%p) VM::ticker - 13\n", isolate); 272 | return handle_scope.Close(resolution.value); 273 | break; 274 | } 275 | } 276 | i++; 277 | } 278 | } 279 | enif_clear_env(env); 280 | } 281 | }; 282 | 283 | 284 | void * start_vm(void *data) { 285 | VM *vm = reinterpret_cast(data); 286 | vm->run(); 287 | enif_mutex_lock(vm->mutex); 288 | enif_mutex_unlock(vm->mutex); 289 | delete vm; 290 | return NULL; 291 | }; 292 | 293 | 294 | static ERL_NIF_TERM new_vm(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 295 | { 296 | ERL_NIF_TERM term; 297 | VM *vm = new VM(); 298 | 299 | vm_res_t *ptr = (vm_res_t *)enif_alloc_resource(vm_resource, sizeof(vm_res_t)); 300 | 301 | ptr->vm = vm; 302 | vm->resource = ptr; 303 | 304 | term = enif_make_resource(env, ptr); 305 | 306 | enif_release_resource(ptr); 307 | 308 | return term; 309 | }; 310 | 311 | static ERL_NIF_TERM set_server(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 312 | { 313 | vm_res_t *res; 314 | if (enif_get_resource(env,argv[0],vm_resource,(void **)(&res))) { 315 | res->vm->server = (ErlNifPid *) malloc(sizeof(ErlNifPid)); 316 | enif_get_local_pid(env, argv[1], res->vm->server); 317 | enif_thread_create((char *)"erlv8", &res->vm->tid, start_vm, res->vm, NULL); 318 | return enif_make_atom(env,"ok"); 319 | }; 320 | return enif_make_badarg(env); 321 | }; 322 | 323 | static ERL_NIF_TERM context(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) 324 | { 325 | vm_res_t *res; 326 | if (enif_get_resource(env,argv[0],vm_resource,(void **)(&res))) { 327 | LHCS(res->vm->isolate, res->vm->context); 328 | ctx_res_t *ptr = (ctx_res_t *)enif_alloc_resource(ctx_resource, sizeof(ctx_res_t)); 329 | ptr->ctx = v8::Persistent::New(v8::Context::GetCurrent()); 330 | 331 | ERL_NIF_TERM term = enif_make_resource(env, ptr); 332 | 333 | enif_release_resource(ptr); 334 | 335 | return term; 336 | }; 337 | return enif_make_badarg(env); 338 | }; 339 | 340 | 341 | 342 | static ERL_NIF_TERM kill(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { 343 | vm_res_t *res; 344 | TRACE("kill - 1\n"); 345 | if (enif_get_resource(env,argv[0],vm_resource,(void **)(&res))) { 346 | TRACE("kill - 2\n"); 347 | res->vm->terminate(); 348 | TRACE("kill - 3\n"); 349 | return enif_make_atom(env,"ok"); 350 | } else { 351 | TRACE("kill - 3\n"); 352 | return enif_make_badarg(env); 353 | } 354 | } 355 | 356 | static ERL_NIF_TERM stop(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { 357 | vm_res_t *res; 358 | int e; 359 | if (enif_get_resource(env,argv[0],vm_resource,(void **)(&res))) { 360 | if ((!enif_is_ref(env, argv[1]))) 361 | return enif_make_badarg(env); 362 | TRACE("(%p) stop\n", res->vm->isolate); 363 | zmq_msg_t tick_msg; 364 | 365 | Tick tick; 366 | tick.env = enif_alloc_env(); 367 | tick.tick = enif_make_tuple1(tick.env, enif_make_atom(tick.env, "stop")); 368 | tick.ref = enif_make_copy(tick.env, argv[1]); 369 | 370 | 371 | zmq_msg_init_size(&tick_msg, sizeof(Tick)); 372 | 373 | memcpy(zmq_msg_data(&tick_msg), &tick, sizeof(Tick)); 374 | 375 | enif_mutex_lock(res->vm->mutex); 376 | do { 377 | e = zmq_send(res->vm->push_socket, &tick_msg, ZMQ_NOBLOCK); 378 | } while (e == EAGAIN); 379 | 380 | zmq_msg_close(&tick_msg); 381 | enif_mutex_unlock(res->vm->mutex); 382 | 383 | return enif_make_atom(env,"ok"); 384 | } else { 385 | return enif_make_badarg(env); 386 | }; 387 | }; 388 | 389 | static ERL_NIF_TERM tick(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { 390 | vm_res_t *res; 391 | int e; 392 | if (enif_get_resource(env,argv[0],vm_resource,(void **)(&res))) { 393 | if ((!enif_is_ref(env, argv[1]))) 394 | return enif_make_badarg(env); 395 | 396 | zmq_msg_t tick_msg; 397 | 398 | Tick tick; 399 | tick.env = enif_alloc_env(); 400 | tick.tick = enif_make_copy(tick.env, argv[2]); 401 | tick.ref = enif_make_copy(tick.env, argv[1]); 402 | 403 | 404 | zmq_msg_init_size(&tick_msg, sizeof(Tick)); 405 | 406 | memcpy(zmq_msg_data(&tick_msg), &tick, sizeof(Tick)); 407 | 408 | do { 409 | e = zmq_send(res->vm->push_socket, &tick_msg, ZMQ_NOBLOCK); 410 | } while (e == EAGAIN); 411 | 412 | zmq_msg_close(&tick_msg); 413 | 414 | return enif_make_atom(env,"tack"); 415 | } else { 416 | return enif_make_badarg(env); 417 | }; 418 | }; 419 | 420 | static ERL_NIF_TERM global(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { 421 | ctx_res_t *res; 422 | vm_res_t *vm_res; 423 | 424 | if ( 425 | enif_get_resource(env, argv[0], vm_resource, (void **)(&vm_res)) 426 | && enif_get_resource(env,argv[1],ctx_resource,(void **)(&res))) { 427 | LHCS(vm_res->vm->isolate, res->ctx); 428 | v8::Handle global = res->ctx->Global(); 429 | return js_to_term(res->ctx, vm_res->vm->isolate, env,global); 430 | } else { 431 | return enif_make_badarg(env); 432 | }; 433 | }; 434 | 435 | static ERL_NIF_TERM new_context(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { 436 | vm_res_t *res; 437 | if (enif_get_resource(env, argv[0], vm_resource, (void **)(&res))) { 438 | LHCS(res->vm->isolate, res->vm->context); 439 | v8::Persistent context = v8::Context::New(NULL, res->vm->global_template); 440 | context->Global()->SetHiddenValue(res->vm->string__erlv8__, v8::External::New(res->vm)); 441 | 442 | ctx_res_t *ptr = (ctx_res_t *)enif_alloc_resource(ctx_resource, sizeof(ctx_res_t)); 443 | ptr->ctx = v8::Persistent::New(context); 444 | ERL_NIF_TERM resource_term = enif_make_resource(env, ptr); 445 | 446 | enif_release_resource(ptr); 447 | 448 | context->Global()->SetHiddenValue(v8::String::New("__erlv8__ctx__"), term_to_external(resource_term)); 449 | 450 | return resource_term; 451 | } else { 452 | return enif_make_badarg(env); 453 | }; 454 | }; 455 | 456 | static ErlNifFunc nif_funcs[] = 457 | { 458 | {"kill", 1, kill}, 459 | {"new_vm", 0, new_vm}, 460 | {"set_server", 2, set_server}, 461 | {"context", 1, context}, 462 | {"new_context", 1, new_context}, 463 | {"global", 2, global}, 464 | {"tick", 3, tick}, 465 | {"stop", 2, stop}, 466 | }; 467 | 468 | v8::Handle EmptyFun(const v8::Arguments &arguments) { 469 | v8::HandleScope handle_scope; 470 | return v8::Undefined(); 471 | } 472 | 473 | v8::Handle WrapFun(const v8::Arguments &arguments) { 474 | v8::HandleScope handle_scope; 475 | VM * vm = (VM *)__ERLV8__(v8::Context::GetCurrent()->Global()); 476 | // each call gets a unique ref 477 | ERL_NIF_TERM ref = enif_make_ref(vm->env); 478 | // prepare arguments 479 | ERL_NIF_TERM *arr = (ERL_NIF_TERM *) malloc(sizeof(ERL_NIF_TERM) * arguments.Length()); 480 | for (int i=0;icontext, vm->isolate, vm->env,arguments[i]); 482 | } 483 | ERL_NIF_TERM arglist = enif_make_list_from_array(vm->env,arr,arguments.Length()); 484 | free(arr); 485 | // send invocation request 486 | SEND(vm->server, 487 | enif_make_tuple3(env, 488 | enif_make_copy(env,external_to_term(arguments.Data())), 489 | enif_make_tuple7(env, 490 | enif_make_atom(env,"erlv8_fun_invocation"), 491 | enif_make_atom(env,arguments.IsConstructCall() ? "true" : "false"), 492 | js_to_term(vm->context, vm->isolate, env, arguments.Holder()), 493 | js_to_term(vm->context, vm->isolate, env, arguments.This()), 494 | enif_make_copy(env, ref), 495 | enif_make_pid(env, vm->server), 496 | enif_make_copy(env, external_to_term(v8::Context::GetCurrent()->Global()->GetHiddenValue(v8::String::New("__erlv8__ctx__"))))), 497 | enif_make_copy(env,arglist))); 498 | return handle_scope.Close(vm->ticker(ref)); 499 | }; 500 | 501 | 502 | static void vm_resource_destroy(ErlNifEnv* env, void* obj) { 503 | }; 504 | 505 | static void val_resource_destroy(ErlNifEnv* env, void* obj) { 506 | }; 507 | 508 | static void ctx_resource_destroy(ErlNifEnv* env, void* obj) { 509 | }; 510 | 511 | 512 | int load(ErlNifEnv *env, void** priv_data, ERL_NIF_TERM load_info) 513 | { 514 | TRACE("load\n"); 515 | zmq_context = zmq_init(0); // we are using inproc only, so no I/O threads 516 | 517 | vm_resource = enif_open_resource_type(env, NULL, "erlv8_vm_resource", vm_resource_destroy, (ErlNifResourceFlags) (ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER), NULL); 518 | val_resource = enif_open_resource_type(env, NULL, "erlv8_val_resource", val_resource_destroy, (ErlNifResourceFlags) (ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER), NULL); 519 | ctx_resource = enif_open_resource_type(env, NULL, "erlv8_ctx_resource", ctx_resource_destroy, (ErlNifResourceFlags) (ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER), NULL); 520 | 521 | v8::V8::Initialize(); 522 | int preemption = 100; // default value 523 | enif_get_int(env, load_info, &preemption); 524 | v8::Locker locker; 525 | v8::Locker::StartPreemption(preemption); 526 | 527 | v8::HandleScope handle_scope; 528 | 529 | 530 | return 0; 531 | }; 532 | 533 | void unload(ErlNifEnv *env, void* priv_data) 534 | { 535 | TRACE("unload\n"); 536 | v8::Locker::StopPreemption(); 537 | zmq_term(zmq_context); 538 | }; 539 | 540 | int reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { 541 | return 0; 542 | } 543 | 544 | int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) { 545 | return 0; 546 | } 547 | 548 | ErlNifResourceType * ctx_resource; 549 | ErlNifResourceType * vm_resource; 550 | ErlNifResourceType * val_resource; 551 | 552 | void *zmq_context; 553 | 554 | ERL_NIF_INIT(erlv8_nif,nif_funcs,load,reload,upgrade,unload) 555 | -------------------------------------------------------------------------------- /c_src/erlv8.hh: -------------------------------------------------------------------------------- 1 | #include "v8.h" 2 | 3 | #include "erl_nif.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | 16 | // Debugging 17 | 18 | #ifdef ERLV8_DEBUG 19 | #define DEBUG(pid,name,code) SEND(pid, enif_make_tuple3(env, enif_make_atom(env,"DEBUG"), name, code)) 20 | #define TRACE printf 21 | #else 22 | #define DEBUG(pid,name,code) 23 | #define TRACE(...) 24 | #endif 25 | 26 | // Debugging end 27 | 28 | using namespace std; 29 | using namespace __gnu_cxx; 30 | 31 | // This should be enough for our tags: 32 | #define MAX_ATOM_LEN 32 33 | 34 | class VM; // fwd 35 | typedef struct _vm_res_t { 36 | VM * vm; 37 | } vm_res_t; 38 | 39 | typedef struct _val_res_t { 40 | v8::Persistent ctx; 41 | v8::Persistent val; 42 | } val_res_t; 43 | 44 | typedef struct _term_ref_t { 45 | ErlNifEnv *env; 46 | ERL_NIF_TERM term; 47 | } term_ref_t; 48 | 49 | typedef struct _ctx_res_t { 50 | v8::Persistent ctx; 51 | } ctx_res_t; 52 | 53 | // Helpers 54 | #define LHCS(iso, ctx) \ 55 | TRACE("(%p) LHCS - 1\n", iso); \ 56 | v8::Locker locker(iso); \ 57 | TRACE("(%p) LHCS - 2\n", iso); \ 58 | v8::Isolate::Scope iscope(iso); \ 59 | TRACE("(%p) LHCS - 3\n", iso); \ 60 | v8::HandleScope handle_scope; \ 61 | TRACE("(%p) LHCS - 4\n", iso); \ 62 | v8::Context::Scope context_scope(ctx); \ 63 | TRACE("(%p) LHCS - 1\n", iso) 64 | 65 | #define SEND(pid, code)\ 66 | {\ 67 | Send send = Send(pid);\ 68 | ErlNifEnv * env = send.env;\ 69 | send.send(code);\ 70 | } 71 | // 72 | 73 | struct cmp_erl_nif_term 74 | { 75 | bool operator()(ERL_NIF_TERM a, ERL_NIF_TERM b) 76 | { 77 | return enif_compare(a,b) < 0; 78 | } 79 | }; 80 | 81 | 82 | // Statics 83 | 84 | extern ErlNifResourceType * vm_resource; 85 | extern ErlNifResourceType * val_resource; 86 | extern ErlNifResourceType * ctx_resource; 87 | 88 | extern void *zmq_context; 89 | 90 | // 91 | 92 | struct Tick { 93 | ErlNifEnv * env; 94 | ERL_NIF_TERM tick; 95 | ERL_NIF_TERM ref; 96 | }; 97 | 98 | // VM 99 | class VM { 100 | public: 101 | v8::Persistent global_template; 102 | v8::Persistent external_template; 103 | v8::Persistent empty_constructor; 104 | v8::Persistent string__erlv8__; 105 | 106 | 107 | v8::Persistent context; 108 | v8::Persistent external_proto_num; 109 | v8::Persistent external_proto_atom; 110 | v8::Persistent external_proto_bin; 111 | v8::Persistent external_proto_ref; 112 | v8::Persistent external_proto_fun; 113 | v8::Persistent external_proto_port; 114 | v8::Persistent external_proto_pid; 115 | v8::Persistent external_proto_tuple; 116 | v8::Persistent external_proto_list; 117 | 118 | ErlNifPid *server; 119 | ErlNifEnv *env; 120 | ErlNifMutex *mutex; 121 | 122 | void * push_socket; 123 | void * ticker_push_socket; 124 | void * pull_socket; 125 | 126 | queue pop_ticks; 127 | 128 | ErlNifTid tid; 129 | 130 | vm_res_t * resource; 131 | 132 | map, cmp_erl_nif_term> fun_map; 133 | map, cmp_erl_nif_term> extern_map; 134 | 135 | VM(); 136 | ~VM(); 137 | void run(); 138 | void terminate(); 139 | v8::Handle ticker(ERL_NIF_TERM ref); 140 | 141 | v8::Isolate* isolate; 142 | }; 143 | 144 | enum TickHandlerResolutionType { DONE, RETURN, NEXT }; 145 | 146 | typedef struct { 147 | v8::Handle value; 148 | TickHandlerResolutionType type; 149 | } TickHandlerResolution; 150 | 151 | #define TickHandler(name) extern TickHandlerResolution name(VM * vm, char * tick_name, ERL_NIF_TERM tick, ERL_NIF_TERM tick_ref, ERL_NIF_TERM ref, int arity, const ERL_NIF_TERM * array) 152 | 153 | TickHandler(StopTickHandler); 154 | TickHandler(ResultTickHandler); 155 | TickHandler(CallTickHandler); 156 | TickHandler(InstantiateTickHandler); 157 | TickHandler(DeleteTickHandler); 158 | TickHandler(TaintTickHandler); 159 | TickHandler(EqualsTickHandler); 160 | TickHandler(StrictEqualsTickHandler); 161 | TickHandler(GetTickHandler); 162 | TickHandler(GetProtoTickHandler); 163 | TickHandler(GetHiddenTickHandler); 164 | TickHandler(SetTickHandler); 165 | TickHandler(SetProtoTickHandler); 166 | TickHandler(SetHiddenTickHandler); 167 | TickHandler(SetAccessorTickHandler); 168 | TickHandler(ProplistTickHandler); 169 | TickHandler(ListTickHandler); 170 | TickHandler(ScriptTickHandler); 171 | TickHandler(GCTickHandler); 172 | TickHandler(TerminateTickHandler); 173 | TickHandler(ToStringTickHandler); 174 | TickHandler(ToDetailStringTickHandler); 175 | TickHandler(ExternProtoTickHandler); 176 | TickHandler(ExternalizeTickHandler); 177 | TickHandler(UnknownTickHandler); 178 | TickHandler(InternalCountTickHandler); 179 | TickHandler(SetInternalTickHandler); 180 | TickHandler(GetInternalTickHandler); 181 | 182 | class Send { 183 | public: 184 | ErlNifPid * pid; 185 | ErlNifEnv * env; 186 | 187 | Send(ErlNifPid *a_pid) : pid(a_pid) { 188 | env = enif_alloc_env(); 189 | }; 190 | 191 | ~Send() { 192 | enif_free_env(env); 193 | }; 194 | 195 | void send(ERL_NIF_TERM term) { 196 | enif_send(NULL, pid, env, term); 197 | enif_clear_env(env); 198 | }; 199 | 200 | }; 201 | 202 | #define __ERLV8__(O) v8::Local::Cast(O->GetHiddenValue(v8::String::New("__erlv8__")))->Value() 203 | 204 | v8::Handle term_to_js(v8::Handle ctx, v8::Isolate* isolate, ErlNifEnv *env, ERL_NIF_TERM term); 205 | ERL_NIF_TERM js_to_term(v8::Handle ctx, v8::Isolate* isolate, ErlNifEnv *env, v8::Handle val); 206 | ERL_NIF_TERM external_to_term(v8::Handle val); 207 | v8::Handle term_to_external(ERL_NIF_TERM term); 208 | v8::PropertyAttribute term_to_property_attribute(ErlNifEnv * env, ERL_NIF_TERM term); 209 | int enif_is_proplist(ErlNifEnv * env, ERL_NIF_TERM term); 210 | v8::PropertyAttribute term_to_property_attribute(ErlNifEnv * env, ERL_NIF_TERM term); 211 | v8::Handle term_to_external(ERL_NIF_TERM term); 212 | ERL_NIF_TERM external_to_term(v8::Handle val); 213 | v8::Handle externalize_term(map, cmp_erl_nif_term> cache, v8::Handle proto, ERL_NIF_TERM term); 214 | v8::Handle term_to_js(ErlNifEnv *env, v8::Isolate* isolat, ERL_NIF_TERM term); 215 | ERL_NIF_TERM js_to_term(ErlNifEnv *env, v8::Isolate* isolat, v8::Handle val); 216 | v8::Handle WrapFun(const v8::Arguments &arguments); 217 | v8::Handle EmptyFun(const v8::Arguments &arguments); 218 | 219 | v8::Handle extern_name_to_proto(VM * vm, char *name); 220 | 221 | 222 | -------------------------------------------------------------------------------- /c_src/erlv8_call.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | void ErlangFun(VM * vm, ERL_NIF_TERM term, ERL_NIF_TERM ref, v8::Handle recv, v8::Handle array); // fwd 4 | 5 | TickHandler(CallTickHandler) { 6 | ErlNifEnv *ref_env = enif_alloc_env(); 7 | ERL_NIF_TERM call_ref = enif_make_copy(ref_env, tick_ref); 8 | val_res_t *fun_res; 9 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&fun_res))) { 10 | ERL_NIF_TERM head, tail; 11 | ERL_NIF_TERM current = array[2]; 12 | unsigned int alen; 13 | 14 | enif_get_list_length(vm->env,array[2],&alen); 15 | 16 | v8::Local *args = NULL; 17 | args = new v8::Local[alen]; 18 | int i = 0; 19 | while (enif_get_list_cell(vm->env, current, &head, &tail)) { 20 | args[i] = v8::Local::New(term_to_js(fun_res->ctx, vm->isolate, vm->env,head)); 21 | i++; current = tail; 22 | } 23 | v8::Local recv; 24 | if (arity == 4) { // this is specified 25 | recv = term_to_js(fun_res->ctx, vm->isolate, vm->env, array[3])->ToObject(); 26 | } else { 27 | recv = fun_res->ctx->Global(); 28 | } 29 | v8::Persistent f = v8::Persistent::Cast(fun_res->val); 30 | 31 | if (!*f->GetHiddenValue(vm->string__erlv8__)) { // js function 32 | v8::TryCatch try_catch; 33 | v8::Local call_result = f->Call(recv, alen, args); 34 | if (call_result.IsEmpty()) { 35 | SEND(vm->server, 36 | enif_make_tuple3(env, 37 | enif_make_atom(env,"result"), 38 | enif_make_copy(env,call_ref), 39 | enif_make_tuple2(env, 40 | enif_make_atom(env,"throw"), 41 | enif_make_tuple2(env, 42 | enif_make_atom(env,"error"), 43 | js_to_term(fun_res->ctx, 44 | vm->isolate, 45 | env, 46 | try_catch.Exception()))))); 47 | } else { 48 | SEND(vm->server, 49 | enif_make_tuple3(env, 50 | enif_make_atom(env,"result"), 51 | enif_make_copy(env,call_ref), 52 | js_to_term(fun_res->ctx, 53 | vm->isolate, 54 | env, 55 | call_result))); 56 | } 57 | } else { // native Erlang function 58 | v8::Local array = v8::Array::New(alen); 59 | 60 | for (unsigned int i=0;iSet(i,args[i]); 62 | } 63 | 64 | ErlangFun(vm, external_to_term(f->GetHiddenValue((vm->string__erlv8__))), call_ref, recv, array); 65 | } 66 | 67 | delete [] args; 68 | args = NULL; 69 | } 70 | enif_free_env(ref_env); 71 | TickHandlerResolution result; 72 | result.type = DONE; 73 | return result; 74 | } 75 | 76 | void ErlangFun(VM * vm, ERL_NIF_TERM term, ERL_NIF_TERM ref, v8::Handle recv, v8::Handle array) { 77 | v8::HandleScope handle_scope; 78 | 79 | // prepare arguments 80 | ERL_NIF_TERM *arr = (ERL_NIF_TERM *) malloc(sizeof(ERL_NIF_TERM) * array->Length()); 81 | for (unsigned int i=0;iLength();i++) { 82 | arr[i] = js_to_term(vm->context, 83 | vm->isolate, 84 | vm->env, 85 | array->Get(v8::Integer::NewFromUnsigned(i))); 86 | } 87 | ERL_NIF_TERM arglist = enif_make_list_from_array(vm->env,arr,array->Length()); 88 | free(arr); 89 | // send invocation request 90 | SEND(vm->server, 91 | enif_make_tuple3(env, 92 | enif_make_copy(env,term), 93 | enif_make_tuple7(env, 94 | enif_make_atom(env,"erlv8_fun_invocation"), 95 | enif_make_atom(env, "false"), 96 | js_to_term(vm->context, vm->isolate, env, recv), // FIXME: not quite sure it's right 97 | js_to_term(vm->context, vm->isolate, env, recv), 98 | enif_make_copy(env, ref), 99 | enif_make_pid(env, vm->server), 100 | enif_make_copy(env, external_to_term(v8::Context::GetCurrent()->Global()->GetHiddenValue(v8::String::New("__erlv8__ctx__")))) 101 | ), 102 | enif_make_copy(env,arglist))); 103 | }; 104 | 105 | -------------------------------------------------------------------------------- /c_src/erlv8_delete.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(DeleteTickHandler) { 4 | val_res_t *obj_res; 5 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 6 | LHCS(vm->isolate, obj_res->ctx); 7 | v8::Handle key = term_to_js(obj_res->ctx,vm->isolate,vm->env,array[2]); 8 | if (key->IsString()) { 9 | obj_res->val->ToObject()->Delete(key->ToString()); 10 | } else if (key->IsNumber()) { 11 | obj_res->val->ToObject()->Delete(key->Uint32Value()); 12 | } 13 | 14 | SEND(vm->server, 15 | enif_make_tuple3(env, 16 | enif_make_atom(env,"result"), 17 | enif_make_copy(env,tick_ref), 18 | enif_make_atom(env, "ok"))); 19 | } 20 | TickHandlerResolution result; 21 | result.type = DONE; 22 | return result; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /c_src/erlv8_equals.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(EqualsTickHandler) { 4 | ErlNifEnv *ref_env = enif_alloc_env(); 5 | ERL_NIF_TERM equals_ref = enif_make_copy(ref_env, tick_ref); 6 | val_res_t *res1; 7 | val_res_t *res2; 8 | bool bres; 9 | 10 | if ((enif_get_resource(vm->env,array[1],val_resource,(void **)(&res1))) && 11 | (enif_get_resource(vm->env,array[2],val_resource,(void **)(&res2)))) { 12 | LHCS(vm->isolate, res1->ctx); 13 | bres = res1->val->ToObject()->Equals(res2->val->ToObject()); 14 | } else { 15 | LHCS(vm->isolate, vm->context); 16 | bres = term_to_js(vm->context, vm->isolate, vm->env,array[1])->Equals(term_to_js(vm->context, vm->isolate, vm->env,array[2])); 17 | }; 18 | 19 | SEND(vm->server, 20 | enif_make_tuple3(env, 21 | enif_make_atom(env,"result"), 22 | enif_make_copy(env,equals_ref), 23 | enif_make_atom(env, bres ? "true" : "false"))); 24 | 25 | enif_free_env(ref_env); 26 | TickHandlerResolution result; 27 | result.type = DONE; 28 | return result; 29 | } 30 | 31 | TickHandler(StrictEqualsTickHandler) { 32 | ErlNifEnv *ref_env = enif_alloc_env(); 33 | ERL_NIF_TERM equals_ref = enif_make_copy(ref_env, tick_ref); 34 | val_res_t *res1; 35 | val_res_t *res2; 36 | bool bres; 37 | 38 | if ((enif_get_resource(vm->env,array[1],val_resource,(void **)(&res1))) && 39 | (enif_get_resource(vm->env,array[2],val_resource,(void **)(&res2)))) { 40 | LHCS(vm->isolate, res1->ctx); 41 | bres = res1->val->ToObject()->StrictEquals(res2->val->ToObject()); 42 | } else { 43 | LHCS(vm->isolate, vm->context); 44 | bres = term_to_js(vm->context, vm->isolate, vm->env,array[1])->StrictEquals(term_to_js(vm->context, vm->isolate, vm->env,array[2])); 45 | }; 46 | 47 | SEND(vm->server, 48 | enif_make_tuple3(env, 49 | enif_make_atom(env,"result"), 50 | enif_make_copy(env,equals_ref), 51 | enif_make_atom(env, bres ? "true" : "false"))); 52 | 53 | enif_free_env(ref_env); 54 | TickHandlerResolution result; 55 | result.type = DONE; 56 | return result; 57 | } 58 | -------------------------------------------------------------------------------- /c_src/erlv8_extern.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | v8::Handle extern_name_to_proto(VM * vm, char *name) { 4 | v8::Handle proto; 5 | 6 | if (!strcmp(name,"num")) { 7 | proto = vm->external_proto_num; 8 | } else if (!strcmp(name,"atom")) { 9 | proto = vm->external_proto_atom; 10 | } else if (!strcmp(name,"bin")) { 11 | proto = vm->external_proto_bin; 12 | } else if (!strcmp(name,"ref")) { 13 | proto = vm->external_proto_ref; 14 | } else if (!strcmp(name,"fun")) { 15 | proto = vm->external_proto_fun; 16 | } else if (!strcmp(name,"port")) { 17 | proto = vm->external_proto_port; 18 | } else if (!strcmp(name,"pid")) { 19 | proto = vm->external_proto_pid; 20 | } else if (!strcmp(name,"tuple")) { 21 | proto = vm->external_proto_tuple; 22 | } else if (!strcmp(name,"list")) { 23 | proto = vm->external_proto_list; 24 | } else { 25 | proto = v8::Object::New(); 26 | } 27 | 28 | return proto; 29 | } 30 | 31 | TickHandler(ExternProtoTickHandler) { 32 | ErlNifEnv *ref_env = enif_alloc_env(); 33 | ERL_NIF_TERM extern_proto_ref = enif_make_copy(ref_env, tick_ref); 34 | char name[MAX_ATOM_LEN]; 35 | unsigned len; 36 | 37 | enif_get_atom_length(vm->env, array[1], &len, ERL_NIF_LATIN1); 38 | enif_get_atom(vm->env,array[1],(char *)&name,len + 1, ERL_NIF_LATIN1); 39 | 40 | v8::Handle proto = extern_name_to_proto(vm, name); 41 | 42 | SEND(vm->server, 43 | enif_make_tuple3(env, 44 | enif_make_atom(env,"result"), 45 | enif_make_copy(env,extern_proto_ref), 46 | js_to_term(vm->context, vm->isolate, env, proto))); 47 | 48 | enif_free_env(ref_env); 49 | TickHandlerResolution result; 50 | result.type = DONE; 51 | return result; 52 | } 53 | 54 | TickHandler(ExternalizeTickHandler) { 55 | ErlNifEnv *ref_env = enif_alloc_env(); 56 | ERL_NIF_TERM extern_ref = enif_make_copy(ref_env, tick_ref); 57 | char name[MAX_ATOM_LEN]; 58 | unsigned len; 59 | 60 | enif_get_atom_length(vm->env, array[1], &len, ERL_NIF_LATIN1); 61 | enif_get_atom(vm->env,array[1],(char *)&name,len + 1, ERL_NIF_LATIN1); 62 | 63 | v8::Handle proto = extern_name_to_proto(vm, name); 64 | v8::Handle obj = externalize_term(vm->extern_map, proto, array[2]); 65 | 66 | ERL_NIF_TERM resource_term; 67 | 68 | val_res_t *ptr; 69 | ptr = (val_res_t *)enif_alloc_resource(val_resource, sizeof(val_res_t)); 70 | ptr->val = v8::Persistent::New(v8::Handle::Cast(obj)); 71 | ptr->ctx = v8::Persistent::New(v8::Context::GetCurrent()); 72 | resource_term = enif_make_resource(vm->env, ptr); 73 | enif_release_resource(ptr); 74 | 75 | SEND(vm->server, 76 | enif_make_tuple3(env, 77 | enif_make_atom(env,"result"), 78 | enif_make_copy(env,extern_ref), 79 | enif_make_tuple3(env, 80 | enif_make_atom(env, "erlv8_object"), 81 | enif_make_copy(env, resource_term), 82 | enif_make_pid(env, vm->server) 83 | ))); 84 | 85 | enif_free_env(ref_env); 86 | TickHandlerResolution result; 87 | result.type = DONE; 88 | return result; 89 | } 90 | -------------------------------------------------------------------------------- /c_src/erlv8_gc.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(GCTickHandler) { 4 | while (!v8::V8::IdleNotification()) 5 | ; 6 | TickHandlerResolution result; 7 | result.type = DONE; 8 | return result; 9 | } 10 | -------------------------------------------------------------------------------- /c_src/erlv8_get.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(GetTickHandler) { 4 | val_res_t *obj_res; 5 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 6 | LHCS(vm->isolate, obj_res->ctx); 7 | v8::Local get_result = obj_res->val->ToObject()->Get(term_to_js(obj_res->ctx,vm->isolate, vm->env,array[2])); 8 | 9 | SEND(vm->server, 10 | enif_make_tuple3(env, 11 | enif_make_atom(env,"result"), 12 | enif_make_copy(env,tick_ref), 13 | js_to_term(obj_res->ctx, vm->isolate, env,get_result))); 14 | } 15 | TickHandlerResolution result; 16 | result.type = DONE; 17 | return result; 18 | } 19 | 20 | TickHandler(GetProtoTickHandler) { 21 | val_res_t *obj_res; 22 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 23 | LHCS(vm->isolate, obj_res->ctx); 24 | v8::Local get_result = obj_res->val->ToObject()->GetPrototype(); 25 | 26 | SEND(vm->server, 27 | enif_make_tuple3(env, 28 | enif_make_atom(env,"result"), 29 | enif_make_copy(env,tick_ref), 30 | js_to_term(obj_res->ctx, vm->isolate, env,get_result))); 31 | } 32 | TickHandlerResolution result; 33 | result.type = DONE; 34 | return result; 35 | } 36 | 37 | TickHandler(GetHiddenTickHandler) { 38 | val_res_t *obj_res; 39 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 40 | LHCS(vm->isolate, obj_res->ctx); 41 | v8::Local get_result = obj_res->val->ToObject()->GetHiddenValue(term_to_js(obj_res->ctx,vm->isolate,vm->env,array[2])->ToString()); 42 | 43 | SEND(vm->server, 44 | enif_make_tuple3(env, 45 | enif_make_atom(env,"result"), 46 | enif_make_copy(env,tick_ref), 47 | js_to_term(obj_res->ctx, vm->isolate, env,get_result))); 48 | } 49 | TickHandlerResolution result; 50 | result.type = DONE; 51 | return result; 52 | } 53 | 54 | TickHandler(GetInternalTickHandler) { 55 | val_res_t *obj_res; 56 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 57 | LHCS(vm->isolate, obj_res->ctx); 58 | 59 | int index; 60 | enif_get_int(vm->env, array[2], &index); 61 | 62 | if (index < 0 || (index + 1 > obj_res->val->ToObject()->InternalFieldCount())) { 63 | SEND(vm->server, 64 | enif_make_tuple3(env, 65 | enif_make_atom(env,"result"), 66 | enif_make_copy(env,tick_ref), 67 | enif_make_atom(env,"error"))); 68 | } else { 69 | 70 | v8::Local get_result = obj_res->val->ToObject()->GetInternalField(index); 71 | 72 | if (get_result->IsExternal()) { 73 | SEND(vm->server, 74 | enif_make_tuple3(env, 75 | enif_make_atom(env,"result"), 76 | enif_make_copy(env,tick_ref), 77 | external_to_term(get_result))); 78 | } else { 79 | SEND(vm->server, 80 | enif_make_tuple3(env, 81 | enif_make_atom(env,"result"), 82 | enif_make_copy(env,tick_ref), 83 | js_to_term(obj_res->ctx, vm->isolate, env,get_result))); 84 | } 85 | 86 | } 87 | } 88 | TickHandlerResolution result; 89 | result.type = DONE; 90 | return result; 91 | } 92 | -------------------------------------------------------------------------------- /c_src/erlv8_instantiate.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | void ErlangConstFun(VM * vm, ERL_NIF_TERM term, ERL_NIF_TERM ref, v8::Handle instance, v8::Handle array); // fwd 4 | 5 | TickHandler(InstantiateTickHandler) { 6 | ErlNifEnv *ref_env = enif_alloc_env(); 7 | ERL_NIF_TERM inst_ref = enif_make_copy(ref_env, tick_ref); 8 | val_res_t *fun_res; 9 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&fun_res))) { 10 | ERL_NIF_TERM head, tail; 11 | ERL_NIF_TERM current = array[2]; 12 | unsigned int alen; 13 | 14 | enif_get_list_length(vm->env,array[2],&alen); 15 | 16 | v8::Local *args = NULL; 17 | args = new v8::Local[alen]; 18 | int i = 0; 19 | while (enif_get_list_cell(vm->env, current, &head, &tail)) { 20 | args[i] = v8::Local::New(term_to_js(fun_res->ctx, vm->isolate, vm->env,head)); 21 | i++; current = tail; 22 | } 23 | 24 | v8::Handle f = v8::Handle::Cast(fun_res->val); 25 | 26 | if (!*f->GetHiddenValue(vm->string__erlv8__)) { // js function 27 | v8::TryCatch try_catch; 28 | v8::Local inst_result = f->NewInstance(alen, args); 29 | if (inst_result.IsEmpty()) { 30 | SEND(vm->server, 31 | enif_make_tuple3(env, 32 | enif_make_atom(env,"result"), 33 | enif_make_copy(env,inst_ref), 34 | enif_make_tuple2(env, 35 | enif_make_atom(env,"throw"), 36 | enif_make_tuple2(env, 37 | enif_make_atom(env,"error"), 38 | js_to_term(fun_res->ctx,vm->isolate, env,try_catch.Exception()))))); 39 | } else { 40 | SEND(vm->server, 41 | enif_make_tuple3(env, 42 | enif_make_atom(env,"result"), 43 | enif_make_copy(env,inst_ref), 44 | js_to_term(fun_res->ctx,vm->isolate, env,inst_result))); 45 | } 46 | } else { // native Erlang function 47 | v8::Local array = v8::Array::New(alen); 48 | 49 | for (unsigned int i=0;iSet(i,args[i]); 51 | } 52 | 53 | ErlangConstFun(vm, 54 | external_to_term(f->GetHiddenValue(v8::String::New("__erlv8__"))), 55 | inst_ref, 56 | vm->empty_constructor->GetFunction()->NewInstance(), 57 | array); 58 | } 59 | 60 | delete [] args; 61 | args = NULL; 62 | } 63 | enif_free_env(ref_env); 64 | TickHandlerResolution result; 65 | result.type = DONE; 66 | return result; 67 | } 68 | 69 | void ErlangConstFun(VM * vm, ERL_NIF_TERM term, ERL_NIF_TERM ref, v8::Handle instance, v8::Handle array) { 70 | v8::HandleScope handle_scope; 71 | 72 | ctx_res_t *ptr = (ctx_res_t *)enif_alloc_resource(ctx_resource, sizeof(ctx_res_t)); 73 | ptr->ctx = v8::Persistent::New(v8::Context::GetCurrent()); 74 | // prepare arguments 75 | ERL_NIF_TERM *arr = (ERL_NIF_TERM *) malloc(sizeof(ERL_NIF_TERM) * array->Length()); 76 | for (unsigned int i=0;iLength();i++) { 77 | arr[i] = js_to_term(vm->context,vm->isolate, vm->env,array->Get(v8::Integer::NewFromUnsigned(i))); 78 | } 79 | ERL_NIF_TERM arglist = enif_make_list_from_array(vm->env,arr,array->Length()); 80 | free(arr); 81 | // send invocation request 82 | SEND(vm->server, 83 | enif_make_tuple3(env, 84 | enif_make_copy(env,term), 85 | enif_make_tuple7(env, 86 | enif_make_atom(env,"erlv8_fun_invocation"), 87 | enif_make_atom(env, "true"), 88 | js_to_term(vm->context, vm->isolate, env, instance), // FIXME: not quite sure it's right 89 | js_to_term(vm->context, vm->isolate, env, instance), 90 | enif_make_copy(env, ref), 91 | enif_make_pid(env, vm->server), 92 | enif_make_resource(env, ptr) 93 | ), 94 | enif_make_copy(env,arglist))); 95 | enif_release_resource(ptr); 96 | }; 97 | -------------------------------------------------------------------------------- /c_src/erlv8_internal.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(InternalCountTickHandler) { 4 | ErlNifEnv *ref_env = enif_alloc_env(); 5 | ERL_NIF_TERM get_ref = enif_make_copy(ref_env, tick_ref); 6 | val_res_t *obj_res; 7 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 8 | LHCS(vm->isolate, obj_res->ctx); 9 | 10 | int ifc = obj_res->val->ToObject()->InternalFieldCount(); 11 | 12 | SEND(vm->server, 13 | enif_make_tuple3(env, 14 | enif_make_atom(env,"result"), 15 | enif_make_copy(env,get_ref), 16 | enif_make_int(env, ifc))); 17 | } 18 | enif_free_env(ref_env); 19 | TickHandlerResolution result; 20 | result.type = DONE; 21 | return result; 22 | } 23 | -------------------------------------------------------------------------------- /c_src/erlv8_list.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(ListTickHandler) { 4 | ErlNifEnv *ref_env = enif_alloc_env(); 5 | ERL_NIF_TERM list_ref = enif_make_copy(ref_env, tick_ref); 6 | 7 | val_res_t *res; 8 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&res))) { 9 | LHCS(vm->isolate, res->ctx); 10 | 11 | if (res->val->IsArray()) { 12 | v8::Handle array = v8::Handle::Cast(res->val->ToObject()); 13 | 14 | ERL_NIF_TERM *arr = (ERL_NIF_TERM *) malloc(sizeof(ERL_NIF_TERM) * array->Length()); 15 | for (unsigned int i=0;iLength();i++) { 16 | arr[i] = js_to_term(res->ctx, vm->isolate, vm->env, array->Get(v8::Integer::NewFromUnsigned(i))); 17 | } 18 | ERL_NIF_TERM list = enif_make_list_from_array(vm->env, arr, array->Length()); 19 | free(arr); 20 | SEND(vm->server, 21 | enif_make_tuple3(env, 22 | enif_make_atom(env, "result"), 23 | enif_make_copy(env, list_ref), 24 | enif_make_copy(env, list))); 25 | } 26 | } 27 | 28 | enif_free_env(ref_env); 29 | TickHandlerResolution result; 30 | result.type = DONE; 31 | return result; 32 | } 33 | -------------------------------------------------------------------------------- /c_src/erlv8_proplist.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(ProplistTickHandler) { 4 | ErlNifEnv *ref_env = enif_alloc_env(); 5 | ERL_NIF_TERM proplist_ref = enif_make_copy(ref_env, tick_ref); 6 | 7 | val_res_t *res; 8 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&res))) { 9 | LHCS(vm->isolate, res->ctx); 10 | 11 | v8::Handle keys; 12 | if (res->val->IsNativeError()) { //workaround for V8 bug #1595 13 | keys = v8::Array::New(3); 14 | keys->Set(v8::String::New("0"), v8::String::New("name")); 15 | keys->Set(v8::String::New("1"), v8::String::New("message")); 16 | keys->Set(v8::String::New("2"), v8::String::New("stack")); 17 | } else { 18 | keys = res->val->ToObject()->GetPropertyNames(); 19 | } 20 | 21 | ERL_NIF_TERM *arr = (ERL_NIF_TERM *) malloc(sizeof(ERL_NIF_TERM) * keys->Length()); 22 | 23 | for (unsigned int i=0;iLength();i++) { 24 | v8::Handle key = keys->Get(v8::Integer::New(i)); 25 | arr[i] = enif_make_tuple2(vm->env, 26 | js_to_term(res->ctx, vm->isolate, vm->env,v8::Handle::Cast(key)), 27 | js_to_term(res->ctx, vm->isolate, vm->env,res->val->ToObject()->Get(key))); 28 | } 29 | ERL_NIF_TERM list = enif_make_list_from_array(vm->env,arr,keys->Length()); 30 | free(arr); 31 | SEND(vm->server, 32 | enif_make_tuple3(env, 33 | enif_make_atom(env,"result"), 34 | enif_make_copy(env,proplist_ref), 35 | enif_make_copy(env,list))); 36 | } 37 | 38 | enif_free_env(ref_env); 39 | TickHandlerResolution result; 40 | result.type = DONE; 41 | return result; 42 | } 43 | -------------------------------------------------------------------------------- /c_src/erlv8_result.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(ResultTickHandler) { 4 | v8::HandleScope handle_scope; 5 | TickHandlerResolution result; 6 | 7 | if (((unsigned long) ref) && 8 | (enif_is_identical(array[1],ref))) { // this is our result 9 | result.value = handle_scope.Close(term_to_js(vm->context, vm->isolate, vm->env,array[2])); 10 | result.type = RETURN; 11 | return result; 12 | } else { 13 | Tick newtick; 14 | 15 | newtick.env = enif_alloc_env(); 16 | newtick.tick = enif_make_copy(newtick.env, tick); 17 | newtick.ref = enif_make_copy(newtick.env, tick_ref); 18 | 19 | vm->pop_ticks.push(newtick); 20 | 21 | result.type = DONE; 22 | return result; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /c_src/erlv8_script.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(ScriptTickHandler) { 4 | ErlNifEnv *ref_env = enif_alloc_env(); 5 | ERL_NIF_TERM script_ref = enif_make_copy(ref_env, tick_ref); 6 | unsigned len; 7 | enif_get_list_length(vm->env, array[2], &len); 8 | char * buf = (char *) malloc(len + 1); 9 | enif_get_string(vm->env,array[2],buf,len + 1, ERL_NIF_LATIN1); 10 | 11 | ctx_res_t *res; 12 | if (enif_get_resource(vm->env,array[1],ctx_resource,(void **)(&res))) { 13 | TRACE("(%p) script - 1\n", vm->isolate); 14 | v8::Isolate::Scope iscope(vm->isolate); 15 | TRACE("(%p) script - 2\n", vm->isolate); 16 | v8::Context::Scope context_scope(res->ctx); 17 | TRACE("(%p) script - 3\n", vm->isolate); 18 | v8::HandleScope handle_scope; 19 | TRACE("(%p) script - 4\n", vm->isolate); 20 | 21 | 22 | v8::TryCatch try_catch; 23 | 24 | TRACE("(%p) script - 4a\n", vm->isolate); 25 | 26 | 27 | v8::ScriptOrigin * origin = new v8::ScriptOrigin(term_to_js(res->ctx,vm->isolate,vm->env,array[3])->ToString(), 28 | term_to_js(res->ctx,vm->isolate,vm->env,array[4])->ToInteger(), 29 | term_to_js(res->ctx,vm->isolate,vm->env,array[5])->ToInteger()); 30 | 31 | TRACE("(%p) script - 5\n", vm->isolate); 32 | 33 | v8::Handle script = v8::String::New(buf, len); 34 | v8::Handle compiled = v8::Script::Compile(script,origin); 35 | 36 | TRACE("(%p) script - 6\n", vm->isolate); 37 | 38 | 39 | delete origin; 40 | 41 | if (compiled.IsEmpty()) { 42 | SEND(vm->server, 43 | enif_make_tuple3(env, 44 | enif_make_atom(env,"result"), 45 | enif_make_copy(env, script_ref), 46 | enif_make_tuple2(env, 47 | enif_make_atom(env,"throw"), 48 | js_to_term(res->ctx,vm->isolate,env,try_catch.Exception())))); 49 | } else { 50 | TRACE("(%p) script - 7\n", vm->isolate); 51 | 52 | v8::Handle value = compiled->Run(); 53 | TRACE("(%p) script - 8\n", vm->isolate); 54 | 55 | if (value.IsEmpty()) { 56 | SEND(vm->server,enif_make_tuple3(env, 57 | enif_make_atom(env,"result"), 58 | enif_make_copy(env, script_ref), 59 | enif_make_tuple2(env, 60 | enif_make_atom(env,"throw"), 61 | js_to_term(res->ctx,vm->isolate,env,try_catch.Exception())))); 62 | } else { 63 | SEND(vm->server,enif_make_tuple3(env, 64 | enif_make_atom(env,"result"), 65 | enif_make_copy(env, script_ref), 66 | enif_make_tuple2(env, enif_make_atom(env,"ok"), 67 | js_to_term(res->ctx,vm->isolate,env,value)))); 68 | } 69 | } 70 | } 71 | enif_free_env(ref_env); 72 | free(buf); 73 | TickHandlerResolution result; 74 | result.type = DONE; 75 | return result; 76 | } 77 | -------------------------------------------------------------------------------- /c_src/erlv8_set.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(SetTickHandler) { 4 | ErlNifEnv *tmp_env = enif_alloc_env(); 5 | ERL_NIF_TERM value = enif_make_copy(tmp_env, array[3]); // stashing it away since Set() might call an accessor, which might change vm->env 6 | val_res_t *obj_res; 7 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 8 | LHCS(vm->isolate, obj_res->ctx); 9 | 10 | v8::PropertyAttribute property_attribute = v8::None; 11 | 12 | if (arity == 5) { 13 | property_attribute = term_to_property_attribute(vm->env,array[4]); 14 | } 15 | 16 | obj_res->val->ToObject()->Set(term_to_js(obj_res->ctx, vm->isolate, vm->env,array[2]), 17 | term_to_js(obj_res->ctx, vm->isolate, tmp_env,value), 18 | property_attribute); 19 | 20 | SEND(vm->server, 21 | enif_make_tuple3(env, 22 | enif_make_atom(env,"result"), 23 | enif_make_copy(env,tick_ref), 24 | enif_make_copy(env,value))); 25 | } 26 | enif_free_env(tmp_env); 27 | TickHandlerResolution result; 28 | result.type = DONE; 29 | return result; 30 | } 31 | 32 | TickHandler(SetProtoTickHandler) { 33 | val_res_t *obj_res; 34 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 35 | LHCS(vm->isolate, obj_res->ctx); 36 | 37 | const char *atom_val = obj_res->val->ToObject()->SetPrototype(term_to_js(obj_res->ctx,vm->isolate,vm->env,array[2])) ? "true" : "false"; 38 | 39 | SEND(vm->server, 40 | enif_make_tuple3(env, 41 | enif_make_atom(env,"result"), 42 | enif_make_copy(env,tick_ref), 43 | enif_make_atom(env,atom_val))); 44 | } 45 | TickHandlerResolution result; 46 | result.type = DONE; 47 | return result; 48 | } 49 | 50 | TickHandler(SetHiddenTickHandler) { 51 | val_res_t *obj_res; 52 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 53 | LHCS(vm->isolate, obj_res->ctx); 54 | 55 | obj_res->val->ToObject()->SetHiddenValue(term_to_js(obj_res->ctx, vm->isolate, vm->env,array[2])->ToString(),term_to_js(obj_res->ctx,vm->isolate, vm->env,array[3])); 56 | 57 | SEND(vm->server, 58 | enif_make_tuple3(env, 59 | enif_make_atom(env,"result"), 60 | enif_make_copy(env,tick_ref), 61 | enif_make_copy(env,array[2]))); 62 | } 63 | TickHandlerResolution result; 64 | result.type = DONE; 65 | return result; 66 | } 67 | 68 | TickHandler(SetInternalTickHandler) { 69 | val_res_t *obj_res; 70 | char name[MAX_ATOM_LEN]; 71 | unsigned len; 72 | 73 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 74 | LHCS(vm->isolate, obj_res->ctx); 75 | 76 | int index; 77 | enif_get_int(vm->env, array[2], &index); 78 | 79 | if (index < 0 || (index + 1 > obj_res->val->ToObject()->InternalFieldCount())) { 80 | SEND(vm->server, 81 | enif_make_tuple3(env, 82 | enif_make_atom(env,"result"), 83 | enif_make_copy(env,tick_ref), 84 | enif_make_atom(env,"error"))); 85 | } else { 86 | 87 | if (!strcmp(tick_name,"set_internal_extern")) { 88 | enif_get_atom_length(vm->env, array[4], &len, ERL_NIF_LATIN1); 89 | enif_get_atom(vm->env,array[4],(char *)&name,len + 1, ERL_NIF_LATIN1); 90 | v8::Handle proto = extern_name_to_proto(vm, name); 91 | obj_res->val->ToObject()->SetInternalField(index,term_to_external(array[3])); 92 | } else { 93 | obj_res->val->ToObject()->SetInternalField(index,term_to_js(obj_res->ctx,vm->isolate, vm->env,array[3])); 94 | } 95 | 96 | SEND(vm->server, 97 | enif_make_tuple3(env, 98 | enif_make_atom(env,"result"), 99 | enif_make_copy(env,tick_ref), 100 | enif_make_copy(env,array[3]))); 101 | } 102 | } 103 | TickHandlerResolution result; 104 | result.type = DONE; 105 | return result; 106 | } 107 | 108 | v8::Handle GetterFun(v8::Local property,const v8::AccessorInfo &info); // fwd 109 | void SetterFun(v8::Local property,v8::Local value,const v8::AccessorInfo &info); // fwd 110 | 111 | void weak_accessor_data_cleaner(v8::Persistent object, void * data) { 112 | if (object.IsNearDeath()) { 113 | object->ToObject()->DeleteHiddenValue(v8::String::New("_getter")); 114 | object->ToObject()->DeleteHiddenValue(v8::String::New("_setter")); 115 | object.Dispose(); 116 | object.Clear(); 117 | } 118 | } 119 | 120 | TickHandler(SetAccessorTickHandler) { 121 | char aname[MAX_ATOM_LEN]; 122 | const char *atom_val; 123 | val_res_t *obj_res; 124 | if (enif_get_resource(vm->env,array[1],val_resource,(void **)(&obj_res))) { 125 | LHCS(vm->isolate, obj_res->ctx); 126 | 127 | if (arity > 3) { 128 | v8::Handle name = term_to_js(obj_res->ctx,vm->isolate, vm->env,array[2]); 129 | if (!name->IsString()) { 130 | goto badarg; 131 | } 132 | v8::AccessorGetter getter = GetterFun; 133 | v8::AccessorSetter setter = 0; 134 | v8::Persistent data = v8::Persistent::New(v8::Object::New()); 135 | data.MakeWeak(NULL,weak_accessor_data_cleaner); // so that we'll release externals when we're done 136 | 137 | if (term_to_js(obj_res->ctx,vm->isolate, vm->env,array[3])->IsUndefined()) { 138 | goto badarg; 139 | } else { 140 | data->SetHiddenValue(v8::String::New("_getter"), term_to_external(array[3])); 141 | } 142 | 143 | if (arity > 4) { 144 | setter = SetterFun; 145 | data->SetHiddenValue(v8::String::New("_setter"), term_to_external(array[4])); 146 | } 147 | 148 | v8::AccessControl access_control = v8::DEFAULT; 149 | 150 | if (arity > 5 && enif_is_atom(vm->env, array[5])) { 151 | unsigned len; 152 | enif_get_atom_length(vm->env, array[5], &len, ERL_NIF_LATIN1); 153 | enif_get_atom(vm->env,array[5], (char *) &aname,len + 1, ERL_NIF_LATIN1); 154 | if (!strcmp(aname,"default")) { 155 | access_control = v8::DEFAULT; 156 | } else if (!strcmp(aname,"all_can_read")) { 157 | access_control = v8::ALL_CAN_READ; 158 | } else if (!strcmp(aname,"all_can_write")) { 159 | access_control = v8::ALL_CAN_WRITE; 160 | } else if (!strcmp(aname,"prohibits_overwriting")) { 161 | access_control = v8::PROHIBITS_OVERWRITING; 162 | } 163 | } 164 | 165 | v8::PropertyAttribute property_attribute = v8::None; 166 | 167 | if (arity > 6) { 168 | property_attribute = term_to_property_attribute(vm->env,array[6]); 169 | } 170 | 171 | atom_val = obj_res->val->ToObject()->SetAccessor(name->ToString(), getter, setter, data, 172 | access_control, property_attribute) ? "true" : "false"; 173 | goto send; 174 | } 175 | badarg: 176 | atom_val = "badarg"; 177 | send: 178 | SEND(vm->server, 179 | enif_make_tuple3(env, 180 | enif_make_atom(env,"result"), 181 | enif_make_copy(env,tick_ref), 182 | enif_make_atom(env, atom_val))); 183 | } 184 | TickHandlerResolution result; 185 | result.type = DONE; 186 | return result; 187 | } 188 | 189 | 190 | 191 | v8::Handle GetterFun(v8::Local property,const v8::AccessorInfo &info) { 192 | v8::HandleScope handle_scope; 193 | VM * vm = (VM *)__ERLV8__(v8::Context::GetCurrent()->Global()); 194 | 195 | v8::Local data = info.Data()->ToObject(); 196 | 197 | // each call gets a unique ref 198 | ERL_NIF_TERM ref = enif_make_ref(vm->env); 199 | 200 | // prepare arguments 201 | ERL_NIF_TERM *arr = (ERL_NIF_TERM *) malloc(sizeof(ERL_NIF_TERM) * 1); 202 | arr[0] = js_to_term(vm->context, vm->isolate, vm->env, property); 203 | ERL_NIF_TERM arglist = enif_make_list_from_array(vm->env,arr,1); 204 | free(arr); 205 | 206 | // send invocation request 207 | SEND(vm->server, 208 | enif_make_tuple3(env, 209 | enif_make_copy(env,external_to_term(data->GetHiddenValue(v8::String::New("_getter")))), 210 | enif_make_tuple7(env, 211 | enif_make_atom(env,"erlv8_fun_invocation"), 212 | enif_make_atom(env,"false"), 213 | js_to_term(vm->context, vm->isolate, env, info.Holder()), 214 | js_to_term(vm->context, vm->isolate, env, info.This()), 215 | enif_make_copy(env, ref), 216 | enif_make_pid(env, vm->server), 217 | enif_make_copy(env, external_to_term(v8::Context::GetCurrent()->Global()->GetHiddenValue(v8::String::New("__erlv8__ctx__")))) 218 | ), 219 | enif_make_copy(env,arglist))); 220 | return handle_scope.Close(vm->ticker(ref)); 221 | } 222 | 223 | void SetterFun(v8::Local property,v8::Local value,const v8::AccessorInfo &info) { 224 | v8::HandleScope handle_scope; 225 | VM * vm = (VM *)__ERLV8__(v8::Context::GetCurrent()->Global()); 226 | 227 | v8::Local data = info.Data()->ToObject(); 228 | 229 | // each call gets a unique ref 230 | ERL_NIF_TERM ref = enif_make_ref(vm->env); 231 | 232 | // prepare arguments 233 | ERL_NIF_TERM *arr = (ERL_NIF_TERM *) malloc(sizeof(ERL_NIF_TERM) * 2); 234 | arr[0] = js_to_term(vm->context, vm->isolate, vm->env, property); 235 | arr[1] = js_to_term(vm->context, vm->isolate, vm->env, value); 236 | ERL_NIF_TERM arglist = enif_make_list_from_array(vm->env,arr,2); 237 | free(arr); 238 | 239 | // send invocation request 240 | SEND(vm->server, 241 | enif_make_tuple3(env, 242 | enif_make_copy(env,external_to_term(data->GetHiddenValue(v8::String::New("_setter")))), 243 | enif_make_tuple7(env, 244 | enif_make_atom(env,"erlv8_fun_invocation"), 245 | enif_make_atom(env,"false"), 246 | js_to_term(vm->context, vm->isolate, env, info.Holder()), 247 | js_to_term(vm->context, vm->isolate, env, info.This()), 248 | enif_make_copy(env, ref), 249 | enif_make_pid(env, vm->server), 250 | enif_make_copy(env, external_to_term(v8::Context::GetCurrent()->Global()->GetHiddenValue(v8::String::New("__erlv8__ctx__")))) 251 | ), 252 | enif_make_copy(env,arglist))); 253 | vm->ticker(ref); 254 | } 255 | -------------------------------------------------------------------------------- /c_src/erlv8_stop.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(StopTickHandler) { 4 | TRACE("(%p) stop - 1\n", vm->isolate); 5 | v8::Isolate::Scope iscope(vm->isolate); 6 | TRACE("(%p) stop - 2\n", vm->isolate); 7 | v8::HandleScope handle_scope; 8 | TRACE("(%p) stop - 3\n", vm->isolate); 9 | TickHandlerResolution result; 10 | TRACE("(%p) stop - 4\n", vm->isolate); 11 | v8::V8::TerminateExecution(); 12 | TRACE("(%p) stop - 5\n", vm->isolate); 13 | result.value = v8::Undefined(); 14 | TRACE("(%p) stop - 6\n", vm->isolate); 15 | result.type = RETURN; 16 | TRACE("(%p) stop - 7\n", vm->isolate); 17 | return result; 18 | } 19 | -------------------------------------------------------------------------------- /c_src/erlv8_string.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(ToStringTickHandler) { 4 | ErlNifEnv *ref_env = enif_alloc_env(); 5 | ERL_NIF_TERM to_string_ref = enif_make_copy(ref_env, tick_ref); 6 | SEND(vm->server, 7 | enif_make_tuple3(env, 8 | enif_make_atom(env,"result"), 9 | enif_make_copy(env,to_string_ref), 10 | js_to_term(vm->context, vm->isolate, env,term_to_js(vm->context, vm->isolate, vm->env,array[1])->ToString()))); 11 | enif_free_env(ref_env); 12 | TickHandlerResolution result; 13 | result.type = DONE; 14 | return result; 15 | }; 16 | 17 | TickHandler(ToDetailStringTickHandler) { 18 | ErlNifEnv *ref_env = enif_alloc_env(); 19 | ERL_NIF_TERM to_string_ref = enif_make_copy(ref_env, tick_ref); 20 | SEND(vm->server, 21 | enif_make_tuple3(env, 22 | enif_make_atom(env,"result"), 23 | enif_make_copy(env,to_string_ref), 24 | js_to_term(vm->context, vm->isolate, env,term_to_js(vm->context,vm->isolate,vm->env,array[1])->ToDetailString()))); 25 | enif_free_env(ref_env); 26 | TickHandlerResolution result; 27 | result.type = DONE; 28 | return result; 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /c_src/erlv8_taint.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(TaintTickHandler) { 4 | TRACE("(%p) tain - 1\n", vm->isolate); 5 | ErlNifEnv *ref_env = enif_alloc_env(); 6 | TRACE("(%p) tain - 2\n", vm->isolate); 7 | ERL_NIF_TERM taint_ref = enif_make_copy(ref_env, tick_ref); 8 | TRACE("(%p) tain - 3\n", vm->isolate); 9 | LHCS(vm->isolate, vm->context); 10 | TRACE("(%p) tain - 4\n", vm->isolate); 11 | SEND(vm->server, 12 | enif_make_tuple3(env, 13 | enif_make_atom(env,"result"), 14 | enif_make_copy(env,taint_ref), 15 | js_to_term(vm->context, vm->isolate, env,term_to_js(vm->context, vm->isolate,vm->env, array[1])))); 16 | 17 | enif_free_env(ref_env); 18 | TickHandlerResolution result; 19 | result.type = DONE; 20 | return result; 21 | } 22 | -------------------------------------------------------------------------------- /c_src/erlv8_term.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | int enif_is_proplist(ErlNifEnv * env, ERL_NIF_TERM term) 4 | { 5 | ERL_NIF_TERM head, tail; 6 | ERL_NIF_TERM current = term; 7 | int arity; 8 | ERL_NIF_TERM *array; 9 | ErlNifBinary string_binary; 10 | 11 | if (!enif_is_list(env,term)) { 12 | return 0; 13 | } 14 | while (enif_get_list_cell(env, current, &head, &tail)) { 15 | if (!enif_is_tuple(env,head)) return 0; // not a tuple -> not a proplist 16 | enif_get_tuple(env,head,&arity,(const ERL_NIF_TERM **)&array); 17 | if (arity != 2) return 0; // does not consist of two elements -> not a proplist 18 | if (enif_is_list(env, array[0])) { 19 | unsigned len; 20 | enif_get_list_length(env, array[0], &len); 21 | char * str = (char *) malloc(len + 1); 22 | if (!enif_get_string(env, array[0], str, len + 1, ERL_NIF_LATIN1)) { 23 | free(str); 24 | return 0; 25 | } 26 | free(str); 27 | } else if (!enif_is_atom(env, array[0])) { 28 | if (enif_inspect_iolist_as_binary(env, array[0], &string_binary)) { // string 29 | return 1; 30 | } 31 | return 0; 32 | } 33 | 34 | current = tail; 35 | } 36 | return 1; 37 | } 38 | 39 | v8::PropertyAttribute term_to_property_attribute(ErlNifEnv * env, ERL_NIF_TERM term) { 40 | unsigned len; 41 | char name[MAX_ATOM_LEN]; 42 | 43 | if (enif_is_atom(env, term)) { 44 | v8::PropertyAttribute property_attribute; 45 | enif_get_atom_length(env, term, &len, ERL_NIF_LATIN1); 46 | enif_get_atom(env,term, (char *) &name,len + 1, ERL_NIF_LATIN1); 47 | if (!strcmp(name,"none")) { 48 | property_attribute = v8::None; 49 | } else if (!strcmp(name,"readonly")) { 50 | property_attribute = v8::ReadOnly; 51 | } else if (!strcmp(name,"dontenum")) { 52 | property_attribute = v8::DontEnum; 53 | } else if (!strcmp(name,"dontdelete")) { 54 | property_attribute = v8::DontDelete; 55 | } else { // always return something 56 | property_attribute = v8::None; 57 | } 58 | return property_attribute; 59 | } else if (enif_is_list(env, term)) { 60 | ERL_NIF_TERM current = term; 61 | ERL_NIF_TERM head, tail; 62 | v8::PropertyAttribute property_attribute = v8::None; 63 | while (enif_get_list_cell(env, current, &head, &tail)) { 64 | property_attribute = (v8::PropertyAttribute) (property_attribute | term_to_property_attribute(env,head)); 65 | current = tail; 66 | } 67 | return property_attribute; 68 | } else { 69 | return v8::None; 70 | } 71 | } 72 | 73 | void weak_external_cleaner(v8::Persistent object, void * data) { 74 | if (object.IsNearDeath()) { 75 | term_ref_t * term_ref = (term_ref_t *) v8::External::Unwrap(v8::Handle::Cast(object)); 76 | enif_free_env(term_ref->env); 77 | enif_free(term_ref); 78 | object.Dispose(); 79 | object.Clear(); 80 | v8::V8::AdjustAmountOfExternalAllocatedMemory(-(long)sizeof(term_ref_t)); 81 | } 82 | } 83 | 84 | inline v8::Handle term_to_external(ERL_NIF_TERM term) { 85 | v8::HandleScope handle_scope; 86 | term_ref_t * term_ref = (term_ref_t *) enif_alloc(sizeof(term_ref_t)); 87 | term_ref->env = enif_alloc_env(); 88 | term_ref->term = enif_make_copy(term_ref->env, term); 89 | v8::Persistent obj = v8::Persistent::New(v8::External::New(term_ref)); 90 | obj.MakeWeak(NULL,weak_external_cleaner); 91 | v8::V8::AdjustAmountOfExternalAllocatedMemory((long)sizeof(term_ref_t)); 92 | return obj; 93 | } 94 | 95 | inline ERL_NIF_TERM external_to_term(v8::Handle val) { 96 | term_ref_t * term_ref = (term_ref_t *) v8::External::Unwrap(v8::Handle::Cast(val)); 97 | return term_ref->term; 98 | } 99 | 100 | v8::Handle externalize_term(map, cmp_erl_nif_term> cache, v8::Handle proto, ERL_NIF_TERM term) { 101 | v8::HandleScope handle_scope; 102 | map, cmp_erl_nif_term>::iterator iter = cache.find(term); 103 | 104 | if (iter != cache.end()) { 105 | return handle_scope.Close(iter->second); // it was cached before 106 | } else { 107 | v8::Handle external = term_to_external(term); 108 | v8::Handle obj = v8::Object::New(); 109 | obj->SetPrototype(proto); 110 | obj->SetHiddenValue(v8::String::New("__erlv8__"), external); 111 | cache.insert(std::pair >(term, obj)); // cache it 112 | return handle_scope.Close(obj); 113 | } 114 | 115 | } 116 | 117 | v8::Handle term_to_js(v8::Handle ctx, v8::Isolate* isolate, ErlNifEnv *env, ERL_NIF_TERM term) { 118 | TRACE("(%p) term_to_js - 1\n", isolate); 119 | LHCS(isolate, ctx); 120 | TRACE("(%p) term_to_js - 2\n", isolate); 121 | int _int; unsigned int _uint; long _long; unsigned long _ulong; ErlNifSInt64 _int64; ErlNifUInt64 _uint64; double _double; 122 | ErlNifBinary string_binary; 123 | unsigned len; 124 | char name[MAX_ATOM_LEN]; 125 | 126 | if (enif_is_atom(env, term)) { 127 | TRACE("(%p) term_to_js - 3 a\n", isolate); 128 | enif_get_atom_length(env, term, &len, ERL_NIF_LATIN1); 129 | enif_get_atom(env, term, (char *) &name,len + 1, ERL_NIF_LATIN1); 130 | v8::Handle result; 131 | // check for special atoms 132 | if (strcmp(name,"false")==0) { 133 | result = v8::Local::New(v8::Boolean::New(0)); 134 | } else if (strcmp(name,"true")==0) { 135 | result = v8::Local::New(v8::Boolean::New(1)); 136 | } else if (strcmp(name,"ok")==0) { 137 | result = v8::Local::New(v8::Boolean::New(1)); 138 | } else if (strcmp(name,"undefined")==0) { 139 | result = v8::Undefined(); 140 | } else if (strcmp(name,"null")==0) { 141 | result = v8::Null(); 142 | } else { // if it is not a special atom, convert it to a string 143 | result = v8::String::New(name); 144 | } 145 | return handle_scope.Close(result); 146 | } else if (enif_get_int(env,term,&_int)) { 147 | TRACE("(%p) term_to_js - 3 b\n", isolate); 148 | return handle_scope.Close(v8::Local::New(v8::Integer::New(_int))); 149 | } else if (enif_get_uint(env,term,&_uint)) { 150 | TRACE("(%p) term_to_js - 3 c\n", isolate); 151 | return handle_scope.Close(v8::Local::New(v8::Integer::NewFromUnsigned(_uint))); 152 | } else if (enif_get_long(env,term,&_long)) { 153 | TRACE("(%p) term_to_js - 3 d\n", isolate); 154 | return handle_scope.Close(v8::Local::New(v8::Number::New(_long))); 155 | } else if (enif_get_ulong(env,term,&_ulong)) { 156 | TRACE("(%p) term_to_js - 3 e\n", isolate); 157 | return handle_scope.Close(v8::Local::New(v8::Number::New(_ulong))); 158 | } else if (enif_get_int64(env,term,&_int64)) { 159 | TRACE("(%p) term_to_js - 3 f\n", isolate); 160 | return handle_scope.Close(v8::Local::New(v8::Number::New(_int64))); 161 | } else if (enif_get_uint64(env,term,&_uint64)) { 162 | TRACE("(%p) term_to_js - 3 g\n", isolate); 163 | return handle_scope.Close(v8::Local::New(v8::Number::New(_uint64))); 164 | } else if (enif_get_double(env,term,&_double)) { 165 | TRACE("(%p) term_to_js - 3 h\n", isolate); 166 | return handle_scope.Close(v8::Local::New(v8::Number::New(_double))); 167 | } else if (enif_inspect_iolist_as_binary(env, term, &string_binary)) { // string 168 | TRACE("(%p) term_to_js - 3 i\n", isolate); 169 | v8::Local s = v8::String::New((const char *)string_binary.data, string_binary.size); 170 | if (s->Utf8Length() != string_binary.size) 171 | printf("%d != %lu\n", s->Utf8Length()-1, string_binary.size); 172 | return handle_scope.Close(s); 173 | } else if (enif_is_tuple(env, term)) { 174 | TRACE("(%p) term_to_js - 3 j\n", isolate); 175 | ERL_NIF_TERM *array; 176 | int arity; 177 | enif_get_tuple(env,term,&arity,(const ERL_NIF_TERM **)&array); 178 | if (arity == 3) { 179 | enif_get_atom_length(env, array[0], &len, ERL_NIF_LATIN1); 180 | enif_get_atom(env,array[0], (char *) &name,len + 1, ERL_NIF_LATIN1); 181 | val_res_t *res; 182 | // check if it is a v8_fun 183 | int isv8fun = strcmp(name,"erlv8_fun")==0; 184 | // check if it is an object 185 | int isobj = strcmp(name,"erlv8_object")==0; 186 | // check if it is an array 187 | int isarray = strcmp(name,"erlv8_array")==0; 188 | 189 | if (isobj||isarray) { 190 | if (enif_get_resource(env,array[1],val_resource,(void **)(&res))) { 191 | return handle_scope.Close(res->val->ToObject()); 192 | } else if (isobj && enif_is_proplist(env,array[1])) { 193 | v8::Local obj = v8::Object::New(); 194 | ERL_NIF_TERM head, tail; 195 | ERL_NIF_TERM current = array[1]; 196 | int arity; 197 | ERL_NIF_TERM *arr; 198 | while (enif_get_list_cell(env, current, &head, &tail)) { 199 | enif_get_tuple(env,head,&arity,(const ERL_NIF_TERM **)&arr); 200 | obj->Set(term_to_js(ctx, isolate, env,arr[0]), 201 | term_to_js(ctx, isolate, env,arr[1])); 202 | 203 | current = tail; 204 | } 205 | return handle_scope.Close(obj); 206 | } else if (isarray && enif_is_list(env, array[1])) { 207 | unsigned int i,alen; 208 | ERL_NIF_TERM head, tail; 209 | ERL_NIF_TERM current = array[1]; 210 | 211 | enif_get_list_length(env, current, &alen); 212 | 213 | v8::Local arrobj = v8::Array::New(alen); 214 | 215 | i = 0; 216 | while (enif_get_list_cell(env, current, &head, &tail)) { 217 | arrobj->Set(v8::Integer::New(i), term_to_js(ctx, isolate, env,head)); 218 | current = tail; 219 | i++; 220 | } 221 | return handle_scope.Close(arrobj); 222 | } 223 | } 224 | 225 | if ((isv8fun) && 226 | (enif_get_resource(env,array[1],val_resource,(void **)(&res)))){ 227 | return handle_scope.Close(res->val); 228 | } else if ((isv8fun) && (enif_is_fun(env, array[1]))) { 229 | v8::Handle f = v8::Handle::Cast(term_to_js(ctx, isolate, env, array[1])); 230 | v8::Handle o = v8::Handle::Cast(term_to_js(ctx, isolate, env, array[2])); 231 | 232 | v8::Local keys = o->GetPropertyNames(); 233 | 234 | for (unsigned int i=0;iLength();i++) { 235 | v8::Local key = keys->Get(v8::Integer::New(i)); 236 | f->Set(key,o->Get(key)); 237 | } 238 | 239 | return handle_scope.Close(f); 240 | 241 | } 242 | 243 | } 244 | 245 | if (arity == 2) { 246 | enif_get_atom_length(env, array[0], &len, ERL_NIF_LATIN1); 247 | enif_get_atom(env,array[0], (char *) &name,len + 1, ERL_NIF_LATIN1); 248 | // check if it is an error 249 | int iserror = strcmp(name,"error")==0; 250 | int isthrow = strcmp(name,"throw")==0; 251 | if (iserror) { 252 | return handle_scope.Close(v8::Exception::Error(v8::Handle::Cast(term_to_js(ctx, isolate, env,array[1])))); 253 | } 254 | if (isthrow) { 255 | return v8::ThrowException(term_to_js(ctx, isolate, env, array[1])); 256 | } 257 | } 258 | 259 | } else if (enif_is_fun(env, term)) { 260 | TRACE("(%p) term_to_js - 3 k\n", isolate); 261 | VM * vm = (VM *) v8::External::Unwrap(v8::Context::GetCurrent()->Global()->GetHiddenValue(v8::String::New("__erlv8__"))); 262 | map, cmp_erl_nif_term>::iterator iter = vm->fun_map.find(term); 263 | 264 | if (iter != vm->fun_map.end()) { 265 | return handle_scope.Close(iter->second->GetFunction()); // it was cached before 266 | } else { 267 | v8::Handle external = term_to_external(term); 268 | v8::Persistent t = v8::Persistent::New(v8::FunctionTemplate::New(WrapFun,external)); 269 | 270 | v8::Local f = t->GetFunction(); 271 | f->SetHiddenValue(vm->string__erlv8__, external); 272 | 273 | vm->fun_map.insert(std::pair >(external_to_term(external), t)); // cache it 274 | return handle_scope.Close(f); 275 | } 276 | } else if (enif_is_pid(env, term)) { 277 | VM * vm = (VM *) v8::External::Unwrap(v8::Context::GetCurrent()->Global()->GetHiddenValue(v8::String::New("__erlv8__"))); 278 | return handle_scope.Close(externalize_term(vm->extern_map, vm->external_proto_pid, term)); 279 | } else if (enif_is_ref(env, term)) { 280 | VM * vm = (VM *) v8::External::Unwrap(v8::Context::GetCurrent()->Global()->GetHiddenValue(v8::String::New("__erlv8__"))); 281 | return handle_scope.Close(externalize_term(vm->extern_map, vm->external_proto_ref, term)); 282 | } 283 | 284 | return v8::Undefined(); // if nothing else works, return undefined 285 | }; 286 | 287 | 288 | ERL_NIF_TERM js_to_term(v8::Handle ctx, v8::Isolate* isolate, ErlNifEnv *env, v8::Handle val) { 289 | TRACE("(%p) js_to_term - 1\n", isolate); 290 | LHCS(isolate, ctx); 291 | TRACE("(%p) js_to_term - 2\n", isolate); 292 | if (val.IsEmpty()) { 293 | TRACE("(%p) js_to_term - 3 a\n", isolate); 294 | return enif_make_atom(env,"undefined"); 295 | } else if (val->IsFunction()) { // the reason why this check is so high up here is because it is also an object, so it should be before any object. 296 | TRACE("(%p) js_to_term - 3 b\n", isolate); 297 | val_res_t *ptr; 298 | TRACE("(%p) js_to_term - 4\n", isolate); 299 | v8::Handle fun = v8::Handle::Cast(val); 300 | ERL_NIF_TERM resource_term; 301 | TRACE("(%p) js_to_term - 5\n", isolate); 302 | ptr = (val_res_t *)enif_alloc_resource(val_resource, sizeof(val_res_t)); 303 | TRACE("(%p) js_to_term - 6\n", isolate); 304 | ptr->ctx = v8::Persistent::New(v8::Context::GetCurrent()); 305 | TRACE("(%p) js_to_term - 7\n", isolate); 306 | ptr->val = v8::Persistent::New(v8::Handle::Cast(val)); 307 | TRACE("(%p) js_to_term - 8\n", isolate); 308 | resource_term = enif_make_resource(env, ptr); 309 | TRACE("(%p) js_to_term - 9\n", isolate); 310 | enif_release_resource(ptr); 311 | TRACE("(%p) js_to_term - 10\n", isolate); 312 | VM * vm = (VM *) v8::External::Unwrap(v8::Context::GetCurrent()->Global()->GetHiddenValue(v8::String::New("__erlv8__"))); 313 | TRACE("(%p) js_to_term - 11\n", isolate); 314 | ERL_NIF_TERM term = enif_make_tuple3(env,enif_make_atom(env,"erlv8_fun"), 315 | resource_term, 316 | enif_make_pid(env, vm->server)); 317 | TRACE("(%p) js_to_term - 11\n", isolate); 318 | 319 | return term; 320 | } else if (val->IsUndefined()) { 321 | TRACE("(%p) js_to_term - 3 c\n", isolate); 322 | return enif_make_atom(env,"undefined"); 323 | } else if (val->IsNull()) { 324 | TRACE("(%p) js_to_term - 3 d\n", isolate); 325 | return enif_make_atom(env,"null"); 326 | } else if (val->IsTrue()) { 327 | TRACE("(%p) js_to_term - 3 c\n", isolate); 328 | return enif_make_atom(env,"true"); 329 | } else if (val->IsFalse()) { 330 | TRACE("(%p) js_to_term - 3 d\n", isolate); 331 | return enif_make_atom(env,"false"); 332 | } else if (val->IsString()) { 333 | TRACE("(%p) js_to_term - 3 e\n", isolate); 334 | ErlNifBinary result_binary = {0}; 335 | { 336 | /* v8::Local S = val->ToString(); 337 | v8::String::Utf8Value V = v8::String::Utf8Value(val->ToString());*/ 338 | /* enif_alloc_binary(S->Utf8Length(), &result_binary); 339 | S->WriteUtf8((char *) result_binary.data, result_binary.size);*/ 340 | } 341 | /* enif_alloc_binary(v8::String::Utf8Value(val->ToString()).length(), &result_binary); 342 | (void)memcpy(result_binary.data, *v8::String::Utf8Value(val->ToString()), result_binary.size);*/ 343 | 344 | enif_alloc_binary(v8::String::Utf8Value(val).length(), &result_binary); 345 | (void)memcpy(result_binary.data, *v8::String::Utf8Value(val), result_binary.size); 346 | 347 | 348 | return enif_make_binary(env, &result_binary); 349 | } else if (val->IsInt32()) { 350 | TRACE("(%p) js_to_term - 3 f\n", isolate); 351 | return enif_make_long(env,val->ToInt32()->Value()); 352 | } else if (val->IsUint32()) { 353 | TRACE("(%p) js_to_term - 3 g\n", isolate); 354 | return enif_make_int64(env,val->ToUint32()->Value()); 355 | } else if (val->IsNumber()) { 356 | TRACE("(%p) js_to_term - 3 h\n", isolate); 357 | double d = val->ToNumber()->Value(); 358 | if (d == round(d)) { 359 | return enif_make_int64(env,d); 360 | } else { 361 | return enif_make_double(env,d); 362 | } 363 | } else if (val->IsArray()) { 364 | TRACE("(%p) js_to_term - 3 i\n", isolate); 365 | val_res_t *ptr; 366 | v8::Handle arr = v8::Handle::Cast(val); 367 | ERL_NIF_TERM resource_term; 368 | ptr = (val_res_t *)enif_alloc_resource(val_resource, sizeof(val_res_t)); 369 | ptr->val = v8::Persistent::New(v8::Handle::Cast(val)); 370 | ptr->ctx = v8::Persistent::New(v8::Context::GetCurrent()); 371 | resource_term = enif_make_resource(env, ptr); 372 | enif_release_resource(ptr); 373 | 374 | VM * vm = (VM *) v8::External::Unwrap(v8::Context::GetCurrent()->Global()->GetHiddenValue(v8::String::New("__erlv8__"))); 375 | ERL_NIF_TERM term = enif_make_tuple3(env, 376 | enif_make_atom(env, "erlv8_array"), 377 | resource_term, 378 | enif_make_pid(env, vm->server) 379 | ); 380 | 381 | return term; 382 | } else if (val->IsObject()) { 383 | TRACE("(%p) js_to_term - 3 j\n", isolate); 384 | v8::Local obj = val->ToObject(); 385 | TRACE("(%p) js_to_term - 4\n", isolate); 386 | v8::Local c = v8::Context::GetCurrent(); 387 | TRACE("(%p) js_to_term - 5\n", isolate); 388 | v8::Local g = c->Global(); 389 | TRACE("(%p) js_to_term - 6\n", isolate); 390 | v8::Local v = g->GetHiddenValue(v8::String::New("__erlv8__")); 391 | TRACE("(%p) js_to_term - 7\n", isolate); 392 | VM * vm = (VM *) v8::External::Unwrap(v); 393 | TRACE("(%p) js_to_term - 8\n", isolate); 394 | if (obj->GetPrototype()->Equals(vm->external_proto_num) || 395 | obj->GetPrototype()->Equals(vm->external_proto_atom) || 396 | obj->GetPrototype()->Equals(vm->external_proto_bin) || 397 | obj->GetPrototype()->Equals(vm->external_proto_ref) || 398 | obj->GetPrototype()->Equals(vm->external_proto_fun) || 399 | obj->GetPrototype()->Equals(vm->external_proto_port) || 400 | obj->GetPrototype()->Equals(vm->external_proto_pid) || 401 | obj->GetPrototype()->Equals(vm->external_proto_tuple) || 402 | obj->GetPrototype()->Equals(vm->external_proto_list)) { 403 | TRACE("(%p) js_to_term - 7\n", isolate); 404 | return enif_make_copy(env, external_to_term(v8::Handle::Cast(obj->GetHiddenValue(vm->string__erlv8__)))); 405 | } else { 406 | TRACE("(%p) js_to_term - 3 k\n", isolate); 407 | ERL_NIF_TERM resource_term; 408 | 409 | val_res_t *ptr; 410 | ptr = (val_res_t *)enif_alloc_resource(val_resource, sizeof(val_res_t)); 411 | ptr->val = v8::Persistent::New(v8::Handle::Cast(val)); 412 | ptr->ctx = v8::Persistent::New(v8::Context::GetCurrent()); 413 | resource_term = enif_make_resource(env, ptr); 414 | enif_release_resource(ptr); 415 | 416 | ERL_NIF_TERM term = enif_make_tuple3(env, 417 | enif_make_atom(env, "erlv8_object"), 418 | resource_term, 419 | enif_make_pid(env, vm->server) 420 | ); 421 | 422 | return term; 423 | } 424 | } else { 425 | return enif_make_atom(env,"$unknown"); 426 | } 427 | }; 428 | -------------------------------------------------------------------------------- /c_src/erlv8_unknown.cc: -------------------------------------------------------------------------------- 1 | #include "erlv8.hh" 2 | 3 | TickHandler(UnknownTickHandler) { 4 | TickHandlerResolution result; 5 | result.type = DONE; 6 | return result; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /include/erlv8.hrl: -------------------------------------------------------------------------------- 1 | -record(erlv8_fun_invocation, { 2 | is_construct_call = false, 3 | holder, 4 | this, 5 | ref, 6 | vm, 7 | ctx 8 | }). 9 | 10 | -define(V8Obj(X),erlv8_object:new(X)). 11 | -define(V8Arr(X),erlv8_array:new(X)). 12 | 13 | -record(erlv8_object, { resource, vm }). 14 | -record(erlv8_fun, { resource, vm }). 15 | -record(erlv8_array, {resource, %% or array() 16 | vm}). %% or proplist() 17 | 18 | 19 | -define(is_v8(X), (is_record(X, erlv8_object) orelse is_record(X, erlv8_fun) orelse is_record(X, erlv8_array))). 20 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beamjs/erlv8/157a7db0a9c284ea1da107fe3272ff7b19813d26/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {lib_dirs, ["deps/eqc/ebin"]}. 2 | {erl_opts, [{i, ".."},{i,"deps"},{i,"../proper/include"},{i,"deps/proper/include"}]}. 3 | {port_sources,["c_src/*.cc"]}. 4 | {port_envs, 5 | [{"DRV_LDFLAGS","deps/v8/libv8.a deps/zeromq2/src/.libs/libzmq.a -shared $ERL_LDFLAGS -lstdc++ -luuid"}, 6 | {"darwin", "DRV_LDFLAGS", "deps/v8/libv8.a deps/zeromq2/src/.libs/libzmq.a -bundle -flat_namespace -undefined suppress $ERL_LDFLAGS"}, 7 | {"DRV_CFLAGS","-Ic_lib/utf8/source -Ic_lib/utf8/source/utf8 -Ic_src -Ideps/v8/include -Ideps/zeromq2/include -g -Wall -fPIC $ERL_CFLAGS $EXTRA_CFLAGS"}]}. 8 | {deps, 9 | [ 10 | {proper, ".*", {git, "https://github.com/manopapad/proper.git", {branch, "master"}}}, 11 | {proper_stdlib, ".*", {git, "https://github.com/spawngrid/proper_stdlib.git", {branch, "master"}}} 12 | ]}. 13 | -------------------------------------------------------------------------------- /src/erlv8.app.src: -------------------------------------------------------------------------------- 1 | {application, erlv8, 2 | [ 3 | {description, "Erlang V8 interface"}, 4 | {vsn, git}, 5 | {registered, []}, 6 | {modules, [erlv8, erlv8_app, erlv8_fun, erlv8_object, erlv8_context, erlv8_array, erlv8_module, erlv8_fun_invocation, erlv8_vm, erlv8_nif, erlv8_extern, erlv8_sup]}, 7 | {applications, [ 8 | kernel, 9 | stdlib 10 | ]}, 11 | {mod, { erlv8_app, []}}, 12 | {env, []} 13 | ]}. 14 | -------------------------------------------------------------------------------- /src/erlv8.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8). 2 | -export([start/0,stop/0]). 3 | -include_lib("erlv8/include/erlv8.hrl"). 4 | 5 | start() -> 6 | application:start(erlv8). 7 | 8 | stop() -> 9 | application:stop(erlv8). 10 | 11 | %% TESTS 12 | -include_lib("eunit/include/eunit.hrl").% 13 | -ifdef(TEST). 14 | 15 | suppress_kernel_logger_test() -> 16 | % not a test, obviously 17 | error_logger:delete_report_handler(error_logger_tty_h). 18 | 19 | valid_vm_creation_test() -> 20 | start(), 21 | {ok, VM} = erlv8_vm:start(), 22 | ?assert(is_pid(VM)), 23 | ?assertEqual({ok, 2}, erlv8_vm:run(VM,"1+1;")), 24 | ok = stop(). 25 | 26 | few_vms_test() -> 27 | start(), 28 | {ok, VM} = erlv8_vm:start(), 29 | ?assertEqual({ok,2}, erlv8_vm:run(VM,"1+1;")), 30 | ?assertEqual({ok,4}, erlv8_vm:run(VM,"2*2;")), 31 | stop(). 32 | 33 | 34 | compilation_error_test() -> 35 | start(), 36 | {ok, VM} = erlv8_vm:start(), 37 | ?assertMatch({throw, _}, erlv8_vm:run(VM,"1+;")), 38 | stop(). 39 | 40 | vm_stopping_test() -> 41 | start(), 42 | {ok, VM} = erlv8_vm:start(), 43 | erlv8_vm:stop(VM), 44 | timer:sleep(100), %% allow time for process to stop 45 | ?assertEqual(false,erlang:is_process_alive(VM)), 46 | stop(). 47 | 48 | vm_global_test() -> 49 | start(), 50 | {ok, VM} = erlv8_vm:start(), 51 | erlv8_vm:run(VM,"var a = 1+1;"), 52 | Global = erlv8_vm:global(VM), 53 | ?assertEqual([{<<"a">>,2}],Global:proplist()), 54 | stop(). 55 | 56 | vm_set_global_test() -> 57 | start(), 58 | {ok, VM} = erlv8_vm:start(), 59 | Global = erlv8_vm:global(VM), 60 | Global:set_value("a",1), 61 | erlv8_vm:run(VM,"var b = a+1;"), 62 | ?assertEqual([{<<"a">>,1},{<<"b">>,2}],Global:proplist()), 63 | stop(). 64 | 65 | term_to_js_string_test() -> 66 | start(), 67 | {ok, VM} = erlv8_vm:start(), 68 | Obj = erlv8_vm:taint(VM, "abc"), 69 | ?assertEqual(<<"abc">>,Obj), 70 | stop(). 71 | 72 | term_to_js_binary_test() -> 73 | start(), 74 | {ok, VM} = erlv8_vm:start(), 75 | Obj = erlv8_vm:taint(VM, <<"abc">>), 76 | ?assertEqual(<<"abc">>,Obj), 77 | stop(). 78 | 79 | term_to_js_iolist_test() -> 80 | start(), 81 | {ok, VM} = erlv8_vm:start(), 82 | Obj = erlv8_vm:taint(VM, [<<"abc">>,$d,"ef"]), 83 | ?assertEqual(<<"abcdef">>,Obj), 84 | stop(). 85 | 86 | term_to_js_object_test() -> 87 | start(), 88 | {ok, VM} = erlv8_vm:start(), 89 | Obj = erlv8_vm:taint(VM,?V8Obj([{"a",1},{"b","c"},{<<"c">>,<<"d">>}])), 90 | ?assertMatch([{<<"a">>,1},{<<"b">>,<<"c">>},{<<"c">>,<<"d">>}],Obj:proplist()), 91 | stop(). 92 | 93 | term_to_js_boolean_test() -> 94 | start(), 95 | {ok, VM} = erlv8_vm:start(), 96 | ?assertEqual(true, erlv8_vm:taint(VM,true)), 97 | ?assertEqual(false, erlv8_vm:taint(VM,false)), 98 | stop(). 99 | 100 | term_to_js_atom_test() -> 101 | start(), 102 | {ok, VM} = erlv8_vm:start(), 103 | ?assertEqual(<<"a">>, erlv8_vm:taint(VM,a)), 104 | ?assertEqual(<<"b">>, erlv8_vm:taint(VM,b)), 105 | stop(). 106 | 107 | term_to_js_undefined_test() -> 108 | start(), 109 | {ok, VM} = erlv8_vm:start(), 110 | ?assertEqual(undefined, erlv8_vm:taint(VM,undefined)), 111 | stop(). 112 | 113 | term_to_js_ok_test() -> 114 | start(), 115 | {ok, VM} = erlv8_vm:start(), 116 | ?assertEqual(true, erlv8_vm:taint(VM,ok)), 117 | stop(). 118 | 119 | term_to_js_null_test() -> 120 | start(), 121 | {ok, VM} = erlv8_vm:start(), 122 | ?assertEqual(null, erlv8_vm:taint(VM,null)), 123 | stop(). 124 | 125 | term_to_js_number_test() -> 126 | start(), 127 | {ok, VM} = erlv8_vm:start(), 128 | Nums = [2147483648,-2147483649,1,4294967296,4294967297,3.555], 129 | [ ?assertEqual(N, erlv8_vm:taint(VM,N)) || N <- Nums ], 130 | stop(). 131 | 132 | term_to_js_array_test() -> 133 | start(), 134 | {ok, VM} = erlv8_vm:start(), 135 | A1 = erlv8_vm:taint(VM,?V8Arr([1,2,3])), 136 | ?assertEqual([1,2,3],A1:list()), 137 | A2 = erlv8_vm:taint(VM,?V8Arr([])), 138 | ?assertEqual([],A2:list()), 139 | stop(). 140 | 141 | term_to_js_pid_test() -> 142 | start(), 143 | {ok, VM} = erlv8_vm:start(), 144 | ?assertEqual(self(), erlv8_vm:taint(VM,self())), 145 | ?assertEqual(self(), erlv8_vm:taint(VM,self())), % the second call is to ensure memory is managed properly (regression) 146 | stop(). 147 | 148 | term_to_js_ref_test() -> 149 | start(), 150 | {ok, VM} = erlv8_vm:start(), 151 | Ref = make_ref(), 152 | ?assertEqual(Ref, erlv8_vm:taint(VM,Ref)), 153 | stop(). 154 | 155 | term_to_js_unsupported_test() -> 156 | start(), 157 | {ok, VM} = erlv8_vm:start(), 158 | ?assertEqual(undefined,erlv8_vm:taint(VM,{this_tuple,is_not_supported})), 159 | stop(). 160 | 161 | term_to_js_object_invalid_proplist_test() -> 162 | start(), 163 | {ok, VM} = erlv8_vm:start(), 164 | ?assertEqual(undefined, erlv8_vm:taint(VM,?V8Obj([{"a",1},{b,2},{3,4}]))), 165 | stop(). 166 | 167 | 168 | js_to_term_fun_test() -> 169 | start(), 170 | {ok, VM} = erlv8_vm:start(), 171 | erlv8_vm:run(VM,"x = function () {}"), 172 | Global = erlv8_vm:global(VM), 173 | #erlv8_fun{vm=VM} = Global:get_value("x"), 174 | stop(). 175 | 176 | js_object_to_term_fun_test() -> 177 | start(), 178 | {ok, VM} = erlv8_vm:start(), 179 | erlv8_vm:run(VM,"x = function () {}; x.a = 1"), 180 | Global = erlv8_vm:global(VM), 181 | X = Global:get_value("x"), 182 | O = X:object(), 183 | ?assertEqual([{<<"a">>,1}],O:proplist()), 184 | stop(). 185 | 186 | term_to_js_object_fun_erlv8_fun_test() -> 187 | start(), 188 | {ok, VM} = erlv8_vm:start(), 189 | Global = erlv8_vm:global(VM), 190 | {ok, #erlv8_fun{vm=VM}=Fun} = erlv8_vm:run(VM,"x = function () {}; x.a = 1; x"), 191 | O = Fun:object(), 192 | ?assertEqual([{<<"a">>,1}],O:proplist()), 193 | Global:set_value("y",Fun), 194 | Y = Global:get_value("y"), 195 | YObj = Y:object(), 196 | ?assertEqual(1, YObj:get_value("a")), 197 | stop(). 198 | 199 | term_to_js_object_fun_test() -> 200 | start(), 201 | {ok, VM} = erlv8_vm:start(), 202 | Global = erlv8_vm:global(VM), 203 | Global:set_value("x",fun (#erlv8_fun_invocation{},[]) -> 123 end), 204 | X = Global:get_value("x"), 205 | XObj = X:object(), 206 | XObj:set_value("y",1), 207 | ?assertMatch({ok, 1}, erlv8_vm:run(VM,"x.y")), 208 | X0 = Global:get_value("x"), X1 = X0:object(), 209 | ?assertMatch(1, X1:get_value("y")), 210 | ?assertMatch({ok, 123}, erlv8_vm:run(VM,"x()")), 211 | stop(). 212 | 213 | term_to_js_error_test() -> 214 | start(), 215 | {ok, VM} = erlv8_vm:start(), 216 | Global = erlv8_vm:global(VM), 217 | Global:set_value("x",fun (#erlv8_fun_invocation{},[]) -> {throw, {error, "Hello"}} end), 218 | {throw, Exception} = erlv8_vm:run(VM,"x()"), 219 | ?assertEqual(<<"Hello">>, Exception:get_value("message")), 220 | Global:set_value("x",fun (#erlv8_fun_invocation{},[]) -> {throw, "Goodbye"} end), 221 | {throw, <<"Goodbye">>} = erlv8_vm:run(VM,"x()"), 222 | stop(). 223 | 224 | object_fun_test() -> 225 | start(), 226 | {ok, VM} = erlv8_vm:start(), 227 | {ok, Fun} = erlv8_vm:run(VM,"f = function() {}; f.y = 1; f"), 228 | FunObj = Fun:object(), 229 | ?assertEqual([{<<"y">>,1}],FunObj:proplist()), 230 | stop(). 231 | 232 | fun_obj_test() -> 233 | start(), 234 | {ok, VM} = erlv8_vm:start(), 235 | F = erlv8_vm:taint(VM, erlv8_fun:new(fun (#erlv8_fun_invocation{},[]) -> 1 end, erlv8_object:new([{"a",1}]))), 236 | FObj = F:object(), 237 | ?assertEqual(1,FObj:get_value("a")), 238 | stop(). 239 | 240 | invocation_test() -> 241 | start(), 242 | {ok, VM} = erlv8_vm:start(), 243 | Global = erlv8_vm:global(VM), 244 | Global:set_value("test", fun (#erlv8_fun_invocation{} = _Invocation,[]) -> 123 end), 245 | ?assertEqual({ok, 123}, erlv8_vm:run(VM,"test()")), 246 | stop(). 247 | 248 | fun_test() -> 249 | start(), 250 | {ok, VM} = erlv8_vm:start(), 251 | Global = erlv8_vm:global(VM), 252 | Global:set_value("test0", fun (#erlv8_fun_invocation{} = _Invocation, [F]) -> F:call([321]) end), 253 | Global:set_value("test", fun (#erlv8_fun_invocation{} = _Invocation,[Val]) -> Val end), 254 | ?assertEqual({ok, 321}, erlv8_vm:run(VM,"f = function(x) { return test(x) }; test0(f);")), 255 | stop(). 256 | 257 | 258 | erlang_fun_test() -> 259 | start(), 260 | {ok, VM} = erlv8_vm:start(), 261 | Global = erlv8_vm:global(VM), 262 | Global:set_value("test", fun (#erlv8_fun_invocation{} = _Invocation,[Val]) -> Val end), 263 | T = Global:get_value("test"), 264 | ?assertEqual(321, T:call([321])), 265 | stop(). 266 | 267 | erlang_fun_call_on_this_test() -> 268 | start(), 269 | {ok, VM} = erlv8_vm:start(), 270 | Global = erlv8_vm:global(VM), 271 | Global:set_value("test", fun (#erlv8_fun_invocation{} = _Invocation, []) -> 321 end), 272 | ?assertEqual(321, (Global:get_value("test")):call(?V8Obj([]))), 273 | stop(). 274 | 275 | 276 | fun_fail_test() -> 277 | start(), 278 | {ok, VM} = erlv8_vm:start(), 279 | Global = erlv8_vm:global(VM), 280 | Global:set_value("test", fun (#erlv8_fun_invocation{} = _Invocation,[Val]) -> Val end), 281 | ?assertMatch({throw, _},erlv8_vm:run(VM,"test();")), 282 | stop(). 283 | 284 | fun_fail_inside_badmatch_test() -> %% TODO: cover all standard exits? 285 | start(), 286 | {ok, VM} = erlv8_vm:start(), 287 | Global = erlv8_vm:global(VM), 288 | Global:set_value("test", fun (#erlv8_fun_invocation{} = _Invocation,[Val]) -> ok = Val end), 289 | ?assertMatch({throw, _}, erlv8_vm:run(VM,"test('help');")), 290 | stop(). 291 | 292 | 293 | fun_vm_is_pid_test() -> 294 | start(), 295 | {ok, VM} = erlv8_vm:start(), 296 | Global = erlv8_vm:global(VM), 297 | Global:set_value("test", fun (#erlv8_fun_invocation{ vm = VM1 } = _Invocation,[]) -> is_pid(VM1) end), 298 | ?assertEqual({ok, true}, erlv8_vm:run(VM,"test();")), 299 | stop(). 300 | 301 | fun_returning_fun_test() -> 302 | start(), 303 | {ok, VM} = erlv8_vm:start(), 304 | Global = erlv8_vm:global(VM), 305 | Global:set_value("test", fun (#erlv8_fun_invocation{} = _Invocation,[Val]) -> Val end), 306 | {ok, #erlv8_fun{vm=VM}=F} = erlv8_vm:run(VM,"f = function() {}; test(f);"), 307 | O = F:object(), 308 | ?assertEqual([],O:proplist()), 309 | stop(). 310 | 311 | fun_new_vm_inside_test() -> 312 | start(), 313 | {ok, VM} = erlv8_vm:start(), 314 | Global = erlv8_vm:global(VM), 315 | Global:set_value("test", fun (#erlv8_fun_invocation{} = _Invocation,[]) -> {ok, _Pid} = erlv8_vm:start(), 321 end), 316 | ?assertEqual({ok, 321},erlv8_vm:run(VM, "test()")), 317 | stop(). 318 | 319 | fun_this_test() -> 320 | start(), 321 | {ok, VM} = erlv8_vm:start(), 322 | Global = erlv8_vm:global(VM), 323 | Global:set_value("x",fun (#erlv8_fun_invocation{}=I,[]) -> I:this() end), 324 | {ok, Result} = erlv8_vm:run(VM,"x()"), 325 | ?assertEqual(Global:proplist(), Result:proplist()), 326 | stop(). 327 | 328 | fun_is_construct_call_test() -> 329 | start(), 330 | {ok, VM} = erlv8_vm:start(), 331 | Global = erlv8_vm:global(VM), 332 | Global:set_value("x",fun (#erlv8_fun_invocation{}=I,[]) -> I:is_construct_call() end), 333 | ?assertEqual({ok, false}, erlv8_vm:run(VM,"x()")), 334 | Global:set_value("x",fun (#erlv8_fun_invocation{ this = This }=I,[]) -> This:set_value("icc",I:is_construct_call()) end), 335 | ?assertEqual({ok, true}, erlv8_vm:run(VM,"new x().icc")), 336 | stop(). 337 | 338 | fun_global_test() -> 339 | start(), 340 | {ok, VM} = erlv8_vm:start(), 341 | Global = erlv8_vm:global(VM), 342 | Global:set_value("x",fun (#erlv8_fun_invocation{}=I,[]) -> 343 | Global = I:global(), 344 | Global:set_value("a",2) 345 | end), 346 | ?assertMatch([{<<"x">>, _}], Global:proplist()), 347 | erlv8_vm:run(VM,"x()"), 348 | ?assertMatch([{<<"x">>, _},{<<"a">>, 2}], Global:proplist()), 349 | stop(). 350 | 351 | fun_callback_test() -> 352 | start(), 353 | {ok, VM} = erlv8_vm:start(), 354 | Self = self(), 355 | Global = erlv8_vm:global(VM), 356 | Global:set_value("test", fun (#erlv8_fun_invocation{} = _Invocation, [Cb]) -> 357 | spawn(fun () -> 358 | timer:sleep(1000), %% allow ample time 359 | Self ! {ok, Cb:call([1])} 360 | end), 361 | undefined 362 | end), 363 | 364 | erlv8_vm:run(VM,"f = function(x) { return x}; test(f);"), 365 | receive 366 | {ok, 1} -> 367 | ok; 368 | Other1 -> 369 | error({bad_result,Other1}) 370 | end, 371 | stop(). 372 | 373 | js_fun_test() -> 374 | start(), 375 | {ok, VM} = erlv8_vm:start(), 376 | Global = erlv8_vm:global(VM), 377 | erlv8_vm:run(VM,"f = function () { return 100; }"), 378 | F = Global:get_value("f"), 379 | ?assertEqual(100,F:call()), 380 | erlv8_vm:run(VM,"f1 = function (x) { return x*100; }"), 381 | F1 = Global:get_value("f1"), 382 | ?assertEqual(200,F1:call([2])), 383 | stop(). 384 | 385 | js_fun_this_test() -> 386 | start(), 387 | {ok, VM} = erlv8_vm:start(), 388 | Global = erlv8_vm:global(VM), 389 | erlv8_vm:run(VM,"f = function (a) { this.x = a*100; }; y = {}"), 390 | F = Global:get_value("f"), 391 | Y = Global:get_value("y"), 392 | F:call(Y,[1]), 393 | ?assertEqual(100, Y:get_value("x")), 394 | Y:call(F,[2]), % test another API 395 | ?assertEqual(200, Y:get_value("x")), 396 | stop(). 397 | 398 | 399 | to_string_test() -> 400 | start(), 401 | {ok, VM} = erlv8_vm:start(), 402 | ?assertEqual(<<"1">>,erlv8_vm:to_string(VM,1)), 403 | ?assertEqual(<<"1">>,erlv8_vm:to_string(VM,"1")), 404 | ?assertEqual(<<"true">>,erlv8_vm:to_string(VM,true)), 405 | ?assertEqual(<<"[object Object]">>,erlv8_vm:to_string(VM,?V8Obj([{a,1}]))), 406 | stop(). 407 | 408 | to_detail_string_test() -> 409 | start(), 410 | {ok, VM} = erlv8_vm:start(), 411 | ?assertEqual(<<"1">>,erlv8_vm:to_detail_string(VM,1)), 412 | ?assertEqual(<<"1">>,erlv8_vm:to_detail_string(VM,"1")), 413 | ?assertEqual(<<"true">>,erlv8_vm:to_detail_string(VM,true)), 414 | ?assertEqual(<<"#">>,erlv8_vm:to_detail_string(VM,?V8Obj([{a,1}]))), 415 | stop(). 416 | 417 | proto_test() -> 418 | start(), 419 | {ok, VM} = erlv8_vm:start(), 420 | Global = erlv8_vm:global(VM), 421 | Global:set_value("proto",erlv8_object:new([{"x",1}])), 422 | Global:set_value("obj",erlv8_object:new([{"y",1}])), 423 | Proto = Global:get_value("proto"), 424 | Obj = Global:get_value("obj"), 425 | ?assertEqual(true, Obj:set_prototype(Proto)), 426 | ObjProto = Obj:get_prototype(), 427 | ?assertEqual(Proto:proplist(),ObjProto:proplist()), 428 | ?assertEqual({ok, 1},erlv8_vm:run(VM,"obj.x")), 429 | stop(). 430 | 431 | hidden_value_test() -> 432 | start(), 433 | {ok, VM} = erlv8_vm:start(), 434 | Global = erlv8_vm:global(VM), 435 | Global:set_hidden_value("a",1), 436 | ?assertEqual(1,Global:get_hidden_value("a")), 437 | ?assertEqual({ok, undefined}, erlv8_vm:run(VM,"this.a")), 438 | ?assertEqual(undefined, Global:get_hidden_value("shouldntbethere")), 439 | stop(). 440 | 441 | objects_equality_test() -> 442 | start(), 443 | {ok, VM} = erlv8_vm:start(), 444 | Global = erlv8_vm:global(VM), 445 | Global:set_value("v1",?V8Obj([{"a",1}])), 446 | Global:set_value("v2",?V8Obj([{"a",1}])), 447 | V1 = Global:get_value("v1"), 448 | V2 = Global:get_value("v2"), 449 | ?assert(V1:equals(V1)), 450 | ?assert(not V1:strict_equals(V2)), 451 | erlv8_vm:run(VM,"f1 = function() { return 1; }; f2 = function() { return 2; };"), 452 | F1 = Global:get_value("f1"), 453 | F2 = Global:get_value("f2"), 454 | ?assert(F1:equals(F1)), 455 | ?assert(not F1:strict_equals(F2)), 456 | stop(). 457 | 458 | primitives_equality_test() -> 459 | start(), 460 | {ok, VM} = erlv8_vm:start(), 461 | ?assert(erlv8_vm:equals(VM, 1,"1")), 462 | ?assert(erlv8_vm:equals(VM, 1,1)), 463 | ?assert(not erlv8_vm:equals(VM, 1,2)), 464 | ?assert(not erlv8_vm:strict_equals(VM, 1,"1")), 465 | ?assert(erlv8_vm:strict_equals(VM, 1,1)), 466 | ?assert(not erlv8_vm:equals(VM, 1,2)), 467 | stop(). 468 | 469 | 470 | taint_test() -> 471 | start(), 472 | {ok, VM} = erlv8_vm:start(), 473 | ?assertMatch(#erlv8_object{},erlv8_vm:taint(VM, ?V8Obj([{"a",1}]))), 474 | stop(). 475 | 476 | implicit_taint_for_erlang_only_calls_test() -> 477 | start(), 478 | {ok, VM} = erlv8_vm:start(), 479 | Global = erlv8_vm:global(VM), 480 | Global:set_value("x",fun (#erlv8_fun_invocation{},[#erlv8_object{vm = VM1}]) -> VM1 =/= undefined end), 481 | X = Global:get_value("x"), 482 | ?assert(X:call([?V8Obj([])])), 483 | stop(). 484 | 485 | fun_extends_object_test() -> 486 | start(), 487 | {ok, VM} = erlv8_vm:start(), 488 | {ok, F} = erlv8_vm:run(VM,"f = function() { return 1; }; f.x = 1; f"), 489 | ?assertEqual(1, F:get_value("x")), 490 | stop(). 491 | 492 | array_length_test() -> 493 | start(), 494 | {ok, VM} = erlv8_vm:start(), 495 | A = erlv8_vm:taint(VM,?V8Arr([1,2,3])), 496 | ?assertEqual(3,A:length()), 497 | stop(). 498 | 499 | array_subscript_test() -> 500 | start(), 501 | {ok, VM} = erlv8_vm:start(), 502 | A = erlv8_vm:taint(VM,?V8Arr([1,2,"a"])), 503 | ?assertEqual(<<"a">>,A:get_value(2)), 504 | A:set_value(1,"b"), 505 | ?assertEqual(<<"b">>,A:get_value(1)), 506 | stop(). 507 | 508 | array_push_test() -> 509 | start(), 510 | {ok, VM} = erlv8_vm:start(), 511 | A = erlv8_vm:taint(VM,?V8Arr([1,2,3])), 512 | A:push(4), 513 | ?assertEqual([1,2,3,4],A:list()), 514 | stop(). 515 | 516 | array_unshift_test() -> 517 | start(), 518 | {ok, VM} = erlv8_vm:start(), 519 | A = erlv8_vm:taint(VM,?V8Arr([1,2,3])), 520 | A:unshift(4), 521 | ?assertEqual([4,1,2,3],A:list()), 522 | stop(). 523 | 524 | object_deletion_test() -> 525 | start(), 526 | {ok, VM} = erlv8_vm:start(), 527 | O = erlv8_vm:taint(VM,?V8Obj([{"a",1},{"b", 2}])), 528 | O:delete("a"), 529 | ?assertEqual(undefined, O:get_value("a")), 530 | stop(). 531 | 532 | array_deletion_test() -> 533 | start(), 534 | {ok, VM} = erlv8_vm:start(), 535 | A = erlv8_vm:taint(VM,?V8Arr([1,2,3])), 536 | A:delete(0), 537 | ?assertEqual([2,3], A:list()), 538 | stop(). 539 | 540 | vm_storage_test() -> 541 | start(), 542 | {ok, VM} = erlv8_vm:start(), 543 | erlv8_vm:stor(VM, {my_mod, data}, "Data"), 544 | ?assertEqual("Data",erlv8_vm:retr(VM, {my_mod, data})), 545 | stop(). 546 | 547 | getter_test() -> 548 | start(), 549 | {ok, VM} = erlv8_vm:start(), 550 | Global = erlv8_vm:global(VM), 551 | true = Global:set_accessor("getter_value", fun (#erlv8_fun_invocation{} = _Invocation, [Prop]) -> 552 | Prop 553 | end), 554 | ?assertEqual(<<"getter_value">>,Global:get_value("getter_value")), 555 | stop(). 556 | 557 | setter_test() -> 558 | start(), 559 | {ok, VM} = erlv8_vm:start(), 560 | Global = erlv8_vm:global(VM), 561 | true = Global:set_accessor("setter_value", fun (#erlv8_fun_invocation{ this = This } = _Invocation, [_Prop]) -> 562 | This:get_value("val") 563 | end, 564 | fun (#erlv8_fun_invocation{ this = This } = _Invocation, [_Prop, Val]) -> 565 | This:set_value("val",Val) 566 | end, default, dontdelete), 567 | Global:set_value("setter_value", 1), 568 | ?assertEqual(1,Global:get_value("setter_value")), 569 | Global:delete("setter_value"), 570 | ?assertEqual(1,Global:get_value("setter_value")), 571 | stop(). 572 | 573 | run_new_ctx_test() -> 574 | start(), 575 | {ok, VM} = erlv8_vm:start(), 576 | Global = erlv8_vm:global(VM), 577 | Global:set_value("x",1), 578 | NewCtx = erlv8_context:new(VM), 579 | NewGlobal = erlv8_context:global(NewCtx), 580 | erlv8_vm:run(VM,NewCtx,"x={a:1}"), 581 | T = NewGlobal:get_value("x"), 582 | ?assertEqual(1,T:get_value("a")), 583 | stop(). 584 | 585 | run_multi_ctx_test() -> 586 | start(), 587 | {ok, VM} = erlv8_vm:start(), 588 | Global = erlv8_vm:global(VM), 589 | Global:set_value("x",1), 590 | NewCtx = erlv8_context:new(VM), 591 | NewCtx1 = erlv8_context:new(VM), 592 | erlv8_vm:run(VM,NewCtx,"x={a:1}"), 593 | NewGlobal1 = erlv8_context:global(NewCtx1), 594 | ?assertEqual(undefined,NewGlobal1:get_value("x")), 595 | stop(). 596 | 597 | ctx_fun_invocation_test() -> 598 | start(), 599 | {ok, VM} = erlv8_vm:start(), 600 | Global = erlv8_vm:global(VM), 601 | NewCtx = erlv8_context:new(VM), 602 | NewGlobal = erlv8_context:global(NewCtx), 603 | NewGlobal:set_value("f",fun (#erlv8_fun_invocation{}=I,[]) -> G= I:global(), G:set_value("x",1) end), 604 | erlv8_vm:run(VM,NewCtx,"f()"), 605 | ?assertEqual(1,NewGlobal:get_value("x")), 606 | ?assertEqual(undefined,Global:get_value("x")), 607 | stop(). 608 | 609 | fun_call_exception_test() -> 610 | start(), 611 | {ok, VM} = erlv8_vm:start(), 612 | Global = erlv8_vm:global(VM), 613 | erlv8_vm:run(VM,"f = function () { throw('exc'); }"), 614 | F = Global:get_value("f"), 615 | ?assertEqual({throw, {error, <<"exc">>}}, F:call()), 616 | stop(). 617 | 618 | js_parallel_fun_call_test_() -> 619 | {timeout, 10, 620 | fun () -> 621 | start(), 622 | {ok, VM} = erlv8_vm:start(), 623 | Global = erlv8_vm:global(VM), 624 | erlv8_vm:run(VM,"f = function (x) { return x }"), 625 | F = Global:get_value("f"), 626 | Self = self(), 627 | lists:map(fun (N) -> 628 | spawn(fun () -> X = F:call([N]), Self ! X end) 629 | end, lists:seq(1,2)), 630 | ?assertEqual(lists:seq(1,2),lists:usort(parallel_call_test_loop(2,[]))), 631 | stop() 632 | end}. 633 | 634 | erl_parallel_fun_call_test_() -> 635 | {timeout, 10, 636 | fun () -> 637 | start(), 638 | {ok, VM} = erlv8_vm:start(), 639 | Global = erlv8_vm:global(VM), 640 | Global:set_value("f",fun (#erlv8_fun_invocation{},[N]) -> N end), 641 | F = Global:get_value("f"), 642 | Self = self(), 643 | lists:map(fun (N) -> 644 | spawn(fun () -> X = F:call([N]), Self ! X end) 645 | end, lists:seq(1,2)), 646 | ?assertEqual(lists:seq(1,2),lists:usort(parallel_call_test_loop(2,[]))), 647 | stop() 648 | end}. 649 | 650 | parallel_call_test_loop(T,L) when length(L) == T -> 651 | L; 652 | parallel_call_test_loop(T,L) -> 653 | receive 654 | N when is_integer(N) -> 655 | parallel_call_test_loop(T,[N|L]); 656 | Other -> 657 | error({bad_result, Other}) 658 | end. 659 | 660 | property_attribute_test() -> 661 | start(), 662 | {ok, VM} = erlv8_vm:start(), 663 | Global = erlv8_vm:global(VM), 664 | Global:set_value("a",1,dontdelete), 665 | Global:set_value("b",1,readonly), 666 | Global:set_value("c",1,[dontdelete,readonly]), 667 | Global:delete("a"), 668 | Global:set_value("b",2), 669 | ?assertEqual(1,Global:get_value("a")), 670 | ?assertEqual(1,Global:get_value("b")), 671 | ?assertEqual(1,Global:get_value("c")), 672 | Global:delete("c"), 673 | ?assertEqual(1,Global:get_value("c")), 674 | stop(). 675 | 676 | instantiate_js_test() -> 677 | start(), 678 | {ok, VM} = erlv8_vm:start(), 679 | {ok, F} = erlv8_vm:run(VM,"f = function(y) { this.x = y}"), 680 | O = F:instantiate([1]), 681 | ?assertEqual(1,O:get_value("x")), 682 | stop(). 683 | 684 | instantiate_erl_test() -> 685 | start(), 686 | {ok, VM} = erlv8_vm:start(), 687 | Global = erlv8_vm:global(VM), 688 | Global:set_value("f",fun (#erlv8_fun_invocation{ this = This },[Y]) -> This:set_value("x",Y) end), 689 | F = Global:get_value("f"), 690 | O = F:instantiate([1]), 691 | ?assertEqual(1,O:get_value("x")), 692 | stop(). 693 | 694 | instantiate_erl_from_js_test() -> 695 | start(), 696 | {ok, VM} = erlv8_vm:start(), 697 | Global = erlv8_vm:global(VM), 698 | Global:set_value("f",fun (#erlv8_fun_invocation{ this = This },[Y]) -> This:set_value("x",Y) end), 699 | ?assertEqual({ok, 1}, erlv8_vm:run(VM,"new f(1).x")), 700 | stop(). 701 | 702 | throwing_object_as_an_error_test() -> 703 | start(), 704 | {ok, VM} = erlv8_vm:start(), 705 | Global = erlv8_vm:global(VM), 706 | Global:set_value("f",fun (#erlv8_fun_invocation{},[]) -> {throw, ?V8Obj([{"a",1}])} end), 707 | {throw, E} = erlv8_vm:run(VM,"f()"), 708 | ?assertEqual(1,E:get_value("a")), 709 | stop(). 710 | 711 | v8_return_value_test() -> 712 | start(), 713 | {ok, VM} = erlv8_vm:start(), 714 | Global = erlv8_vm:global(VM), 715 | Global:set_value("f",fun (#erlv8_fun_invocation{},[X]) -> X end), 716 | F = Global:get_value("f"), 717 | O = erlv8_vm:taint(VM, ?V8Obj([{"a",1}])), 718 | O1 = F:call([O]), 719 | ?assert(O:equals(O1)), 720 | stop(). 721 | 722 | clear_env_lockup_regression_test() -> 723 | start(), 724 | {ok, VM} = erlv8_vm:start(), 725 | Global = erlv8_vm:global(VM), 726 | Global:set_value("f",fun (#erlv8_fun_invocation{},[X]) -> X end), 727 | Global:set_value("f1",fun (#erlv8_fun_invocation{},[F]) -> F:call([1]) end), 728 | ?assertEqual({ok, 1}, erlv8_vm:run(VM, "f1(f)")), 729 | stop(). 730 | 731 | extern_proto_test() -> 732 | start(), 733 | {ok, VM} = erlv8_vm:start(), 734 | Global = erlv8_vm:global(VM), 735 | lists:foreach(fun({Type, Val}) -> 736 | Proto = erlv8_extern:get_proto(VM, Type), 737 | Proto:set_value("toString", fun(#erlv8_fun_invocation{},[]) -> Type end), 738 | Global:set_value("val", Val), 739 | ?assertEqual({ok, atom_to_binary(Type, utf8)}, erlv8_vm:run(VM, "val.toString()")) 740 | end, [{ref, make_ref()}, 741 | {pid, self()}]), 742 | stop(). 743 | 744 | externalize_proto_test() -> 745 | start(), 746 | {ok, VM} = erlv8_vm:start(), 747 | Global = erlv8_vm:global(VM), 748 | Self = self(), 749 | spawn(fun () -> Self ! open_port({spawn, "ls"},[stream]) end), 750 | Port = receive 751 | P -> P 752 | end, 753 | lists:foreach(fun(Val) -> 754 | Global:set_value("val", erlv8_extern:extern(VM, Val)), 755 | ?assertEqual(Val, Global:get_value("val")) 756 | end, [1, 757 | atom, 758 | <<>>, 759 | make_ref(), 760 | fun() -> ok end, 761 | Port, 762 | self(), 763 | {1,2,3, self()}, 764 | [1,2,{3,2,1}]]), 765 | stop(). 766 | 767 | internal_field_test() -> 768 | start(), 769 | {ok, VM} = erlv8_vm:start(), 770 | Global = erlv8_vm:global(VM), 771 | ?assertEqual(0,Global:internal_field_count()), 772 | ?assertEqual(error, Global:get_internal_field(-1)), 773 | ?assertEqual(error, Global:get_internal_field(0)), 774 | ?assertEqual(error, Global:set_internal_field(0, 1)), 775 | 776 | %% TODO: Externals don't have internal fields anymore, find another way to test this: 777 | %% Extern = erlv8_extern:extern(VM, atom), 778 | 779 | %% ?assertEqual(1,Extern:internal_field_count()), 780 | %% ?assertEqual(error, Global:get_internal_field(1)), 781 | %% ?assertEqual(error, Global:set_internal_field(1, 1)), 782 | 783 | %% ?assertEqual(atom, Extern:get_internal_field(0)), 784 | 785 | %% Extern:set_internal_field(0,yes), 786 | %% ?assertEqual("yes", Extern:get_internal_field(0)), 787 | 788 | %% Extern:set_internal_field(0,erlv8_extern:extern(VM, yes)), 789 | %% ?assertEqual(yes, Extern:get_internal_field(0)), 790 | 791 | %% Extern:set_internal_field(0,{extern, yes}), 792 | %% ?assertEqual(yes, Extern:get_internal_field(0)), 793 | stop(). 794 | 795 | nested_result_tick_regression_test() -> 796 | start(), 797 | {ok, VM} = erlv8_vm:start(), 798 | Global = erlv8_vm:global(VM), 799 | Global:set_value("f1",fun (#erlv8_fun_invocation{},[]) -> register(erlv8_test_f1, self()), receive X -> X end end), 800 | Global:set_value("f2",fun (#erlv8_fun_invocation{},[]) -> register(erlv8_test_f2, self()), receive X -> X end end), 801 | Self = self(), 802 | spawn(fun () -> 803 | Self ! {f1, erlv8_vm:run(VM, "f1()")} 804 | end), 805 | spawn(fun () -> 806 | Self ! {f2, erlv8_vm:run(VM, "f2()")} 807 | end), 808 | timer:sleep(200), %% give them some time to start (I know, I know) 809 | %% so now the ticker should be in f2 810 | %% but we'll return value for f1 811 | erlv8_test_f1 ! 1, 812 | %% and only then for f2 813 | erlv8_test_f2 ! 2, 814 | nested_result_tick_regression_test_loop([]), 815 | stop(). 816 | 817 | nested_result_tick_regression_test_loop([f1,f2]) -> 818 | ok; 819 | nested_result_tick_regression_test_loop([f2,f1]) -> 820 | ok; 821 | nested_result_tick_regression_test_loop(L) -> 822 | receive 823 | {f1, Result} -> 824 | ?assertEqual({ok, 1}, Result), 825 | nested_result_tick_regression_test_loop([f1|L]); 826 | {f2, Result} -> 827 | ?assertEqual({ok, 2}, Result), 828 | nested_result_tick_regression_test_loop([f2|L]) 829 | end. 830 | 831 | -endif. 832 | -------------------------------------------------------------------------------- /src/erlv8_app.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_app). 2 | 3 | -behaviour(application). 4 | 5 | %% Application callbacks 6 | -export([start/2, stop/1]). 7 | 8 | %% =================================================================== 9 | %% Application callbacks 10 | %% =================================================================== 11 | 12 | start(_StartType, _StartArgs) -> 13 | erlv8_sup:start_link(). 14 | 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /src/erlv8_array.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_array). 2 | 3 | -include("erlv8.hrl"). 4 | 5 | -extends(erlv8_object). 6 | 7 | -compile({no_auto_import,[length/1]}). 8 | 9 | -export([list/1, object/1, length/1, push/2, unpush/1, unshift/2, delete/2, 10 | 11 | new/1, new/2]). 12 | 13 | list(#erlv8_array{resource = Resource, vm = VM}) -> 14 | erlv8_vm:enqueue_tick(VM,{list,Resource}). 15 | 16 | object(#erlv8_array{resource = Resource, vm = VM}) -> 17 | erlv8_object:new(Resource,VM). 18 | 19 | new(O) -> 20 | new(O, undefined). 21 | 22 | new(O,V) -> 23 | #erlv8_array{resource = O, vm = V}. 24 | 25 | length(Self) -> 26 | erlang:length(list(Self)). %% TODO: I guess it will be more efficient if we had a NIF for that? 27 | 28 | push(Val, Self) -> 29 | M = Self:object(), 30 | M:set_value(length(Self),Val). 31 | 32 | unpush(Self) -> 33 | M = Self:object(), 34 | M:delete(M:length()-1). 35 | 36 | unshift(Val, Self) -> 37 | M = Self:object(), 38 | L = length(Self), 39 | lists:foreach(fun (I) -> 40 | M:set_value(L-I,M:get_value(L-I-1)) 41 | end, lists:seq(0,L-1)), 42 | M:set_value(0,Val). 43 | 44 | delete(Index, Self) -> 45 | M = Self:object(), 46 | L = length(Self), 47 | V = M:get_value(Index), 48 | lists:foreach(fun (I) -> 49 | M:set_value(I,M:get_value(I+1)) 50 | end, lists:seq(Index,L-1)), 51 | M:set_value(length,L-1), 52 | V. 53 | -------------------------------------------------------------------------------- /src/erlv8_context.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_context). 2 | -export([get/1,global/1,new/1]). 3 | 4 | get(Server) -> 5 | gen_server2:call(Server,context). 6 | 7 | new(Server) -> 8 | gen_server2:call(Server,new_context). 9 | 10 | global({Server, Resource}) -> 11 | gen_server2:call(Server,{global, Resource}). 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/erlv8_extern.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_extern). 2 | -export([get_proto/2, extern/2, type/1]). 3 | 4 | get_proto(VM, Proto) -> 5 | erlv8_vm:enqueue_tick(VM, {extern_proto, Proto}). 6 | 7 | extern(VM, Value) -> 8 | erlv8_vm:enqueue_tick(VM, {externalize, type(Value), Value}). 9 | 10 | type(Value) when is_number(Value) -> 11 | num; 12 | type(Value) when is_atom(Value) -> 13 | atom; 14 | type(Value) when is_binary(Value) -> 15 | bin; 16 | type(Value) when is_reference(Value) -> 17 | ref; 18 | type(Value) when is_function(Value) -> 19 | 'fun'; 20 | type(Value) when is_port(Value) -> 21 | port; 22 | type(Value) when is_pid(Value) -> 23 | pid; 24 | type(Value) when is_tuple(Value) -> 25 | tuple; 26 | type(Value) when is_list(Value) -> 27 | list. 28 | 29 | -------------------------------------------------------------------------------- /src/erlv8_fun.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_fun). 2 | 3 | -include("erlv8.hrl"). 4 | 5 | -extends(erlv8_object). 6 | 7 | -export([call/1,call/2,call/3,instantiate/1, instantiate/2, object/1, 8 | 9 | new/1, new/2]). 10 | 11 | call(Self) -> 12 | call([], Self). 13 | 14 | call({erlv8_object, _,_}=T, Self) -> 15 | call(T,[], Self); 16 | 17 | call(Args, #erlv8_fun{resource = Resource, vm = VM}) when is_list(Args) -> 18 | erlv8_vm:enqueue_tick(VM, {call, Resource, Args}). 19 | 20 | call({erlv8_object, _,_}=This, Args, #erlv8_fun{resource = Resource, vm = VM}) when is_list(Args) -> 21 | erlv8_vm:enqueue_tick(VM, {call, Resource, Args, This}). 22 | 23 | instantiate(Self) -> 24 | instantiate([], Self). 25 | 26 | instantiate(Args, #erlv8_fun{resource = Resource, vm = VM}) when is_list(Args) -> 27 | erlv8_vm:enqueue_tick(VM, {inst, Resource, Args}). 28 | 29 | object(#erlv8_fun{resource = Resource, vm = VM}) -> 30 | {erlv8_object, Resource, VM}. 31 | 32 | new(O) -> 33 | new(O,undefined). 34 | 35 | new(O, V) -> 36 | #erlv8_fun{resource = O, vm = V}. 37 | -------------------------------------------------------------------------------- /src/erlv8_fun_invocation.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_fun_invocation,[ICC,Holder,This,Ref,VM, Ctx]). 2 | -export([is_construct_call/0, holder/0, this/0, global/0, vm/0]). 3 | 4 | is_construct_call() -> 5 | ICC. 6 | 7 | holder() -> 8 | Holder. 9 | 10 | this() -> 11 | This. 12 | 13 | global() -> 14 | erlv8_context:global({VM,Ctx}). 15 | 16 | vm() -> 17 | VM. 18 | -------------------------------------------------------------------------------- /src/erlv8_nif.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_nif). 2 | -on_load(init/0). 3 | 4 | -export([init/0, new_vm/0, set_server/2, global/2, context/1, new_context/1, 5 | tick/3, stop/2, kill/1]). 6 | 7 | -define(DEFAULT_PREEMPTION, 100). 8 | 9 | init() -> 10 | Preemption = 11 | case application:get_env(erlv8, preemption_ms) of 12 | {ok, V} -> 13 | V; 14 | _ -> 15 | ?DEFAULT_PREEMPTION 16 | end, 17 | case os:getenv("ERLV8_SO_PATH") of 18 | false -> 19 | case code:which(erlv8_nif) of 20 | Filename when is_list(Filename) -> 21 | erlang:load_nif(filename:join([filename:dirname(Filename),"../priv/erlv8_drv"]), Preemption); 22 | Err -> 23 | Err 24 | end; 25 | Path -> 26 | Filename = filename:join([Path,"erlv8_drv"]), 27 | erlang:load_nif(Filename,Preemption) 28 | end. 29 | 30 | 31 | new_vm() -> 32 | error(not_loaded). 33 | 34 | set_server(_VMObject,_Pid) -> 35 | error(not_loaded). 36 | 37 | context(_VMObject) -> 38 | error(not_loaded). 39 | 40 | new_context(_VMObject) -> 41 | error(not_loaded). 42 | 43 | global(_VMObject, _ContextObject) -> 44 | error(not_loaded). 45 | 46 | tick(_VMObject, _Ref, _Tick) -> 47 | error(not_loaded). 48 | 49 | stop(_VMObject, _Ref) -> 50 | error(not_loaded). 51 | 52 | kill(_VMObject) -> 53 | error(not_loaded). 54 | -------------------------------------------------------------------------------- /src/erlv8_object.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_object). 2 | 3 | -include("erlv8.hrl"). 4 | 5 | -export([vm/1, 6 | 7 | proplist/1, set_value/3, set_value/4, set_hidden_value/3, get_value/2, get_value/3, get_hidden_value/2, get_hidden_value/3, 8 | internal_field_count/1, get_internal_field/2, set_internal_field/3, 9 | set_prototype/2, get_prototype/1, delete/2, set_accessor/3, set_accessor/4, set_accessor/5, set_accessor/6, 10 | equals/2, strict_equals/2, call/2, call/3, 11 | 12 | new/1, new/2]). 13 | 14 | vm({Erlv8Obj, _Resource, VM}) when ?is_v8(Erlv8Obj) -> 15 | VM. 16 | 17 | proplist({_Erlv8Obj, Resource, VM}) -> 18 | erlv8_vm:enqueue_tick(VM,{proplist, Resource}). 19 | 20 | set_value(Key, Value, {_Erlv8Obj, Resource, VM}) -> 21 | erlv8_vm:enqueue_tick(VM, {set, Resource, Key, Value}). 22 | 23 | set_value(Key,Value,PropertyAttribute, {_Erlv8Obj, Resource, VM}) -> 24 | erlv8_vm:enqueue_tick(VM, {set, Resource, Key, Value, PropertyAttribute}). 25 | 26 | set_hidden_value(Key,Value, {_Erlv8Obj, Resource, VM}) -> 27 | erlv8_vm:enqueue_tick(VM, {set_hidden, Resource, Key, Value}). 28 | 29 | get_value(Key, {_Erlv8Obj, _Resource, _VM} = Self) -> 30 | get_value(Key, undefined, Self). 31 | 32 | get_value(Key, Default, {_Erlv8Obj, Resource, VM}) -> 33 | case erlv8_vm:enqueue_tick(VM, {get, Resource, Key}) of 34 | undefined -> 35 | Default; 36 | Val -> 37 | Val 38 | end. 39 | 40 | get_hidden_value(Key, {_Erlv8Obj, _Resource, _VM} = Self) -> 41 | get_hidden_value(Key, undefined, Self). 42 | 43 | get_hidden_value(Key, Default, {_Erlv8Obj, Resource, VM}) -> 44 | case erlv8_vm:enqueue_tick(VM, {get_hidden, Resource, Key}) of 45 | undefined -> 46 | Default; 47 | Val -> 48 | Val 49 | end. 50 | 51 | internal_field_count({_Erlv8Obj, Resource, VM}) -> 52 | erlv8_vm:enqueue_tick(VM, {internal_count, Resource}). 53 | 54 | get_internal_field(Index, {_Erlv8Obj, Resource, VM}) -> 55 | erlv8_vm:enqueue_tick(VM, {get_internal, Resource, Index}). 56 | 57 | set_internal_field(Index, {extern, Value}, {_Erlv8Obj, Resource, VM}) -> 58 | erlv8_vm:enqueue_tick(VM, {set_internal_extern, Resource, Index, Value, erlv8_extern:type(Value)}); 59 | 60 | set_internal_field(Index, Value, {_Erlv8Obj, Resource, VM}) -> 61 | erlv8_vm:enqueue_tick(VM, {set_internal, Resource, Index, Value}). 62 | 63 | set_prototype(Proto, {_Erlv8Obj, Resource, VM}) -> 64 | erlv8_vm:enqueue_tick(VM, {set_proto, Resource, Proto}). 65 | 66 | get_prototype({_Erlv8Obj, Resource, VM}) -> 67 | erlv8_vm:enqueue_tick(VM, {get_proto, Resource}). 68 | 69 | delete(Key, {_Erlv8Obj, Resource, VM}) -> 70 | erlv8_vm:enqueue_tick(VM, {delete, Resource, Key}). 71 | 72 | set_accessor(Property, Getter, {_Erlv8Obj, Resource, VM}) -> 73 | case erlv8_vm:enqueue_tick(VM, {set_accessor, Resource, Property, Getter}) of 74 | badarg -> 75 | throw(badarg); 76 | Result -> 77 | Result 78 | end. 79 | 80 | set_accessor(Property, Getter, Setter, {_Erlv8Obj, Resource, VM}) -> 81 | case erlv8_vm:enqueue_tick(VM, {set_accessor, Resource, Property, Getter, Setter}) of 82 | badarg -> 83 | throw(badarg); 84 | Result -> 85 | Result 86 | end. 87 | 88 | set_accessor(Property, Getter, Setter, AccessControl, {_Erlv8Obj, Resource, VM}) -> 89 | case erlv8_vm:enqueue_tick(VM, {set_accessor, Resource, Property, Getter, Setter, AccessControl}) of 90 | badarg -> 91 | throw(badarg); 92 | Result -> 93 | Result 94 | end. 95 | 96 | set_accessor(Property, Getter, Setter, AccessControl, PropertyAttribute, {_Erlv8Obj, Resource, VM}) -> 97 | case erlv8_vm:enqueue_tick(VM, {set_accessor, Resource, Property, Getter, Setter, AccessControl, PropertyAttribute}) of 98 | badarg -> 99 | throw(badarg); 100 | Result -> 101 | Result 102 | end. 103 | 104 | equals({_Tag,AnotherObject,_}, {_Erlv8Obj, Resource, VM}) -> 105 | erlv8_value:equals(VM, Resource, AnotherObject). 106 | 107 | strict_equals({_Tag,AnotherObject,_}, {_Erlv8Obj, Resource, VM}) -> 108 | erlv8_value:strict_equals(VM, Resource, AnotherObject). 109 | 110 | call(Fun, Self) -> 111 | call(Fun,[], Self). 112 | 113 | call(Fun,Args, {_Erlv8Obj, Resource, VM}) -> 114 | Fun:call({erlv8_object, Resource,VM}, Args). 115 | 116 | new(O) -> 117 | new(O,undefined). 118 | 119 | new(O, V) -> 120 | #erlv8_object{resource = O, vm = V}. 121 | -------------------------------------------------------------------------------- /src/erlv8_sup.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_sup). 2 | 3 | -behaviour(supervisor2). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% Supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% Helper macro for declaring children of supervisor 12 | -define(CHILD(I, Restart, Type), {I, {I, start_link, []}, Restart, 5000, Type, [I]}). 13 | 14 | %% =================================================================== 15 | %% API functions 16 | %% =================================================================== 17 | 18 | start_link() -> 19 | supervisor2:start_link({local, ?MODULE}, ?MODULE, []). 20 | 21 | %% =================================================================== 22 | %% Supervisor callbacks 23 | %% =================================================================== 24 | 25 | init([]) -> 26 | {ok, { {simple_one_for_one_terminate, 5, 10}, [?CHILD(erlv8_vm,transient,worker)]} }. 27 | 28 | -------------------------------------------------------------------------------- /src/erlv8_utils.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_utils). 2 | 3 | -export([clone/1, 4 | copy_properties/2]). 5 | 6 | -include("erlv8.hrl"). 7 | 8 | clone(Source) -> 9 | erlv8_vm:taint(Source:vm(), ?V8Obj(Source:proplist())). 10 | 11 | copy_properties(Destination, Source) -> 12 | lists:foreach(fun({K, V}) -> 13 | Destination:set_value(K, V) 14 | end, Source:proplist()). 15 | -------------------------------------------------------------------------------- /src/erlv8_value.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_value). 2 | -export([equals/3,strict_equals/3]). 3 | 4 | equals(VM,V1,V2) -> 5 | erlv8_vm:equals(VM, V1, V2). 6 | 7 | strict_equals(VM, V1,V2) -> 8 | erlv8_vm:strict_equals(VM, V1, V2). 9 | -------------------------------------------------------------------------------- /src/erlv8_vm.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_vm). 2 | 3 | -behaviour(gen_server2). 4 | -include_lib("erlv8/include/erlv8.hrl"). 5 | 6 | %% API 7 | -export([start_link/1,start/0,vm_resource/1, 8 | run/2, run/3, run/4, 9 | run_timed/3, run_timed/4, run_timed/5, 10 | global/1,stop/1, 11 | to_string/2,to_detail_string/2,taint/2,untaint/1,equals/3, strict_equals/3, 12 | enqueue_tick/2, enqueue_tick/3, enqueue_tick/4, next_tick/2, next_tick/3, next_tick/4, 13 | stor/3, retr/2, gc/1, kill/1]). 14 | 15 | %% gen_server2 callbacks 16 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 17 | terminate/2, code_change/3,prioritise_info/2]). 18 | 19 | -define(SERVER, ?MODULE). 20 | 21 | -record(state, { 22 | vm, 23 | ticked, 24 | storage = [], 25 | context, 26 | debug 27 | }). 28 | 29 | -define(Error(Msg), lists:flatten(io_lib:format("~s: ~p",[Msg,Trace]))). 30 | -define(ErrorVal(Msg), lists:flatten(io_lib:format("~s: ~p ~p",[Msg,Val,Trace]))). 31 | 32 | 33 | %%%=================================================================== 34 | %%% API 35 | %%%=================================================================== 36 | start() -> 37 | VM = erlv8_nif:new_vm(), 38 | supervisor2:start_child(erlv8_sup,[VM]). 39 | 40 | vm_resource(Server) -> 41 | gen_server2:call(Server, vm_resource). 42 | 43 | run(Server, Source) -> 44 | run(Server, erlv8_context:get(Server), Source). 45 | 46 | run_timed(Server, Source, Timeout) -> 47 | run_timed(Server, erlv8_context:get(Server), Source, Timeout). 48 | 49 | run_timed(Server, {_, _CtxRes} = Context, Source, Timeout) -> 50 | run_timed(Server, Context, Source, {"unknown",0,0}, Timeout). 51 | 52 | run_timed(Server, {C, CtxRes}, Source, {Name, LineOffset, ColumnOffset}, Timeout) -> 53 | Pid = spawn_link(fun() -> 54 | receive {'$gen_call', From, run} -> 55 | Res = run(Server, {C , CtxRes}, Source, {Name, LineOffset, ColumnOffset}), 56 | gen_server:reply(From, Res) 57 | end 58 | end), 59 | try gen_server:call(Pid, run, Timeout) 60 | catch 61 | exit:{timeout ,_} -> 62 | erlv8_vm:kill(Server), 63 | {error, timeout} 64 | end. 65 | 66 | 67 | run(Server, {_, _CtxRes} = Context, Source) -> 68 | run(Server, Context, Source, {"unknown",0,0}). 69 | 70 | run(Server, A, Source, B) when is_binary(Source) -> 71 | run(Server, A, binary_to_list(Source), B); 72 | run(Server, {_, CtxRes}, Source, {Name, LineOffset, ColumnOffset}) when is_list(Source) -> 73 | enqueue_tick(Server, {script, CtxRes, Source, Name, LineOffset, ColumnOffset}). 74 | 75 | 76 | global(Server) -> 77 | Ctx = erlv8_context:get(Server), 78 | erlv8_context:global(Ctx). 79 | 80 | stop(Server) -> 81 | gen_server2:call(Server,stop). 82 | 83 | to_string(Server, Val) -> 84 | enqueue_tick(Server, {to_string, Val}). 85 | 86 | to_detail_string(Server, Val) -> 87 | enqueue_tick(Server, {to_detail_string, Val}). 88 | 89 | enqueue_tick(Server, Tick) -> 90 | gen_server2:call(Server,{enqueue_tick, Tick}, infinity). 91 | 92 | enqueue_tick(Server, Tick, Ref) when is_reference(Ref) -> 93 | gen_server2:call(Server,{enqueue_tick, Tick, Ref}, infinity); 94 | 95 | enqueue_tick(Server, Tick, Timeout) -> 96 | gen_server2:call(Server,{enqueue_tick, Tick}, Timeout). 97 | 98 | enqueue_tick(Server, Tick, Timeout, Ref) when is_reference(Ref) -> 99 | gen_server2:call(Server,{enqueue_tick, Tick, Ref}, Timeout). 100 | 101 | next_tick(Server, Tick) -> 102 | gen_server2:call(Server,{next_tick, Tick}, infinity). 103 | 104 | next_tick(Server, Tick, Ref) when is_reference(Ref) -> 105 | gen_server2:call(Server,{next_tick, Tick, Ref}, infinity); 106 | 107 | next_tick(Server, Tick, Timeout) -> 108 | gen_server2:call(Server,{next_tick, Tick}, Timeout). 109 | 110 | next_tick(Server, Tick, Timeout, Ref) when is_reference(Ref) -> 111 | gen_server2:call(Server,{next_tick, Tick, Ref}, Timeout). 112 | 113 | taint(Server, Value) when ?is_v8(Value) -> 114 | enqueue_tick(Server, {taint, Value}); 115 | 116 | taint(Server, {Error, _} = Value) when Error == error; 117 | Error == throw -> 118 | enqueue_tick(Server, {taint, Value}); 119 | 120 | taint(Server, Value) when is_list(Value); 121 | is_binary(Value); 122 | is_atom(Value); 123 | is_number(Value); 124 | is_reference(Value); 125 | is_function(Value); 126 | is_pid(Value) -> 127 | enqueue_tick(Server, {taint, Value}); 128 | 129 | taint(_Server, _) -> 130 | undefined. 131 | 132 | 133 | equals(Server, V1, V2) -> 134 | enqueue_tick(Server, {equals, V1, V2}). 135 | 136 | strict_equals(Server, V1, V2) -> 137 | enqueue_tick(Server, {strict_equals, V1, V2}). 138 | 139 | 140 | stor(Server, Key, Value) -> 141 | gen_server2:call(Server, {stor, Key, Value}). 142 | 143 | retr(Server, Key) -> 144 | gen_server2:call(Server, {retr, Key}). 145 | 146 | 147 | untaint({erlv8_object, _,_}=O) -> 148 | {erlv8_object,lists:map(fun ({Key, Val}) -> 149 | {Key, untaint(Val)} 150 | end,O:proplist()), undefined}; 151 | untaint({erlv8_array, _,_}=O) -> 152 | {erlv8_array,lists:map(fun untaint/1,O:list()), undefined}; 153 | untaint({erlv8_fun, _,_}=F) -> %% broken 154 | {erlv8_object,untaint(F:object()),undefined}; 155 | untaint([H|T]) -> 156 | [untaint(H)|untaint(T)]; 157 | untaint([]) -> 158 | []; 159 | untaint(Other) -> 160 | Other. 161 | 162 | gc(Server) -> 163 | (catch enqueue_tick(Server, {gc}, 0)), 164 | ok. 165 | 166 | kill(Server) -> 167 | gen_server2:call(Server, kill), 168 | erlv8_vm:run(Server, "1"), % hide returning {throw, null}. 169 | ok. 170 | 171 | %%-------------------------------------------------------------------- 172 | %% @doc 173 | %% Starts the server 174 | %% 175 | %% @spec start_link(VM) -> {ok, Pid} | ignore | {error, Error} 176 | %% @end 177 | %%-------------------------------------------------------------------- 178 | start_link(VM) -> 179 | gen_server2:start_link(?MODULE, [VM], []). 180 | 181 | %%%=================================================================== 182 | %%% gen_server callbacks 183 | %%%=================================================================== 184 | 185 | %%-------------------------------------------------------------------- 186 | %% @private 187 | %% @doc 188 | %% Initializes the server 189 | %% 190 | %% @spec init(Args) -> {ok, State} | 191 | %% {ok, State, Timeout} | 192 | %% ignore | 193 | %% {stop, Reason} 194 | %% @end 195 | %%-------------------------------------------------------------------- 196 | init([VM]) -> 197 | process_flag(trap_exit, true), 198 | erlv8_nif:set_server(VM, self()), 199 | Ctx = erlv8_nif:context(VM), 200 | {ok, #state{vm = VM, context = Ctx, debug = ets:new(erlv8_vm_debug,[]), ticked = ets:new(erlv8_vm_ticked,[public]) }}. 201 | 202 | %%-------------------------------------------------------------------- 203 | %% @private 204 | %% @doc 205 | %% Handling call messages 206 | %% 207 | %% @spec handle_call(Request, From, State) -> 208 | %% {reply, Reply, State} | 209 | %% {reply, Reply, State, Timeout} | 210 | %% {noreply, State} | 211 | %% {noreply, State, Timeout} | 212 | %% {stop, Reason, Reply, State} | 213 | %% {stop, Reason, State} 214 | %% @end 215 | %%-------------------------------------------------------------------- 216 | handle_call(vm_resource, _From, #state{ vm = VM } = State) -> 217 | {reply, VM, State}; 218 | 219 | handle_call({stor, Key, Value}, _From, #state{ storage = Storage } = State) -> 220 | {reply, ok, State#state{ storage = [{Key, Value}|Storage] }}; 221 | 222 | handle_call({retr, Key}, _From, #state{ storage = Storage } = State) -> 223 | {reply, proplists:get_value(Key, Storage), State}; 224 | 225 | handle_call(context, _From, #state{} = State) -> 226 | {reply, {self(), State#state.context}, State}; 227 | 228 | handle_call(new_context, _From, #state{ vm = VM } = State) -> 229 | {reply, {self(), erlv8_nif:new_context(VM)}, State}; 230 | 231 | handle_call({global, Resource}, _From, #state{vm = VM} = State) -> 232 | {reply, erlv8_nif:global(VM, Resource), State}; 233 | 234 | handle_call({to_string, Val}, _From, #state { vm = VM } = State) -> 235 | Reply = erlv8_nif:to_string(VM, Val), 236 | {reply, Reply, State}; 237 | 238 | handle_call({to_detail_string, Val}, _From, #state { vm = VM } = State) -> 239 | Reply = erlv8_nif:to_detail_string(VM, Val), 240 | {reply, Reply, State}; 241 | 242 | handle_call(kill, _From, #state { vm = VM } = State) -> 243 | Reply = erlv8_nif:kill(VM), 244 | {reply, Reply, State}; 245 | 246 | handle_call(stop, _From, State) -> 247 | {stop, normal, ok, State}; 248 | 249 | handle_call({enqueue_tick, Tick}, From, State) -> 250 | Ref = make_ref(), 251 | handle_call({enqueue_tick, Tick, Ref}, From, State); 252 | 253 | handle_call({enqueue_tick, Tick, Ref}, From, #state{ vm = VM, ticked = Ticked } = State) -> 254 | tack = erlv8_nif:tick(VM, Ref, Tick), 255 | update_ticked(Ref, From, Tick, Ticked), 256 | {noreply, State}; 257 | 258 | handle_call({next_tick, Tick}, From, State) -> 259 | Ref = make_ref(), 260 | handle_call({next_tick, Tick, Ref}, From, State); 261 | 262 | handle_call({next_tick, Tick, Ref}, From, #state{ vm = VM, ticked = Ticked } = State) -> 263 | tack = erlv8_nif:tick(VM, Ref, Tick), 264 | update_ticked(Ref, From, Tick, Ticked), 265 | {noreply, State}; 266 | 267 | handle_call(_Request, _From, State) -> 268 | {noreply, State}. 269 | 270 | %%-------------------------------------------------------------------- 271 | %% @private 272 | %% @doc 273 | %% Handling cast messages 274 | %% 275 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 276 | %% {noreply, State, Timeout} | 277 | %% {stop, Reason, State} 278 | %% @end 279 | %%-------------------------------------------------------------------- 280 | handle_cast(run, #state{ vm = VM } = State) -> 281 | erlv8_nif:run(VM, self()), 282 | {noreply, State}; 283 | 284 | handle_cast(_Msg, State) -> 285 | {noreply, State}. 286 | 287 | %%-------------------------------------------------------------------- 288 | %% @private 289 | %% @doc 290 | %% Handling all non call/cast messages 291 | %% 292 | %% @spec handle_info(Info, State) -> {noreply, State} | 293 | %% {noreply, State, Timeout} | 294 | %% {stop, Reason, State} 295 | %% @end 296 | %%-------------------------------------------------------------------- 297 | %% Invocation 298 | handle_info({F,#erlv8_fun_invocation{ is_construct_call = ICC, this = This, ref = Ref } = Invocation,Args}, #state{ ticked = Ticked } = State) when is_function(F), is_list(Args) -> 299 | Self = self(), 300 | spawn(fun () -> 301 | Result = (catch erlang:apply(F,[Invocation,Args])), 302 | Result1 = 303 | case Result of 304 | {'EXIT',{Val, Trace}} when is_atom(Val) -> 305 | {throw, {error, ?Error(Val)}}; 306 | {'EXIT',{{Tag, Val}, Trace}} -> 307 | {throw, {error, ?ErrorVal(Tag)}}; 308 | _ -> 309 | case ICC of 310 | true -> 311 | This; 312 | false -> 313 | Result 314 | end 315 | end, 316 | case ets:lookup(Ticked, Ref) of 317 | [{Ref, {From, {call, _, _, _}}}] -> 318 | gen_server2:reply(From, Result1), 319 | ets:delete(Ticked, Ref); 320 | [{Ref, {From, {call, _, _}}}] -> 321 | gen_server2:reply(From, Result1), 322 | ets:delete(Ticked, Ref); 323 | [{Ref, {From, {inst, _, _}}}] -> 324 | gen_server2:reply(From, Result1), 325 | ets:delete(Ticked, Ref); 326 | _ -> 327 | enqueue_tick(Self, {result, Ref, Result1}) 328 | end 329 | end), 330 | {noreply, State}; 331 | handle_info({result, Ref, Result}, #state{ ticked = Ticked } = State) -> 332 | 333 | case ets:lookup(Ticked, Ref) of 334 | [] -> 335 | {noreply, State}; 336 | [{Ref, {From, _Tick}}] -> 337 | gen_server2:reply(From, Result), 338 | ets:delete(Ticked, Ref), 339 | {noreply, State} 340 | end; 341 | 342 | handle_info({'DEBUG',Name,Payload}, #state{ debug = Debug } = State) -> 343 | ets:insert(Debug, {Name, Payload}), 344 | {noreply, State}; 345 | 346 | handle_info(timeout, State) -> 347 | kill(self()), 348 | {noreply, State}; 349 | handle_info(_Info, State) -> 350 | {noreply, State}. 351 | 352 | prioritise_info({retick, _}, _State) -> 353 | 1; 354 | prioritise_info(tick_me,_State) -> 355 | 0; 356 | prioritise_info(_,_State) -> 357 | 0. 358 | 359 | 360 | %%-------------------------------------------------------------------- 361 | %% @private 362 | %% @doc 363 | %% This function is called by a gen_server when it is about to 364 | %% terminate. It should be the opposite of Module:init/1 and do any 365 | %% necessary cleaning up. When it returns, the gen_server terminates 366 | %% with Reason. The return value is ignored. 367 | %% 368 | %% @spec terminate(Reason, State) -> void() 369 | %% @end 370 | %%-------------------------------------------------------------------- 371 | terminate(_Reason, #state{ vm = VM } = _State) -> 372 | ok = erlv8_nif:stop(VM,make_ref()). 373 | 374 | %%-------------------------------------------------------------------- 375 | %% @private 376 | %% @doc 377 | %% Convert process state when code is changed 378 | %% 379 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 380 | %% @end 381 | %%-------------------------------------------------------------------- 382 | code_change(_OldVsn, State, _Extra) -> 383 | {ok, State}. 384 | 385 | %%%=================================================================== 386 | %%% Internal functions 387 | %%%=================================================================== 388 | update_ticked(_Ref, From, {result, _, _}, Ticked) -> %% do not insert results, nobody is going to reply on them 389 | gen_server2:reply(From, ok), 390 | Ticked; 391 | update_ticked(Ref, From, Tick, Ticked) -> 392 | ets:insert(Ticked, {Ref, {From, Tick}}). 393 | -------------------------------------------------------------------------------- /src/priority_queue.erl: -------------------------------------------------------------------------------- 1 | %% The contents of this file are subject to the Mozilla Public License 2 | %% Version 1.1 (the "License"); you may not use this file except in 3 | %% compliance with the License. You may obtain a copy of the License at 4 | %% http://www.mozilla.org/MPL/ 5 | %% 6 | %% Software distributed under the License is distributed on an "AS IS" 7 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 8 | %% License for the specific language governing rights and limitations 9 | %% under the License. 10 | %% 11 | %% The Original Code is RabbitMQ. 12 | %% 13 | %% The Initial Developers of the Original Code are LShift Ltd, 14 | %% Cohesive Financial Technologies LLC, and Rabbit Technologies Ltd. 15 | %% 16 | %% Portions created before 22-Nov-2008 00:00:00 GMT by LShift Ltd, 17 | %% Cohesive Financial Technologies LLC, or Rabbit Technologies Ltd 18 | %% are Copyright (C) 2007-2008 LShift Ltd, Cohesive Financial 19 | %% Technologies LLC, and Rabbit Technologies Ltd. 20 | %% 21 | %% Portions created by LShift Ltd are Copyright (C) 2007-2010 LShift 22 | %% Ltd. Portions created by Cohesive Financial Technologies LLC are 23 | %% Copyright (C) 2007-2010 Cohesive Financial Technologies 24 | %% LLC. Portions created by Rabbit Technologies Ltd are Copyright 25 | %% (C) 2007-2010 Rabbit Technologies Ltd. 26 | %% 27 | %% All Rights Reserved. 28 | %% 29 | %% Contributor(s): ______________________________________. 30 | %% 31 | 32 | %% Priority queues have essentially the same interface as ordinary 33 | %% queues, except that a) there is an in/3 that takes a priority, and 34 | %% b) we have only implemented the core API we need. 35 | %% 36 | %% Priorities should be integers - the higher the value the higher the 37 | %% priority - but we don't actually check that. 38 | %% 39 | %% in/2 inserts items with priority 0. 40 | %% 41 | %% We optimise the case where a priority queue is being used just like 42 | %% an ordinary queue. When that is the case we represent the priority 43 | %% queue as an ordinary queue. We could just call into the 'queue' 44 | %% module for that, but for efficiency we implement the relevant 45 | %% functions directly in here, thus saving on inter-module calls and 46 | %% eliminating a level of boxing. 47 | %% 48 | %% When the queue contains items with non-zero priorities, it is 49 | %% represented as a sorted kv list with the inverted Priority as the 50 | %% key and an ordinary queue as the value. Here again we use our own 51 | %% ordinary queue implemention for efficiency, often making recursive 52 | %% calls into the same function knowing that ordinary queues represent 53 | %% a base case. 54 | 55 | 56 | -module(priority_queue). 57 | 58 | -export([new/0, is_queue/1, is_empty/1, len/1, to_list/1, in/2, in/3, 59 | out/1, join/2]). 60 | 61 | %%---------------------------------------------------------------------------- 62 | 63 | -ifdef(use_specs). 64 | 65 | -type(priority() :: integer()). 66 | -type(squeue() :: {queue, [any()], [any()]}). 67 | -type(pqueue() :: squeue() | {pqueue, [{priority(), squeue()}]}). 68 | 69 | -spec(new/0 :: () -> pqueue()). 70 | -spec(is_queue/1 :: (any()) -> boolean()). 71 | -spec(is_empty/1 :: (pqueue()) -> boolean()). 72 | -spec(len/1 :: (pqueue()) -> non_neg_integer()). 73 | -spec(to_list/1 :: (pqueue()) -> [{priority(), any()}]). 74 | -spec(in/2 :: (any(), pqueue()) -> pqueue()). 75 | -spec(in/3 :: (any(), priority(), pqueue()) -> pqueue()). 76 | -spec(out/1 :: (pqueue()) -> {empty | {value, any()}, pqueue()}). 77 | -spec(join/2 :: (pqueue(), pqueue()) -> pqueue()). 78 | 79 | -endif. 80 | 81 | %%---------------------------------------------------------------------------- 82 | 83 | new() -> 84 | {queue, [], []}. 85 | 86 | is_queue({queue, R, F}) when is_list(R), is_list(F) -> 87 | true; 88 | is_queue({pqueue, Queues}) when is_list(Queues) -> 89 | lists:all(fun ({P, Q}) -> is_integer(P) andalso is_queue(Q) end, 90 | Queues); 91 | is_queue(_) -> 92 | false. 93 | 94 | is_empty({queue, [], []}) -> 95 | true; 96 | is_empty(_) -> 97 | false. 98 | 99 | len({queue, R, F}) when is_list(R), is_list(F) -> 100 | length(R) + length(F); 101 | len({pqueue, Queues}) -> 102 | lists:sum([len(Q) || {_, Q} <- Queues]). 103 | 104 | to_list({queue, In, Out}) when is_list(In), is_list(Out) -> 105 | [{0, V} || V <- Out ++ lists:reverse(In, [])]; 106 | to_list({pqueue, Queues}) -> 107 | [{-P, V} || {P, Q} <- Queues, {0, V} <- to_list(Q)]. 108 | 109 | in(Item, Q) -> 110 | in(Item, 0, Q). 111 | 112 | in(X, 0, {queue, [_] = In, []}) -> 113 | {queue, [X], In}; 114 | in(X, 0, {queue, In, Out}) when is_list(In), is_list(Out) -> 115 | {queue, [X|In], Out}; 116 | in(X, Priority, _Q = {queue, [], []}) -> 117 | in(X, Priority, {pqueue, []}); 118 | in(X, Priority, Q = {queue, _, _}) -> 119 | in(X, Priority, {pqueue, [{0, Q}]}); 120 | in(X, Priority, {pqueue, Queues}) -> 121 | P = -Priority, 122 | {pqueue, case lists:keysearch(P, 1, Queues) of 123 | {value, {_, Q}} -> 124 | lists:keyreplace(P, 1, Queues, {P, in(X, Q)}); 125 | false -> 126 | lists:keysort(1, [{P, {queue, [X], []}} | Queues]) 127 | end}. 128 | 129 | out({queue, [], []} = Q) -> 130 | {empty, Q}; 131 | out({queue, [V], []}) -> 132 | {{value, V}, {queue, [], []}}; 133 | out({queue, [Y|In], []}) -> 134 | [V|Out] = lists:reverse(In, []), 135 | {{value, V}, {queue, [Y], Out}}; 136 | out({queue, In, [V]}) when is_list(In) -> 137 | {{value,V}, r2f(In)}; 138 | out({queue, In,[V|Out]}) when is_list(In) -> 139 | {{value, V}, {queue, In, Out}}; 140 | out({pqueue, [{P, Q} | Queues]}) -> 141 | {R, Q1} = out(Q), 142 | NewQ = case is_empty(Q1) of 143 | true -> case Queues of 144 | [] -> {queue, [], []}; 145 | [{0, OnlyQ}] -> OnlyQ; 146 | [_|_] -> {pqueue, Queues} 147 | end; 148 | false -> {pqueue, [{P, Q1} | Queues]} 149 | end, 150 | {R, NewQ}. 151 | 152 | join(A, {queue, [], []}) -> 153 | A; 154 | join({queue, [], []}, B) -> 155 | B; 156 | join({queue, AIn, AOut}, {queue, BIn, BOut}) -> 157 | {queue, BIn, AOut ++ lists:reverse(AIn, BOut)}; 158 | join(A = {queue, _, _}, {pqueue, BPQ}) -> 159 | {Pre, Post} = lists:splitwith(fun ({P, _}) -> P < 0 end, BPQ), 160 | Post1 = case Post of 161 | [] -> [ {0, A} ]; 162 | [ {0, ZeroQueue} | Rest ] -> [ {0, join(A, ZeroQueue)} | Rest ]; 163 | _ -> [ {0, A} | Post ] 164 | end, 165 | {pqueue, Pre ++ Post1}; 166 | join({pqueue, APQ}, B = {queue, _, _}) -> 167 | {Pre, Post} = lists:splitwith(fun ({P, _}) -> P < 0 end, APQ), 168 | Post1 = case Post of 169 | [] -> [ {0, B} ]; 170 | [ {0, ZeroQueue} | Rest ] -> [ {0, join(ZeroQueue, B)} | Rest ]; 171 | _ -> [ {0, B} | Post ] 172 | end, 173 | {pqueue, Pre ++ Post1}; 174 | join({pqueue, APQ}, {pqueue, BPQ}) -> 175 | {pqueue, merge(APQ, BPQ, [])}. 176 | 177 | merge([], BPQ, Acc) -> 178 | lists:reverse(Acc, BPQ); 179 | merge(APQ, [], Acc) -> 180 | lists:reverse(Acc, APQ); 181 | merge([{P, A}|As], [{P, B}|Bs], Acc) -> 182 | merge(As, Bs, [ {P, join(A, B)} | Acc ]); 183 | merge([{PA, A}|As], Bs = [{PB, _}|_], Acc) when PA < PB -> 184 | merge(As, Bs, [ {PA, A} | Acc ]); 185 | merge(As = [{_, _}|_], [{PB, B}|Bs], Acc) -> 186 | merge(As, Bs, [ {PB, B} | Acc ]). 187 | 188 | r2f([]) -> {queue, [], []}; 189 | r2f([_] = R) -> {queue, [], R}; 190 | r2f([X,Y]) -> {queue, [X], [Y]}; 191 | r2f([X,Y|R]) -> {queue, [X,Y], lists:reverse(R, [])}. 192 | -------------------------------------------------------------------------------- /src/supervisor2.erl: -------------------------------------------------------------------------------- 1 | %% This file is a copy of supervisor.erl from the R13B-3 Erlang/OTP 2 | %% distribution, with the following modifications: 3 | %% 4 | %% 1) the module name is supervisor2 5 | %% 6 | %% 2) there is a new strategy called 7 | %% simple_one_for_one_terminate. This is exactly the same as for 8 | %% simple_one_for_one, except that children *are* explicitly 9 | %% terminated as per the shutdown component of the child_spec. 10 | %% 11 | %% 3) child specifications can contain, as the restart type, a tuple 12 | %% {permanent, Delay} | {transient, Delay} where Delay >= 0. The 13 | %% delay, in seconds, indicates what should happen if a child, upon 14 | %% being restarted, exceeds the MaxT and MaxR parameters. Thus, if 15 | %% a child exits, it is restarted as normal. If it exits 16 | %% sufficiently quickly and often to exceed the boundaries set by 17 | %% the MaxT and MaxR parameters, and a Delay is specified, then 18 | %% rather than stopping the supervisor, the supervisor instead 19 | %% continues and tries to start up the child again, Delay seconds 20 | %% later. 21 | %% 22 | %% Note that you can never restart more frequently than the MaxT 23 | %% and MaxR parameters allow: i.e. you must wait until *both* the 24 | %% Delay has passed *and* the MaxT and MaxR parameters allow the 25 | %% child to be restarted. 26 | %% 27 | %% Also note that the Delay is a *minimum*. There is no guarantee 28 | %% that the child will be restarted within that time, especially if 29 | %% other processes are dying and being restarted at the same time - 30 | %% essentially we have to wait for the delay to have passed and for 31 | %% the MaxT and MaxR parameters to permit the child to be 32 | %% restarted. This may require waiting for longer than Delay. 33 | %% 34 | %% 4) Added an 'intrinsic' restart type. Like the transient type, this 35 | %% type means the child should only be restarted if the child exits 36 | %% abnormally. Unlike the transient type, if the child exits 37 | %% normally, the supervisor itself also exits normally. If the 38 | %% child is a supervisor and it exits normally (i.e. with reason of 39 | %% 'shutdown') then the child's parent also exits normally. 40 | %% 41 | %% All modifications are (C) 2010 Rabbit Technologies Ltd. 42 | %% 43 | %% %CopyrightBegin% 44 | %% 45 | %% Copyright Ericsson AB 1996-2009. All Rights Reserved. 46 | %% 47 | %% The contents of this file are subject to the Erlang Public License, 48 | %% Version 1.1, (the "License"); you may not use this file except in 49 | %% compliance with the License. You should have received a copy of the 50 | %% Erlang Public License along with this software. If not, it can be 51 | %% retrieved online at http://www.erlang.org/. 52 | %% 53 | %% Software distributed under the License is distributed on an "AS IS" 54 | %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 55 | %% the License for the specific language governing rights and limitations 56 | %% under the License. 57 | %% 58 | %% %CopyrightEnd% 59 | %% 60 | -module(supervisor2). 61 | 62 | -behaviour(gen_server). 63 | 64 | %% External exports 65 | -export([start_link/2,start_link/3, 66 | start_child/2, restart_child/2, 67 | delete_child/2, terminate_child/2, 68 | which_children/1, find_child/2, 69 | check_childspecs/1]). 70 | 71 | -export([behaviour_info/1]). 72 | 73 | %% Internal exports 74 | -export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]). 75 | -export([handle_cast/2]). 76 | -export([delayed_restart/2]). 77 | 78 | -define(DICT, dict). 79 | 80 | -record(state, {name, 81 | strategy, 82 | children = [], 83 | dynamics = ?DICT:new(), 84 | intensity, 85 | period, 86 | restarts = [], 87 | module, 88 | args}). 89 | 90 | -record(child, {pid = undefined, % pid is undefined when child is not running 91 | name, 92 | mfa, 93 | restart_type, 94 | shutdown, 95 | child_type, 96 | modules = []}). 97 | 98 | -define(is_simple(State), State#state.strategy =:= simple_one_for_one orelse 99 | State#state.strategy =:= simple_one_for_one_terminate). 100 | -define(is_terminate_simple(State), 101 | State#state.strategy =:= simple_one_for_one_terminate). 102 | 103 | behaviour_info(callbacks) -> 104 | [{init,1}]; 105 | behaviour_info(_Other) -> 106 | undefined. 107 | 108 | %%% --------------------------------------------------- 109 | %%% This is a general process supervisor built upon gen_server.erl. 110 | %%% Servers/processes should/could also be built using gen_server.erl. 111 | %%% SupName = {local, atom()} | {global, atom()}. 112 | %%% --------------------------------------------------- 113 | start_link(Mod, Args) -> 114 | gen_server:start_link(?MODULE, {self, Mod, Args}, []). 115 | 116 | start_link(SupName, Mod, Args) -> 117 | gen_server:start_link(SupName, ?MODULE, {SupName, Mod, Args}, []). 118 | 119 | %%% --------------------------------------------------- 120 | %%% Interface functions. 121 | %%% --------------------------------------------------- 122 | start_child(Supervisor, ChildSpec) -> 123 | call(Supervisor, {start_child, ChildSpec}). 124 | 125 | restart_child(Supervisor, Name) -> 126 | call(Supervisor, {restart_child, Name}). 127 | 128 | delete_child(Supervisor, Name) -> 129 | call(Supervisor, {delete_child, Name}). 130 | 131 | %%----------------------------------------------------------------- 132 | %% Func: terminate_child/2 133 | %% Returns: ok | {error, Reason} 134 | %% Note that the child is *always* terminated in some 135 | %% way (maybe killed). 136 | %%----------------------------------------------------------------- 137 | terminate_child(Supervisor, Name) -> 138 | call(Supervisor, {terminate_child, Name}). 139 | 140 | which_children(Supervisor) -> 141 | call(Supervisor, which_children). 142 | 143 | find_child(Supervisor, Name) -> 144 | [Pid || {Name1, Pid, _Type, _Modules} <- which_children(Supervisor), 145 | Name1 =:= Name]. 146 | 147 | call(Supervisor, Req) -> 148 | gen_server:call(Supervisor, Req, infinity). 149 | 150 | check_childspecs(ChildSpecs) when is_list(ChildSpecs) -> 151 | case check_startspec(ChildSpecs) of 152 | {ok, _} -> ok; 153 | Error -> {error, Error} 154 | end; 155 | check_childspecs(X) -> {error, {badarg, X}}. 156 | 157 | delayed_restart(Supervisor, RestartDetails) -> 158 | gen_server:cast(Supervisor, {delayed_restart, RestartDetails}). 159 | 160 | %%% --------------------------------------------------- 161 | %%% 162 | %%% Initialize the supervisor. 163 | %%% 164 | %%% --------------------------------------------------- 165 | init({SupName, Mod, Args}) -> 166 | process_flag(trap_exit, true), 167 | case Mod:init(Args) of 168 | {ok, {SupFlags, StartSpec}} -> 169 | case init_state(SupName, SupFlags, Mod, Args) of 170 | {ok, State} when ?is_simple(State) -> 171 | init_dynamic(State, StartSpec); 172 | {ok, State} -> 173 | init_children(State, StartSpec); 174 | Error -> 175 | {stop, {supervisor_data, Error}} 176 | end; 177 | ignore -> 178 | ignore; 179 | Error -> 180 | {stop, {bad_return, {Mod, init, Error}}} 181 | end. 182 | 183 | init_children(State, StartSpec) -> 184 | SupName = State#state.name, 185 | case check_startspec(StartSpec) of 186 | {ok, Children} -> 187 | case start_children(Children, SupName) of 188 | {ok, NChildren} -> 189 | {ok, State#state{children = NChildren}}; 190 | {error, NChildren} -> 191 | terminate_children(NChildren, SupName), 192 | {stop, shutdown} 193 | end; 194 | Error -> 195 | {stop, {start_spec, Error}} 196 | end. 197 | 198 | init_dynamic(State, [StartSpec]) -> 199 | case check_startspec([StartSpec]) of 200 | {ok, Children} -> 201 | {ok, State#state{children = Children}}; 202 | Error -> 203 | {stop, {start_spec, Error}} 204 | end; 205 | init_dynamic(_State, StartSpec) -> 206 | {stop, {bad_start_spec, StartSpec}}. 207 | 208 | %%----------------------------------------------------------------- 209 | %% Func: start_children/2 210 | %% Args: Children = [#child] in start order 211 | %% SupName = {local, atom()} | {global, atom()} | {pid(),Mod} 212 | %% Purpose: Start all children. The new list contains #child's 213 | %% with pids. 214 | %% Returns: {ok, NChildren} | {error, NChildren} 215 | %% NChildren = [#child] in termination order (reversed 216 | %% start order) 217 | %%----------------------------------------------------------------- 218 | start_children(Children, SupName) -> start_children(Children, [], SupName). 219 | 220 | start_children([Child|Chs], NChildren, SupName) -> 221 | case do_start_child(SupName, Child) of 222 | {ok, Pid} -> 223 | start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName); 224 | {ok, Pid, _Extra} -> 225 | start_children(Chs, [Child#child{pid = Pid}|NChildren], SupName); 226 | {error, Reason} -> 227 | report_error(start_error, Reason, Child, SupName), 228 | {error, lists:reverse(Chs) ++ [Child | NChildren]} 229 | end; 230 | start_children([], NChildren, _SupName) -> 231 | {ok, NChildren}. 232 | 233 | do_start_child(SupName, Child) -> 234 | #child{mfa = {M, F, A}} = Child, 235 | case catch apply(M, F, A) of 236 | {ok, Pid} when is_pid(Pid) -> 237 | NChild = Child#child{pid = Pid}, 238 | report_progress(NChild, SupName), 239 | {ok, Pid}; 240 | {ok, Pid, Extra} when is_pid(Pid) -> 241 | NChild = Child#child{pid = Pid}, 242 | report_progress(NChild, SupName), 243 | {ok, Pid, Extra}; 244 | ignore -> 245 | {ok, undefined}; 246 | {error, What} -> {error, What}; 247 | What -> {error, What} 248 | end. 249 | 250 | do_start_child_i(M, F, A) -> 251 | case catch apply(M, F, A) of 252 | {ok, Pid} when is_pid(Pid) -> 253 | {ok, Pid}; 254 | {ok, Pid, Extra} when is_pid(Pid) -> 255 | {ok, Pid, Extra}; 256 | ignore -> 257 | {ok, undefined}; 258 | {error, Error} -> 259 | {error, Error}; 260 | What -> 261 | {error, What} 262 | end. 263 | 264 | 265 | %%% --------------------------------------------------- 266 | %%% 267 | %%% Callback functions. 268 | %%% 269 | %%% --------------------------------------------------- 270 | handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) -> 271 | #child{mfa = {M, F, A}} = hd(State#state.children), 272 | Args = A ++ EArgs, 273 | case do_start_child_i(M, F, Args) of 274 | {ok, Pid} -> 275 | NState = State#state{dynamics = 276 | ?DICT:store(Pid, Args, State#state.dynamics)}, 277 | {reply, {ok, Pid}, NState}; 278 | {ok, Pid, Extra} -> 279 | NState = State#state{dynamics = 280 | ?DICT:store(Pid, Args, State#state.dynamics)}, 281 | {reply, {ok, Pid, Extra}, NState}; 282 | What -> 283 | {reply, What, State} 284 | end; 285 | 286 | %%% The requests terminate_child, delete_child and restart_child are 287 | %%% invalid for simple_one_for_one and simple_one_for_one_terminate 288 | %%% supervisors. 289 | handle_call({_Req, _Data}, _From, State) when ?is_simple(State) -> 290 | {reply, {error, State#state.strategy}, State}; 291 | 292 | handle_call({start_child, ChildSpec}, _From, State) -> 293 | case check_childspec(ChildSpec) of 294 | {ok, Child} -> 295 | {Resp, NState} = handle_start_child(Child, State), 296 | {reply, Resp, NState}; 297 | What -> 298 | {reply, {error, What}, State} 299 | end; 300 | 301 | handle_call({restart_child, Name}, _From, State) -> 302 | case get_child(Name, State) of 303 | {value, Child} when Child#child.pid =:= undefined -> 304 | case do_start_child(State#state.name, Child) of 305 | {ok, Pid} -> 306 | NState = replace_child(Child#child{pid = Pid}, State), 307 | {reply, {ok, Pid}, NState}; 308 | {ok, Pid, Extra} -> 309 | NState = replace_child(Child#child{pid = Pid}, State), 310 | {reply, {ok, Pid, Extra}, NState}; 311 | Error -> 312 | {reply, Error, State} 313 | end; 314 | {value, _} -> 315 | {reply, {error, running}, State}; 316 | _ -> 317 | {reply, {error, not_found}, State} 318 | end; 319 | 320 | handle_call({delete_child, Name}, _From, State) -> 321 | case get_child(Name, State) of 322 | {value, Child} when Child#child.pid =:= undefined -> 323 | NState = remove_child(Child, State), 324 | {reply, ok, NState}; 325 | {value, _} -> 326 | {reply, {error, running}, State}; 327 | _ -> 328 | {reply, {error, not_found}, State} 329 | end; 330 | 331 | handle_call({terminate_child, Name}, _From, State) -> 332 | case get_child(Name, State) of 333 | {value, Child} -> 334 | NChild = do_terminate(Child, State#state.name), 335 | {reply, ok, replace_child(NChild, State)}; 336 | _ -> 337 | {reply, {error, not_found}, State} 338 | end; 339 | 340 | handle_call(which_children, _From, State) when ?is_simple(State) -> 341 | [#child{child_type = CT, modules = Mods}] = State#state.children, 342 | Reply = lists:map(fun ({Pid, _}) -> {undefined, Pid, CT, Mods} end, 343 | ?DICT:to_list(State#state.dynamics)), 344 | {reply, Reply, State}; 345 | 346 | handle_call(which_children, _From, State) -> 347 | Resp = 348 | lists:map(fun (#child{pid = Pid, name = Name, 349 | child_type = ChildType, modules = Mods}) -> 350 | {Name, Pid, ChildType, Mods} 351 | end, 352 | State#state.children), 353 | {reply, Resp, State}. 354 | 355 | 356 | handle_cast({delayed_restart, {RestartType, Reason, Child}}, State) 357 | when ?is_simple(State) -> 358 | {ok, NState} = do_restart(RestartType, Reason, Child, State), 359 | {noreply, NState}; 360 | handle_cast({delayed_restart, {RestartType, Reason, Child}}, State) -> 361 | case get_child(Child#child.name, State) of 362 | {value, Child} -> 363 | {ok, NState} = do_restart(RestartType, Reason, Child, State), 364 | {noreply, NState}; 365 | _ -> 366 | {noreply, State} 367 | end; 368 | 369 | %%% Hopefully cause a function-clause as there is no API function 370 | %%% that utilizes cast. 371 | handle_cast(null, State) -> 372 | error_logger:error_msg("ERROR: Supervisor received cast-message 'null'~n", 373 | []), 374 | 375 | {noreply, State}. 376 | 377 | %% 378 | %% Take care of terminated children. 379 | %% 380 | handle_info({'EXIT', Pid, Reason}, State) -> 381 | case restart_child(Pid, Reason, State) of 382 | {ok, State1} -> 383 | {noreply, State1}; 384 | {shutdown, State1} -> 385 | {stop, shutdown, State1} 386 | end; 387 | 388 | handle_info(Msg, State) -> 389 | error_logger:error_msg("Supervisor received unexpected message: ~p~n", 390 | [Msg]), 391 | {noreply, State}. 392 | %% 393 | %% Terminate this server. 394 | %% 395 | terminate(_Reason, State) when ?is_terminate_simple(State) -> 396 | terminate_simple_children( 397 | hd(State#state.children), State#state.dynamics, State#state.name), 398 | ok; 399 | terminate(_Reason, State) -> 400 | terminate_children(State#state.children, State#state.name), 401 | ok. 402 | 403 | %% 404 | %% Change code for the supervisor. 405 | %% Call the new call-back module and fetch the new start specification. 406 | %% Combine the new spec. with the old. If the new start spec. is 407 | %% not valid the code change will not succeed. 408 | %% Use the old Args as argument to Module:init/1. 409 | %% NOTE: This requires that the init function of the call-back module 410 | %% does not have any side effects. 411 | %% 412 | code_change(_, State, _) -> 413 | case (State#state.module):init(State#state.args) of 414 | {ok, {SupFlags, StartSpec}} -> 415 | case catch check_flags(SupFlags) of 416 | ok -> 417 | {Strategy, MaxIntensity, Period} = SupFlags, 418 | update_childspec(State#state{strategy = Strategy, 419 | intensity = MaxIntensity, 420 | period = Period}, 421 | StartSpec); 422 | Error -> 423 | {error, Error} 424 | end; 425 | ignore -> 426 | {ok, State}; 427 | Error -> 428 | Error 429 | end. 430 | 431 | check_flags({Strategy, MaxIntensity, Period}) -> 432 | validStrategy(Strategy), 433 | validIntensity(MaxIntensity), 434 | validPeriod(Period), 435 | ok; 436 | check_flags(What) -> 437 | {bad_flags, What}. 438 | 439 | update_childspec(State, StartSpec) when ?is_simple(State) -> 440 | case check_startspec(StartSpec) of 441 | {ok, [Child]} -> 442 | {ok, State#state{children = [Child]}}; 443 | Error -> 444 | {error, Error} 445 | end; 446 | 447 | update_childspec(State, StartSpec) -> 448 | case check_startspec(StartSpec) of 449 | {ok, Children} -> 450 | OldC = State#state.children, % In reverse start order ! 451 | NewC = update_childspec1(OldC, Children, []), 452 | {ok, State#state{children = NewC}}; 453 | Error -> 454 | {error, Error} 455 | end. 456 | 457 | update_childspec1([Child|OldC], Children, KeepOld) -> 458 | case update_chsp(Child, Children) of 459 | {ok,NewChildren} -> 460 | update_childspec1(OldC, NewChildren, KeepOld); 461 | false -> 462 | update_childspec1(OldC, Children, [Child|KeepOld]) 463 | end; 464 | update_childspec1([], Children, KeepOld) -> 465 | % Return them in (keeped) reverse start order. 466 | lists:reverse(Children ++ KeepOld). 467 | 468 | update_chsp(OldCh, Children) -> 469 | case lists:map(fun (Ch) when OldCh#child.name =:= Ch#child.name -> 470 | Ch#child{pid = OldCh#child.pid}; 471 | (Ch) -> 472 | Ch 473 | end, 474 | Children) of 475 | Children -> 476 | false; % OldCh not found in new spec. 477 | NewC -> 478 | {ok, NewC} 479 | end. 480 | 481 | %%% --------------------------------------------------- 482 | %%% Start a new child. 483 | %%% --------------------------------------------------- 484 | 485 | handle_start_child(Child, State) -> 486 | case get_child(Child#child.name, State) of 487 | false -> 488 | case do_start_child(State#state.name, Child) of 489 | {ok, Pid} -> 490 | Children = State#state.children, 491 | {{ok, Pid}, 492 | State#state{children = 493 | [Child#child{pid = Pid}|Children]}}; 494 | {ok, Pid, Extra} -> 495 | Children = State#state.children, 496 | {{ok, Pid, Extra}, 497 | State#state{children = 498 | [Child#child{pid = Pid}|Children]}}; 499 | {error, What} -> 500 | {{error, {What, Child}}, State} 501 | end; 502 | {value, OldChild} when OldChild#child.pid =/= undefined -> 503 | {{error, {already_started, OldChild#child.pid}}, State}; 504 | {value, _OldChild} -> 505 | {{error, already_present}, State} 506 | end. 507 | 508 | %%% --------------------------------------------------- 509 | %%% Restart. A process has terminated. 510 | %%% Returns: {ok, #state} | {shutdown, #state} 511 | %%% --------------------------------------------------- 512 | 513 | restart_child(Pid, Reason, State) when ?is_simple(State) -> 514 | case ?DICT:find(Pid, State#state.dynamics) of 515 | {ok, Args} -> 516 | [Child] = State#state.children, 517 | RestartType = Child#child.restart_type, 518 | {M, F, _} = Child#child.mfa, 519 | NChild = Child#child{pid = Pid, mfa = {M, F, Args}}, 520 | do_restart(RestartType, Reason, NChild, State); 521 | error -> 522 | {ok, State} 523 | end; 524 | restart_child(Pid, Reason, State) -> 525 | Children = State#state.children, 526 | case lists:keysearch(Pid, #child.pid, Children) of 527 | {value, Child} -> 528 | RestartType = Child#child.restart_type, 529 | do_restart(RestartType, Reason, Child, State); 530 | _ -> 531 | {ok, State} 532 | end. 533 | 534 | do_restart({RestartType, Delay}, Reason, Child, State) -> 535 | case restart1(Child, State) of 536 | {ok, NState} -> 537 | {ok, NState}; 538 | {terminate, NState} -> 539 | {ok, _TRef} = timer:apply_after( 540 | trunc(Delay*1000), ?MODULE, delayed_restart, 541 | [self(), {{RestartType, Delay}, Reason, Child}]), 542 | {ok, NState} 543 | end; 544 | do_restart(permanent, Reason, Child, State) -> 545 | report_error(child_terminated, Reason, Child, State#state.name), 546 | restart(Child, State); 547 | do_restart(intrinsic, normal, Child, State) -> 548 | {shutdown, state_del_child(Child, State)}; 549 | do_restart(intrinsic, shutdown, Child = #child{child_type = supervisor}, 550 | State) -> 551 | {shutdown, state_del_child(Child, State)}; 552 | do_restart(_, normal, Child, State) -> 553 | NState = state_del_child(Child, State), 554 | {ok, NState}; 555 | do_restart(_, shutdown, Child, State) -> 556 | NState = state_del_child(Child, State), 557 | {ok, NState}; 558 | do_restart(Type, Reason, Child, State) when Type =:= transient orelse 559 | Type =:= intrinsic -> 560 | report_error(child_terminated, Reason, Child, State#state.name), 561 | restart(Child, State); 562 | do_restart(temporary, Reason, Child, State) -> 563 | report_error(child_terminated, Reason, Child, State#state.name), 564 | NState = state_del_child(Child, State), 565 | {ok, NState}. 566 | 567 | restart(Child, State) -> 568 | case add_restart(State) of 569 | {ok, NState} -> 570 | restart(NState#state.strategy, Child, NState, fun restart/2); 571 | {terminate, NState} -> 572 | report_error(shutdown, reached_max_restart_intensity, 573 | Child, State#state.name), 574 | {shutdown, state_del_child(Child, NState)} 575 | end. 576 | 577 | restart1(Child, State) -> 578 | case add_restart(State) of 579 | {ok, NState} -> 580 | restart(NState#state.strategy, Child, NState, fun restart1/2); 581 | {terminate, _NState} -> 582 | %% we've reached the max restart intensity, but the 583 | %% add_restart will have added to the restarts 584 | %% field. Given we don't want to die here, we need to go 585 | %% back to the old restarts field otherwise we'll never 586 | %% attempt to restart later. 587 | {terminate, State} 588 | end. 589 | 590 | restart(Strategy, Child, State, Restart) 591 | when Strategy =:= simple_one_for_one orelse 592 | Strategy =:= simple_one_for_one_terminate -> 593 | #child{mfa = {M, F, A}} = Child, 594 | Dynamics = ?DICT:erase(Child#child.pid, State#state.dynamics), 595 | case do_start_child_i(M, F, A) of 596 | {ok, Pid} -> 597 | NState = State#state{dynamics = ?DICT:store(Pid, A, Dynamics)}, 598 | {ok, NState}; 599 | {ok, Pid, _Extra} -> 600 | NState = State#state{dynamics = ?DICT:store(Pid, A, Dynamics)}, 601 | {ok, NState}; 602 | {error, Error} -> 603 | report_error(start_error, Error, Child, State#state.name), 604 | Restart(Child, State) 605 | end; 606 | restart(one_for_one, Child, State, Restart) -> 607 | case do_start_child(State#state.name, Child) of 608 | {ok, Pid} -> 609 | NState = replace_child(Child#child{pid = Pid}, State), 610 | {ok, NState}; 611 | {ok, Pid, _Extra} -> 612 | NState = replace_child(Child#child{pid = Pid}, State), 613 | {ok, NState}; 614 | {error, Reason} -> 615 | report_error(start_error, Reason, Child, State#state.name), 616 | Restart(Child, State) 617 | end; 618 | restart(rest_for_one, Child, State, Restart) -> 619 | {ChAfter, ChBefore} = split_child(Child#child.pid, State#state.children), 620 | ChAfter2 = terminate_children(ChAfter, State#state.name), 621 | case start_children(ChAfter2, State#state.name) of 622 | {ok, ChAfter3} -> 623 | {ok, State#state{children = ChAfter3 ++ ChBefore}}; 624 | {error, ChAfter3} -> 625 | Restart(Child, State#state{children = ChAfter3 ++ ChBefore}) 626 | end; 627 | restart(one_for_all, Child, State, Restart) -> 628 | Children1 = del_child(Child#child.pid, State#state.children), 629 | Children2 = terminate_children(Children1, State#state.name), 630 | case start_children(Children2, State#state.name) of 631 | {ok, NChs} -> 632 | {ok, State#state{children = NChs}}; 633 | {error, NChs} -> 634 | Restart(Child, State#state{children = NChs}) 635 | end. 636 | 637 | %%----------------------------------------------------------------- 638 | %% Func: terminate_children/2 639 | %% Args: Children = [#child] in termination order 640 | %% SupName = {local, atom()} | {global, atom()} | {pid(),Mod} 641 | %% Returns: NChildren = [#child] in 642 | %% startup order (reversed termination order) 643 | %%----------------------------------------------------------------- 644 | terminate_children(Children, SupName) -> 645 | terminate_children(Children, SupName, []). 646 | 647 | terminate_children([Child | Children], SupName, Res) -> 648 | NChild = do_terminate(Child, SupName), 649 | terminate_children(Children, SupName, [NChild | Res]); 650 | terminate_children([], _SupName, Res) -> 651 | Res. 652 | 653 | terminate_simple_children(Child, Dynamics, SupName) -> 654 | dict:fold(fun (Pid, _Args, _Any) -> 655 | do_terminate(Child#child{pid = Pid}, SupName) 656 | end, ok, Dynamics), 657 | ok. 658 | 659 | do_terminate(Child, SupName) when Child#child.pid =/= undefined -> 660 | ReportError = fun (Reason) -> 661 | report_error(shutdown_error, Reason, Child, SupName) 662 | end, 663 | case shutdown(Child#child.pid, Child#child.shutdown) of 664 | ok -> 665 | ok; 666 | {error, normal} -> 667 | case Child#child.restart_type of 668 | permanent -> ReportError(normal); 669 | {permanent, _Delay} -> ReportError(normal); 670 | _ -> ok 671 | end; 672 | {error, OtherReason} -> 673 | ReportError(OtherReason) 674 | end, 675 | Child#child{pid = undefined}; 676 | do_terminate(Child, _SupName) -> 677 | Child. 678 | 679 | %%----------------------------------------------------------------- 680 | %% Shutdowns a child. We must check the EXIT value 681 | %% of the child, because it might have died with another reason than 682 | %% the wanted. In that case we want to report the error. We put a 683 | %% monitor on the child an check for the 'DOWN' message instead of 684 | %% checking for the 'EXIT' message, because if we check the 'EXIT' 685 | %% message a "naughty" child, who does unlink(Sup), could hang the 686 | %% supervisor. 687 | %% Returns: ok | {error, OtherReason} (this should be reported) 688 | %%----------------------------------------------------------------- 689 | shutdown(Pid, brutal_kill) -> 690 | 691 | case monitor_child(Pid) of 692 | ok -> 693 | exit(Pid, kill), 694 | receive 695 | {'DOWN', _MRef, process, Pid, killed} -> 696 | ok; 697 | {'DOWN', _MRef, process, Pid, OtherReason} -> 698 | {error, OtherReason} 699 | end; 700 | {error, Reason} -> 701 | {error, Reason} 702 | end; 703 | 704 | shutdown(Pid, Time) -> 705 | 706 | case monitor_child(Pid) of 707 | ok -> 708 | exit(Pid, shutdown), %% Try to shutdown gracefully 709 | receive 710 | {'DOWN', _MRef, process, Pid, shutdown} -> 711 | ok; 712 | {'DOWN', _MRef, process, Pid, OtherReason} -> 713 | {error, OtherReason} 714 | after Time -> 715 | exit(Pid, kill), %% Force termination. 716 | receive 717 | {'DOWN', _MRef, process, Pid, OtherReason} -> 718 | {error, OtherReason} 719 | end 720 | end; 721 | {error, Reason} -> 722 | {error, Reason} 723 | end. 724 | 725 | %% Help function to shutdown/2 switches from link to monitor approach 726 | monitor_child(Pid) -> 727 | 728 | %% Do the monitor operation first so that if the child dies 729 | %% before the monitoring is done causing a 'DOWN'-message with 730 | %% reason noproc, we will get the real reason in the 'EXIT'-message 731 | %% unless a naughty child has already done unlink... 732 | erlang:monitor(process, Pid), 733 | unlink(Pid), 734 | 735 | receive 736 | %% If the child dies before the unlik we must empty 737 | %% the mail-box of the 'EXIT'-message and the 'DOWN'-message. 738 | {'EXIT', Pid, Reason} -> 739 | receive 740 | {'DOWN', _, process, Pid, _} -> 741 | {error, Reason} 742 | end 743 | after 0 -> 744 | %% If a naughty child did unlink and the child dies before 745 | %% monitor the result will be that shutdown/2 receives a 746 | %% 'DOWN'-message with reason noproc. 747 | %% If the child should die after the unlink there 748 | %% will be a 'DOWN'-message with a correct reason 749 | %% that will be handled in shutdown/2. 750 | ok 751 | end. 752 | 753 | 754 | %%----------------------------------------------------------------- 755 | %% Child/State manipulating functions. 756 | %%----------------------------------------------------------------- 757 | state_del_child(#child{pid = Pid}, State) when ?is_simple(State) -> 758 | NDynamics = ?DICT:erase(Pid, State#state.dynamics), 759 | State#state{dynamics = NDynamics}; 760 | state_del_child(Child, State) -> 761 | NChildren = del_child(Child#child.name, State#state.children), 762 | State#state{children = NChildren}. 763 | 764 | del_child(Name, [Ch|Chs]) when Ch#child.name =:= Name -> 765 | [Ch#child{pid = undefined} | Chs]; 766 | del_child(Pid, [Ch|Chs]) when Ch#child.pid =:= Pid -> 767 | [Ch#child{pid = undefined} | Chs]; 768 | del_child(Name, [Ch|Chs]) -> 769 | [Ch|del_child(Name, Chs)]; 770 | del_child(_, []) -> 771 | []. 772 | 773 | %% Chs = [S4, S3, Ch, S1, S0] 774 | %% Ret: {[S4, S3, Ch], [S1, S0]} 775 | split_child(Name, Chs) -> 776 | split_child(Name, Chs, []). 777 | 778 | split_child(Name, [Ch|Chs], After) when Ch#child.name =:= Name -> 779 | {lists:reverse([Ch#child{pid = undefined} | After]), Chs}; 780 | split_child(Pid, [Ch|Chs], After) when Ch#child.pid =:= Pid -> 781 | {lists:reverse([Ch#child{pid = undefined} | After]), Chs}; 782 | split_child(Name, [Ch|Chs], After) -> 783 | split_child(Name, Chs, [Ch | After]); 784 | split_child(_, [], After) -> 785 | {lists:reverse(After), []}. 786 | 787 | get_child(Name, State) -> 788 | lists:keysearch(Name, #child.name, State#state.children). 789 | replace_child(Child, State) -> 790 | Chs = do_replace_child(Child, State#state.children), 791 | State#state{children = Chs}. 792 | 793 | do_replace_child(Child, [Ch|Chs]) when Ch#child.name =:= Child#child.name -> 794 | [Child | Chs]; 795 | do_replace_child(Child, [Ch|Chs]) -> 796 | [Ch|do_replace_child(Child, Chs)]. 797 | 798 | remove_child(Child, State) -> 799 | Chs = lists:keydelete(Child#child.name, #child.name, State#state.children), 800 | State#state{children = Chs}. 801 | 802 | %%----------------------------------------------------------------- 803 | %% Func: init_state/4 804 | %% Args: SupName = {local, atom()} | {global, atom()} | self 805 | %% Type = {Strategy, MaxIntensity, Period} 806 | %% Strategy = one_for_one | one_for_all | simple_one_for_one | 807 | %% rest_for_one 808 | %% MaxIntensity = integer() 809 | %% Period = integer() 810 | %% Mod :== atom() 811 | %% Arsg :== term() 812 | %% Purpose: Check that Type is of correct type (!) 813 | %% Returns: {ok, #state} | Error 814 | %%----------------------------------------------------------------- 815 | init_state(SupName, Type, Mod, Args) -> 816 | case catch init_state1(SupName, Type, Mod, Args) of 817 | {ok, State} -> 818 | {ok, State}; 819 | Error -> 820 | Error 821 | end. 822 | 823 | init_state1(SupName, {Strategy, MaxIntensity, Period}, Mod, Args) -> 824 | validStrategy(Strategy), 825 | validIntensity(MaxIntensity), 826 | validPeriod(Period), 827 | {ok, #state{name = supname(SupName,Mod), 828 | strategy = Strategy, 829 | intensity = MaxIntensity, 830 | period = Period, 831 | module = Mod, 832 | args = Args}}; 833 | init_state1(_SupName, Type, _, _) -> 834 | {invalid_type, Type}. 835 | 836 | validStrategy(simple_one_for_one_terminate) -> true; 837 | validStrategy(simple_one_for_one) -> true; 838 | validStrategy(one_for_one) -> true; 839 | validStrategy(one_for_all) -> true; 840 | validStrategy(rest_for_one) -> true; 841 | validStrategy(What) -> throw({invalid_strategy, What}). 842 | 843 | validIntensity(Max) when is_integer(Max), 844 | Max >= 0 -> true; 845 | validIntensity(What) -> throw({invalid_intensity, What}). 846 | 847 | validPeriod(Period) when is_integer(Period), 848 | Period > 0 -> true; 849 | validPeriod(What) -> throw({invalid_period, What}). 850 | 851 | supname(self,Mod) -> {self(),Mod}; 852 | supname(N,_) -> N. 853 | 854 | %%% ------------------------------------------------------ 855 | %%% Check that the children start specification is valid. 856 | %%% Shall be a six (6) tuple 857 | %%% {Name, Func, RestartType, Shutdown, ChildType, Modules} 858 | %%% where Name is an atom 859 | %%% Func is {Mod, Fun, Args} == {atom, atom, list} 860 | %%% RestartType is permanent | temporary | transient | 861 | %%% intrinsic | {permanent, Delay} | 862 | %%% {transient, Delay} where Delay >= 0 863 | %%% Shutdown = integer() | infinity | brutal_kill 864 | %%% ChildType = supervisor | worker 865 | %%% Modules = [atom()] | dynamic 866 | %%% Returns: {ok, [#child]} | Error 867 | %%% ------------------------------------------------------ 868 | 869 | check_startspec(Children) -> check_startspec(Children, []). 870 | 871 | check_startspec([ChildSpec|T], Res) -> 872 | case check_childspec(ChildSpec) of 873 | {ok, Child} -> 874 | case lists:keymember(Child#child.name, #child.name, Res) of 875 | true -> {duplicate_child_name, Child#child.name}; 876 | false -> check_startspec(T, [Child | Res]) 877 | end; 878 | Error -> Error 879 | end; 880 | check_startspec([], Res) -> 881 | {ok, lists:reverse(Res)}. 882 | 883 | check_childspec({Name, Func, RestartType, Shutdown, ChildType, Mods}) -> 884 | catch check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods); 885 | check_childspec(X) -> {invalid_child_spec, X}. 886 | 887 | check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods) -> 888 | validName(Name), 889 | validFunc(Func), 890 | validRestartType(RestartType), 891 | validChildType(ChildType), 892 | validShutdown(Shutdown, ChildType), 893 | validMods(Mods), 894 | {ok, #child{name = Name, mfa = Func, restart_type = RestartType, 895 | shutdown = Shutdown, child_type = ChildType, modules = Mods}}. 896 | 897 | validChildType(supervisor) -> true; 898 | validChildType(worker) -> true; 899 | validChildType(What) -> throw({invalid_child_type, What}). 900 | 901 | validName(_Name) -> true. 902 | 903 | validFunc({M, F, A}) when is_atom(M), 904 | is_atom(F), 905 | is_list(A) -> true; 906 | validFunc(Func) -> throw({invalid_mfa, Func}). 907 | 908 | validRestartType(permanent) -> true; 909 | validRestartType(temporary) -> true; 910 | validRestartType(transient) -> true; 911 | validRestartType(intrinsic) -> true; 912 | validRestartType({permanent, Delay}) -> validDelay(Delay); 913 | validRestartType({transient, Delay}) -> validDelay(Delay); 914 | validRestartType(RestartType) -> throw({invalid_restart_type, 915 | RestartType}). 916 | 917 | validDelay(Delay) when is_number(Delay), 918 | Delay >= 0 -> true; 919 | validDelay(What) -> throw({invalid_delay, What}). 920 | 921 | validShutdown(Shutdown, _) 922 | when is_integer(Shutdown), Shutdown > 0 -> true; 923 | validShutdown(infinity, supervisor) -> true; 924 | validShutdown(brutal_kill, _) -> true; 925 | validShutdown(Shutdown, _) -> throw({invalid_shutdown, Shutdown}). 926 | 927 | validMods(dynamic) -> true; 928 | validMods(Mods) when is_list(Mods) -> 929 | lists:foreach(fun (Mod) -> 930 | if 931 | is_atom(Mod) -> ok; 932 | true -> throw({invalid_module, Mod}) 933 | end 934 | end, 935 | Mods); 936 | validMods(Mods) -> throw({invalid_modules, Mods}). 937 | 938 | %%% ------------------------------------------------------ 939 | %%% Add a new restart and calculate if the max restart 940 | %%% intensity has been reached (in that case the supervisor 941 | %%% shall terminate). 942 | %%% All restarts accured inside the period amount of seconds 943 | %%% are kept in the #state.restarts list. 944 | %%% Returns: {ok, State'} | {terminate, State'} 945 | %%% ------------------------------------------------------ 946 | 947 | add_restart(State) -> 948 | I = State#state.intensity, 949 | P = State#state.period, 950 | R = State#state.restarts, 951 | Now = erlang:now(), 952 | R1 = add_restart([Now|R], Now, P), 953 | State1 = State#state{restarts = R1}, 954 | case length(R1) of 955 | CurI when CurI =< I -> 956 | {ok, State1}; 957 | _ -> 958 | {terminate, State1} 959 | end. 960 | 961 | add_restart([R|Restarts], Now, Period) -> 962 | case inPeriod(R, Now, Period) of 963 | true -> 964 | [R|add_restart(Restarts, Now, Period)]; 965 | _ -> 966 | [] 967 | end; 968 | add_restart([], _, _) -> 969 | []. 970 | 971 | inPeriod(Time, Now, Period) -> 972 | case difference(Time, Now) of 973 | T when T > Period -> 974 | false; 975 | _ -> 976 | true 977 | end. 978 | 979 | %% 980 | %% Time = {MegaSecs, Secs, MicroSecs} (NOTE: MicroSecs is ignored) 981 | %% Calculate the time elapsed in seconds between two timestamps. 982 | %% If MegaSecs is equal just subtract Secs. 983 | %% Else calculate the Mega difference and add the Secs difference, 984 | %% note that Secs difference can be negative, e.g. 985 | %% {827, 999999, 676} diff {828, 1, 653753} == > 2 secs. 986 | %% 987 | difference({TimeM, TimeS, _}, {CurM, CurS, _}) when CurM > TimeM -> 988 | ((CurM - TimeM) * 1000000) + (CurS - TimeS); 989 | difference({_, TimeS, _}, {_, CurS, _}) -> 990 | CurS - TimeS. 991 | 992 | %%% ------------------------------------------------------ 993 | %%% Error and progress reporting. 994 | %%% ------------------------------------------------------ 995 | 996 | report_error(Error, Reason, Child, SupName) -> 997 | ErrorMsg = [{supervisor, SupName}, 998 | {errorContext, Error}, 999 | {reason, Reason}, 1000 | {offender, extract_child(Child)}], 1001 | error_logger:error_report(supervisor_report, ErrorMsg). 1002 | 1003 | 1004 | extract_child(Child) -> 1005 | [{pid, Child#child.pid}, 1006 | {name, Child#child.name}, 1007 | {mfa, Child#child.mfa}, 1008 | {restart_type, Child#child.restart_type}, 1009 | {shutdown, Child#child.shutdown}, 1010 | {child_type, Child#child.child_type}]. 1011 | 1012 | report_progress(Child, SupName) -> 1013 | Progress = [{supervisor, SupName}, 1014 | {started, extract_child(Child)}], 1015 | error_logger:info_report(progress, Progress). 1016 | -------------------------------------------------------------------------------- /test/erlv8_tests.erl: -------------------------------------------------------------------------------- 1 | -module(erlv8_tests). 2 | 3 | -include_lib("proper/include/proper.hrl"). 4 | -include_lib("eunit/include/eunit.hrl"). 5 | 6 | -behaviour(proper_statem). 7 | 8 | -export([initial_state/0, command/1, precondition/2, postcondition/3, 9 | next_state/3]). 10 | 11 | -compile(export_all). 12 | 13 | -record(state, { 14 | vms = [], 15 | passed = [] 16 | }). 17 | 18 | %% helpers 19 | start_vm() -> 20 | {ok, VM} = erlv8_vm:start(), 21 | VM. 22 | 23 | exit_vm(VM) -> 24 | erlv8_vm:stop(VM), 25 | VM. 26 | 27 | run_tests(VM) -> 28 | ets:insert(?MODULE, {vm, VM}), 29 | proper:module(?MODULE, 30 | [{'on_output', 31 | fun(Format, Data) -> 32 | io:format(standard_error, Format, Data) 33 | end}, 34 | {numtests, 100}]). 35 | 36 | 37 | %% statem 38 | 39 | initial_state() -> 40 | #state{}. 41 | 42 | command(#state{ vms = VMs, passed = Passed }) -> 43 | frequency([ 44 | {1 - (length(VMs) + length(Passed)), {call, ?MODULE, start_vm, []}}, 45 | {length(Passed), {call, ?MODULE, exit_vm, [oneof(Passed)]}}, 46 | {length(VMs), {call, ?MODULE, run_tests, [oneof(VMs)]}} 47 | ]). 48 | 49 | precondition(_State, _Call) -> 50 | true. 51 | 52 | postcondition(_S, {call, ?MODULE, run_tests, [_VM]}, []) -> 53 | true; 54 | 55 | postcondition(_S, {call, ?MODULE, run_tests, [_VM]}, _) -> 56 | false; 57 | 58 | postcondition(_S, _C, _R) -> 59 | true. 60 | 61 | next_state(#state{ vms = VMs } = State, V, {call, ?MODULE, start_vm, []}) -> 62 | State#state{ vms = [V|VMs] }; 63 | 64 | next_state(#state{ passed = VMs } = State, _V, {call, ?MODULE, exit_vm, [VM]}) -> 65 | State#state{ passed = VMs -- [VM] }; 66 | 67 | next_state(#state{ vms = VMs, passed = Passed } = State, _V, {call, ?MODULE, run_tests, [VM]}) -> 68 | State#state{ vms = VMs -- [VM], passed = [VM|Passed] }; 69 | 70 | next_state(State, _V, _C) -> 71 | State. 72 | 73 | 74 | statem() -> 75 | ?FORALL(Cmds, commands(?MODULE), 76 | ?TRAPEXIT( 77 | begin 78 | {History,State,Result} = run_commands(?MODULE, Cmds), 79 | [ erlv8_vm:stop(VM) || VM <- State#state.vms ], 80 | ?WHENFAIL(io:format("History: ~p\nState: ~p\nResult: ~p\n", 81 | [History, State, Result]), 82 | aggregate(command_names(Cmds), Result =:= ok)) 83 | end)). 84 | 85 | %%% generators 86 | js_string() -> 87 | oneof([list(proper_stdgen:utf8_char()), 88 | proper_stdgen:utf8_bin()]). 89 | 90 | %%% properties 91 | 92 | %% type conversion 93 | prop_to_js_string() -> 94 | ?FORALL(String, js_string(), 95 | begin 96 | [{vm, VM}] = ets:lookup(?MODULE, vm), 97 | Bin = unicode:characters_to_binary(String), 98 | Obj = erlv8_vm:taint(VM, Bin), 99 | case Obj =:= Bin of 100 | true -> 101 | ok; 102 | false -> 103 | io:format("->~p/~p~n", [Bin, Obj]), 104 | ok 105 | end, 106 | Obj =:= Bin 107 | end). 108 | 109 | 110 | %% eunit 111 | t_properties() -> 112 | ?assertEqual(true, proper:quickcheck(statem(), 113 | [{'on_output', 114 | fun(Format, Data) -> 115 | io:format(standard_error, Format, Data) 116 | end}, 117 | {numtests, 10}])). 118 | 119 | 120 | erlv8_test_() -> 121 | [{setup, 122 | fun() -> 123 | ets:new(?MODULE,[named_table, public]), 124 | ok = application:start(erlv8) 125 | end, 126 | fun(_) -> 127 | ets:delete(?MODULE), 128 | application:stop(erlv8) 129 | end, 130 | [ 131 | {timeout, 30, {"PropEr tests", ?_test(t_properties())}} 132 | ]}]. 133 | --------------------------------------------------------------------------------