11
11
]).
12
12
-include (" elixir.hrl" ).
13
13
-define (system , 'Elixir.System' ).
14
+ -define (elixir_eval_env , {elixir , eval_env }).
14
15
15
16
% % Top level types
16
17
% % TODO: Remove char_list type on v2.0
@@ -357,8 +358,25 @@ eval_forms(Tree, Binding, OrigE, Opts) ->
357
358
_ -> [Erl ]
358
359
end ,
359
360
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
+
362
380
PruneBefore = if Prune -> length (Binding ); true -> - 1 end ,
363
381
364
382
{DumpedBinding , DumpedVars } =
@@ -369,52 +387,62 @@ eval_forms(Tree, Binding, OrigE, Opts) ->
369
387
370
388
% % TODO: Remove conditional once we require Erlang/OTP 25+.
371
389
-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 )
415
400
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 .
418
446
419
447
% % We need to check if we have dropped any frames.
420
448
% % 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);
426
454
drop_common ([], [{? MODULE , _ , _ , _ } | T2 ], _ToDrop ) -> T2 ;
427
455
drop_common ([], [_ | T2 ], true ) -> T2 ;
428
456
drop_common ([], T2 , _ ) -> T2 .
429
- - else .
430
- eval_external_handler (_Env ) ->
431
- none .
432
- - endif .
433
457
434
458
% % Converts a quoted expression to Erlang abstract format
435
459
0 commit comments