/src/erlv8_vm.erl

http://github.com/beamjs/erlv8 · Erlang · 392 lines · 237 code · 72 blank · 83 comment · 2 complexity · 56ca291c3d955761535f1ba806ba0a98 MD5 · raw file

  1. -module(erlv8_vm).
  2. -behaviour(gen_server2).
  3. -include_lib("erlv8/include/erlv8.hrl").
  4. %% API
  5. -export([start_link/1,start/0,vm_resource/1,
  6. run/2, run/3, run/4,
  7. run_timed/3, run_timed/4, run_timed/5,
  8. global/1,stop/1,
  9. to_string/2,to_detail_string/2,taint/2,untaint/1,equals/3, strict_equals/3,
  10. enqueue_tick/2, enqueue_tick/3, enqueue_tick/4, next_tick/2, next_tick/3, next_tick/4,
  11. stor/3, retr/2, gc/1, kill/1]).
  12. %% gen_server2 callbacks
  13. -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
  14. terminate/2, code_change/3,prioritise_info/2]).
  15. -define(SERVER, ?MODULE).
  16. -record(state, {
  17. vm,
  18. ticked,
  19. storage = [],
  20. context,
  21. debug
  22. }).
  23. -define(Error(Msg), lists:flatten(io_lib:format("~s: ~p",[Msg,Trace]))).
  24. -define(ErrorVal(Msg), lists:flatten(io_lib:format("~s: ~p ~p",[Msg,Val,Trace]))).
  25. %%%===================================================================
  26. %%% API
  27. %%%===================================================================
  28. start() ->
  29. VM = erlv8_nif:new_vm(),
  30. supervisor2:start_child(erlv8_sup,[VM]).
  31. vm_resource(Server) ->
  32. gen_server2:call(Server, vm_resource).
  33. run(Server, Source) ->
  34. run(Server, erlv8_context:get(Server), Source).
  35. run_timed(Server, Source, Timeout) ->
  36. run_timed(Server, erlv8_context:get(Server), Source, Timeout).
  37. run_timed(Server, {_, _CtxRes} = Context, Source, Timeout) ->
  38. run_timed(Server, Context, Source, {"unknown",0,0}, Timeout).
  39. run_timed(Server, {C, CtxRes}, Source, {Name, LineOffset, ColumnOffset}, Timeout) ->
  40. Pid = spawn_link(fun() ->
  41. receive {'$gen_call', From, run} ->
  42. Res = run(Server, {C , CtxRes}, Source, {Name, LineOffset, ColumnOffset}),
  43. gen_server:reply(From, Res)
  44. end
  45. end),
  46. try gen_server:call(Pid, run, Timeout)
  47. catch
  48. exit:{timeout ,_} ->
  49. erlv8_vm:kill(Server),
  50. {error, timeout}
  51. end.
  52. run(Server, {_, _CtxRes} = Context, Source) ->
  53. run(Server, Context, Source, {"unknown",0,0}).
  54. run(Server, A, Source, B) when is_binary(Source) ->
  55. run(Server, A, binary_to_list(Source), B);
  56. run(Server, {_, CtxRes}, Source, {Name, LineOffset, ColumnOffset}) when is_list(Source) ->
  57. enqueue_tick(Server, {script, CtxRes, Source, Name, LineOffset, ColumnOffset}).
  58. global(Server) ->
  59. Ctx = erlv8_context:get(Server),
  60. erlv8_context:global(Ctx).
  61. stop(Server) ->
  62. gen_server2:call(Server,stop).
  63. to_string(Server, Val) ->
  64. enqueue_tick(Server, {to_string, Val}).
  65. to_detail_string(Server, Val) ->
  66. enqueue_tick(Server, {to_detail_string, Val}).
  67. enqueue_tick(Server, Tick) ->
  68. gen_server2:call(Server,{enqueue_tick, Tick}, infinity).
  69. enqueue_tick(Server, Tick, Ref) when is_reference(Ref) ->
  70. gen_server2:call(Server,{enqueue_tick, Tick, Ref}, infinity);
  71. enqueue_tick(Server, Tick, Timeout) ->
  72. gen_server2:call(Server,{enqueue_tick, Tick}, Timeout).
  73. enqueue_tick(Server, Tick, Timeout, Ref) when is_reference(Ref) ->
  74. gen_server2:call(Server,{enqueue_tick, Tick, Ref}, Timeout).
  75. next_tick(Server, Tick) ->
  76. gen_server2:call(Server,{next_tick, Tick}, infinity).
  77. next_tick(Server, Tick, Ref) when is_reference(Ref) ->
  78. gen_server2:call(Server,{next_tick, Tick, Ref}, infinity);
  79. next_tick(Server, Tick, Timeout) ->
  80. gen_server2:call(Server,{next_tick, Tick}, Timeout).
  81. next_tick(Server, Tick, Timeout, Ref) when is_reference(Ref) ->
  82. gen_server2:call(Server,{next_tick, Tick, Ref}, Timeout).
  83. taint(Server, Value) when ?is_v8(Value) ->
  84. enqueue_tick(Server, {taint, Value});
  85. taint(Server, {Error, _} = Value) when Error == error;
  86. Error == throw ->
  87. enqueue_tick(Server, {taint, Value});
  88. taint(Server, Value) when is_list(Value);
  89. is_binary(Value);
  90. is_atom(Value);
  91. is_number(Value);
  92. is_reference(Value);
  93. is_function(Value);
  94. is_pid(Value) ->
  95. enqueue_tick(Server, {taint, Value});
  96. taint(_Server, _) ->
  97. undefined.
  98. equals(Server, V1, V2) ->
  99. enqueue_tick(Server, {equals, V1, V2}).
  100. strict_equals(Server, V1, V2) ->
  101. enqueue_tick(Server, {strict_equals, V1, V2}).
  102. stor(Server, Key, Value) ->
  103. gen_server2:call(Server, {stor, Key, Value}).
  104. retr(Server, Key) ->
  105. gen_server2:call(Server, {retr, Key}).
  106. untaint({erlv8_object, _,_}=O) ->
  107. {erlv8_object,lists:map(fun ({Key, Val}) ->
  108. {Key, untaint(Val)}
  109. end,O:proplist()), undefined};
  110. untaint({erlv8_array, _,_}=O) ->
  111. {erlv8_array,lists:map(fun untaint/1,O:list()), undefined};
  112. untaint({erlv8_fun, _,_}=F) -> %% broken
  113. {erlv8_object,untaint(F:object()),undefined};
  114. untaint([H|T]) ->
  115. [untaint(H)|untaint(T)];
  116. untaint([]) ->
  117. [];
  118. untaint(Other) ->
  119. Other.
  120. gc(Server) ->
  121. (catch enqueue_tick(Server, {gc}, 0)),
  122. ok.
  123. kill(Server) ->
  124. gen_server2:call(Server, kill),
  125. erlv8_vm:run(Server, "1"), % hide returning {throw, null}.
  126. ok.
  127. %%--------------------------------------------------------------------
  128. %% @doc
  129. %% Starts the server
  130. %%
  131. %% @spec start_link(VM) -> {ok, Pid} | ignore | {error, Error}
  132. %% @end
  133. %%--------------------------------------------------------------------
  134. start_link(VM) ->
  135. gen_server2:start_link(?MODULE, [VM], []).
  136. %%%===================================================================
  137. %%% gen_server callbacks
  138. %%%===================================================================
  139. %%--------------------------------------------------------------------
  140. %% @private
  141. %% @doc
  142. %% Initializes the server
  143. %%
  144. %% @spec init(Args) -> {ok, State} |
  145. %% {ok, State, Timeout} |
  146. %% ignore |
  147. %% {stop, Reason}
  148. %% @end
  149. %%--------------------------------------------------------------------
  150. init([VM]) ->
  151. process_flag(trap_exit, true),
  152. erlv8_nif:set_server(VM, self()),
  153. Ctx = erlv8_nif:context(VM),
  154. {ok, #state{vm = VM, context = Ctx, debug = ets:new(erlv8_vm_debug,[]), ticked = ets:new(erlv8_vm_ticked,[public]) }}.
  155. %%--------------------------------------------------------------------
  156. %% @private
  157. %% @doc
  158. %% Handling call messages
  159. %%
  160. %% @spec handle_call(Request, From, State) ->
  161. %% {reply, Reply, State} |
  162. %% {reply, Reply, State, Timeout} |
  163. %% {noreply, State} |
  164. %% {noreply, State, Timeout} |
  165. %% {stop, Reason, Reply, State} |
  166. %% {stop, Reason, State}
  167. %% @end
  168. %%--------------------------------------------------------------------
  169. handle_call(vm_resource, _From, #state{ vm = VM } = State) ->
  170. {reply, VM, State};
  171. handle_call({stor, Key, Value}, _From, #state{ storage = Storage } = State) ->
  172. {reply, ok, State#state{ storage = [{Key, Value}|Storage] }};
  173. handle_call({retr, Key}, _From, #state{ storage = Storage } = State) ->
  174. {reply, proplists:get_value(Key, Storage), State};
  175. handle_call(context, _From, #state{} = State) ->
  176. {reply, {self(), State#state.context}, State};
  177. handle_call(new_context, _From, #state{ vm = VM } = State) ->
  178. {reply, {self(), erlv8_nif:new_context(VM)}, State};
  179. handle_call({global, Resource}, _From, #state{vm = VM} = State) ->
  180. {reply, erlv8_nif:global(VM, Resource), State};
  181. handle_call({to_string, Val}, _From, #state { vm = VM } = State) ->
  182. Reply = erlv8_nif:to_string(VM, Val),
  183. {reply, Reply, State};
  184. handle_call({to_detail_string, Val}, _From, #state { vm = VM } = State) ->
  185. Reply = erlv8_nif:to_detail_string(VM, Val),
  186. {reply, Reply, State};
  187. handle_call(kill, _From, #state { vm = VM } = State) ->
  188. Reply = erlv8_nif:kill(VM),
  189. {reply, Reply, State};
  190. handle_call(stop, _From, State) ->
  191. {stop, normal, ok, State};
  192. handle_call({enqueue_tick, Tick}, From, State) ->
  193. Ref = make_ref(),
  194. handle_call({enqueue_tick, Tick, Ref}, From, State);
  195. handle_call({enqueue_tick, Tick, Ref}, From, #state{ vm = VM, ticked = Ticked } = State) ->
  196. tack = erlv8_nif:tick(VM, Ref, Tick),
  197. update_ticked(Ref, From, Tick, Ticked),
  198. {noreply, State};
  199. handle_call({next_tick, Tick}, From, State) ->
  200. Ref = make_ref(),
  201. handle_call({next_tick, Tick, Ref}, From, State);
  202. handle_call({next_tick, Tick, Ref}, From, #state{ vm = VM, ticked = Ticked } = State) ->
  203. tack = erlv8_nif:tick(VM, Ref, Tick),
  204. update_ticked(Ref, From, Tick, Ticked),
  205. {noreply, State};
  206. handle_call(_Request, _From, State) ->
  207. {noreply, State}.
  208. %%--------------------------------------------------------------------
  209. %% @private
  210. %% @doc
  211. %% Handling cast messages
  212. %%
  213. %% @spec handle_cast(Msg, State) -> {noreply, State} |
  214. %% {noreply, State, Timeout} |
  215. %% {stop, Reason, State}
  216. %% @end
  217. %%--------------------------------------------------------------------
  218. handle_cast(run, #state{ vm = VM } = State) ->
  219. erlv8_nif:run(VM, self()),
  220. {noreply, State};
  221. handle_cast(_Msg, State) ->
  222. {noreply, State}.
  223. %%--------------------------------------------------------------------
  224. %% @private
  225. %% @doc
  226. %% Handling all non call/cast messages
  227. %%
  228. %% @spec handle_info(Info, State) -> {noreply, State} |
  229. %% {noreply, State, Timeout} |
  230. %% {stop, Reason, State}
  231. %% @end
  232. %%--------------------------------------------------------------------
  233. %% Invocation
  234. 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) ->
  235. Self = self(),
  236. spawn(fun () ->
  237. Result = (catch erlang:apply(F,[Invocation,Args])),
  238. Result1 =
  239. case Result of
  240. {'EXIT',{Val, Trace}} when is_atom(Val) ->
  241. {throw, {error, ?Error(Val)}};
  242. {'EXIT',{{Tag, Val}, Trace}} ->
  243. {throw, {error, ?ErrorVal(Tag)}};
  244. _ ->
  245. case ICC of
  246. true ->
  247. This;
  248. false ->
  249. Result
  250. end
  251. end,
  252. case ets:lookup(Ticked, Ref) of
  253. [{Ref, {From, {call, _, _, _}}}] ->
  254. gen_server2:reply(From, Result1),
  255. ets:delete(Ticked, Ref);
  256. [{Ref, {From, {call, _, _}}}] ->
  257. gen_server2:reply(From, Result1),
  258. ets:delete(Ticked, Ref);
  259. [{Ref, {From, {inst, _, _}}}] ->
  260. gen_server2:reply(From, Result1),
  261. ets:delete(Ticked, Ref);
  262. _ ->
  263. enqueue_tick(Self, {result, Ref, Result1})
  264. end
  265. end),
  266. {noreply, State};
  267. handle_info({result, Ref, Result}, #state{ ticked = Ticked } = State) ->
  268. case ets:lookup(Ticked, Ref) of
  269. [] ->
  270. {noreply, State};
  271. [{Ref, {From, _Tick}}] ->
  272. gen_server2:reply(From, Result),
  273. ets:delete(Ticked, Ref),
  274. {noreply, State}
  275. end;
  276. handle_info({'DEBUG',Name,Payload}, #state{ debug = Debug } = State) ->
  277. ets:insert(Debug, {Name, Payload}),
  278. {noreply, State};
  279. handle_info(timeout, State) ->
  280. kill(self()),
  281. {noreply, State};
  282. handle_info(_Info, State) ->
  283. {noreply, State}.
  284. prioritise_info({retick, _}, _State) ->
  285. 1;
  286. prioritise_info(tick_me,_State) ->
  287. 0;
  288. prioritise_info(_,_State) ->
  289. 0.
  290. %%--------------------------------------------------------------------
  291. %% @private
  292. %% @doc
  293. %% This function is called by a gen_server when it is about to
  294. %% terminate. It should be the opposite of Module:init/1 and do any
  295. %% necessary cleaning up. When it returns, the gen_server terminates
  296. %% with Reason. The return value is ignored.
  297. %%
  298. %% @spec terminate(Reason, State) -> void()
  299. %% @end
  300. %%--------------------------------------------------------------------
  301. terminate(_Reason, #state{ vm = VM } = _State) ->
  302. ok = erlv8_nif:stop(VM,make_ref()).
  303. %%--------------------------------------------------------------------
  304. %% @private
  305. %% @doc
  306. %% Convert process state when code is changed
  307. %%
  308. %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
  309. %% @end
  310. %%--------------------------------------------------------------------
  311. code_change(_OldVsn, State, _Extra) ->
  312. {ok, State}.
  313. %%%===================================================================
  314. %%% Internal functions
  315. %%%===================================================================
  316. update_ticked(_Ref, From, {result, _, _}, Ticked) -> %% do not insert results, nobody is going to reply on them
  317. gen_server2:reply(From, ok),
  318. Ticked;
  319. update_ticked(Ref, From, Tick, Ticked) ->
  320. ets:insert(Ticked, {Ref, {From, Tick}}).