aboutsummaryrefslogtreecommitdiffstats
path: root/lib/tools/src/xref_base.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tools/src/xref_base.erl')
-rw-r--r--lib/tools/src/xref_base.erl1804
1 files changed, 1804 insertions, 0 deletions
diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl
new file mode 100644
index 0000000000..d0dbf4a2b4
--- /dev/null
+++ b/lib/tools/src/xref_base.erl
@@ -0,0 +1,1804 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(xref_base).
+
+-export([new/0, new/1, delete/1,
+ add_directory/2, add_directory/3,
+ add_module/2, add_module/3,
+ add_application/2, add_application/3,
+ replace_module/3, replace_module/4,
+ replace_application/3, replace_application/4,
+ remove_module/2, remove_application/2, remove_release/2,
+ add_release/2, add_release/3,
+ get_library_path/1, set_library_path/2, set_library_path/3,
+ set_up/1, set_up/2,
+ q/2, q/3, info/1, info/2, info/3, update/1, update/2,
+ forget/1, forget/2, variables/1, variables/2,
+ analyze/2, analyze/3, analysis/1,
+ get_default/2, set_default/3,
+ get_default/1, set_default/2]).
+
+-export([format_error/1]).
+
+%% The following functions are exported for testing purposes only:
+-export([do_add_module/4, do_add_application/2, do_add_release/2,
+ do_remove_module/2]).
+
+-import(lists,
+ [filter/2, flatten/1, foldl/3, keysearch/3, map/2, mapfoldl/3,
+ member/2, reverse/1, sort/1, usort/1]).
+
+-import(sofs,
+ [constant_function/2, converse/1, difference/2, domain/1,
+ empty_set/0, family/1, family_difference/2, intersection/2,
+ family_projection/2, family_to_relation/1, family_union/1,
+ family_union/2, from_sets/1, from_term/1, a_function/1,
+ image/2, family_intersection/2, inverse/1, is_empty_set/1,
+ multiple_relative_product/2, no_elements/1,
+ partition_family/2, projection/2, range/1, relation/1,
+ relation_to_family/1, relative_product1/2, restriction/2,
+ restriction/3, set/1, specification/2, substitution/2,
+ to_external/1, union/1, union/2, union_of_family/1]).
+
+-include("xref.hrl").
+
+-define(Suffix, ".beam").
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(FORMAT(P, A), io:format(P, A)).
+-else.
+-define(FORMAT(P, A), ok).
+-endif.
+
+%%
+%% Exported functions
+%%
+
+new() ->
+ new([]).
+
+%% -> {ok, InitialState}
+new(Options) ->
+ Modes = [functions,modules,function,module],
+ case xref_utils:options(Options, [{xref_mode,Modes}]) of
+ {[[function]], []} ->
+ {ok, #xref{mode = functions}};
+ {[[module]], []} ->
+ {ok, #xref{mode = modules}};
+ {[[OM]], []} ->
+ {ok, #xref{mode = OM}};
+ _ ->
+ error({invalid_options, Options})
+ end.
+
+%% -> ok
+%% Need not be called by the server.
+delete(State) when State#xref.variables =:= not_set_up ->
+ ok;
+delete(State) ->
+ Fun = fun({X, _}) ->
+ case catch digraph:info(X) of
+ Info when is_list(Info) ->
+ true = digraph:delete(X);
+ _Else ->
+ ok
+ end
+ end,
+ map(Fun, dict:to_list(State#xref.variables)),
+ ok.
+
+add_directory(State, Dir) ->
+ add_directory(State, Dir, []).
+
+%% -> {ok, Modules, NewState} | Error
+add_directory(State, Dir, Options) ->
+ ValOptions = option_values([builtins, recurse, verbose, warnings], State),
+ case xref_utils:options(Options, ValOptions) of
+ {[[OB], [OR], [OV], [OW]], []} ->
+ catch do_add_directory(Dir, [], OB, OR, OV, OW, State);
+ _ ->
+ error({invalid_options, Options})
+ end.
+
+add_module(State, File) ->
+ add_module(State, File, []).
+
+%% -> {ok, Module, NewState} | Error
+add_module(State, File, Options) ->
+ ValOptions = option_values([builtins, verbose, warnings], State),
+ case xref_utils:options(Options, ValOptions) of
+ {[[OB], [OV], [OW]], []} ->
+ case catch do_add_a_module(File, [], OB, OV, OW, State) of
+ {ok, [Module], NewState} ->
+ {ok, Module, NewState};
+ {ok, [], _NewState} ->
+ error({no_debug_info, File});
+ Error ->
+ Error
+ end;
+ _ ->
+ error({invalid_options, Options})
+ end.
+
+add_application(State, AppDir) ->
+ add_application(State, AppDir, []).
+
+%% -> {ok, AppName, NewState} | Error
+add_application(State, AppDir, Options) ->
+ OptVals = option_values([builtins, verbose, warnings], State),
+ ValidOptions = [{name, ["", fun check_name/1]} | OptVals],
+ case xref_utils:options(Options, ValidOptions) of
+ {[ApplName, [OB], [OV], [OW]], []} ->
+ catch do_add_application(AppDir, [], ApplName, OB, OV, OW, State);
+ _ ->
+ error({invalid_options, Options})
+ end.
+
+replace_module(State, Module, File) ->
+ replace_module(State, Module, File, []).
+
+%% -> {ok, Module, NewState} | Error
+replace_module(State, Module, File, Options) ->
+ ValidOptions = option_values([verbose, warnings], State),
+ case xref_utils:options(Options, ValidOptions) of
+ {[[OV], [OW]], []} ->
+ catch do_replace_module(Module, File, OV, OW, State);
+ _ ->
+ error({invalid_options, Options})
+ end.
+
+replace_application(State, Appl, Dir) ->
+ replace_application(State, Appl, Dir, []).
+
+%% -> {ok, AppName, NewState} | Error
+replace_application(State, Appl, Dir, Options) ->
+ ValidOptions = option_values([builtins, verbose, warnings], State),
+ case xref_utils:options(Options, ValidOptions) of
+ {[[OB], [OV], [OW]], []} ->
+ catch do_replace_application(Appl, Dir, OB, OV, OW, State);
+ _ ->
+ error({invalid_options, Options})
+ end.
+
+%% -> {ok, NewState} | Error
+remove_module(State, Mod) when is_atom(Mod) ->
+ remove_module(State, [Mod]);
+remove_module(State, [Mod | Mods]) ->
+ case catch do_remove_module(State, Mod) of
+ {ok, _OldXMod, NewState} ->
+ remove_module(NewState, Mods);
+ Error ->
+ Error
+ end;
+remove_module(State, []) ->
+ {ok, State}.
+
+%% -> {ok, NewState} | Error
+remove_application(State, Appl) when is_atom(Appl) ->
+ remove_application(State, [Appl]);
+remove_application(State, [Appl | Appls]) ->
+ case catch do_remove_application(State, Appl) of
+ {ok, _OldXApp, NewState} ->
+ remove_application(NewState, Appls);
+ Error ->
+ Error
+ end;
+remove_application(State, []) ->
+ {ok, State}.
+
+%% -> {ok, NewState} | Error
+remove_release(State, Rel) when is_atom(Rel) ->
+ remove_release(State, [Rel]);
+remove_release(State, [Rel | Rels]) ->
+ case catch do_remove_release(State, Rel) of
+ {ok, _OldXRel, NewState} ->
+ remove_release(NewState, Rels);
+ Error ->
+ Error
+ end;
+remove_release(State, []) ->
+ {ok, State}.
+
+add_release(State, RelDir) ->
+ add_release(State, RelDir, []).
+
+%% -> {ok, ReleaseName, NewState} | Error
+add_release(State, RelDir, Options) ->
+ ValidOptions0 = option_values([builtins, verbose, warnings], State),
+ ValidOptions = [{name, ["", fun check_name/1]} | ValidOptions0],
+ case xref_utils:options(Options, ValidOptions) of
+ {[RelName, [OB], [OV], [OW]], []} ->
+ catch do_add_release(RelDir, RelName, OB, OV, OW, State);
+ _ ->
+ error({invalid_options, Options})
+ end.
+
+get_library_path(State) ->
+ {ok, State#xref.library_path}.
+
+set_library_path(State, Path) ->
+ set_library_path(State, Path, []).
+
+%% -> {ok, NewState} | Error
+set_library_path(State, code_path, _Options) ->
+ S1 = State#xref{library_path = code_path, libraries = dict:new()},
+ {ok, take_down(S1)};
+set_library_path(State, Path, Options) ->
+ case xref_utils:is_path(Path) of
+ true ->
+ ValidOptions = option_values([verbose], State),
+ case xref_utils:options(Options, ValidOptions) of
+ {[[OV]], []} ->
+ do_add_libraries(Path, OV, State);
+ _ ->
+ error({invalid_options, Options})
+ end;
+ false ->
+ error({invalid_path, Path})
+ end.
+
+set_up(State) ->
+ set_up(State, []).
+
+%% -> {ok, NewState} | Error
+set_up(State, Options) ->
+ ValidOptions = option_values([verbose], State),
+ case xref_utils:options(Options, ValidOptions) of
+ {[[Verbose]], []} ->
+ do_set_up(State, Verbose);
+ _ ->
+ error({invalid_options, Options})
+ end.
+
+q(S, Q) ->
+ q(S, Q, []).
+
+%% -> {{ok, Answer}, NewState} | {Error, NewState}
+q(S, Q, Options) when is_atom(Q) ->
+ q(S, atom_to_list(Q), Options);
+q(S, Q, Options) ->
+ case xref_utils:is_string(Q, 1) of
+ true ->
+ case set_up(S, Options) of
+ {ok, S1} ->
+ case xref_compiler:compile(Q, S1#xref.variables) of
+ {NewT, Ans} ->
+ {{ok, Ans}, S1#xref{variables = NewT}};
+ Error ->
+ {Error, S1}
+ end;
+ Error ->
+ {Error, S}
+ end;
+ false ->
+ {error({invalid_query, Q}), S}
+ end.
+
+%% -> InfoList
+info(State) ->
+ D0 = sort(dict:to_list(State#xref.modules)),
+ D = map(fun({_M, XMod}) -> XMod end, D0),
+ NoApps = length(dict:to_list(State#xref.applications)),
+ NoRels = length(dict:to_list(State#xref.releases)),
+ No = no_sum(State, D),
+ [{library_path, State#xref.library_path}, {mode, State#xref.mode},
+ {no_releases, NoRels}, {no_applications, NoApps}] ++ No.
+
+info(State, What) ->
+ do_info(State, What).
+
+%% -> [{what(), InfoList}]
+info(State, What, Qual) ->
+ catch do_info(State, What, Qual).
+
+update(State) ->
+ update(State, []).
+
+%% -> {ok, NewState, Modules} | Error
+update(State, Options) ->
+ ValidOptions = option_values([verbose, warnings], State),
+ case xref_utils:options(Options, ValidOptions) of
+ {[[OV],[OW]], []} ->
+ catch do_update(OV, OW, State);
+ _ ->
+ error({invalid_options, Options})
+ end.
+
+%% -> {ok, NewState}
+forget(State) ->
+ {U, _P} = do_variables(State),
+ {ok, foldl(fun(V, S) -> {ok, NS} = forget(S, V), NS end, State, U)}.
+
+%% -> {ok, NewState} | Error
+forget(State, Variable) when State#xref.variables =:= not_set_up ->
+ error({not_user_variable, Variable});
+forget(State, Variable) when is_atom(Variable) ->
+ forget(State, [Variable]);
+forget(State, Variables) ->
+ Vars = State#xref.variables,
+ do_forget(Variables, Vars, Variables, State).
+
+variables(State) ->
+ variables(State, [user]).
+
+%% -> {{ok, Answer}, NewState} | {Error, NewState}
+%% Answer = [{vartype(), [VariableName]}]
+variables(State, Options) ->
+ ValidOptions = option_values([verbose], State),
+ case xref_utils:options(Options, [user, predefined | ValidOptions]) of
+ {[User,Predef,[OV]],[]} ->
+ case do_set_up(State, OV) of
+ {ok, NewState} ->
+ {U, P} = do_variables(NewState),
+ R1 = if User -> [{user, U}]; true -> [] end,
+ R = if
+ Predef -> [{predefined,P} | R1];
+ true -> R1
+ end,
+ {{ok, R}, NewState};
+ Error ->
+ {Error, State}
+ end;
+ _ ->
+ {error({invalid_options, Options}), State}
+ end.
+
+analyze(State, Analysis) ->
+ analyze(State, Analysis, []).
+
+%% -> {{ok, Answer}, NewState} | {Error, NewState}
+analyze(State, Analysis, Options) ->
+ case analysis(Analysis, State#xref.mode) of
+ P when is_list(P) ->
+ q(State, P, Options);
+ error ->
+ R = case analysis(Analysis, functions) of
+ error -> unknown_analysis;
+ P when is_list(P) -> unavailable_analysis
+ end,
+ Error = error({R, Analysis}),
+ {Error, State}
+ end.
+
+analysis(Analysis) ->
+ analysis(Analysis, functions).
+
+%% -> string() | Error
+analysis(undefined_function_calls, functions) ->
+ "(XC - UC) || (XU - X - B)";
+analysis(undefined_functions, modules) ->
+ %% "XU * (L + U)" is equivalent, but the following works when L is
+ %% not available.
+ "XU - X - B";
+analysis(undefined_functions, functions) ->
+ %% "XU * ((L + U) - range UC)" is equivalent.
+ "XU - range UC - X - B";
+analysis(locals_not_used, functions) ->
+ %% The Inter Call Graph is used to get local functions that are not
+ %% used (indirectly) from any export: "(domain EE + range EE) * L".
+ %% But then we only get locals that make some calls, so we add
+ %% locals that are not used at all: "L * (UU + XU - LU)".
+ "L * ((UU + XU - LU) + domain EE + range EE)";
+analysis(exports_not_used, _) ->
+ %% Local calls are not considered here. "X * UU" would do otherwise.
+ "X - XU";
+analysis({call, F}, functions) ->
+ make_query("range (E | ~w : Fun)", [F]);
+analysis({use, F}, functions) ->
+ make_query("domain (E || ~w : Fun)", [F]);
+analysis({module_call, M}, _) ->
+ make_query("range (ME | ~w : Mod)", [M]);
+analysis({module_use, M}, _) ->
+ make_query("domain (ME || ~w : Mod)", [M]);
+analysis({application_call, A}, _) ->
+ make_query("range (AE | ~w : App)", [A]);
+analysis({application_use, A}, _) ->
+ make_query("domain (AE || ~w : App)", [A]);
+analysis({release_call, R}, _) ->
+ make_query("range (RE | ~w : Rel)", [R]);
+analysis({release_use, R}, _) ->
+ make_query("domain (RE || ~w : Rel)", [R]);
+analysis(deprecated_function_calls, functions) ->
+ "XC || DF";
+analysis({deprecated_function_calls,Flag}, functions) ->
+ case deprecated_flag(Flag) of
+ undefined -> error;
+ I -> make_query("XC || DF_~w", [I])
+ end;
+analysis(deprecated_functions, _) ->
+ "XU * DF";
+analysis({deprecated_functions,Flag}, _) ->
+ case deprecated_flag(Flag) of
+ undefined -> error;
+ I -> make_query("XU * DF_~w", [I])
+ end;
+analysis(_, _) ->
+ error.
+
+%% -> {ok, OldValue, NewState} | Error
+set_default(State, Option, Value) ->
+ case get_default(State, Option) of
+ {ok, OldValue} ->
+ Values = option_values([Option], State),
+ case xref_utils:options([{Option,Value}], Values) of
+ {_, []} ->
+ NewState = set_def(Option, Value, State),
+ {ok, OldValue, NewState};
+ {_, Unknown} ->
+ error({invalid_options, Unknown})
+ end;
+ Error ->
+ Error
+ end.
+
+%% -> {ok, Value} | Error
+get_default(State, Option) ->
+ case catch current_default(State, Option) of
+ {'EXIT', _} ->
+ error({invalid_options, [Option]});
+ Value ->
+ {ok, Value}
+ end.
+
+%% -> [{Option, Value}]
+get_default(State) ->
+ Fun = fun(O) -> V = current_default(State, O), {O, V} end,
+ map(Fun, [builtins, recurse, verbose, warnings]).
+
+%% -> {ok, NewState} -> Error
+set_default(State, Options) ->
+ Opts = [builtins, recurse, verbose, warnings],
+ ValidOptions = option_values(Opts, State),
+ case xref_utils:options(Options, ValidOptions) of
+ {Values = [[_], [_], [_], [_]], []} ->
+ {ok, set_defaults(Opts, Values, State)};
+ _ ->
+ error({invalid_options, Options})
+ end.
+
+format_error({error, Module, Error}) ->
+ Module:format_error(Error);
+format_error({invalid_options, Options}) ->
+ io_lib:format("Unknown option(s) or invalid option value(s): ~p~n",
+ [Options]);
+format_error({invalid_filename, Term}) ->
+ io_lib:format("A file name (a string) was expected: ~p~n", [Term]);
+format_error({no_debug_info, FileName}) ->
+ io_lib:format("The BEAM file ~p has no debug info~n", [FileName]);
+format_error({invalid_path, Term}) ->
+ io_lib:format("A path (a list of strings) was expected: ~p~n", [Term]);
+format_error({invalid_query, Term}) ->
+ io_lib:format("A query (a string or an atom) was expected: ~p~n", [Term]);
+format_error({not_user_variable, Variable}) ->
+ io_lib:format("~p is not a user variable~n", [Variable]);
+format_error({unknown_analysis, Term}) ->
+ io_lib:format("~p is not a predefined analysis~n", [Term]);
+format_error({module_mismatch, Module, ReadModule}) ->
+ io_lib:format("Name of read module ~p does not match analyzed module ~p~n",
+ [ReadModule, Module]);
+format_error({release_clash, {Release, Dir, OldDir}}) ->
+ io_lib:format("The release ~p read from ~p clashes with release "
+ "already read from ~p~n", [Release, Dir, OldDir]);
+format_error({application_clash, {Application, Dir, OldDir}}) ->
+ io_lib:format("The application ~p read from ~p clashes with application "
+ "already read from ~p~n", [Application, Dir, OldDir]);
+format_error({module_clash, {Module, Dir, OldDir}}) ->
+ io_lib:format("The module ~p read from ~p clashes with module "
+ "already read from ~p~n", [Module, Dir, OldDir]);
+format_error({no_such_release, Name}) ->
+ io_lib:format("There is no analyzed release ~p~n", [Name]);
+format_error({no_such_application, Name}) ->
+ io_lib:format("There is no analyzed application ~p~n", [Name]);
+format_error({no_such_module, Name}) ->
+ io_lib:format("There is no analyzed module ~p~n", [Name]);
+format_error({no_such_info, Term}) ->
+ io_lib:format("~p is not one of 'modules', 'applications', "
+ "'releases' and 'libraries'~n", [Term]);
+format_error(E) ->
+ io_lib:format("~p~n", [E]).
+
+%%
+%% Local functions
+%%
+
+check_name([N]) when is_atom(N) -> true;
+check_name(_) -> false.
+
+do_update(OV, OW, State) ->
+ Changed = updated_modules(State),
+ Fun = fun({Mod,File}, S) ->
+ {ok, _M, NS} = do_replace_module(Mod, File, OV, OW, S),
+ NS
+ end,
+ NewState = foldl(Fun, State, Changed),
+ {ok, NewState, to_external(domain(a_function(Changed)))}.
+
+%% -> [{Module, File}]
+updated_modules(State) ->
+ Fun = fun({M,XMod}, L) ->
+ RTime = XMod#xref_mod.mtime,
+ File = module_file(XMod),
+ case xref_utils:file_info(File) of
+ {ok, {_, file, readable, MTime}} when MTime =/= RTime ->
+ [{M,File} | L];
+ _Else ->
+ L
+ end
+ end,
+ foldl(Fun, [], dict:to_list(State#xref.modules)).
+
+do_forget([Variable | Variables], Vars, Vs, State) ->
+ case dict:find(Variable, Vars) of
+ {ok, #xref_var{vtype = user}} ->
+ do_forget(Variables, Vars, Vs, State);
+ _ ->
+ error({not_user_variable, Variable})
+ end;
+do_forget([], Vars, Vs, State) ->
+ Fun = fun(V, VT) ->
+ {ok, #xref_var{value = Value}} = dict:find(V, VT),
+ VT1 = xref_compiler:update_graph_counter(Value, -1, VT),
+ dict:erase(V, VT1)
+ end,
+ NewVars = foldl(Fun, Vars, Vs),
+ NewState = State#xref{variables = NewVars},
+ {ok, NewState}.
+
+%% -> {ok, Module, State} | throw(Error)
+do_replace_module(Module, File, OV, OW, State) ->
+ {ok, OldXMod, State1} = do_remove_module(State, Module),
+ OldApp = OldXMod#xref_mod.app_name,
+ OB = OldXMod#xref_mod.builtins,
+ case do_add_a_module(File, OldApp, OB, OV, OW, State1) of
+ {ok, [Module], NewState} ->
+ {ok, Module, NewState};
+ {ok, [ReadModule], _State} ->
+ throw_error({module_mismatch, Module, ReadModule});
+ {ok, [], _NewState} ->
+ throw_error({no_debug_info, File})
+ end.
+
+do_replace_application(Appl, Dir, OB, OV, OW, State) ->
+ {ok, OldXApp, State1} = do_remove_application(State, Appl),
+ Rel = OldXApp#xref_app.rel_name,
+ N = OldXApp#xref_app.name,
+ %% The application name is kept; the name of Dir is not used
+ %% as source for a "new" application name.
+ do_add_application(Dir, Rel, [N], OB, OV, OW, State1).
+
+%% -> {ok, ReleaseName, NewState} | throw(Error)
+do_add_release(Dir, RelName, OB, OV, OW, State) ->
+ ok = is_filename(Dir),
+ case xref_utils:release_directory(Dir, true, "ebin") of
+ {ok, ReleaseDirName, ApplDir, Dirs} ->
+ ApplDirs = xref_utils:select_last_application_version(Dirs),
+ Release = case RelName of
+ [[]] -> ReleaseDirName;
+ [Name] -> Name
+ end,
+ XRel = #xref_rel{name = Release, dir = ApplDir},
+ NewState = do_add_release(State, XRel),
+ add_rel_appls(ApplDirs, [Release], OB, OV, OW, NewState);
+ Error ->
+ throw(Error)
+ end.
+
+do_add_release(S, XRel) ->
+ Release = XRel#xref_rel.name,
+ case dict:find(Release, S#xref.releases) of
+ {ok, OldXRel} ->
+ Dir = XRel#xref_rel.dir,
+ OldDir = OldXRel#xref_rel.dir,
+ throw_error({release_clash, {Release, Dir, OldDir}});
+ error ->
+ D1 = dict:store(Release, XRel, S#xref.releases),
+ S#xref{releases = D1}
+ end.
+
+add_rel_appls([ApplDir | ApplDirs], Release, OB, OV, OW, State) ->
+ {ok, _AppName, NewState} =
+ add_appldir(ApplDir, Release, [[]], OB, OV, OW, State),
+ add_rel_appls(ApplDirs, Release, OB, OV, OW, NewState);
+add_rel_appls([], [Release], _OB, _OV, _OW, NewState) ->
+ {ok, Release, NewState}.
+
+do_add_application(Dir0, Release, Name, OB, OV, OW, State) ->
+ ok = is_filename(Dir0),
+ case xref_utils:select_application_directories([Dir0], "ebin") of
+ {ok, [ApplD]} ->
+ add_appldir(ApplD, Release, Name, OB, OV, OW, State);
+ Error ->
+ throw(Error)
+ end.
+
+%% -> {ok, AppName, NewState} | throw(Error)
+add_appldir(ApplDir, Release, Name, OB, OV, OW, OldState) ->
+ {AppName0, Vsn, Dir} = ApplDir,
+ AppName = case Name of
+ [[]] -> AppName0;
+ [N] -> N
+ end,
+ AppInfo = #xref_app{name = AppName, rel_name = Release,
+ vsn = Vsn, dir = Dir},
+ State1 = do_add_application(OldState, AppInfo),
+ {ok, _Modules, NewState} =
+ do_add_directory(Dir, [AppName], OB, false, OV, OW, State1),
+ {ok, AppName, NewState}.
+
+%% -> State | throw(Error)
+do_add_application(S, XApp) ->
+ Application = XApp#xref_app.name,
+ case dict:find(Application, S#xref.applications) of
+ {ok, OldXApp} ->
+ Dir = XApp#xref_app.dir,
+ OldDir = OldXApp#xref_app.dir,
+ throw_error({application_clash, {Application, Dir, OldDir}});
+ error ->
+ D1 = dict:store(Application, XApp, S#xref.applications),
+ S#xref{applications = D1}
+ end.
+
+%% -> {ok, Modules, NewState} | throw(Error)
+do_add_directory(Dir, AppName, Bui, Rec, Ver, War, State) ->
+ ok = is_filename(Dir),
+ {FileNames, Errors, Jams, Unreadable} =
+ xref_utils:scan_directory(Dir, Rec, [?Suffix], [".jam"]),
+ warnings(War, jam, Jams),
+ warnings(War, unreadable, Unreadable),
+ case Errors of
+ [] ->
+ do_add_modules(FileNames, AppName, Bui, Ver, War, State, []);
+ [Error | _] ->
+ throw(Error)
+ end.
+
+do_add_modules([], _AppName, _OB, _OV, _OW, State, Modules) ->
+ {ok, sort(Modules), State};
+do_add_modules([File | Files], AppName, OB, OV, OW, State, Modules) ->
+ {ok, M, NewState} = do_add_module(File, AppName, OB, OV, OW, State),
+ do_add_modules(Files, AppName, OB, OV, OW, NewState, M ++ Modules).
+
+%% -> {ok, Module, State} | throw(Error)
+do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
+ case xref_utils:split_filename(File, ?Suffix) of
+ false ->
+ throw_error({invalid_filename, File});
+ Splitname ->
+ do_add_module(Splitname, AppName, Builtins, Verbose,
+ Warnings, State)
+ end.
+
+%% -> {ok, Module, State} | throw(Error)
+%% Options: verbose, warnings, builtins
+do_add_module({Dir, Basename}, AppName, Builtins, Verbose, Warnings, State) ->
+ File = filename:join(Dir, Basename),
+ {ok, M, Bad, NewState} =
+ do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State),
+ filter(fun({Tag,B}) -> warnings(Warnings, Tag, [[File,B]]) end, Bad),
+ {ok, M, NewState}.
+
+do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State) ->
+ message(Verbose, reading_beam, [File]),
+ Mode = State#xref.mode,
+ Me = self(),
+ Fun = fun() -> Me ! {self(), abst(File, Builtins, Mode)} end,
+ case xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]) of
+ {ok, _M, no_abstract_code} when Verbose ->
+ message(Verbose, skipped_beam, []),
+ {ok, [], [], State};
+ {ok, _M, no_abstract_code} when not Verbose ->
+ message(Warnings, no_debug_info, [File]),
+ {ok, [], [], State};
+ {ok, M, Data, UnresCalls0} ->
+ %% Remove duplicates. Identical unresolved calls on the
+ %% same line are counted as _one_ unresolved call.
+ UnresCalls = usort(UnresCalls0),
+ message(Verbose, done, []),
+ NoUnresCalls = length(UnresCalls),
+ case NoUnresCalls of
+ 0 -> ok;
+ 1 -> warnings(Warnings, unresolved_summary1, [[M]]);
+ N -> warnings(Warnings, unresolved_summary, [[M, N]])
+ end,
+ T = case xref_utils:file_info(File) of
+ {ok, {_, _, _, Time}} -> Time;
+ Error -> throw(Error)
+ end,
+ XMod = #xref_mod{name = M, app_name = AppName, dir = Dir,
+ mtime = T, builtins = Builtins,
+ no_unresolved = NoUnresCalls},
+ do_add_module(State, XMod, UnresCalls, Data);
+ Error ->
+ message(Verbose, error, []),
+ throw(Error)
+ end.
+
+abst(File, Builtins, Mode) when Mode =:= functions ->
+ case beam_lib:chunks(File, [abstract_code, exports, attributes]) of
+ {ok, {M,[{abstract_code,NoA},_X,_A]}} when NoA =:= no_abstract_code ->
+ {ok, M, NoA};
+ {ok, {M, [{abstract_code, {abstract_v1, Forms}},
+ {exports,X0}, {attributes,A}]}} ->
+ %% R7.
+ X = xref_utils:fa_to_mfa(X0, M),
+ D = deprecated(A, X, M),
+ xref_reader:module(M, Forms, Builtins, X, D);
+ {ok, {M, [{abstract_code, {abstract_v2, Forms}},
+ {exports,X0}, {attributes,A}]}} ->
+ %% R8-R9B.
+ X = xref_utils:fa_to_mfa(X0, M),
+ D = deprecated(A, X, M),
+ xref_reader:module(M, Forms, Builtins, X, D);
+ {ok, {M, [{abstract_code, {raw_abstract_v1, Code}},
+ {exports,X0}, {attributes,A}]}} ->
+ %% R9C-
+ Forms0 = epp:interpret_file_attribute(Code),
+ {_,_,Forms,_} = sys_pre_expand:module(Forms0, []),
+ X = mfa_exports(X0, A, M),
+ D = deprecated(A, X, M),
+ xref_reader:module(M, Forms, Builtins, X, D);
+ Error when element(1, Error) =:= error ->
+ Error
+ end;
+abst(File, Builtins, Mode) when Mode =:= modules ->
+ case beam_lib:chunks(File, [exports, imports, attributes]) of
+ {ok, {Mod, [{exports,X0}, {imports,I0}, {attributes,At}]}} ->
+ X1 = mfa_exports(X0, At, Mod),
+ X = filter(fun(MFA) -> not (predef_fun())(MFA) end, X1),
+ D = deprecated(At, X, Mod),
+ I = case Builtins of
+ true ->
+ I0;
+ false ->
+ Fun = fun({M,F,A}) ->
+ not xref_utils:is_builtin(M, F, A)
+ end,
+ filter(Fun, I0)
+ end,
+ {ok, Mod, {X, I, D}, []};
+ Error when element(1, Error) =:= error ->
+ Error
+ end.
+
+mfa_exports(X0, Attributes, M) ->
+ %% Adjust arities for abstract modules.
+ X1 = case xref_utils:is_abstract_module(Attributes) of
+ true ->
+ [{F,adjust_arity(F,A)} || {F,A} <- X0];
+ false ->
+ X0
+ end,
+ xref_utils:fa_to_mfa(X1, M).
+
+adjust_arity(F, A) ->
+ case xref_utils:is_static_function(F, A) of
+ true -> A;
+ false -> A - 1
+ end.
+
+deprecated(A, X, M) ->
+ DF = {[],[],[],[]},
+ case keysearch(deprecated, 1, A) of
+ {value, {deprecated, D0}} ->
+ depr(D0, M, DF, X, []);
+ false ->
+ {DF,[]}
+ end.
+
+depr([D | Depr], M, DF, X, Bad) ->
+ case depr_cat(D, M, X) of
+ {I,Dt} ->
+ NDF = setelement(I, DF, Dt ++ element(I, DF)),
+ depr(Depr, M, NDF, X, Bad);
+ undefined ->
+ depr(Depr, M, DF, X, [D | Bad])
+ end;
+depr([], _M, DF, _X, Bad) ->
+ {DF, reverse(Bad)}.
+
+depr_cat({F, A, Flg}, M, X) ->
+ case deprecated_flag(Flg) of
+ undefined -> undefined;
+ I -> depr_fa(F, A, X, M, I)
+ end;
+depr_cat({F, A}, M, X) ->
+ depr_fa(F, A, X, M, 4);
+depr_cat(module, M, X) ->
+ depr_fa('_', '_', X, M, 4);
+depr_cat(_D, _M, _X) ->
+ undefined.
+
+depr_fa('_', '_', X, _M, I) ->
+ {I, X};
+depr_fa(F, '_', X, _M, I) when is_atom(F) ->
+ {I, filter(fun({_,F1,_}) -> F1 =:= F end, X)};
+depr_fa(F, A, _X, M, I) when is_atom(F), is_integer(A), A >= 0 ->
+ {I, [{M,F,A}]};
+depr_fa(_F, _A, _X, _M, _I) ->
+ undefined.
+
+%% deprecated_flag(Flag) -> integer() | undefined
+%% Maps symbolic flags for deprecated functions to integers.
+
+%deprecated_flag(1) -> 1;
+%deprecated_flag(2) -> 2;
+%deprecated_flag(3) -> 3;
+deprecated_flag(next_version) -> 1;
+deprecated_flag(next_major_release) -> 2;
+deprecated_flag(eventually) -> 3;
+deprecated_flag(_) -> undefined.
+
+%% -> {ok, Module, Bad, State} | throw(Error)
+%% Assumes:
+%% L U X is a subset of dom DefAt
+%% dom CallAt = LC U XC
+%% Attrs is collected from the attribute 'xref' (experimental).
+do_add_module(S, XMod, Unres, Data) ->
+ M = XMod#xref_mod.name,
+ case dict:find(M, S#xref.modules) of
+ {ok, OldXMod} ->
+ BF2 = module_file(XMod),
+ BF1 = module_file(OldXMod),
+ throw_error({module_clash, {M, BF1, BF2}});
+ error ->
+ do_add_module(S, M, XMod, Unres, Data)
+ end.
+
+%%do_add_module(S, M, _XMod, _Unres, Data)->
+%% {ok, M, [], S};
+do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
+ {DefAt0, LPreCAt0, XPreCAt0, LC0, XC0, X0, Attrs, Depr} = Data,
+ %% Bad is a list of bad values of 'xref' attributes.
+ {ALC0,AXC0,Bad0} = Attrs,
+ FT = [tspec(func)],
+ FET = [tspec(fun_edge)],
+ PCA = [tspec(pre_call_at)],
+
+ XPreCAt1 = xref_utils:xset(XPreCAt0, PCA),
+ LPreCAt1 = xref_utils:xset(LPreCAt0, PCA),
+ DefAt = xref_utils:xset(DefAt0, [tspec(def_at)]),
+ X1 = xref_utils:xset(X0, FT),
+ XC1 = xref_utils:xset(XC0, FET),
+ LC1 = xref_utils:xset(LC0, FET),
+ AXC1 = xref_utils:xset(AXC0, PCA),
+ ALC1 = xref_utils:xset(ALC0, PCA),
+ UnresCalls = xref_utils:xset(Unres0, PCA),
+ Unres = domain(UnresCalls),
+
+ DefinedFuns = domain(DefAt),
+ {AXC, ALC, Bad1, LPreCAt2, XPreCAt2} =
+ extra_edges(AXC1, ALC1, Bad0, DefinedFuns),
+ Bad = map(fun(B) -> {xref_attr, B} end, Bad1),
+ LPreCAt = union(LPreCAt1, LPreCAt2),
+ XPreCAt = union(XPreCAt1, XPreCAt2),
+ NoCalls = no_elements(LPreCAt) + no_elements(XPreCAt),
+ LCallAt = relation_to_family(LPreCAt),
+ XCallAt = relation_to_family(XPreCAt),
+ CallAt = family_union(LCallAt, XCallAt),
+ %% Local and exported functions with no definitions are removed.
+ L = difference(DefinedFuns, X1),
+ X = difference(DefinedFuns, L),
+ XC = union(XC1, AXC),
+ LC = union(LC1, ALC),
+
+ {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X),
+
+ %% {EE, ECallAt} = inter_graph(X, L, LC, XC, LCallAt, XCallAt),
+ Self = self(),
+ Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end,
+ {EE, ECallAt} =
+ xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]),
+
+ [DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2,
+ DF2,DF_12,DF_22,DF_32] =
+ pack([DefAt,L,X,LCallAt,XCallAt,CallAt,LC,XC,EE,ECallAt,
+ DF1,DF_11,DF_21,DF_31]),
+
+ %% Foo = [DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2,
+ %% DF2,DF_12,DF_22,DF_32],
+ %% io:format("{~p, ~p, ~p},~n", [M, pack:lsize(Foo), pack:usize(Foo)]),
+
+ LU = range(LC2),
+
+ LPredefined = predefined_funs(LU),
+
+ MS = xref_utils:xset(M, atom),
+ T = from_sets({MS,DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,
+ LC2,XC2,LU,EE2,ECallAt2,Unres,LPredefined,
+ DF2,DF_12,DF_22,DF_32}),
+
+ NoUnres = XMod#xref_mod.no_unresolved,
+ Info = no_info(X2, L2, LC2, XC2, EE2, Unres, NoCalls, NoUnres),
+
+ XMod1 = XMod#xref_mod{data = T, info = Info},
+ S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)},
+ {ok, [M], DBad++Bad, take_down(S1)};
+do_add_module(S, M, XMod, _Unres, Data) when S#xref.mode =:= modules ->
+ {X0, I0, Depr} = Data,
+ X1 = xref_utils:xset(X0, [tspec(func)]),
+ I1 = xref_utils:xset(I0, [tspec(func)]),
+ {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X1),
+ [X2,I2,DF2,DF_12,DF_22,DF_32] = pack([X1,I1,DF1,DF_11,DF_21,DF_31]),
+ MS = xref_utils:xset(M, atom),
+ T = from_sets({MS, X2, I2, DF2, DF_12, DF_22, DF_32}),
+ Info = [],
+ XMod1 = XMod#xref_mod{data = T, info = Info},
+ S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)},
+ {ok, [M], DBad, take_down(S1)}.
+
+depr_mod({Depr,Bad0}, X) ->
+ %% Bad0 are badly formed deprecated attributes.
+ %% Here deprecated functions that are neither BIFs nor exported
+ %% are deemed bad. do_set_up filters away BIFs if necessary.
+ {DF_10,DF_20,DF_30,DF0} = Depr,
+ FT = [tspec(func)],
+ DF1 = xref_utils:xset(DF0, FT),
+ DF_11 = xref_utils:xset(DF_10, FT),
+ DF_21 = xref_utils:xset(DF_20, FT),
+ DF_31 = xref_utils:xset(DF_30, FT),
+
+ All = union(from_sets([DF1,DF_11,DF_21,DF_31])),
+ Fun = {external, fun({M,F,A}) -> xref_utils:is_builtin(M, F, A) end},
+ XB = union(X, specification(Fun, All)),
+ DF_1 = intersection(DF_11, XB),
+ DF_2 = union(intersection(DF_21, XB), DF_1),
+ DF_3 = union(intersection(DF_31, XB), DF_2),
+ DF = union(intersection(DF1, XB), DF_3),
+
+ Bad1 = difference(All, XB),
+ Bad2 = to_external(difference(Bad1, predefined_funs(Bad1))),
+ Bad = map(fun(B) -> {depr_attr, B} end, usort(Bad2++Bad0)),
+ {DF,DF_1,DF_2,DF_3,Bad}.
+
+%% Extra edges gathered from the attribute 'xref' (experimental)
+extra_edges(CAX, CAL, Bad0, F) ->
+ AXC0 = domain(CAX),
+ ALC0 = domain(CAL),
+ AXC = restriction(AXC0, F),
+ ALC = restriction(2, restriction(ALC0, F), F),
+ LPreCAt2 = restriction(CAL, ALC),
+ XPreCAt2 = restriction(CAX, AXC),
+ Bad = Bad0 ++ to_external(difference(AXC0, AXC))
+ ++ to_external(difference(ALC0, ALC)),
+ {AXC, ALC, Bad, LPreCAt2, XPreCAt2}.
+
+no_info(X, L, LC, XC, EE, Unres, NoCalls, NoUnresCalls) ->
+ NoUnres = no_elements(Unres),
+ [{no_calls, {NoCalls-NoUnresCalls, NoUnresCalls}},
+ {no_function_calls, {no_elements(LC), no_elements(XC)-NoUnres, NoUnres}},
+ {no_functions, {no_elements(L), no_elements(X)}},
+ %% Note: this is overwritten in do_set_up():
+ {no_inter_function_calls, no_elements(EE)}].
+
+inter_graph(Pid, X, L, LC, XC, CallAt) ->
+ Pid ! {self(), inter_graph(X, L, LC, XC, CallAt)}.
+
+%% Inter Call Graph.
+%inter_graph(_X, _L, _LC, _XC, _CallAt) ->
+% {empty_set(), empty_set()};
+inter_graph(X, L, LC, XC, CallAt) ->
+ G = xref_utils:relation_to_graph(LC),
+
+ Reachable0 = digraph_utils:reachable_neighbours(to_external(X), G),
+ Reachable = xref_utils:xset(Reachable0, [tspec(func)]),
+ % XL includes exports and locals that are not used by any exports
+ % (the locals are tacitly ignored in the comments below).
+ XL = union(difference(L, Reachable), X),
+
+ % Immediate local calls between the module's own exports are qualified.
+ LEs = restriction(restriction(2, LC, XL), XL),
+ % External calls to the module's exports are qualified.
+ XEs = restriction(XC, XL),
+ Es = union(LEs, XEs),
+
+ E1 = to_external(restriction(difference(LC, LEs), XL)),
+ R0 = xref_utils:xset(reachable(E1, G, []),
+ [{tspec(func), tspec(fun_edge)}]),
+ true = digraph:delete(G),
+
+ % RL is a set of indirect local calls to exports.
+ RL = restriction(R0, XL),
+ % RX is a set of indirect external calls to exports.
+ RX = relative_product1(R0, XC),
+ R = union(RL, converse(RX)),
+
+ EE0 = projection({external, fun({Ee2,{Ee1,_L}}) -> {Ee1,Ee2} end}, R),
+ EE = union(Es, EE0),
+
+ % The first call in each chain, {e1,l}, contributes with the line
+ % number(s) l.
+ SFun = {external, fun({Ee2,{Ee1,Ls}}) -> {{Ee1,Ls},{Ee1,Ee2}} end},
+ ECallAt1 = relative_product1(projection(SFun, R), CallAt),
+ ECallAt2 = union(ECallAt1, restriction(CallAt, Es)),
+ ECallAt = family_union(relation_to_family(ECallAt2)),
+
+ ?FORMAT("XL=~p~nXEs=~p~nLEs=~p~nE1=~p~nR0=~p~nRL=~p~nRX=~p~nR=~p~n"
+ "EE=~p~nECallAt1=~p~nECallAt2=~p~nECallAt=~p~n~n",
+ [XL, XEs, LEs, E1, R0, RL, RX, R, EE,
+ ECallAt1, ECallAt2, ECallAt]),
+ {EE, ECallAt}.
+
+%% -> set of {V2,{V1,L1}}
+reachable([E = {_X, L} | Xs], G, R) ->
+ Ns = digraph_utils:reachable([L], G),
+ reachable(Xs, G, reach(Ns, E, R));
+reachable([], _G, R) ->
+ R.
+
+reach([N | Ns], E, L) ->
+ reach(Ns, E, [{N, E} | L]);
+reach([], _E, L) ->
+ L.
+
+tspec(func) -> {atom, atom, atom};
+tspec(fun_edge) -> {tspec(func), tspec(func)};
+tspec(def_at) -> {tspec(func), atom};
+tspec(pre_call_at) -> {tspec(fun_edge), atom}.
+
+%% -> {ok, OldXrefRel, NewState} | throw(Error)
+do_remove_release(S, RelName) ->
+ case dict:find(RelName, S#xref.releases) of
+ error ->
+ throw_error({no_such_release, RelName});
+ {ok, XRel} ->
+ S1 = take_down(S),
+ S2 = remove_rel(S1, RelName),
+ {ok, XRel, S2}
+ end.
+
+%% -> {ok, OldXrefApp, NewState} | throw(Error)
+do_remove_application(S, AppName) ->
+ case dict:find(AppName, S#xref.applications) of
+ error ->
+ throw_error({no_such_application, AppName});
+ {ok, XApp} ->
+ S1 = take_down(S),
+ S2 = remove_apps(S1, [AppName]),
+ {ok, XApp, S2}
+ end.
+
+%% -> {ok, OldXMod, NewState} | throw(Error)
+do_remove_module(S, Module) ->
+ case dict:find(Module, S#xref.modules) of
+ error ->
+ throw_error({no_such_module, Module});
+ {ok, XMod} ->
+ S1 = take_down(S),
+ {ok, XMod, remove_modules(S1, [Module])}
+ end.
+
+remove_rel(S, RelName) ->
+ Rels = [RelName],
+ Fun = fun({A,XApp}, L) when XApp#xref_app.rel_name =:= Rels ->
+ [A | L];
+ (_, L) -> L
+ end,
+ Apps = foldl(Fun, [], dict:to_list(S#xref.applications)),
+ S1 = remove_apps(S, Apps),
+ NewReleases = remove_erase(Rels, S1#xref.releases),
+ S1#xref{releases = NewReleases}.
+
+remove_apps(S, Apps) ->
+ Fun = fun({M,XMod}, L) ->
+ case XMod#xref_mod.app_name of
+ [] -> L;
+ [AppName] -> [{AppName,M} | L]
+ end
+ end,
+ Ms = foldl(Fun, [], dict:to_list(S#xref.modules)),
+ Modules = to_external(image(relation(Ms), set(Apps))),
+ S1 = remove_modules(S, Modules),
+ NewApplications = remove_erase(Apps, S1#xref.applications),
+ S1#xref{applications = NewApplications}.
+
+remove_modules(S, Modules) ->
+ NewModules = remove_erase(Modules, S#xref.modules),
+ S#xref{modules = NewModules}.
+
+remove_erase([K | Ks], D) ->
+ remove_erase(Ks, dict:erase(K, D));
+remove_erase([], D) ->
+ D.
+
+do_add_libraries(Path, Verbose, State) ->
+ message(Verbose, lib_search, []),
+ {C, E} = xref_utils:list_path(Path, [?Suffix]),
+ message(Verbose, done, []),
+ MDs = to_external(relation_to_family(relation(C))),
+ %% message(Verbose, lib_check, []),
+ Reply = check_file(MDs, [], E, Path, State),
+ %% message(Verbose, done, []),
+ Reply.
+
+%%check_file([{_M, [{_N, Dir, File} | _]} | MDs], L, E, Path, State) ->
+%% case beam_lib:version(filename:join(Dir, File)) of
+%% {ok, {Module, _Version}} ->
+%% XLib = #xref_lib{name = Module, dir = Dir},
+%% check_file(MDs, [{Module,XLib} | L], E, Path, State);
+%% Error ->
+%% check_file(MDs, L, [Error | E], Path, State)
+%% end;
+check_file([{Module, [{_N, Dir, _File} | _]} | MDs], L, E, Path, State) ->
+ XLib = #xref_lib{name = Module, dir = Dir},
+ check_file(MDs, [{Module,XLib} | L], E, Path, State);
+check_file([], L, [], Path, State) ->
+ D = dict:from_list(L),
+ State1 = State#xref{library_path = Path, libraries = D},
+ %% Take down everything, that's simplest.
+ NewState = take_down(State1),
+ {ok, NewState};
+check_file([], _L, [E | _], _Path, _State) ->
+ E.
+
+%% -> {ok, NewState} | Error
+%% Finding libraries may fail.
+do_set_up(S, _VerboseOpt) when S#xref.variables =/= not_set_up ->
+ {ok, S};
+do_set_up(S, VerboseOpt) ->
+ message(VerboseOpt, set_up, []),
+ Reply = (catch do_set_up(S)),
+ message(VerboseOpt, done, []),
+ Reply.
+
+%% If data has been supplied using add_module/9 (and that is the only
+%% sanctioned way), then DefAt, L, X, LCallAt, XCallAt, CallAt, XC, LC,
+%% and LU are guaranteed to be functions (with all supplied
+%% modules as domain (disregarding unknown modules, that is, modules
+%% not supplied but hosting unknown functions)).
+%% As a consequence, V and E are also functions. V is defined for unknown
+%% modules also.
+%% UU is also a function (thanks to sofs:family_difference/2...).
+%% XU on the other hand can be a partial function (that is, not defined
+%% for all modules). U is derived from XU, so U is also partial.
+%% The inverse variables - LC_1, XC_1, E_1 and EE_1 - are all partial.
+%% B is also partial.
+do_set_up(S) when S#xref.mode =:= functions ->
+ ModDictList = dict:to_list(S#xref.modules),
+ [DefAt0, L, X0, LCallAt, XCallAt, CallAt, LC, XC, LU,
+ EE0, ECallAt, UC, LPredefined,
+ Mod_DF,Mod_DF_1,Mod_DF_2,Mod_DF_3] = make_families(ModDictList, 18),
+
+ {XC_1, XU, XPredefined} = do_set_up_1(XC),
+ LC_1 = user_family(union_of_family(LC)),
+ E_1 = family_union(XC_1, LC_1),
+ Predefined = family_union(XPredefined, LPredefined),
+
+ %% Add "hidden" functions to the exports.
+ X1 = family_union(X0, Predefined),
+
+ F1 = family_union(L, X1),
+ V = family_union(F1, XU),
+ E = family_union(LC, XC),
+
+ M = domain(V),
+ M2A = make_M2A(ModDictList),
+ {A2R,A} = make_A2R(S#xref.applications),
+ R = set(dict:fetch_keys(S#xref.releases)),
+
+ %% Converting from edges of functions to edges of modules.
+ VEs = union_of_family(E),
+ Fun = {external, fun({{M1,_F1,_A1},{M2,_F2,_A2}}) -> {M1,M2} end},
+ ME = projection(Fun, VEs),
+ ME2AE = multiple_relative_product({M2A, M2A}, ME),
+
+ AE = range(ME2AE),
+ AE2RE = multiple_relative_product({A2R, A2R}, AE),
+ RE = range(AE2RE),
+
+ AM = domain(F1),
+ %% Undef is the union of U0 and Lib:
+ {Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} =
+ make_libs(XU, F1, AM, S#xref.library_path, S#xref.libraries),
+ {B, U} = make_builtins(U0),
+ X1_B = family_union(X1, B),
+ F = family_union(F1, Lib),
+ DF = family_union(family_intersection(Mod_DF, X1_B), Lib_DF),
+ DF_1 = family_union(family_intersection(Mod_DF_1, X1_B), Lib_DF_1),
+ DF_2 = family_union(family_intersection(Mod_DF_2, X1_B), Lib_DF_2),
+ DF_3 = family_union(family_intersection(Mod_DF_3, X1_B), Lib_DF_3),
+ % If we have 'used' too, then there will be a set LU U XU...
+ UU = family_difference(family_difference(F1, LU), XU),
+ DefAt = make_defat(Undef, DefAt0),
+
+ LM = domain(Lib),
+ UM = difference(difference(domain(U), AM), LM),
+ X = family_union(X1, Lib),
+
+ %% Inter Call Graph. Calls to exported functions (library
+ %% functions inclusive) as well as calls within modules. This is a
+ %% way to discard calls to local functions in other modules.
+ EE_conv = converse(union_of_family(EE0)),
+ EE_exported = restriction(EE_conv, union_of_family(X)),
+ EE_local =
+ specification({external, fun({{M1,_,_},{M2,_,_}}) -> M1 =:= M2 end},
+ EE_conv),
+ EE_0 = converse(union(EE_local, EE_exported)),
+ EE_1 = user_family(EE_0),
+ EE1 = partition_family({external, fun({{M1,_,_}, _MFA2}) -> M1 end},
+ EE_0),
+ %% Make sure EE is defined for all modules:
+ EE = family_union(family_difference(EE0, EE0), EE1),
+ IFun =
+ fun({Mod,EE_M}, XMods) ->
+ IMFun =
+ fun(XrefMod) ->
+ [NoCalls, NoFunctionCalls,
+ NoFunctions, _NoInter] = XrefMod#xref_mod.info,
+ NewInfo = [NoCalls, NoFunctionCalls, NoFunctions,
+ {no_inter_function_calls,length(EE_M)}],
+ XrefMod#xref_mod{info = NewInfo}
+ end,
+ dict:update(Mod, IMFun,XMods)
+ end,
+ XrefMods1 = foldl(IFun, S#xref.modules, to_external(EE)),
+ S1 = S#xref{modules = XrefMods1},
+
+ UC_1 = user_family(union_of_family(UC)),
+
+ ?FORMAT("DefAt ~p~n", [DefAt]),
+ ?FORMAT("U=~p~nLib=~p~nB=~p~nLU=~p~nXU=~p~nUU=~p~n", [U,Lib,B,LU,XU,UU]),
+ ?FORMAT("E_1=~p~nLC_1=~p~nXC_1=~p~n", [E_1,LC_1,XC_1]),
+ ?FORMAT("EE=~p~nEE_1=~p~nECallAt=~p~n", [EE, EE_1, ECallAt]),
+ ?FORMAT("DF=~p~nDF_1=~p~nDF_2=~p~nDF_3=~p~n", [DF, DF_1, DF_2, DF_3]),
+
+ Vs = [{'L',L}, {'X',X},{'F',F},{'U',U},{'B',B},{'UU',UU},
+ {'XU',XU},{'LU',LU},{'V',V},{v,V},
+ {'LC',{LC,LC_1}},{'XC',{XC,XC_1}},{'E',{E,E_1}},{e,{E,E_1}},
+ {'EE',{EE,EE_1}},{'UC',{UC,UC_1}},
+ {'M',M},{'A',A},{'R',R},
+ {'AM',AM},{'UM',UM},{'LM',LM},
+ {'ME',ME},{'AE',AE},{'RE',RE},
+ {'DF',DF},{'DF_1',DF_1},{'DF_2',DF_2},{'DF_3',DF_3},
+ {me2ae, ME2AE},{ae, AE2RE},{m2a, M2A},{a2r, A2R},
+ {def_at, DefAt}, {call_at, CallAt}, {e_call_at, ECallAt},
+ {l_call_at, LCallAt}, {x_call_at, XCallAt}],
+ finish_set_up(S1, Vs);
+do_set_up(S) when S#xref.mode =:= modules ->
+ ModDictList = dict:to_list(S#xref.modules),
+ [X0, I0, Mod_DF, Mod_DF_1, Mod_DF_2, Mod_DF_3] =
+ make_families(ModDictList, 7),
+ I = union_of_family(I0),
+ AM = domain(X0),
+
+ {XU, Predefined} = make_predefined(I, AM),
+ %% Add "hidden" functions to the exports.
+ X1 = family_union(X0, Predefined),
+ V = family_union(X1, XU),
+
+ M = union(AM, domain(XU)),
+ M2A = make_M2A(ModDictList),
+ {A2R,A} = make_A2R(S#xref.applications),
+ R = set(dict:fetch_keys(S#xref.releases)),
+
+ ME = projection({external, fun({M1,{M2,_F2,_A2}}) -> {M1,M2} end},
+ family_to_relation(I0)),
+ ME2AE = multiple_relative_product({M2A, M2A}, ME),
+
+ AE = range(ME2AE),
+ AE2RE = multiple_relative_product({A2R, A2R}, AE),
+ RE = range(AE2RE),
+
+ %% Undef is the union of U0 and Lib:
+ {_Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} =
+ make_libs(XU, X1, AM, S#xref.library_path, S#xref.libraries),
+ {B, U} = make_builtins(U0),
+ X1_B = family_union(X1, B),
+ DF = family_union(family_intersection(Mod_DF, X1_B), Lib_DF),
+ DF_1 = family_union(family_intersection(Mod_DF_1, X1_B), Lib_DF_1),
+ DF_2 = family_union(family_intersection(Mod_DF_2, X1_B), Lib_DF_2),
+ DF_3 = family_union(family_intersection(Mod_DF_3, X1_B), Lib_DF_3),
+
+ LM = domain(Lib),
+ UM = difference(difference(domain(U), AM), LM),
+ X = family_union(X1, Lib),
+
+ Empty = empty_set(),
+ Vs = [{'X',X},{'U',U},{'B',B},{'XU',XU},{v,V},
+ {e,{Empty,Empty}},
+ {'M',M},{'A',A},{'R',R},
+ {'AM',AM},{'UM',UM},{'LM',LM},
+ {'ME',ME},{'AE',AE},{'RE',RE},
+ {'DF',DF},{'DF_1',DF_1},{'DF_2',DF_2},{'DF_3',DF_3},
+ {me2ae, ME2AE},{ae, AE2RE},{m2a, M2A},{a2r, A2R},
+ {def_at, Empty}, {call_at, Empty}, {e_call_at, Empty},
+ {l_call_at, Empty}, {x_call_at, Empty}],
+ finish_set_up(S, Vs).
+
+finish_set_up(S, Vs) ->
+ T = do_finish_set_up(Vs, dict:new()),
+ S1 = S#xref{variables = T},
+ %% io:format("~p <= state <= ~p~n", [pack:lsize(S), pack:usize(S)]),
+ {ok, S1}.
+
+do_finish_set_up([{Key, Value} | Vs], T) ->
+ {Type, OType} = var_type(Key),
+ Val = #xref_var{name = Key, value = Value, vtype = predef,
+ otype = OType, type = Type},
+ T1 = dict:store(Key, Val, T),
+ do_finish_set_up(Vs, T1);
+do_finish_set_up([], T) ->
+ T.
+
+var_type('B') -> {function, vertex};
+var_type('F') -> {function, vertex};
+var_type('L') -> {function, vertex};
+var_type('LU') -> {function, vertex};
+var_type('U') -> {function, vertex};
+var_type('UU') -> {function, vertex};
+var_type('V') -> {function, vertex};
+var_type('X') -> {function, vertex};
+var_type('XU') -> {function, vertex};
+var_type('DF') -> {function, vertex};
+var_type('DF_1') -> {function, vertex};
+var_type('DF_2') -> {function, vertex};
+var_type('DF_3') -> {function, vertex};
+var_type('A') -> {application, vertex};
+var_type('AM') -> {module, vertex};
+var_type('LM') -> {module, vertex};
+var_type('M') -> {module, vertex};
+var_type('UM') -> {module, vertex};
+var_type('R') -> {release, vertex};
+var_type('E') -> {function, edge};
+var_type('EE') -> {function, edge};
+var_type('LC') -> {function, edge};
+var_type('UC') -> {function, edge};
+var_type('XC') -> {function, edge};
+var_type('AE') -> {application, edge};
+var_type('ME') -> {module, edge};
+var_type('RE') -> {release, edge};
+var_type(_) -> {foo, bar}.
+
+make_families(ModDictList, N) ->
+ Fun1 = fun({_,XMod}) -> XMod#xref_mod.data end,
+ Ss = from_sets(map(Fun1, ModDictList)),
+ %% io:format("~n~p <= module data <= ~p~n",
+ %% [pack:lsize(Ss), pack:usize(Ss)]),
+ make_fams(N, Ss, []).
+
+make_fams(1, _Ss, L) ->
+ L;
+make_fams(I, Ss, L) ->
+ Fun = {external, fun(R) -> {element(1, R), element(I, R)} end},
+ make_fams(I-1, Ss, [projection(Fun, Ss) | L]).
+
+make_M2A(ModDictList) ->
+ Fun = fun({M,XMod}) -> {M, XMod#xref_mod.app_name} end,
+ Mod0 = family(map(Fun, ModDictList)),
+ Mod = family_to_relation(Mod0),
+ Mod.
+
+make_A2R(ApplDict) ->
+ AppDict = dict:to_list(ApplDict),
+ Fun = fun({A,XApp}) -> {A, XApp#xref_app.rel_name} end,
+ Appl0 = family(map(Fun, AppDict)),
+ AllApps = domain(Appl0),
+ Appl = family_to_relation(Appl0),
+ {Appl, AllApps}.
+
+do_set_up_1(XC) ->
+ %% Call Graph cross reference...
+ XCp = union_of_family(XC),
+ XC_1 = user_family(XCp),
+
+ %% I - functions used externally from some module
+ %% XU - functions used externally per module.
+ I = range(XCp),
+
+ {XU, XPredefined} = make_predefined(I, domain(XC)),
+ {XC_1, XU, XPredefined}.
+
+make_predefined(I, CallingModules) ->
+ XPredefined0 = predefined_funs(I),
+ XPredefined1 = converse(substitution(1, XPredefined0)),
+ %% predefined funs in undefined modules are still undefined...
+ XPredefined2 = restriction(XPredefined1, CallingModules),
+ XPredefined = relation_to_family(XPredefined2),
+ XU = partition_family(1, I),
+ {XU, XPredefined}.
+
+predefined_funs(Functions) ->
+ specification({external, predef_fun()}, Functions).
+
+predef_fun() ->
+ PredefinedFuns = xref_utils:predefined_functions(),
+ fun({_M,F,A}) -> member({F,A}, PredefinedFuns) end.
+
+make_defat(Undef, DefAt0) ->
+ % Complete DefAt with unknown functions:
+ Zero = from_term(0),
+ DAL = family_projection(fun(S) -> constant_function(S, Zero) end, Undef),
+ family_union(DefAt0, DAL).
+
+%% -> {Unknown U Lib, Unknown, Lib} | throw(Error)
+make_libs(XU, F, AM, LibPath, LibDict) ->
+ Undef = family_difference(XU, F),
+ UM = difference(domain(family_to_relation(Undef)), AM),
+ Fs = case is_empty_set(UM) of
+ true ->
+ [];
+ false when LibPath =:= code_path ->
+ BFun = fun(M, A) -> case xref_utils:find_beam(M) of
+ {ok, File} -> [File | A];
+ _ -> A
+ end
+ end,
+ foldl(BFun, [], to_external(UM));
+ false ->
+ Libraries = dict:to_list(LibDict),
+ Lb = restriction(a_function(Libraries), UM),
+ MFun = fun({M,XLib}) ->
+ #xref_lib{dir = Dir} = XLib,
+ xref_utils:module_filename(Dir, M)
+ end,
+ map(MFun, to_external(Lb))
+ end,
+ Fun = fun(FileName, Deprs) ->
+ case beam_lib:chunks(FileName, [exports, attributes]) of
+ {ok, {M, [{exports,X}, {attributes,A}]}} ->
+ Exports = mfa_exports(X, A, M),
+ %% No warnings for bad attributes...
+ {Deprecated,_Bad} = deprecated(A, Exports, M),
+ {{M,Exports}, [{M,Deprecated} | Deprs]};
+ Error ->
+ throw(Error)
+ end
+ end,
+ {XL, DL} = mapfoldl(Fun, [], Fs),
+ LF = from_term(XL),
+ %% Undef is the first argument to make sure that the whole of LF
+ %% becomes garbage:
+ Lib = family_intersection(Undef, LF),
+ {B,_} = make_builtins(Undef),
+ DLib = family_union(Lib, B),
+ [DF_1,DF_21,DF_31,DF1] = depr_lib(4, DL, DL, [], [], DLib),
+ DF_2 = family_union(DF_21, DF_1),
+ DF_3 = family_union(DF_31, DF_2),
+ DF = family_union(DF1, DF_3),
+ U = family_difference(Undef, Lib),
+ {Undef, U, Lib, DF, DF_1, DF_2, DF_3}.
+
+depr_lib(0, _, _, LL, [], _Lib) ->
+ LL;
+depr_lib(I, [], DL, LL, L, Lib) ->
+ DT = family_intersection(Lib, from_term(L)),
+ depr_lib(I-1, DL, DL, [DT | LL], [], Lib);
+depr_lib(I, [{M,D} | Ds], DL, LL, L, Lib) ->
+ depr_lib(I, Ds, DL, LL, [{M,element(I, D)} | L], Lib).
+
+make_builtins(U0) ->
+ Tmp = family_to_relation(U0),
+ Fun2 = {external, fun({_M,{M,F,A}}) -> xref_utils:is_builtin(M, F, A) end},
+ B = relation_to_family(specification(Fun2, Tmp)),
+ U = family_difference(U0, B),
+ {B, U}.
+
+% Returns a family that may not be defined for all modules.
+user_family(R) ->
+ partition_family({external, fun({_MFA1, {M2,_,_}}) -> M2 end}, R).
+
+do_variables(State) ->
+ Fun = fun({Name, #xref_var{vtype = user}}, {P,U}) ->
+ {P,[Name | U]};
+ ({Name, #xref_var{vtype = predef}}, A={P,U}) ->
+ case atom_to_list(Name) of
+ [H|_] when H>= $a, H=<$z -> A;
+ _Else -> {[Name | P], U}
+ end;
+ ({{tmp, V}, _}, A) ->
+ io:format("Bug in ~p: temporary ~p~n", [?MODULE, V]), A;
+ (_V, A) -> A
+ end,
+ {U,P} = foldl(Fun, {[],[]}, dict:to_list(State#xref.variables)),
+ {sort(P), sort(U)}.
+
+%% Throws away the variables derived from raw data.
+take_down(S) when S#xref.variables =:= not_set_up ->
+ S;
+take_down(S) ->
+ S#xref{variables = not_set_up}.
+
+make_query(Format, Args) ->
+ flatten(io_lib:format(Format, Args)).
+
+set_defaults([O | Os], [[V] | Vs], State) ->
+ NewState = set_def(O, V, State),
+ set_defaults(Os, Vs, NewState);
+set_defaults([], [], State) ->
+ State.
+
+set_def(builtins, Value, State) ->
+ State#xref{builtins_default = Value};
+set_def(recurse, Value, State) ->
+ State#xref{recurse_default = Value};
+set_def(verbose, Value, State) ->
+ State#xref{verbose_default = Value};
+set_def(warnings, Value, State) ->
+ State#xref{warnings_default = Value}.
+
+option_values([Option | Options], State) ->
+ Default = current_default(State, Option),
+ [{Option, [Default,true,false]} | option_values(Options, State)];
+option_values([], _State) ->
+ [].
+
+current_default(State, builtins) ->
+ State#xref.builtins_default;
+current_default(State, recurse) ->
+ State#xref.recurse_default;
+current_default(State, verbose) ->
+ State#xref.verbose_default;
+current_default(State, warnings) ->
+ State#xref.warnings_default.
+
+%% sets are used here to avoid long execution times
+do_info(S, modules) ->
+ D = sort(dict:to_list(S#xref.modules)),
+ map(fun({_M,XMod}) -> mod_info(XMod) end, D);
+do_info(S, applications) ->
+ AppMods = to_external(relation_to_family(relation(app_mods(S)))),
+ Sum = sum_mods(S, AppMods),
+ map(fun(AppSum) -> app_info(AppSum, S) end, Sum);
+do_info(S, releases) ->
+ {RA, RRA} = rel_apps(S),
+ rel_apps_sums(RA, RRA, S);
+do_info(S, libraries) ->
+ D = sort(dict:to_list(S#xref.libraries)),
+ map(fun({_L,XLib}) -> lib_info(XLib) end, D);
+do_info(_S, I) ->
+ error({no_such_info, I}).
+
+do_info(S, Type, E) when is_atom(E) ->
+ do_info(S, Type, [E]);
+do_info(S, modules, Modules0) when is_list(Modules0) ->
+ Modules = to_external(set(Modules0)),
+ XMods = find_info(Modules, S#xref.modules, no_such_module),
+ map(fun(XMod) -> mod_info(XMod) end, XMods);
+do_info(S, applications, Applications) when is_list(Applications) ->
+ _XA = find_info(Applications, S#xref.applications, no_such_application),
+ AM = relation(app_mods(S)),
+ App = set(Applications),
+ AppMods_S = relation_to_family(restriction(AM, App)),
+ AppSums = sum_mods(S, to_external(AppMods_S)),
+ map(fun(AppSum) -> app_info(AppSum, S) end, AppSums);
+do_info(S, releases, Releases) when is_list(Releases) ->
+ _XR = find_info(Releases, S#xref.releases, no_such_release),
+ {AR, RRA} = rel_apps(S),
+ AR_S = restriction(2, relation(AR), set(Releases)),
+ rel_apps_sums(to_external(AR_S), RRA, S);
+do_info(S, libraries, Libraries0) when is_list(Libraries0) ->
+ Libraries = to_external(set(Libraries0)),
+ XLibs = find_info(Libraries, S#xref.libraries, no_such_library),
+ map(fun(XLib) -> lib_info(XLib) end, XLibs);
+do_info(_S, I, J) when is_list(J) ->
+ throw_error({no_such_info, I}).
+
+find_info([E | Es], Dict, Error) ->
+ case dict:find(E, Dict) of
+ error ->
+ throw_error({Error, E});
+ {ok, X} ->
+ [X | find_info(Es, Dict, Error)]
+ end;
+find_info([], _Dict, _Error) ->
+ [].
+
+%% -> {[{AppName, RelName}], [{RelName, XApp}]}
+rel_apps(S) ->
+ D = sort(dict:to_list(S#xref.applications)),
+ Fun = fun({_A, XApp}, Acc={AR, RRA}) ->
+ case XApp#xref_app.rel_name of
+ [] -> Acc;
+ [R] ->
+ AppName = XApp#xref_app.name,
+ {[{AppName, R} | AR], [{R, XApp} | RRA]}
+ end
+ end,
+ foldl(Fun, {[], []}, D).
+
+%% -> [{{RelName, [XApp]}, Sums}]
+rel_apps_sums(AR, RRA0, S) ->
+ AppMods = app_mods(S), % [{AppName, XMod}]
+ RRA1 = relation_to_family(relation(RRA0)),
+ RRA = inverse(substitution(1, RRA1)),
+ %% RRA is [{RelName,{RelName,[XApp]}}]
+ RelMods = relative_product1(relation(AR), relation(AppMods)),
+ RelAppsMods = relative_product1(RRA, RelMods),
+ RelsAppsMods = to_external(relation_to_family(RelAppsMods)),
+ %% [{{RelName, [XApp]}, [XMod]}]
+ Sum = sum_mods(S, RelsAppsMods),
+ map(fun(RelAppsSums) -> rel_info(RelAppsSums, S) end, Sum).
+
+%% -> [{AppName, XMod}]
+app_mods(S) ->
+ D = sort(dict:to_list(S#xref.modules)),
+ Fun = fun({_M,XMod}, Acc) ->
+ case XMod#xref_mod.app_name of
+ [] -> Acc;
+ [AppName] -> [{AppName, XMod} | Acc]
+ end
+ end,
+ foldl(Fun, [], D).
+
+mod_info(XMod) ->
+ #xref_mod{name = M, app_name = AppName, builtins = BuiltIns,
+ dir = Dir, info = Info} = XMod,
+ App = sup_info(AppName),
+ {M, [{application, App}, {builtins, BuiltIns}, {directory, Dir} | Info]}.
+
+app_info({AppName, ModSums}, S) ->
+ XApp = dict:fetch(AppName, S#xref.applications),
+ #xref_app{rel_name = RelName, vsn = Vsn, dir = Dir} = XApp,
+ Release = sup_info(RelName),
+ {AppName, [{directory,Dir}, {release, Release}, {version,Vsn} | ModSums]}.
+
+rel_info({{RelName, XApps}, ModSums}, S) ->
+ NoApps = length(XApps),
+ XRel = dict:fetch(RelName, S#xref.releases),
+ Dir = XRel#xref_rel.dir,
+ {RelName, [{directory, Dir}, {no_applications, NoApps} | ModSums]}.
+
+lib_info(XLib) ->
+ #xref_lib{name = LibName, dir = Dir} = XLib,
+ {LibName, [{directory,Dir}]}.
+
+sup_info([]) -> [];
+sup_info([Name]) ->
+ [Name].
+
+sum_mods(S, AppsMods) ->
+ sum_mods(S, AppsMods, []).
+
+sum_mods(S, [{N, XMods} | NX], L) ->
+ sum_mods(S, NX, [{N, no_sum(S, XMods)} | L]);
+sum_mods(_S, [], L) ->
+ reverse(L).
+
+no_sum(S, L) when S#xref.mode =:= functions ->
+ no_sum(L, 0, 0, 0, 0, 0, 0, 0, 0, length(L));
+no_sum(S, L) when S#xref.mode =:= modules ->
+ [{no_analyzed_modules, length(L)}].
+
+no_sum([XMod | D], C0, UC0, LC0, XC0, UFC0, L0, X0, EV0, NoM) ->
+ [{no_calls, {C,UC}},
+ {no_function_calls, {LC,XC,UFC}},
+ {no_functions, {L,X}},
+ {no_inter_function_calls, EV}] = XMod#xref_mod.info,
+ no_sum(D, C0+C, UC0+UC, LC0+LC, XC0+XC, UFC0+UFC, L0+L, X0+X, EV0+EV, NoM);
+no_sum([], C, UC, LC, XC, UFC, L, X, EV, NoM) ->
+ [{no_analyzed_modules, NoM},
+ {no_calls, {C,UC}},
+ {no_function_calls, {LC,XC,UFC}},
+ {no_functions, {L,X}},
+ {no_inter_function_calls, EV}].
+
+%% -> ok | throw(Error)
+is_filename(F) when is_atom(F) ->
+ ok;
+is_filename(F) ->
+ case xref_utils:is_string(F, 31) of
+ true ->
+ ok;
+ false ->
+ throw_error({invalid_filename, F})
+ end.
+
+module_file(XMod) ->
+ xref_utils:module_filename(XMod#xref_mod.dir, XMod#xref_mod.name).
+
+warnings(_Flag, _Message, []) -> true;
+warnings(Flag, Message, [F | Fs]) ->
+ message(Flag, Message, F),
+ warnings(Flag, Message, Fs).
+
+%% pack(term()) -> term()
+%%
+%% The identify function. The returned term does not use more heap
+%% than the given term. Tuples that are equal (=:=/2) are made
+%% "the same".
+%%
+%% The process dictionary is used because it seems to be faster than
+%% anything else right now...
+%%
+%pack(T) -> T;
+pack(T) ->
+ PD = erase(),
+ NT = pack1(T),
+ %% true = T =:= NT,
+ %% io:format("erasing ~p elements...~n", [length(erase())]),
+ erase(), % wasting heap (and time)...
+ map(fun({K,V}) -> put(K, V) end, PD),
+ NT.
+
+pack1(C) when not is_tuple(C), not is_list(C) ->
+ C;
+pack1([T | Ts]) ->
+ %% don't store conscells...
+ [pack1(T) | pack1(Ts)];
+%% Optimization.
+pack1(T={Mod,Fun,_}) when is_atom(Mod), is_atom(Fun) -> % MFA
+ case get(T) of
+ undefined -> put(T, T), T;
+ NT -> NT
+ end;
+pack1({C, L}) when is_list(L) -> % CallAt
+ {pack1(C), L};
+pack1({MFA, L}) when is_integer(L) -> % DefAt
+ {pack1(MFA), L};
+%% End optimization.
+pack1([]) ->
+ [];
+pack1(T) -> % when is_tuple(T)
+ case get(T) of
+ undefined ->
+ NT = tpack(T, tuple_size(T), []),
+ put(NT, NT),
+ NT;
+ NT ->
+ NT
+ end.
+
+tpack(_T, 0, L) ->
+ list_to_tuple(L);
+tpack(T, I, L) ->
+ tpack(T, I-1, [pack1(element(I, T)) | L]).
+
+message(true, What, Arg) ->
+ case What of
+ reading_beam ->
+ io:format("~s... ", Arg);
+ skipped_beam ->
+ io:format("skipped (no debug information)~n", Arg);
+ no_debug_info ->
+ io:format("Skipping ~s (no debug information)~n", Arg);
+ unresolved_summary1 ->
+ io:format("~p: 1 unresolved call~n", Arg);
+ unresolved_summary ->
+ io:format("~p: ~p unresolved calls~n", Arg);
+ jam ->
+ io:format("Skipping ~s (probably JAM file)~n", [Arg]);
+ unreadable ->
+ io:format("Skipping ~s (unreadable)~n", [Arg]);
+ xref_attr ->
+ io:format("~s: Skipping 'xref' attribute ~w~n", Arg);
+ depr_attr ->
+ io:format("~s: Skipping 'deprecated' attribute ~w~n", Arg);
+ lib_search ->
+ io:format("Scanning library path for BEAM files... ", []);
+ lib_check ->
+ io:format("Checking library files... ", []);
+ set_up ->
+ io:format("Setting up...", Arg);
+ done ->
+ io:format("done~n", Arg);
+ error ->
+ io:format("error~n", Arg);
+ Else ->
+ io:format("~p~n", [{Else,Arg}])
+ end;
+message(_, _, _) ->
+ true.
+
+throw_error(Reason) ->
+ throw(error(Reason)).
+
+error(Reason) ->
+ {error, ?MODULE, Reason}.