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);
426454drop_common ([], [{? MODULE , _ , _ , _ } | T2 ], _ToDrop ) -> T2 ;
427455drop_common ([], [_ | T2 ], true ) -> T2 ;
428456drop_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