Skip to content

Commit e91b1fa

Browse files
committed
Do not tie eval functions to Elixir version
1 parent 863e668 commit e91b1fa

File tree

1 file changed

+75
-51
lines changed

1 file changed

+75
-51
lines changed

lib/elixir/src/elixir.erl

Lines changed: 75 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
]).
1212
-include("elixir.hrl").
1313
-define(system, 'Elixir.System').
14+
-define(elixir_eval_env, {elixir, eval_env}).
1415

1516
%% Top level types
1617
%% TODO: Remove char_list type on v2.0
@@ -357,8 +358,25 @@ eval_forms(Tree, Binding, OrigE, Opts) ->
357358
_ -> [Erl]
358359
end,
359360

360-
ExternalHandler = eval_external_handler(NewE),
361-
{value, Value, NewBinding} = erl_eval:exprs(Exprs, ErlBinding, none, ExternalHandler),
361+
ExternalHandler = eval_external_handler(),
362+
363+
{value, Value, NewBinding} =
364+
try
365+
%% ?elixir_eval_env is used by the external handler.
366+
%%
367+
%% The reason why we use the process dictionary to pass the environment
368+
%% is because we want to avoid passing closures to erl_eval, as that
369+
%% would effectively tie the eval code to the Elixir version and it is
370+
%% best if it depends solely on Erlang/OTP.
371+
%%
372+
%% The downside is that functions that escape the eval context will no
373+
%% longer have the original environment they came from.
374+
erlang:put(?elixir_eval_env, NewE),
375+
erl_eval:exprs(Exprs, ErlBinding, none, ExternalHandler)
376+
after
377+
erlang:erase(?elixir_eval_env)
378+
end,
379+
362380
PruneBefore = if Prune -> length(Binding); true -> -1 end,
363381

364382
{DumpedBinding, DumpedVars} =
@@ -369,52 +387,62 @@ eval_forms(Tree, Binding, OrigE, Opts) ->
369387

370388
%% TODO: Remove conditional once we require Erlang/OTP 25+.
371389
-if(?OTP_RELEASE >= 25).
372-
eval_external_handler(Env) ->
373-
Fun = fun(Ann, FunOrModFun, Args) ->
374-
try
375-
case FunOrModFun of
376-
{Mod, Fun} -> apply(Mod, Fun, Args);
377-
Fun -> apply(Fun, Args)
378-
end
379-
catch
380-
Kind:Reason:Stacktrace ->
381-
%% Take everything up to the Elixir module
382-
Pruned =
383-
lists:takewhile(fun
384-
({elixir,_,_,_}) -> false;
385-
(_) -> true
386-
end, Stacktrace),
387-
388-
Caller =
389-
lists:dropwhile(fun
390-
({elixir,_,_,_}) -> false;
391-
(_) -> true
392-
end, Stacktrace),
393-
394-
%% Now we prune any shared code path from erl_eval
395-
{current_stacktrace, Current} =
396-
erlang:process_info(self(), current_stacktrace),
397-
398-
%% We need to make sure that we don't generate more
399-
%% frames than supported. So we do our best to drop
400-
%% from the Caller, but if the caller has no frames,
401-
%% we need to drop from Pruned.
402-
{DroppedCaller, ToDrop} =
403-
case Caller of
404-
[] -> {[], true};
405-
_ -> {lists:droplast(Caller), false}
406-
end,
407-
408-
Reversed = drop_common(lists:reverse(Current), lists:reverse(Pruned), ToDrop),
409-
File = elixir_utils:characters_to_list(?key(Env, file)),
410-
Location = [{file, File}, {line, erl_anno:line(Ann)}],
411-
412-
%% Add file+line information at the bottom
413-
Custom = lists:reverse([{elixir_eval, '__FILE__', 1, Location} | Reversed], DroppedCaller),
414-
erlang:raise(Kind, Reason, Custom)
390+
eval_external_handler() -> {value, fun eval_external_handler/3}.
391+
-else.
392+
eval_external_handler() -> none.
393+
-endif.
394+
395+
eval_external_handler(Ann, FunOrModFun, Args) ->
396+
try
397+
case FunOrModFun of
398+
{Mod, Fun} -> apply(Mod, Fun, Args);
399+
Fun -> apply(Fun, Args)
415400
end
416-
end,
417-
{value, Fun}.
401+
catch
402+
Kind:Reason:Stacktrace ->
403+
%% Take everything up to the Elixir module
404+
Pruned =
405+
lists:takewhile(fun
406+
({elixir,_,_,_}) -> false;
407+
(_) -> true
408+
end, Stacktrace),
409+
410+
Caller =
411+
lists:dropwhile(fun
412+
({elixir,_,_,_}) -> false;
413+
(_) -> true
414+
end, Stacktrace),
415+
416+
%% Now we prune any shared code path from erl_eval
417+
{current_stacktrace, Current} =
418+
erlang:process_info(self(), current_stacktrace),
419+
420+
%% We need to make sure that we don't generate more
421+
%% frames than supported. So we do our best to drop
422+
%% from the Caller, but if the caller has no frames,
423+
%% we need to drop from Pruned.
424+
{DroppedCaller, ToDrop} =
425+
case Caller of
426+
[] -> {[], true};
427+
_ -> {lists:droplast(Caller), false}
428+
end,
429+
430+
Reversed = drop_common(lists:reverse(Current), lists:reverse(Pruned), ToDrop),
431+
432+
%% Add file+line information at the bottom
433+
Bottom =
434+
case erlang:get(?elixir_eval_env) of
435+
#{file := File} ->
436+
[{elixir_eval, '__FILE__', 1,
437+
[{file, elixir_utils:characters_to_list(File)}, {line, erl_anno:line(Ann)}]}];
438+
439+
_ ->
440+
[]
441+
end,
442+
443+
Custom = lists:reverse(Bottom ++ Reversed, DroppedCaller),
444+
erlang:raise(Kind, Reason, Custom)
445+
end.
418446

419447
%% We need to check if we have dropped any frames.
420448
%% If we have not dropped frames, then we need to drop one
@@ -426,10 +454,6 @@ drop_common([_ | T1], T2, ToDrop) -> drop_common(T1, T2, ToDrop);
426454
drop_common([], [{?MODULE, _, _, _} | T2], _ToDrop) -> T2;
427455
drop_common([], [_ | T2], true) -> T2;
428456
drop_common([], T2, _) -> T2.
429-
-else.
430-
eval_external_handler(_Env) ->
431-
none.
432-
-endif.
433457

434458
%% Converts a quoted expression to Erlang abstract format
435459

0 commit comments

Comments
 (0)