Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
Please login to access the resource
home:Ledest:erlang:24
erlang
2411-Traverse-eval-forms-for-function-used-vars...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 2411-Traverse-eval-forms-for-function-used-vars-once.patch of Package erlang
From 20d15bd115b34d814d2fa3bae5509b35f5ae9664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@dashbit.co> Date: Tue, 7 Dec 2021 15:51:51 +0100 Subject: [PATCH] Traverse eval forms for function used vars once Prior to this implementation, every time an anonymous function was defined via eval, we would traverse its tree to find its used vars. However, if there are nested anonymous functions, they are traversed once by their parent and then traversed again each time they are executed. This means any form of nested loops are extremely expensive in the shell. With this patch, we traverse the tree once and then we keep the tree of used vars for all of its anonymous functions inside. This drastically speeds up those scenarios. Take this Elixir code: lines |> Enum.reduce(%{}, fn [x, y1, x, y2], grid -> Enum.reduce(y1..y2, grid, fn y, grid -> Map.update(grid, {x, y}, 1, &(&1 + 1)) end) [x1, y, x2, y], grid -> Enum.reduce(x1..x2, grid, fn x, grid -> Map.update(grid, {x, y}, 1, &(&1 + 1)) end) [x1, y1, x2, y2], grid -> Enum.reduce(Enum.zip(x1..x2, y1..y2), grid, fn point, grid -> Map.update(grid, point, 1, &(&1 + 1)) end) end) |> Enum.count(fn {_, v} -> v > 1 end) In the code above, there are three levels of anonymous function, which then become very expensive to traverse on every iteration. With this patch, the time to evaluate the code above goes from 2s to 0.6s for a large input. --- lib/stdlib/src/erl_eval.erl | 480 ++++++++++++++++------------- lib/stdlib/src/erl_lint.erl | 25 +- lib/stdlib/test/erl_eval_SUITE.erl | 40 +-- 3 files changed, 306 insertions(+), 239 deletions(-) diff --git a/lib/stdlib/src/erl_eval.erl b/lib/stdlib/src/erl_eval.erl index abb6d6813b..135d2713e7 100644 --- a/lib/stdlib/src/erl_eval.erl +++ b/lib/stdlib/src/erl_eval.erl @@ -69,6 +69,8 @@ -define(STACKTRACE, element(2, erlang:process_info(self(), current_stacktrace))). +empty_fun_used_vars() -> #{}. + %% exprs(ExpressionSeq, Bindings) %% exprs(ExpressionSeq, Bindings, LocalFuncHandler) %% exprs(ExpressionSeq, Bindings, LocalFuncHandler, ExternalFuncHandler) @@ -87,8 +89,8 @@ NewBindings :: binding_struct()). exprs(Exprs, Bs) -> case check_command(Exprs, Bs) of - ok -> - exprs(Exprs, Bs, none, none, none); + ok -> + exprs(Exprs, Bs, none, none, none, empty_fun_used_vars()); {error,{_Location,_Mod,Error}} -> erlang:raise(error, Error, ?STACKTRACE) end. @@ -101,7 +103,7 @@ exprs(Exprs, Bs) -> Value :: value(), NewBindings :: binding_struct()). exprs(Exprs, Bs, Lf) -> - exprs(Exprs, Bs, Lf, none, none). + exprs(Exprs, Bs, Lf, none, none, empty_fun_used_vars()). -spec(exprs(Expressions, Bindings, LocalFunctionHandler, NonLocalFunctionHandler) -> @@ -113,14 +115,25 @@ exprs(Exprs, Bs, Lf) -> Value :: value(), NewBindings :: binding_struct()). exprs(Exprs, Bs, Lf, Ef) -> - exprs(Exprs, Bs, Lf, Ef, none). + exprs(Exprs, Bs, Lf, Ef, none, empty_fun_used_vars()). -exprs([E], Bs0, Lf, Ef, RBs) -> - expr(E, Bs0, Lf, Ef, RBs); -exprs([E|Es], Bs0, Lf, Ef, RBs) -> +-spec(exprs(Expressions, Bindings, LocalFunctionHandler, + NonLocalFunctionHandler, ReturnFormat, FunUsedVars) -> + {value, Value, NewBindings} when + Expressions :: expressions(), + Bindings :: binding_struct(), + LocalFunctionHandler :: local_function_handler(), + NonLocalFunctionHandler :: non_local_function_handler(), + ReturnFormat :: none | value, + FunUsedVars :: erl_lint:fun_used_vars(), + Value :: value(), + NewBindings :: binding_struct()). +exprs([E], Bs0, Lf, Ef, RBs, FUVs) -> + expr(E, Bs0, Lf, Ef, RBs, FUVs); +exprs([E|Es], Bs0, Lf, Ef, RBs, FUVs) -> RBs1 = none, - {value,_V,Bs} = expr(E, Bs0, Lf, Ef, RBs1), - exprs(Es, Bs, Lf, Ef, RBs). + {value,_V,Bs} = expr(E, Bs0, Lf, Ef, RBs1, FUVs), + exprs(Es, Bs, Lf, Ef, RBs, FUVs). %% expr(Expression, Bindings) %% expr(Expression, Bindings, LocalFuncHandler) @@ -138,7 +151,7 @@ exprs([E|Es], Bs0, Lf, Ef, RBs) -> NewBindings :: binding_struct()). expr(E, Bs) -> case check_command([E], Bs) of - ok -> + ok -> expr(E, Bs, none, none, none); {error,{_Location,_Mod,Error}} -> erlang:raise(error, Error, ?STACKTRACE) @@ -184,9 +197,9 @@ fun_data(F) when is_function(F) -> case erlang:fun_info(F, module) of {module,?MODULE} -> case erlang:fun_info(F, env) of - {env,[{FBs,_FLf,_FEf,FCs}]} -> + {env,[{FBs,_FLf,_FEf,_FUVs,FCs}]} -> {fun_data,FBs,FCs}; - {env,[{FBs,_FLf,_FEf,FCs,FName}]} -> + {env,[{FBs,_FLf,_FEf,_FUVs,FCs,FName}]} -> {named_fun_data,FBs,FName,FCs} end; _ -> @@ -205,49 +218,63 @@ fun_data(_T) -> ReturnFormat :: none | value, Value :: value(), NewBindings :: binding_struct()). -expr({var,_,V}, Bs, _Lf, _Ef, RBs) -> +expr(Expr, Bs, Lf, Ef, Rbs) -> + expr(Expr, Bs, Lf, Ef, Rbs, empty_fun_used_vars()). + +-spec(expr(Expression, Bindings, LocalFunctionHandler, + NonLocalFunctionHandler, ReturnFormat, FunUsedVars) -> + {value, Value, NewBindings} | Value when + Expression :: expression(), + Bindings :: binding_struct(), + LocalFunctionHandler :: local_function_handler(), + NonLocalFunctionHandler :: non_local_function_handler(), + ReturnFormat :: none | value, + FunUsedVars :: erl_lint:fun_used_vars(), + Value :: value(), + NewBindings :: binding_struct()). +expr({var,_,V}, Bs, _Lf, _Ef, RBs, _FUVs) -> case binding(V, Bs) of {value,Val} -> ret_expr(Val, Bs, RBs); unbound -> % Cannot not happen if checked by erl_lint erlang:raise(error, {unbound,V}, ?STACKTRACE) end; -expr({char,_,C}, Bs, _Lf, _Ef, RBs) -> +expr({char,_,C}, Bs, _Lf, _Ef, RBs, _FUVs) -> ret_expr(C, Bs, RBs); -expr({integer,_,I}, Bs, _Lf, _Ef, RBs) -> +expr({integer,_,I}, Bs, _Lf, _Ef, RBs, _FUVs) -> ret_expr(I, Bs, RBs); -expr({float,_,F}, Bs, _Lf, _Ef, RBs) -> +expr({float,_,F}, Bs, _Lf, _Ef, RBs, _FUVs) -> ret_expr(F, Bs, RBs); -expr({atom,_,A}, Bs, _Lf, _Ef, RBs) -> +expr({atom,_,A}, Bs, _Lf, _Ef, RBs, _FUVs) -> ret_expr(A, Bs, RBs); -expr({string,_,S}, Bs, _Lf, _Ef, RBs) -> +expr({string,_,S}, Bs, _Lf, _Ef, RBs, _FUVs) -> ret_expr(S, Bs, RBs); -expr({nil, _}, Bs, _Lf, _Ef, RBs) -> +expr({nil, _}, Bs, _Lf, _Ef, RBs, _FUVs) -> ret_expr([], Bs, RBs); -expr({cons,_,H0,T0}, Bs0, Lf, Ef, RBs) -> - {value,H,Bs1} = expr(H0, Bs0, Lf, Ef, none), - {value,T,Bs2} = expr(T0, Bs0, Lf, Ef, none), +expr({cons,_,H0,T0}, Bs0, Lf, Ef, RBs, FUVs) -> + {value,H,Bs1} = expr(H0, Bs0, Lf, Ef, none, FUVs), + {value,T,Bs2} = expr(T0, Bs0, Lf, Ef, none, FUVs), ret_expr([H|T], merge_bindings(Bs1, Bs2), RBs); -expr({lc,_,E,Qs}, Bs, Lf, Ef, RBs) -> - eval_lc(E, Qs, Bs, Lf, Ef, RBs); -expr({bc,_,E,Qs}, Bs, Lf, Ef, RBs) -> - eval_bc(E, Qs, Bs, Lf, Ef, RBs); -expr({tuple,_,Es}, Bs0, Lf, Ef, RBs) -> - {Vs,Bs} = expr_list(Es, Bs0, Lf, Ef), +expr({lc,_,E,Qs}, Bs, Lf, Ef, RBs, FUVs) -> + eval_lc(E, Qs, Bs, Lf, Ef, RBs, FUVs); +expr({bc,_,E,Qs}, Bs, Lf, Ef, RBs, FUVs) -> + eval_bc(E, Qs, Bs, Lf, Ef, RBs, FUVs); +expr({tuple,_,Es}, Bs0, Lf, Ef, RBs, FUVs) -> + {Vs,Bs} = expr_list(Es, Bs0, Lf, Ef, FUVs), ret_expr(list_to_tuple(Vs), Bs, RBs); -expr({record_field,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs) -> +expr({record_field,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs, _FUVs) -> erlang:raise(error, {undef_record,Name}, ?STACKTRACE); -expr({record_index,_,Name,_}, _Bs, _Lf, _Ef, _RBs) -> +expr({record_index,_,Name,_}, _Bs, _Lf, _Ef, _RBs, _FUVs) -> erlang:raise(error, {undef_record,Name}, ?STACKTRACE); -expr({record,_,Name,_}, _Bs, _Lf, _Ef, _RBs) -> +expr({record,_,Name,_}, _Bs, _Lf, _Ef, _RBs, _FUVs) -> erlang:raise(error, {undef_record,Name}, ?STACKTRACE); -expr({record,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs) -> +expr({record,_,_,Name,_}, _Bs, _Lf, _Ef, _RBs, _FUVs) -> erlang:raise(error, {undef_record,Name}, ?STACKTRACE); %% map -expr({map,_,Binding,Es}, Bs0, Lf, Ef, RBs) -> - {value, Map0, Bs1} = expr(Binding, Bs0, Lf, Ef, none), - {Vs,Bs2} = eval_map_fields(Es, Bs0, Lf, Ef), +expr({map,_,Binding,Es}, Bs0, Lf, Ef, RBs, FUVs) -> + {value, Map0, Bs1} = expr(Binding, Bs0, Lf, Ef, none, FUVs), + {Vs,Bs2} = eval_map_fields(Es, Bs0, Lf, Ef, FUVs), _ = maps:put(k, v, Map0), %Validate map. Map1 = lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K, V, Mi); @@ -255,39 +282,39 @@ expr({map,_,Binding,Es}, Bs0, Lf, Ef, RBs) -> maps:update(K, V, Mi) end, Map0, Vs), ret_expr(Map1, merge_bindings(Bs2, Bs1), RBs); -expr({map,_,Es}, Bs0, Lf, Ef, RBs) -> - {Vs,Bs} = eval_map_fields(Es, Bs0, Lf, Ef), +expr({map,_,Es}, Bs0, Lf, Ef, RBs, FUVs) -> + {Vs,Bs} = eval_map_fields(Es, Bs0, Lf, Ef, FUVs), ret_expr(lists:foldl(fun ({map_assoc,K,V}, Mi) -> maps:put(K,V,Mi) end, maps:new(), Vs), Bs, RBs); -expr({block,_,Es}, Bs, Lf, Ef, RBs) -> - exprs(Es, Bs, Lf, Ef, RBs); -expr({'if',_,Cs}, Bs, Lf, Ef, RBs) -> - if_clauses(Cs, Bs, Lf, Ef, RBs); -expr({'case',_,E,Cs}, Bs0, Lf, Ef, RBs) -> - {value,Val,Bs} = expr(E, Bs0, Lf, Ef, none), - case_clauses(Val, Cs, Bs, Lf, Ef, RBs); -expr({'try',_,B,Cases,Catches,AB}, Bs, Lf, Ef, RBs) -> - try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs); -expr({'receive',_,Cs}, Bs, Lf, Ef, RBs) -> - receive_clauses(Cs, Bs, Lf, Ef, RBs); -expr({'receive',_, Cs, E, TB}, Bs0, Lf, Ef, RBs) -> - {value,T,Bs} = expr(E, Bs0, Lf, Ef, none), - receive_clauses(T, Cs, {TB,Bs}, Bs0, Lf, Ef, RBs); -expr({'fun',_Anno,{function,Mod0,Name0,Arity0}}, Bs0, Lf, Ef, RBs) -> - {[Mod,Name,Arity],Bs} = expr_list([Mod0,Name0,Arity0], Bs0, Lf, Ef), +expr({block,_,Es}, Bs, Lf, Ef, RBs, FUVs) -> + exprs(Es, Bs, Lf, Ef, RBs, FUVs); +expr({'if',_,Cs}, Bs, Lf, Ef, RBs, FUVs) -> + if_clauses(Cs, Bs, Lf, Ef, RBs, FUVs); +expr({'case',_,E,Cs}, Bs0, Lf, Ef, RBs, FUVs) -> + {value,Val,Bs} = expr(E, Bs0, Lf, Ef, none, FUVs), + case_clauses(Val, Cs, Bs, Lf, Ef, RBs, FUVs); +expr({'try',_,B,Cases,Catches,AB}, Bs, Lf, Ef, RBs, FUVs) -> + try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs, FUVs); +expr({'receive',_,Cs}, Bs, Lf, Ef, RBs, FUVs) -> + receive_clauses(Cs, Bs, Lf, Ef, RBs, FUVs); +expr({'receive',_, Cs, E, TB}, Bs0, Lf, Ef, RBs, FUVs) -> + {value,T,Bs} = expr(E, Bs0, Lf, Ef, none, FUVs), + receive_clauses(T, Cs, {TB,Bs}, Bs0, Lf, Ef, RBs, FUVs); +expr({'fun',_Anno,{function,Mod0,Name0,Arity0}}, Bs0, Lf, Ef, RBs, FUVs) -> + {[Mod,Name,Arity],Bs} = expr_list([Mod0,Name0,Arity0], Bs0, Lf, Ef, FUVs), F = erlang:make_fun(Mod, Name, Arity), - ret_expr(F, Bs, RBs); -expr({'fun',_Anno,{function,Name,Arity}}, _Bs0, _Lf, _Ef, _RBs) -> % R8 + ret_expr(F, Bs, RBs); +expr({'fun',_Anno,{function,Name,Arity}}, _Bs0, _Lf, _Ef, _RBs, _FUVs) -> % R8 %% Don't know what to do... erlang:raise(error, undef, [{?MODULE,Name,Arity}|?STACKTRACE]); -expr({'fun',Anno,{clauses,Cs}} = Ex, Bs, Lf, Ef, RBs) -> - {ok,Used} = erl_lint:used_vars([Ex], bindings(Bs)), - En = filter_bindings(fun(K,_V) -> member(K,Used) end, Bs), - Info = {En,Lf,Ef,Cs}, +expr({'fun',Anno,{clauses,Cs}} = Ex, Bs, Lf, Ef, RBs, FUVs) -> + {En,NewFUVs} = fun_used_bindings(Ex, Cs, Bs, FUVs), + Info = {En,Lf,Ef,NewFUVs,Cs}, + %% This is a really ugly hack! - F = + F = case length(element(3,hd(Cs))) of 0 -> fun () -> eval_fun([], Info) end; 1 -> fun (A) -> eval_fun([A], Info) end; @@ -327,10 +354,10 @@ expr({'fun',Anno,{clauses,Cs}} = Ex, Bs, Lf, Ef, RBs) -> ?STACKTRACE) end, ret_expr(F, Bs, RBs); -expr({named_fun,Anno,Name,Cs} = Ex, Bs, Lf, Ef, RBs) -> - {ok,Used} = erl_lint:used_vars([Ex], bindings(Bs)), - En = filter_bindings(fun(K,_V) -> member(K,Used) end, Bs), - Info = {En,Lf,Ef,Cs,Name}, +expr({named_fun,Anno,Name,Cs} = Ex, Bs, Lf, Ef, RBs, FUVs) -> + {En,NewFUVs} = fun_used_bindings(Ex, Cs, Bs, FUVs), + Info = {En,Lf,Ef,NewFUVs,Cs,Name}, + %% This is a really ugly hack! F = case length(element(3,hd(Cs))) of @@ -378,27 +405,27 @@ expr({named_fun,Anno,Name,Cs} = Ex, Bs, Lf, Ef, RBs) -> ?STACKTRACE) end, ret_expr(F, Bs, RBs); -expr({call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[{lc,_,_E,_Qs}=LC | As0]}, - Bs0, Lf, Ef, RBs) when length(As0) =< 1 -> +expr({call,_,{remote,_,{atom,_,qlc},{atom,_,q}},[{lc,_,_E,_Qs}=LC | As0]}, + Bs0, Lf, Ef, RBs, FUVs) when length(As0) =< 1 -> %% No expansion or evaluation of module name or function name. MaxLine = find_maxline(LC), {LC1, D} = hide_calls(LC, MaxLine), case qlc:transform_from_evaluator(LC1, Bs0) of {ok,{call,A,Remote,[QLC]}} -> QLC1 = unhide_calls(QLC, MaxLine, D), - expr({call,A,Remote,[QLC1 | As0]}, Bs0, Lf, Ef, RBs); + expr({call,A,Remote,[QLC1 | As0]}, Bs0, Lf, Ef, RBs, FUVs); {not_ok,Error} -> ret_expr(Error, Bs0, RBs) end; expr({call,A1,{remote,A2,{record_field,_,{atom,_,''},{atom,_,qlc}=Mod}, {atom,_,q}=Func}, - [{lc,_,_E,_Qs} | As0]=As}, - Bs, Lf, Ef, RBs) when length(As0) =< 1 -> - expr({call,A1,{remote,A2,Mod,Func},As}, Bs, Lf, Ef, RBs); -expr({call,_,{remote,_,Mod,Func},As0}, Bs0, Lf, Ef, RBs) -> - {value,M,Bs1} = expr(Mod, Bs0, Lf, Ef, none), - {value,F,Bs2} = expr(Func, Bs0, Lf, Ef, none), - {As,Bs3} = expr_list(As0, merge_bindings(Bs1, Bs2), Lf, Ef), + [{lc,_,_E,_Qs} | As0]=As}, + Bs, Lf, Ef, RBs, FUVs) when length(As0) =< 1 -> + expr({call,A1,{remote,A2,Mod,Func},As}, Bs, Lf, Ef, RBs, FUVs); +expr({call,_,{remote,_,Mod,Func},As0}, Bs0, Lf, Ef, RBs, FUVs) -> + {value,M,Bs1} = expr(Mod, Bs0, Lf, Ef, none, FUVs), + {value,F,Bs2} = expr(Func, Bs0, Lf, Ef, none, FUVs), + {As,Bs3} = expr_list(As0, merge_bindings(Bs1, Bs2), Lf, Ef, FUVs), %% M could be a parameterized module (not an atom). case is_atom(M) andalso erl_internal:bif(M, F, length(As)) of true -> @@ -406,25 +433,25 @@ expr({call,_,{remote,_,Mod,Func},As0}, Bs0, Lf, Ef, RBs) -> false -> do_apply(M, F, As, Bs3, Ef, RBs) end; -expr({call,_,{atom,_,Func},As0}, Bs0, Lf, Ef, RBs) -> +expr({call,_,{atom,_,Func},As0}, Bs0, Lf, Ef, RBs, FUVs) -> case erl_internal:bif(Func, length(As0)) of true -> {As,Bs} = expr_list(As0, Bs0, Lf, Ef), bif(Func, As, Bs, Ef, RBs); false -> - local_func(Func, As0, Bs0, Lf, Ef, RBs) + local_func(Func, As0, Bs0, Lf, Ef, RBs, FUVs) end; -expr({call,_,Func0,As0}, Bs0, Lf, Ef, RBs) -> % function or {Mod,Fun} - {value,Func,Bs1} = expr(Func0, Bs0, Lf, Ef, none), - {As,Bs2} = expr_list(As0, Bs1, Lf, Ef), +expr({call,_,Func0,As0}, Bs0, Lf, Ef, RBs, FUVs) -> % function or {Mod,Fun} + {value,Func,Bs1} = expr(Func0, Bs0, Lf, Ef, none, FUVs), + {As,Bs2} = expr_list(As0, Bs1, Lf, Ef, FUVs), case Func of {M,F} when is_atom(M), is_atom(F) -> erlang:raise(error, {badfun,Func}, ?STACKTRACE); _ -> do_apply(Func, As, Bs2, Ef, RBs) end; -expr({'catch',_,Expr}, Bs0, Lf, Ef, RBs) -> - try expr(Expr, Bs0, Lf, Ef, none) of +expr({'catch',_,Expr}, Bs0, Lf, Ef, RBs, FUVs) -> + try expr(Expr, Bs0, Lf, Ef, none, FUVs) of {value,V,Bs} -> ret_expr(V, Bs, RBs) catch @@ -435,47 +462,47 @@ expr({'catch',_,Expr}, Bs0, Lf, Ef, RBs) -> error:Reason:Stacktrace -> ret_expr({'EXIT',{Reason,Stacktrace}}, Bs0, RBs) end; -expr({match,_,Lhs,Rhs0}, Bs0, Lf, Ef, RBs) -> - {value,Rhs,Bs1} = expr(Rhs0, Bs0, Lf, Ef, none), +expr({match,_,Lhs,Rhs0}, Bs0, Lf, Ef, RBs, FUVs) -> + {value,Rhs,Bs1} = expr(Rhs0, Bs0, Lf, Ef, none, FUVs), case match(Lhs, Rhs, Bs1) of {match,Bs} -> ret_expr(Rhs, Bs, RBs); nomatch -> erlang:raise(error, {badmatch,Rhs}, ?STACKTRACE) end; -expr({op,_,Op,A0}, Bs0, Lf, Ef, RBs) -> - {value,A,Bs} = expr(A0, Bs0, Lf, Ef, none), +expr({op,_,Op,A0}, Bs0, Lf, Ef, RBs, FUVs) -> + {value,A,Bs} = expr(A0, Bs0, Lf, Ef, none, FUVs), eval_op(Op, A, Bs, Ef, RBs); -expr({op,_,'andalso',L0,R0}, Bs0, Lf, Ef, RBs) -> - {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none), +expr({op,_,'andalso',L0,R0}, Bs0, Lf, Ef, RBs, FUVs) -> + {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs), V = case L of true -> - {value,R,_} = expr(R0, Bs1, Lf, Ef, none), + {value,R,_} = expr(R0, Bs1, Lf, Ef, none, FUVs), R; false -> false; _ -> erlang:raise(error, {badarg,L}, ?STACKTRACE) end, ret_expr(V, Bs1, RBs); -expr({op,_,'orelse',L0,R0}, Bs0, Lf, Ef, RBs) -> - {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none), +expr({op,_,'orelse',L0,R0}, Bs0, Lf, Ef, RBs, FUVs) -> + {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs), V = case L of true -> true; false -> - {value,R,_} = expr(R0, Bs1, Lf, Ef, none), + {value,R,_} = expr(R0, Bs1, Lf, Ef, none, FUVs), R; _ -> erlang:raise(error, {badarg,L}, ?STACKTRACE) end, ret_expr(V, Bs1, RBs); -expr({op,_,Op,L0,R0}, Bs0, Lf, Ef, RBs) -> - {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none), - {value,R,Bs2} = expr(R0, Bs0, Lf, Ef, none), +expr({op,_,Op,L0,R0}, Bs0, Lf, Ef, RBs, FUVs) -> + {value,L,Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs), + {value,R,Bs2} = expr(R0, Bs0, Lf, Ef, none, FUVs), eval_op(Op, L, R, merge_bindings(Bs1, Bs2), Ef, RBs); -expr({bin,_,Fs}, Bs0, Lf, Ef, RBs) -> - EvalFun = fun(E, B) -> expr(E, B, Lf, Ef, none) end, +expr({bin,_,Fs}, Bs0, Lf, Ef, RBs, FUVs) -> + EvalFun = fun(E, B) -> expr(E, B, Lf, Ef, none, FUVs) end, {value,V,Bs} = eval_bits:expr_grp(Fs, Bs0, EvalFun), ret_expr(V, Bs, RBs); -expr({remote,_,_,_}, _Bs, _Lf, _Ef, _RBs) -> +expr({remote,_,_,_}, _Bs, _Lf, _Ef, _RBs, _FUVs) -> erlang:raise(error, {badexpr,':'}, ?STACKTRACE); -expr({value,_,Val}, Bs, _Lf, _Ef, RBs) -> % Special case straight values. +expr({value,_,Val}, Bs, _Lf, _Ef, RBs, _FUVs) -> % Special case straight values. ret_expr(Val, Bs, RBs). find_maxline(LC) -> @@ -496,6 +523,26 @@ find_maxline(LC) -> _ = erl_parse:map_anno(F, LC), erase('$erl_eval_max_line'). +fun_used_bindings(Fun, Cs, Bs, FUVs) -> + {Used,InnerFUVs} = + case FUVs of + %% If this clause is in our fun used vars tree, + %% then we do not need to compute to traverse it again. + #{Cs := UsedAndFUVs} -> + UsedAndFUVs; + + #{} -> + %% Save only used variables in the function environment. + AllUsedVars = erl_lint:used_vars([Fun], bindings(Bs)), + + %% At the root we should see only a single function, + %% so we extract its used vars and its tree out. + [{_,UsedAndFUVs}] = maps:to_list(AllUsedVars), + UsedAndFUVs + end, + + {filter_bindings(fun(K,_V) -> member(K,Used) end, Bs),InnerFUVs}. + hide_calls(LC, MaxLine) -> LineId0 = MaxLine + 1, {NLC, _, D} = hide(LC, LineId0, maps:new()), @@ -507,19 +554,19 @@ hide({call,A,{atom,_,N}=Atom,Args}, Id0, D0) -> C = case erl_internal:bif(N, length(Args)) of true -> {call,A,Atom,NArgs}; - false -> + false -> Anno = erl_anno:new(Id), {call,Anno,{remote,A,{atom,A,m},{atom,A,f}},NArgs} end, {C, Id+1, maps:put(Id, {call,Atom}, D)}; -hide(T0, Id0, D0) when is_tuple(T0) -> +hide(T0, Id0, D0) when is_tuple(T0) -> {L, Id, D} = hide(tuple_to_list(T0), Id0, D0), {list_to_tuple(L), Id, D}; -hide([E0 | Es0], Id0, D0) -> +hide([E0 | Es0], Id0, D0) -> {E, Id1, D1} = hide(E0, Id0, D0), {Es, Id, D} = hide(Es0, Id1, D1), {[E | Es], Id, D}; -hide(E, Id, D) -> +hide(E, Id, D) -> {E, Id, D}. unhide_calls({call,Anno,{remote,A,{atom,A,m},{atom,A,f}}=F,Args}, @@ -532,41 +579,41 @@ unhide_calls({call,Anno,{remote,A,{atom,A,m},{atom,A,f}}=F,Args}, true -> {call,Anno,F,unhide_calls(Args, MaxLine, D)} end; -unhide_calls(T, MaxLine, D) when is_tuple(T) -> +unhide_calls(T, MaxLine, D) when is_tuple(T) -> list_to_tuple(unhide_calls(tuple_to_list(T), MaxLine, D)); -unhide_calls([E | Es], MaxLine, D) -> +unhide_calls([E | Es], MaxLine, D) -> [unhide_calls(E, MaxLine, D) | unhide_calls(Es, MaxLine, D)]; -unhide_calls(E, _MaxLine, _D) -> +unhide_calls(E, _MaxLine, _D) -> E. %% local_func(Function, Arguments, Bindings, LocalFuncHandler, -%% ExternalFuncHandler, RBs) -> +%% ExternalFuncHandler, RBs, FunUsedVars) -> %% {value,Value,Bindings} | Value when %% LocalFuncHandler = {value,F} | {value,F,Eas} | %% {eval,F} | {eval,F,Eas} | none. -local_func(Func, As0, Bs0, {value,F}, Ef, value) -> - {As1,_Bs1} = expr_list(As0, Bs0, {value,F}, Ef), +local_func(Func, As0, Bs0, {value,F}, Ef, value, FUVs) -> + {As1,_Bs1} = expr_list(As0, Bs0, {value,F}, Ef, FUVs), %% Make tail recursive calls when possible. F(Func, As1); -local_func(Func, As0, Bs0, {value,F}, Ef, RBs) -> - {As1,Bs1} = expr_list(As0, Bs0, {value,F}, Ef), +local_func(Func, As0, Bs0, {value,F}, Ef, RBs, FUVs) -> + {As1,Bs1} = expr_list(As0, Bs0, {value,F}, Ef, FUVs), ret_expr(F(Func, As1), Bs1, RBs); -local_func(Func, As0, Bs0, {value,F,Eas}, Ef, RBs) -> +local_func(Func, As0, Bs0, {value,F,Eas}, Ef, RBs, FUVs) -> Fun = fun(Name, Args) -> apply(F, [Name,Args|Eas]) end, - local_func(Func, As0, Bs0, {value, Fun}, Ef, RBs); -local_func(Func, As, Bs, {eval,F}, _Ef, RBs) -> + local_func(Func, As0, Bs0, {value, Fun}, Ef, RBs, FUVs); +local_func(Func, As, Bs, {eval,F}, _Ef, RBs, _FUVs) -> local_func2(F(Func, As, Bs), RBs); -local_func(Func, As, Bs, {eval,F,Eas}, _Ef, RBs) -> +local_func(Func, As, Bs, {eval,F,Eas}, _Ef, RBs, _FUVs) -> local_func2(apply(F, [Func,As,Bs|Eas]), RBs); %% These two clauses are for backwards compatibility. -local_func(Func, As0, Bs0, {M,F}, Ef, RBs) -> - {As1,Bs1} = expr_list(As0, Bs0, {M,F}, Ef), +local_func(Func, As0, Bs0, {M,F}, Ef, RBs, FUVs) -> + {As1,Bs1} = expr_list(As0, Bs0, {M,F}, Ef, FUVs), ret_expr(M:F(Func,As1), Bs1, RBs); -local_func(Func, As, _Bs, {M,F,Eas}, _Ef, RBs) -> +local_func(Func, As, _Bs, {M,F,Eas}, _Ef, RBs, _FUVs) -> local_func2(apply(M, F, [Func,As|Eas]), RBs); %% Default unknown function handler to undefined function. -local_func(Func, As0, _Bs0, none, _Ef, _RBs) -> +local_func(Func, As0, _Bs0, none, _Ef, _RBs, _FUVs) -> erlang:raise(error, undef, [{?MODULE,Func,length(As0)}|?STACKTRACE]). local_func2({value,V,Bs}, RBs) -> @@ -609,7 +656,7 @@ do_apply({M,F}=Func, As, Bs0, Ef, RBs) end; do_apply(Func, As, Bs0, Ef, RBs) -> Env = if - is_function(Func) -> + is_function(Func) -> case {erlang:fun_info(Func, module), erlang:fun_info(Func, env)} of {{module,?MODULE},{env,Env1}} when Env1 =/= [] -> @@ -617,12 +664,12 @@ do_apply(Func, As, Bs0, Ef, RBs) -> _ -> no_env end; - true -> + true -> no_env end, case {Env,Ef} of - {{env,[{FBs,FLf,FEf,FCs}]},_} -> - %% If we are evaluting within another function body + {{env,[{FBs,FLf,FEf,FFUVs,FCs}]},_} -> + %% If we are evaluting within another function body %% (RBs =/= none), we return RBs when this function body %% has been evalutated, otherwise we return Bs0, the %% bindings when evalution of this function body started. @@ -632,18 +679,18 @@ do_apply(Func, As, Bs0, Ef, RBs) -> end, case {erlang:fun_info(Func, arity), length(As)} of {{arity, Arity}, Arity} -> - eval_fun(FCs, As, FBs, FLf, FEf, NRBs); + eval_fun(FCs, As, FBs, FLf, FEf, NRBs, FFUVs); _ -> erlang:raise(error, {badarity,{Func,As}},?STACKTRACE) end; - {{env,[{FBs,FLf,FEf,FCs,FName}]},_} -> + {{env,[{FBs,FLf,FEf,FFUVs,FCs,FName}]},_} -> NRBs = if RBs =:= none -> Bs0; true -> RBs end, case {erlang:fun_info(Func, arity), length(As)} of {{arity, Arity}, Arity} -> - eval_named_fun(FCs, As, FBs, FLf, FEf, FName, Func, NRBs); + eval_named_fun(FCs, As, FBs, FLf, FEf, FName, Func, NRBs, FFUVs); _ -> erlang:raise(error, {badarity,{Func,As}},?STACKTRACE) end; @@ -671,48 +718,48 @@ do_apply(Mod, Func, As, Bs0, Ef, RBs) -> ret_expr(F({Mod,Func}, As), Bs0, RBs) end. -%% eval_lc(Expr, [Qualifier], Bindings, LocalFunctionHandler, +%% eval_lc(Expr, [Qualifier], Bindings, LocalFunctionHandler, %% ExternalFuncHandler, RetBindings) -> %% {value,Value,Bindings} | Value -eval_lc(E, Qs, Bs, Lf, Ef, RBs) -> - ret_expr(lists:reverse(eval_lc1(E, Qs, Bs, Lf, Ef, [])), Bs, RBs). +eval_lc(E, Qs, Bs, Lf, Ef, RBs, FUVs) -> + ret_expr(lists:reverse(eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, [])), Bs, RBs). -eval_lc1(E, [{generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) -> - {value,L1,_Bs1} = expr(L0, Bs0, Lf, Ef, none), - CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, Acc) end, +eval_lc1(E, [{generate,_,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) -> + {value,L1,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs), + CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end, eval_generate(L1, P, Bs0, Lf, Ef, CompFun, Acc0); -eval_lc1(E, [{b_generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) -> - {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none), - CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, Acc) end, +eval_lc1(E, [{b_generate,_,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) -> + {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs), + CompFun = fun(Bs, Acc) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end, eval_b_generate(Bin, P, Bs0, Lf, Ef, CompFun, Acc0); -eval_lc1(E, [F|Qs], Bs0, Lf, Ef, Acc) -> - CompFun = fun(Bs) -> eval_lc1(E, Qs, Bs, Lf, Ef, Acc) end, - eval_filter(F, Bs0, Lf, Ef, CompFun, Acc); -eval_lc1(E, [], Bs, Lf, Ef, Acc) -> - {value,V,_} = expr(E, Bs, Lf, Ef, none), +eval_lc1(E, [F|Qs], Bs0, Lf, Ef, FUVs, Acc) -> + CompFun = fun(Bs) -> eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end, + eval_filter(F, Bs0, Lf, Ef, CompFun, FUVs, Acc); +eval_lc1(E, [], Bs, Lf, Ef, FUVs, Acc) -> + {value,V,_} = expr(E, Bs, Lf, Ef, none, FUVs), [V|Acc]. -%% eval_bc(Expr, [Qualifier], Bindings, LocalFunctionHandler, +%% eval_bc(Expr, [Qualifier], Bindings, LocalFunctionHandler, %% ExternalFuncHandler, RetBindings) -> %% {value,Value,Bindings} | Value -eval_bc(E, Qs, Bs, Lf, Ef, RBs) -> - ret_expr(eval_bc1(E, Qs, Bs, Lf, Ef, <<>>), Bs, RBs). +eval_bc(E, Qs, Bs, Lf, Ef, RBs, FUVs) -> + ret_expr(eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, <<>>), Bs, RBs). -eval_bc1(E, [{b_generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) -> - {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none), - CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, Acc) end, +eval_bc1(E, [{b_generate,_,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) -> + {value,Bin,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs), + CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end, eval_b_generate(Bin, P, Bs0, Lf, Ef, CompFun, Acc0); -eval_bc1(E, [{generate,_,P,L0}|Qs], Bs0, Lf, Ef, Acc0) -> - {value,List,_Bs1} = expr(L0, Bs0, Lf, Ef, none), - CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, Acc) end, +eval_bc1(E, [{generate,_,P,L0}|Qs], Bs0, Lf, Ef, FUVs, Acc0) -> + {value,List,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs), + CompFun = fun(Bs, Acc) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end, eval_generate(List, P, Bs0, Lf, Ef, CompFun, Acc0); -eval_bc1(E, [F|Qs], Bs0, Lf, Ef, Acc) -> - CompFun = fun(Bs) -> eval_bc1(E, Qs, Bs, Lf, Ef, Acc) end, - eval_filter(F, Bs0, Lf, Ef, CompFun, Acc); -eval_bc1(E, [], Bs, Lf, Ef, Acc) -> - {value,V,_} = expr(E, Bs, Lf, Ef, none), +eval_bc1(E, [F|Qs], Bs0, Lf, Ef, FUVs, Acc) -> + CompFun = fun(Bs) -> eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, Acc) end, + eval_filter(F, Bs0, Lf, Ef, CompFun, FUVs, Acc); +eval_bc1(E, [], Bs, Lf, Ef, FUVs, Acc) -> + {value,V,_} = expr(E, Bs, Lf, Ef, none, FUVs), <<Acc/bitstring,V/bitstring>>. eval_generate([V|Rest], P, Bs0, Lf, Ef, CompFun, Acc) -> @@ -721,7 +768,7 @@ eval_generate([V|Rest], P, Bs0, Lf, Ef, CompFun, Acc) -> Bs2 = add_bindings(Bsn, Bs0), NewAcc = CompFun(Bs2, Acc), eval_generate(Rest, P, Bs0, Lf, Ef, CompFun, NewAcc); - nomatch -> + nomatch -> eval_generate(Rest, P, Bs0, Lf, Ef, CompFun, Acc) end; eval_generate([], _P, _Bs0, _Lf, _Ef, _CompFun, Acc) -> @@ -745,7 +792,7 @@ eval_b_generate(<<_/bitstring>>=Bin, P, Bs0, Lf, Ef, CompFun, Acc) -> eval_b_generate(Term, _P, _Bs0, _Lf, _Ef, _CompFun, _Acc) -> erlang:raise(error, {bad_generator,Term}, ?STACKTRACE). -eval_filter(F, Bs0, Lf, Ef, CompFun, Acc) -> +eval_filter(F, Bs0, Lf, Ef, CompFun, FUVs, Acc) -> case erl_lint:is_guard_test(F) of true -> case guard_test(F, Bs0, Lf, Ef) of @@ -753,10 +800,10 @@ eval_filter(F, Bs0, Lf, Ef, CompFun, Acc) -> {value,false,_} -> Acc end; false -> - case expr(F, Bs0, Lf, Ef, none) of + case expr(F, Bs0, Lf, Ef, none, FUVs) of {value,true,Bs1} -> CompFun(Bs1); {value,false,_} -> Acc; - {value,V,_} -> + {value,V,_} -> erlang:raise(error, {bad_filter,V}, ?STACKTRACE) end end. @@ -765,18 +812,18 @@ eval_filter(F, Bs0, Lf, Ef, CompFun, Acc) -> %% ExternalFuncHandler) -> %% {[{map_assoc | map_exact,Key,Value}],Bindings} -eval_map_fields(Fs, Bs, Lf, Ef) -> - eval_map_fields(Fs, Bs, Lf, Ef, []). - -eval_map_fields([{map_field_assoc,_,K0,V0}|Fs], Bs0, Lf, Ef, Acc) -> - {value,K1,Bs1} = expr(K0, Bs0, Lf, Ef, none), - {value,V1,Bs2} = expr(V0, Bs1, Lf, Ef, none), - eval_map_fields(Fs, Bs2, Lf, Ef, [{map_assoc,K1,V1}|Acc]); -eval_map_fields([{map_field_exact,_,K0,V0}|Fs], Bs0, Lf, Ef, Acc) -> - {value,K1,Bs1} = expr(K0, Bs0, Lf, Ef, none), - {value,V1,Bs2} = expr(V0, Bs1, Lf, Ef, none), - eval_map_fields(Fs, Bs2, Lf, Ef, [{map_exact,K1,V1}|Acc]); -eval_map_fields([], Bs, _Lf, _Ef, Acc) -> +eval_map_fields(Fs, Bs, Lf, Ef, FUVs) -> + eval_map_fields(Fs, Bs, Lf, Ef, FUVs, []). + +eval_map_fields([{map_field_assoc,_,K0,V0}|Fs], Bs0, Lf, Ef, FUVs, Acc) -> + {value,K1,Bs1} = expr(K0, Bs0, Lf, Ef, none, FUVs), + {value,V1,Bs2} = expr(V0, Bs1, Lf, Ef, none, FUVs), + eval_map_fields(Fs, Bs2, Lf, Ef, FUVs, [{map_assoc,K1,V1}|Acc]); +eval_map_fields([{map_field_exact,_,K0,V0}|Fs], Bs0, Lf, Ef, FUVs, Acc) -> + {value,K1,Bs1} = expr(K0, Bs0, Lf, Ef, none, FUVs), + {value,V1,Bs2} = expr(V0, Bs1, Lf, Ef, none, FUVs), + eval_map_fields(Fs, Bs2, Lf, Ef, FUVs, [{map_exact,K1,V1}|Acc]); +eval_map_fields([], Bs, _Lf, _Ef, _FUVs, Acc) -> {lists:reverse(Acc),Bs}. @@ -793,45 +840,45 @@ ret_expr(V, _Bs, RBs) when is_list(RBs); is_map(RBs) -> {value,V,RBs}. %% eval_fun(Arguments, {Bindings,LocalFunctionHandler, -%% ExternalFunctionHandler,Clauses}) -> Value +%% ExternalFunctionHandler,FunUsedVars,Clauses}) -> Value %% This function is called when the fun is called from compiled code %% or from apply. -eval_fun(As, {Bs0,Lf,Ef,Cs}) -> - eval_fun(Cs, As, Bs0, Lf, Ef, value). +eval_fun(As, {Bs0,Lf,Ef,FUVs,Cs}) -> + eval_fun(Cs, As, Bs0, Lf, Ef, value, FUVs). -eval_fun([{clause,_,H,G,B}|Cs], As, Bs0, Lf, Ef, RBs) -> +eval_fun([{clause,_,H,G,B}|Cs], As, Bs0, Lf, Ef, RBs, FUVs) -> case match_list(H, As, new_bindings(Bs0), Bs0) of {match,Bsn} -> % The new bindings for the head Bs1 = add_bindings(Bsn, Bs0), % which then shadow! case guard(G, Bs1, Lf, Ef) of - true -> exprs(B, Bs1, Lf, Ef, RBs); - false -> eval_fun(Cs, As, Bs0, Lf, Ef, RBs) + true -> exprs(B, Bs1, Lf, Ef, RBs, FUVs); + false -> eval_fun(Cs, As, Bs0, Lf, Ef, RBs, FUVs) end; nomatch -> - eval_fun(Cs, As, Bs0, Lf, Ef, RBs) + eval_fun(Cs, As, Bs0, Lf, Ef, RBs, FUVs) end; -eval_fun([], As, _Bs, _Lf, _Ef, _RBs) -> - erlang:raise(error, function_clause, +eval_fun([], As, _Bs, _Lf, _Ef, _RBs, _FUVs) -> + erlang:raise(error, function_clause, [{?MODULE,'-inside-an-interpreted-fun-',As}|?STACKTRACE]). -eval_named_fun(As, Fun, {Bs0,Lf,Ef,Cs,Name}) -> - eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, value). +eval_named_fun(As, Fun, {Bs0,Lf,Ef,FUVs,Cs,Name}) -> + eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, value, FUVs). -eval_named_fun([{clause,_,H,G,B}|Cs], As, Bs0, Lf, Ef, Name, Fun, RBs) -> +eval_named_fun([{clause,_,H,G,B}|Cs], As, Bs0, Lf, Ef, Name, Fun, RBs, FUVs) -> Bs1 = add_binding(Name, Fun, Bs0), case match_list(H, As, new_bindings(Bs0), Bs1) of {match,Bsn} -> % The new bindings for the head Bs2 = add_bindings(Bsn, Bs1), % which then shadow! case guard(G, Bs2, Lf, Ef) of - true -> exprs(B, Bs2, Lf, Ef, RBs); - false -> eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, RBs) + true -> exprs(B, Bs2, Lf, Ef, RBs, FUVs); + false -> eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, RBs, FUVs) end; nomatch -> - eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, RBs) + eval_named_fun(Cs, As, Bs0, Lf, Ef, Name, Fun, RBs, FUVs) end; -eval_named_fun([], As, _Bs, _Lf, _Ef, _Name, _Fun, _RBs) -> +eval_named_fun([], As, _Bs, _Lf, _Ef, _Name, _Fun, _RBs, _FUVs) -> erlang:raise(error, function_clause, [{?MODULE,'-inside-an-interpreted-fun-',As}|?STACKTRACE]). @@ -847,7 +894,7 @@ eval_named_fun([], As, _Bs, _Lf, _Ef, _Name, _Fun, _RBs) -> ValueList :: [value()], NewBindings :: binding_struct()). expr_list(Es, Bs) -> - expr_list(Es, Bs, none, none). + expr_list(Es, Bs, none, none, empty_fun_used_vars()). -spec(expr_list(ExpressionList, Bindings, LocalFunctionHandler) -> {ValueList, NewBindings} when @@ -857,7 +904,7 @@ expr_list(Es, Bs) -> ValueList :: [value()], NewBindings :: binding_struct()). expr_list(Es, Bs, Lf) -> - expr_list(Es, Bs, Lf, none). + expr_list(Es, Bs, Lf, none, empty_fun_used_vars()). -spec(expr_list(ExpressionList, Bindings, LocalFunctionHandler, NonLocalFunctionHandler) -> @@ -869,12 +916,15 @@ expr_list(Es, Bs, Lf) -> ValueList :: [value()], NewBindings :: binding_struct()). expr_list(Es, Bs, Lf, Ef) -> - expr_list(Es, [], Bs, Bs, Lf, Ef). + expr_list(Es, Bs, Lf, Ef, empty_fun_used_vars()). + +expr_list(Es, Bs, Lf, Ef, FUVs) -> + expr_list(Es, [], Bs, Bs, Lf, Ef, FUVs). -expr_list([E|Es], Vs, BsOrig, Bs0, Lf, Ef) -> - {value,V,Bs1} = expr(E, BsOrig, Lf, Ef, none), - expr_list(Es, [V|Vs], BsOrig, merge_bindings(Bs1, Bs0), Lf, Ef); -expr_list([], Vs, _, Bs, _Lf, _Ef) -> +expr_list([E|Es], Vs, BsOrig, Bs0, Lf, Ef, FUVs) -> + {value,V,Bs1} = expr(E, BsOrig, Lf, Ef, none, FUVs), + expr_list(Es, [V|Vs], BsOrig, merge_bindings(Bs1, Bs0), Lf, Ef, FUVs); +expr_list([], Vs, _, Bs, _Lf, _Ef, _FUVs) -> {reverse(Vs),Bs}. eval_op(Op, Arg1, Arg2, Bs, Ef, RBs) -> @@ -885,26 +935,26 @@ eval_op(Op, Arg, Bs, Ef, RBs) -> %% if_clauses(Clauses, Bindings, LocalFuncHandler, ExtFuncHandler, RBs) -if_clauses([{clause,_,[],G,B}|Cs], Bs, Lf, Ef, RBs) -> +if_clauses([{clause,_,[],G,B}|Cs], Bs, Lf, Ef, RBs, FUVs) -> case guard(G, Bs, Lf, Ef) of - true -> exprs(B, Bs, Lf, Ef, RBs); - false -> if_clauses(Cs, Bs, Lf, Ef, RBs) + true -> exprs(B, Bs, Lf, Ef, RBs, FUVs); + false -> if_clauses(Cs, Bs, Lf, Ef, RBs, FUVs) end; -if_clauses([], _Bs, _Lf, _Ef, _RBs) -> +if_clauses([], _Bs, _Lf, _Ef, _RBs, _FUVs) -> erlang:raise(error, if_clause, ?STACKTRACE). -%% try_clauses(Body, CaseClauses, CatchClauses, AfterBody, Bindings, +%% try_clauses(Body, CaseClauses, CatchClauses, AfterBody, Bindings, %% LocalFuncHandler, ExtFuncHandler, RBs) -try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs) -> +try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs, FUVs) -> check_stacktrace_vars(Catches, Bs), - try exprs(B, Bs, Lf, Ef, none) of + try exprs(B, Bs, Lf, Ef, none, FUVs) of {value,V,Bs1} when Cases =:= [] -> ret_expr(V, Bs1, RBs); {value,V,Bs1} -> case match_clause(Cases, [V], Bs1, Lf, Ef) of {B2,Bs2} -> - exprs(B2, Bs2, Lf, Ef, RBs); + exprs(B2, Bs2, Lf, Ef, RBs, FUVs); nomatch -> erlang:raise(error, {try_clause,V}, ?STACKTRACE) end @@ -915,15 +965,15 @@ try_clauses(B, Cases, Catches, AB, Bs, Lf, Ef, RBs) -> V = {Class,Reason,Stacktrace}, case match_clause(Catches, [V], Bs, Lf, Ef) of {B2,Bs2} -> - exprs(B2, Bs2, Lf, Ef, RBs); + exprs(B2, Bs2, Lf, Ef, RBs, FUVs); nomatch -> erlang:raise(Class, Reason, Stacktrace) end after - if AB =:= [] -> + if AB =:= [] -> Bs; % any true -> - exprs(AB, Bs, Lf, Ef, none) + exprs(AB, Bs, Lf, Ef, none, FUVs) end end. @@ -944,13 +994,13 @@ check_stacktrace_vars([{clause,_,[{tuple,_,[_,_,STV]}],_,_}|Cs], Bs) -> check_stacktrace_vars([], _Bs) -> ok. -%% case_clauses(Value, Clauses, Bindings, LocalFuncHandler, ExtFuncHandler, +%% case_clauses(Value, Clauses, Bindings, LocalFuncHandler, ExtFuncHandler, %% RBs) -case_clauses(Val, Cs, Bs, Lf, Ef, RBs) -> +case_clauses(Val, Cs, Bs, Lf, Ef, RBs, FUVs) -> case match_clause(Cs, [Val], Bs, Lf, Ef) of {B, Bs1} -> - exprs(B, Bs1, Lf, Ef, RBs); + exprs(B, Bs1, Lf, Ef, RBs, FUVs); nomatch -> erlang:raise(error, {case_clause,Val}, ?STACKTRACE) end. @@ -958,20 +1008,20 @@ case_clauses(Val, Cs, Bs, Lf, Ef, RBs) -> %% %% receive_clauses(Clauses, Bindings, LocalFuncHnd,ExtFuncHnd, RBs) %% -receive_clauses(Cs, Bs, Lf, Ef, RBs) -> - receive_clauses(infinity, Cs, unused, Bs, Lf, Ef, RBs). +receive_clauses(Cs, Bs, Lf, Ef, RBs, FUVs) -> + receive_clauses(infinity, Cs, unused, Bs, Lf, Ef, RBs, FUVs). %% -%% receive_clauses(TimeOut, Clauses, TimeoutBody, Bindings, +%% receive_clauses(TimeOut, Clauses, TimeoutBody, Bindings, %% ExternalFuncHandler, LocalFuncHandler, RBs) %% -receive_clauses(T, Cs, TB, Bs, Lf, Ef, RBs) -> +receive_clauses(T, Cs, TB, Bs, Lf, Ef, RBs, FUVs) -> F = fun (M) -> match_clause(Cs, [M], Bs, Lf, Ef) end, case prim_eval:'receive'(F, T) of {B, Bs1} -> - exprs(B, Bs1, Lf, Ef, RBs); + exprs(B, Bs1, Lf, Ef, RBs, FUVs); timeout -> {B, Bs1} = TB, - exprs(B, Bs1, Lf, Ef, RBs) + exprs(B, Bs1, Lf, Ef, RBs, FUVs) end. %% match_clause -> {Body, Bindings} or nomatch @@ -1050,7 +1100,7 @@ guard_test(G, Bs0, Lf, Ef) -> expr_guard_test(G, Bs0, Lf, Ef) -> try {value,true,_} = expr(G, Bs0, Lf, Ef, none) catch error:_ -> {value,false,Bs0} end. - + type_test(integer) -> is_integer; type_test(float) -> is_float; type_test(number) -> is_number; @@ -1140,7 +1190,7 @@ match1({cons,_,H,T}, [H1|T1], Bs0, BBs) -> match1(T, T1, Bs, BBs); match1({cons,_,_,_}, _, _Bs, _BBs) -> throw(nomatch); -match1({tuple,_,Elts}, Tuple, Bs, BBs) +match1({tuple,_,Elts}, Tuple, Bs, BBs) when length(Elts) =:= tuple_size(Tuple) -> match_tuple(Elts, Tuple, 1, Bs, BBs); match1({tuple,_,_}, _, _Bs, _BBs) -> @@ -1287,7 +1337,7 @@ merge_bindings(Bs1, Bs2) -> foldl(fun ({Name,Val}, Bs) -> case orddict:find(Name, Bs) of {ok,Val} -> Bs; %Already with SAME value - {ok,V1} -> + {ok,V1} -> erlang:raise(error, {badmatch,V1}, ?STACKTRACE); error -> orddict:store(Name, Val, Bs) end end, diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 3693a3d439..b9f737297b 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -31,6 +31,7 @@ -export([check_format_string/1]). -export_type([error_info/0, error_description/0]). +-export_type([fun_used_vars/0]). % Used from erl_eval.erl. -import(lists, [all/2,any/2, foldl/3,foldr/3, @@ -103,6 +104,8 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) -> -type used_type() :: #used_type{}. +-type fun_used_vars() :: #{erl_parse:abstract_expr() => {[atom()], fun_used_vars()}}. + %% Usage of records, functions, and imports. The variable table, which %% is passed on as an argument, holds the usage of variables. -record(usage, { @@ -151,6 +154,8 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) -> %outside any fun or lc xqlc= false :: boolean(), %true if qlc.hrl included called= [] :: [{fa(),anno()}], %Called functions + fun_used_vars = undefined %Funs used vars + :: fun_used_vars() | undefined, usage = #usage{} :: #usage{}, specs = maps:new() %Type specifications :: #{mfa() => anno()}, @@ -529,10 +534,9 @@ used_vars(Exprs, BindingsList) -> ({V,_Val}, Vs0) -> [{V,{bound,unused,[]}} | Vs0] end, [], BindingsList), Vt = orddict:from_list(Vs), - {Evt,_St} = exprs(set_file(Exprs, "nofile"), Vt, start()), - {ok, foldl(fun({V,{_,used,_}}, L) -> [V | L]; - (_, L) -> L - end, [], Evt)}. + St0 = (start())#lint{fun_used_vars=maps:new()}, + {_Evt,St1} = exprs(Exprs, Vt, St0), + St1#lint.fun_used_vars. %% module([Form]) -> %% module([Form], FileName) -> @@ -3613,7 +3617,20 @@ handle_bitstring_gen_pat(_,St) -> %% unless it was introduced in a fun or an lc. Only if pat_var finds %% such variables can the correct line number be given. +%% If fun_used_vars is set, we want to compute the tree of used +%% vars across functions. This is by erl_eval to compute used vars +%% without having to traverse the tree multiple times. + +fun_clauses(Cs, Vt, #lint{fun_used_vars=(#{}=FUV)} = St) -> + {Uvt, St0} = fun_clauses1(Cs, Vt, St#lint{fun_used_vars=maps:new()}), + #lint{fun_used_vars=InnerFUV} = St0, + UsedVars = [V || {V, {_, used, _}} <- Uvt], + OuterFUV = maps:put(Cs, {UsedVars, InnerFUV}, FUV), + {Uvt, St0#lint{fun_used_vars=OuterFUV}}; fun_clauses(Cs, Vt, St) -> + fun_clauses1(Cs, Vt, St). + +fun_clauses1(Cs, Vt, St) -> OldRecDef = St#lint.recdef_top, {Bvt,St2} = foldl(fun (C, {Bvt0, St0}) -> {Cvt,St1} = fun_clause(C, Vt, St0), diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl index 0bf59cf60e..f4d58e61c5 100644 --- a/lib/stdlib/test/erl_eval_SUITE.erl +++ b/lib/stdlib/test/erl_eval_SUITE.erl @@ -1000,14 +1000,14 @@ otp_14826(_Config) -> backtrace_check("fun(P) when is_pid(P) -> true end(a).", function_clause, [{erl_eval,'-inside-an-interpreted-fun-',[a],[]}, - {erl_eval,eval_fun,6}, + {erl_eval,eval_fun,7}, ?MODULE]), backtrace_check("B.", {unbound_var, 'B'}, [{erl_eval,expr,2}, ?MODULE]), backtrace_check("B.", {unbound, 'B'}, - [{erl_eval,expr,5}, ?MODULE], + [{erl_eval,expr,6}, ?MODULE], none, none), backtrace_check("1/0.", badarith, @@ -1037,18 +1037,18 @@ otp_14826(_Config) -> {erl_eval,do_apply,6}]), backtrace_check("try a of b -> bar after foo end.", {try_clause,a}, - [{erl_eval,try_clauses,8}]), + [{erl_eval,try_clauses,9}]), check(fun() -> X = try foo:bar() catch A:B:C -> {A,B} end, X end, "try foo:bar() catch A:B:C -> {A,B} end.", {error, undef}), backtrace_check("C = 4, try foo:bar() catch A:B:C -> {A,B,C} end.", stacktrace_bound, [{erl_eval,check_stacktrace_vars,2}, - {erl_eval,try_clauses,8}], + {erl_eval,try_clauses,9}], none, none), backtrace_catch("catch (try a of b -> bar after foo end).", {try_clause,a}, - [{erl_eval,try_clauses,8}]), + [{erl_eval,try_clauses,9}]), backtrace_check("try 1/0 catch exit:a -> foo end.", badarith, [{erlang,'/',[1,0],[]}, @@ -1063,7 +1063,7 @@ otp_14826(_Config) -> catch error:{illegal_stacktrace_variable,{atom,1,'C'}}:S -> [{erl_eval,check_stacktrace_vars,2,_}, - {erl_eval,try_clauses,8,_}|_] = S + {erl_eval,try_clauses,9,_}|_] = S end, backtrace_check("{1,1} = {A = 1, A = 2}.", {badmatch, 1}, @@ -1076,24 +1076,24 @@ otp_14826(_Config) -> [{erl_eval,match,4}], none, none), backtrace_check("case a of b -> ok end.", {case_clause,a}, - [{erl_eval,case_clauses,6}, ?MODULE]), + [{erl_eval,case_clauses,7}, ?MODULE]), backtrace_check("if a =:= b -> ok end.", if_clause, - [{erl_eval,if_clauses,5}, ?MODULE]), + [{erl_eval,if_clauses,6}, ?MODULE]), backtrace_check("fun A(b) -> ok end(a).", function_clause, [{erl_eval,'-inside-an-interpreted-fun-',[a],[]}, - {erl_eval,eval_named_fun,8}, + {erl_eval,eval_named_fun,9}, ?MODULE]), backtrace_check("[A || A <- a].", {bad_generator, a}, - [{erl_eval,eval_generate,7}, {erl_eval, eval_lc, 6}]), + [{erl_eval,eval_generate,7}, {erl_eval, eval_lc, 7}]), backtrace_check("<< <<A>> || <<A>> <= a>>.", {bad_generator, a}, - [{erl_eval,eval_b_generate,7}, {erl_eval, eval_bc, 6}]), + [{erl_eval,eval_b_generate,7}, {erl_eval, eval_bc, 7}]), backtrace_check("[A || A <- [1], begin a end].", {bad_filter, a}, - [{erl_eval,eval_filter,6}, {erl_eval, eval_generate, 7}]), + [{erl_eval,eval_filter,7}, {erl_eval, eval_generate, 7}]), fun() -> {'EXIT', {{badarity, {_Fun, []}}, BT}} = (catch parse_and_run("fun(A) -> A end().")), @@ -1106,20 +1106,20 @@ otp_14826(_Config) -> end(), backtrace_check("foo().", undef, - [{erl_eval,foo,0},{erl_eval,local_func,6}], + [{erl_eval,foo,0},{erl_eval,local_func,7}], none, none), backtrace_check("a orelse false.", {badarg, a}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("a andalso false.", {badarg, a}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("t = u.", {badmatch, u}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("{math,sqrt}(2).", {badfun, {math,sqrt}}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("erl_eval_SUITE:simple().", simple, [{?MODULE,simple1,0},{?MODULE,simple,0},erl_eval]), @@ -1128,15 +1128,15 @@ otp_14826(_Config) -> "19,20,21,22,23,24,25,26,27,28,29,30) -> a end.", {argument_limit, {'fun',1,[{clause,1,Args,[],[{atom,1,a}]}]}}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("fun F(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18," "19,20,21,22,23,24,25,26,27,28,29,30) -> a end.", {argument_limit, {named_fun,1,'F',[{clause,1,Args,[],[{atom,1,a}]}]}}, - [{erl_eval,expr,5}, ?MODULE]), + [{erl_eval,expr,6}, ?MODULE]), backtrace_check("#r{}.", {undef_record,r}, - [{erl_eval,expr,5}, ?MODULE], + [{erl_eval,expr,6}, ?MODULE], none, none), %% eval_bits backtrace_check("<<100:8/bitstring>>.", -- 2.31.1
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor