cerl_typean.erl

来自「OTP是开放电信平台的简称」· ERL 代码 · 共 994 行 · 第 1/2 页

ERL
994
字号
%% -*- erlang-indent-level: 4 -*-%% =====================================================================%% Type analysis of Core Erlang programs.%%%% Copyright (C) 2001-2002 Richard Carlsson%%%% This library is free software; you can redistribute it and/or modify%% it under the terms of the GNU Lesser General Public License as%% published by the Free Software Foundation; either version 2 of the%% License, or (at your option) any later version.%%%% This library is distributed in the hope that it will be useful, but%% WITHOUT ANY WARRANTY; without even the implied warranty of%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU%% Lesser General Public License for more details.%%%% You should have received a copy of the GNU Lesser General Public%% License along with this library; if not, write to the Free Software%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307%% USA%%%% Author contact: richardc@it.uu.se%%%% $Id$%%%% @doc Type analysis of Core Erlang programs.%% TODO: filters must handle conjunctions for better precision!%% TODO: should get filters from patterns as well as guards.%% TODO: unused functions are being included in the analysis.-module(cerl_typean).-export([core_transform/2, analyze/1, pp_hook/0]).%%-export([analyze/2, analyze/5, annotate/1, annotate/2, annotate/5]).-import(erl_types, [t_any/0, t_atom/0, t_is_atom/1, t_binary/0, t_cons/2,		    t_cons_hd/1, t_cons_tl/1, t_pos_improper_list/0,		    t_components/1, t_float/0, t_fun/0, t_fun/2,		    t_inf/2, t_integer/0, t_atom_vals/1, t_is_cons/1,		    t_is_pos_improper_list/1, t_is_list/1, t_is_tuple/1,		    t_is_none/1, t_is_any/1, t_limit/2,		    t_list_elements/1, t_number/0, t_pid/0, t_port/0,		    t_product/1, t_ref/0, t_tuple/0, t_tuple/1,		    t_tuple_args/1, t_tuple_arity/1, t_tuple_subtypes/1, 		    t_sup/2,		    t_from_range/2, t_from_term/1, t_none/0]).-import(cerl, [ann_c_fun/3, ann_c_var/2, alias_pat/1, alias_var/1,	       apply_args/1, apply_op/1, atom_val/1, bitstr_size/1,	       bitstr_val/1, bitstr_type/1, bitstr_flags/1, binary_segments/1, 	       c_letrec/2, c_nil/0,	       c_values/1, call_args/1, call_module/1, call_name/1,	       case_arg/1, case_clauses/1, catch_body/1, clause_body/1,	       clause_guard/1, clause_pats/1, concrete/1, cons_hd/1,	       cons_tl/1, fun_body/1, fun_vars/1, get_ann/1,	       int_val/1, is_c_atom/1, is_c_int/1, let_arg/1, let_body/1, let_vars/1,	       letrec_body/1, letrec_defs/1, module_defs/1,	       module_defs/1, module_exports/1, pat_vars/1,	       primop_args/1, primop_name/1, receive_action/1,	       receive_clauses/1, receive_timeout/1, seq_arg/1,	       seq_body/1, set_ann/2, try_arg/1, try_body/1,	       try_evars/1, try_handler/1, try_vars/1, tuple_arity/1,	       tuple_es/1, type/1, values_es/1, var_name/1]).-import(cerl_trees, [get_label/1]).-ifdef(DEBUG).-define(ANNOTATE(X), case erl_types:t_to_string(X) of Q when length(Q) < 255 -> list_to_atom(Q); Q -> Q end).-else.-define(ANNOTATE(X), X).-endif.%% Limit for type representation depth.-define(DEF_LIMIT, 3).%% @spec core_transform(Module::cerl_records(), Options::[term()]) ->%%           cerl_records()%%%% @doc Annotates a module represented by records with type%% information. See <code>annotate/1</code> for details.%%%% <p>Use the compiler option <code>{core_transform, cerl_typean}</code>%% to insert this function as a compilation pass.</p>%%%% @see module/2core_transform(Code, _Opts) ->    {Code1, _} = cerl_trees:label(cerl:from_records(Code)),    %% io:fwrite("Running type analysis..."),    %% {T1,_} = statistics(runtime),    {Code2, _, _} = annotate(Code1),    %% {T2,_} = statistics(runtime),    %% io:fwrite("(~w ms).\n", [T2 - T1]),    cerl:to_records(Code2).%% =====================================================================%% annotate(Tree) -> {Tree1, Type, Vars}%%%%	    Tree = coreErlang()%%%%	Analyzes `Tree' (see `analyze') and appends terms `{type, Type}'%%	to the annotation list of each fun-expression node and%%	apply-expression node of `Tree', respectively, where `Labels' is%%	an ordered-set list of labels of fun-expressions in `Tree',%%	possibly also containing the atom `external', corresponding to%%	the dependency information derived by the analysis. Any previous%%	such annotations are removed from `Tree'. `Tree1' is the%%	modified tree; for details on `OutList', `Outputs' ,%%	`Dependencies' and `Escapes', see `analyze'.%%%%	Note: `Tree' must be annotated with labels in order to use this%%	function; see `analyze' for details.annotate(T) ->    annotate(T, ?DEF_LIMIT).annotate(T, Limit) ->    {_, _, Esc, Dep, Par} = cerl_closurean:analyze(T),    annotate(T, Limit, Esc, Dep, Par).annotate(T, Limit, Esc, Dep, Par) ->    {Type, Out, Vars} = analyze(T, Limit, Esc, Dep, Par),    DelAnn = fun (T) -> set_ann(T, delete_ann(type, get_ann(T))) end,    SetType = fun (T, Dict) ->		      case dict:find(get_label(T), Dict) of			  {ok, X} ->			      case t_is_any(X) of				  true ->				      DelAnn(T);				  false ->				      set_ann(T, append_ann(type,							    ?ANNOTATE(X),							    get_ann(T)))			      end;			  error ->			      DelAnn(T)		      end	      end,    F = fun (T) ->		case type(T) of		    var ->			SetType(T, Vars);		    apply ->			SetType(T, Out);		    call ->			SetType(T, Out);		    primop ->			SetType(T, Out);		    'fun' ->			SetType(T, Out);		    _ ->			DelAnn(T)		end	end,    {cerl_trees:map(F, T), Type, Vars}.append_ann(Tag, Val, [X | Xs]) ->    if is_tuple(X), size(X) >= 1, element(1, X) =:= Tag -> 	    append_ann(Tag, Val, Xs);       true ->	    [X | append_ann(Tag, Val, Xs)]    end;append_ann(Tag, Val, []) ->    [{Tag, Val}].delete_ann(Tag, [X | Xs]) ->    if is_tuple(X), size(X) >= 1, element(1, X) =:= Tag -> 	    delete_ann(Tag, Xs);       true ->	    [X | delete_ann(Tag, Xs)]    end;delete_ann(_, []) ->    [].%% =====================================================================%% analyze(Tree) -> {OutList, Outputs, Dependencies, Escapes}%%%%	    Tree = coreErlang()%%	    OutList = [LabelSet] | none%%	    Outputs = dict(integer(), OutList)%%	    Dependencies = dict(integer(), LabelSet)%%	    LabelSet = ordset(Label)%%	    Label = integer() | external%%%%	Analyzes a module or an expression represented by `Tree'.%%%%	The returned `OutList' is a list of sets of labels of%%	fun-expressions which correspond to the possible closures in the%%	value list produced by `Tree' (viewed as an expression; the%%	"value" of a module contains its exported functions). The atom%%	`none' denotes missing or conflicting information.%%%%	The atom `external' in any label set denotes any possible%%	function outside `Tree', including those in `Escapes'.%%%%	`Outputs' is a mapping from the labels of fun-expressions in%%	`Tree' to corresponding lists of sets of labels of%%	fun-expressions (or the atom `none'), representing the possible%%	closures in the value lists returned by the respective%%	functions.%%%%	`Dependencies' is a similar mapping from the labels of%%	fun-expressions and apply-expressions in `Tree' to sets of%%	labels of corresponding fun-expressions which may contain call%%	sites of the functions or be called from the call sites,%%	respectively. Any such label not defined in `Dependencies'%%	represents an unreachable function or a dead or faulty%%	application.%%%%	`Escapes' is the set of labels of fun-expressions in `Tree' such%%	that corresponding closures may be accessed from outside `Tree'.%%%%	Note: `Tree' must be annotated with labels (as done by the%%	function `cerl_trees:label/1') in order to use this function.%%	The label annotation `{label, L}' (where L should be an integer)%%	must be the first element of the annotation list of each node in%%	the tree. Instances of variables bound in `Tree' which denote%%	the same variable must have the same label; apart from this,%%	labels should be unique. Constant literals do not need to be%%	labeled.-record(state, {k, vars, out, dep, work, funs, envs}).%% Note: In order to keep our domain simple, we assume that all remote%% calls and primops return a single value, if any.%% We wrap the given syntax tree T in a fun-expression labeled `top',%% which is initially in the set of escaped labels. `top' will be%% visited at least once.%%%% We create a separate function labeled `external', defined as:%% "External = fun () -> Any", which will represent any and all%% functions outside T, and whose return value has unknown type.analyze(T) ->    analyze(T, ?DEF_LIMIT).analyze(T, Limit) ->    {_, _, Esc, Dep, Par} = cerl_closurean:analyze(T),    analyze(T, Limit, Esc, Dep, Par).analyze(T, Limit, Esc0, Dep0, Par) ->    %% Note that we use different name spaces for variable labels and    %% function/call site labels. We assume that the labeling of T only    %% uses integers, not atoms.    External = ann_c_var([{label, external}], {external, 1}),    ExtFun = ann_c_fun([{label, external}], [],		       ann_c_var([{label, any}], 'Any')),%%%     io:fwrite("external fun:\n~s.\n",%%% 	      [cerl_prettypr:format(ExtFun, [noann, {paper, 80}])]),    Top = ann_c_var([{label, top}], {top, 0}),    TopFun = ann_c_fun([{label, top}], [], T),    %% The "start fun" just makes the initialisation easier. It is not    %% itself in the call graph.    StartFun =  ann_c_fun([{label, start}], [],			  c_letrec([{External, ExtFun}, {Top, TopFun}],				   c_nil())),%%%     io:fwrite("start fun:\n~s.\n",%%% 	      [cerl_prettypr:format(StartFun, [{paper, 80}])]),    %% Gather a database of all fun-expressions in T and initialise    %% their outputs and parameter variables. All escaping functions can    %% receive any values as inputs. Also add an extra dependency edge    %% from each fun-expression label to its parent fun-expression.%%%     io:fwrite("Escape: ~p.\n",[Esc0]),    Esc = sets:from_list(Esc0),    Any = t_any(),    None = t_none(),    Funs0 = dict:new(),    Vars0 = dict:store(any, Any, dict:new()),    Out0 = dict:store(top, None,		      dict:store(external, None, dict:new())),    Envs0 = dict:store(top, dict:new(),		       dict:store(external, dict:new(), dict:new())),    F = fun (T, S = {Fs, Vs, Os, Es}) ->		case type(T) of		    'fun' ->			L = get_label(T),			As = fun_vars(T),			X = case sets:is_element(L, Esc) of				true -> Any;				false -> None			    end,			{dict:store(L, T, Fs),			 bind_vars_single(As, X, Vs),			 dict:store(L, None, Os),			 dict:store(L, dict:new(), Es)};		    _ ->			S		end	end,    {Funs, Vars, Out, Envs} = cerl_trees:fold(F, {Funs0, Vars0, Out0,						  Envs0}, StartFun),    %% Add dependencies from funs to their parent funs.    Dep = lists:foldl(fun ({L, L1}, D) -> add_dep(L, L1, D) end,		      Dep0, dict:to_list(Par)),    %% Enter the fixpoint iteration at the StartFun.    St = loop(TopFun, top, #state{vars = Vars,				  out = Out,				  dep = Dep,				  work = init_work(),				  funs = Funs,				  envs = Envs,				  k = Limit}),    {dict:fetch(top, St#state.out),     tidy_dict([top, external], St#state.out),     tidy_dict([any], St#state.vars)}.tidy_dict([X | Xs], D) ->    tidy_dict(Xs, dict:erase(X, D));tidy_dict([], D) ->    D.loop(T, L, St0) ->%%%     io:fwrite("analyzing: ~w.\n",[L]),%%%     io:fwrite("work: ~w.\n", [Queue0]),    Env = dict:fetch(L, St0#state.envs),    X0 = dict:fetch(L, St0#state.out),    {X1, St1} = visit(fun_body(T), Env, St0),    X = limit(X1, St1#state.k),    {W, M} = case equal(X0, X) of		 true ->		     {St1#state.work, St1#state.out};		 false ->%%%        		     io:fwrite("out (~w) changed: ~s <- ~s.\n",%%%        			       [L, erl_types:t_to_string(X),%%%    				erl_types:t_to_string(X0)]),		     M1 = dict:store(L, X, St1#state.out),		     case dict:find(L, St1#state.dep) of			 {ok, S} ->%%% 			     io:fwrite("adding work: ~w.\n", [S]),			     {add_work(S, St1#state.work), M1};			 error ->			     {St1#state.work, M1}		     end	     end,    St2 = St1#state{out = M},    case take_work(W) of	{ok, L1, W1} -> 	    T1 = dict:fetch(L1, St2#state.funs), 	    loop(T1, L1, St2#state{work = W1});	none ->	    St2    end.visit(T, Env, St) ->    case type(T) of	literal ->	    {t_from_term(concrete(T)), St};	var ->	    %% If a variable is not already in the store at this point,	    %% we initialize it to 'none()'.	    L = get_label(T),	    Vars = St#state.vars,	    case dict:find(L, Vars) of		{ok, X} ->		    case dict:find(var_name(T), Env) of			{ok, X1} ->%%%   			    io:fwrite("filtered variable reference: ~w:~s.\n",%%%   				      [var_name(T), erl_types:t_to_string(X1)]),			    {meet(X, X1), St};			error ->			    {X, St}		    end;		error ->		    X = t_none(),		    Vars1 = dict:store(L, X, Vars),		    St1 = St#state{vars = Vars1},		    {X, St1}	    end;	'fun' ->	    %% Must revisit the fun also, because its environment might	    %% have changed. (We don't keep track of such dependencies.)	    L = get_label(T),	    Xs = [dict:fetch(get_label(V), St#state.vars)		  || V <- fun_vars(T)],	    X = dict:fetch(L, St#state.out),	    St1 = St#state{work = add_work([L], St#state.work),			   envs = dict:store(L, Env, St#state.envs)},	    {t_fun(Xs, X), St1};	values ->	    {Xs, St1} = visit_list(values_es(T), Env, St),	    {t_product(Xs), St1};	cons ->	    {[X1, X2], St1} = visit_list([cons_hd(T), cons_tl(T)], Env,					 St),	    {t_cons(X1, X2), St1};	tuple ->	    {Xs, St1} = visit_list(tuple_es(T), Env, St),	    {t_tuple(Xs), St1};	'let' ->	    {X, St1} = visit(let_arg(T), Env, St),	    Vars = bind_vars(let_vars(T), t_components(X), St1#state.vars),	    visit(let_body(T), Env, St1#state{vars = Vars});	seq ->	    {_, St1} = visit(seq_arg(T), Env, St),	    visit(seq_body(T), Env, St1);	apply ->	    {_F, St1} = visit(apply_op(T), Env, St),	    {As, St2} = visit_list(apply_args(T), Env, St1),	    L = get_label(T),	    Ls = get_deps(L, St#state.dep),	    Out = St2#state.out,	    X = join_list([dict:fetch(L1, Out) || L1 <- Ls]),	    Out1 = dict:store(L, X, Out),	    {X, call_site(Ls, As, St2#state{out = Out1})};	call ->	    M = call_module(T),	    F = call_name(T),	    As = call_args(T),	    {[X1, X2], St1} = visit_list([M, F], Env, St),	    {Xs, St2} = visit_list(As, Env, St1),%%% 	    io:fwrite("call: ~w:~w(~w).\n",[X1,X2,Xs]),	    X = case {t_atom_vals(X1), t_atom_vals(X2)} of		    {[M1], [F1]} ->			A = length(As),%%% 			io:fwrite("known call: ~w:~w/~w.\n",%%% 				  [M1, F1, A]),			call_type(M1, F1, A, Xs);		    _ ->			t_any()		end,	    L = get_label(T),	    {X, St2#state{out = dict:store(L, X, St2#state.out)}};	primop ->	    As = primop_args(T),	    {Xs, St1} = visit_list(As, Env, St),	    F = atom_val(primop_name(T)),	    A = length(As),	    L = get_label(T),	    X = primop_type(F, A, Xs),	    {X, St1#state{out = dict:store(L, X, St1#state.out)}};	'case' ->	    {X, St1} = visit(case_arg(T), Env, St),	    join_visit_clauses(t_components(X), case_clauses(T), Env,			       St1);	'receive' ->	    Any = t_any(),	    {X1, St1} = join_visit_clauses([Any], receive_clauses(T),					   Env, St),	    {X2, St2} = visit(receive_timeout(T), Env, St1),	    case t_is_atom(X2) andalso (t_atom_vals(X2) =:= [infinity]) of		true ->		    {X1, St2};		false ->		    {X3, St3} = visit(receive_action(T), Env, St2),		    {join(X1, X3), St3}	    end;	'try' ->	    {X, St1} = visit(try_arg(T), Env, St),	    Any = t_any(),	    Atom = t_atom(),	    Vars = bind_vars(try_vars(T), t_components(X), St1#state.vars),	    {X1, St2} = visit(try_body(T), Env, St1#state{vars = Vars}),	    EVars = bind_vars(try_evars(T), [Atom, Any, Any], St2#state.vars),	    {X2, St3} = visit(try_handler(T), Env, St2#state{vars = EVars}),	    {join(X1, X2), St3};	'catch' ->	    {_, St1} = visit(catch_body(T), Env, St),	    {t_any(), St1};	binary ->	    {_, St1} = visit_list(binary_segments(T), Env, St),	    {t_binary(), St1};	bitstr ->	    %% The other fields are constant literals.	    {_, St1} = visit(bitstr_val(T), Env, St),	    {_, St2} = visit(bitstr_size(T), Env, St1),	    {t_none(), St2};	letrec ->	    %% All the bound funs should be revisited, because the	    %% environment might have changed.	    Vars = bind_defs(letrec_defs(T), St#state.vars,			     St#state.out),	    Ls = [get_label(F) || {_, F} <- letrec_defs(T)],	    St1 = St#state{work = add_work(Ls, St#state.work),			   vars = Vars},	    visit(letrec_body(T), Env, St1);	module ->	    %% We handle a module as a sequence of function variables in	    %% the body of a `letrec'.	    {_, St1} = visit(c_letrec(module_defs(T),				      c_values(module_exports(T))),			     Env, St),	    {t_none(), St1}    end.visit_clause(T, Xs, Env, St) ->    Env1 = Env,    Vars = bind_pats(clause_pats(T), Xs, St#state.vars),

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?