diff options
Diffstat (limited to 'lib/tools/src/xref.erl')
-rw-r--r-- | lib/tools/src/xref.erl | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/lib/tools/src/xref.erl b/lib/tools/src/xref.erl new file mode 100644 index 0000000000..0693bec019 --- /dev/null +++ b/lib/tools/src/xref.erl @@ -0,0 +1,607 @@ +%% +%% %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). + +-behaviour(gen_server). + +%% External exports +-export([start/1, start/2, stop/1]). + +-export([m/1, d/1, + add_release/2, add_release/3, + add_application/2, add_application/3, + add_module/2, add_module/3, + add_directory/2, add_directory/3, + replace_module/3, replace_module/4, + replace_application/3, replace_application/4, + remove_module/2, remove_application/2, remove_release/2, + get_library_path/1, set_library_path/2, set_library_path/3, + 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, analyse/2, analyse/3, + get_default/1, get_default/2, + set_default/2, set_default/3]). + +-export([format_error/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-import(lists, [keydelete/3, keysearch/3]). + +-import(sofs, [to_external/1, is_sofs_set/1]). + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- + +%% add_release(Servername, Directory) -> +%% {ok, ReleaseName} | Error +%% add_release(Servername, Directory, Options) -> +%% {ok, ReleaseName} | Error +%% add_application(Servername, Directory) -> +%% {ok, AppName} | Error +%% add_application(Servername, Directory, Options) -> +%% {ok, AppName} | Error +%% add_module(ServerName, Filename) -> +%% {ok, ModuleName} | Error +%% add_module(ServerName, Filename, Options) -> +%% {ok, ModuleName} | Error +%% add_directory(ServerName, Directory) -> +%% {ok, [ModuleName]} | Error +%% add_directory(ServerName, Directory, Options) -> +%% {ok, [ModuleName]} | Error +%% replace_module(ServerName, Module, Filename) -> +%% {ok, Module} | Error +%% replace_module(ServerName, Module, Filename, Options) -> +%% {ok, Module} | Error +%% replace_application(ServerName, Application, Directory) -> +%% {ok, AppName} | Error +%% replace_application(ServerName, Application, Directory, Options) -> +%% {ok, AppName} | Error +%% remove_module(ServerName, Module) -> ok | Error +%% remove_application(ServerName, Application) -> ok | Error +%% remove_release(ServerName, Release) -> ok | Error +%% get_library_path(Servername) -> {ok, Path} +%% set_library_path(Servername, Path) -> ok | Error +%% set_library_path(Servername, Path, Options) -> ok | Error +%% info(Servername) -> InfoList +%% info(Servername, What) -> [{what(), InfoList}] | Error +%% info(Servername, What, Qual) -> [{what(), InfoList}] | Error +%% update(Servername) -> {ok, [Module]} | Error +%% update(Servername, Options) -> {ok, [Module]} | Error +%% forget(Servername) -> ok +%% forget(Servername, VariableName) -> ok | Error +%% variables(Servername) -> {ok, [{VarType, [VariableName]}]} | Error +%% variables(Servername, [VarType]) -> {ok, [{VarType, [VariableName]}]} +%% analyze(ServerName, What) -> {ok, Answer} | Error +%% analyze(ServerName, What, Options) -> {ok, Answer} | Error +%% q(Servername, Query) -> {ok, Answer} | Error +%% q(Servername, Query, Options) -> {ok, Answer} | Error +%% get_default(ServerName, Option) -> {ok, Value} | Error +%% set_default(ServerName, Option, Value) -> {ok, OldValue} | Error +%% get_default(ServerName) -> [{Option, Value}] +%% set_default(ServerName, [{Option, Value}]) -> ok | Error +%% format_error(Error) -> io_string() +%% m(Module) -> [Result] | Error +%% m(File) -> [Result] | Error +%% d(Directory) -> [Result] | Error + +%% -> [Faulty] | Error; Faulty = {undefined, Calls} | {unused, Funs} +%% No user variables have been assigned digraphs, so there is no +%% need to call xref_base:delete/1. +m(Module) when is_atom(Module) -> + case xref_utils:find_beam(Module) of + {ok, File} -> + Fun = fun(S) -> + xref_base:add_module(S, File, {builtins,true}) + end, + case catch do_functions_analysis(Fun) of + {error, _, {no_debug_info, _}} -> + catch do_modules_analysis(Fun); + Result -> + Result + end; + Error -> Error + end; +m(File) -> + case xref_utils:split_filename(File, ".beam") of + false -> + {error, xref_base, {invalid_filename, File}}; + {Dir, BaseName} -> + BeamFile = filename:join(Dir, BaseName), + Fun = fun(S) -> + xref_base:add_module(S, BeamFile, {builtins, true}) + end, + case catch do_functions_analysis(Fun) of + {error, _, {no_debug_info, _}} -> + catch do_modules_analysis(Fun); + Result -> + Result + end + end. + +%% -> [Faulty] | Error; Faulty = {undefined, Calls} | {unused, Funs} +d(Directory) -> + Fun = fun(S) -> + xref_base:add_directory(S, Directory, {builtins, true}) + end, + Fun1 = fun(S) -> + case Fun(S) of + {ok, [], _S} -> + no_modules; + Reply -> + Reply + end + end, + case catch do_functions_analysis(Fun1) of + no_modules -> + catch do_modules_analysis(Fun); + Result -> + Result + end. + +start(Name) when is_atom(Name) -> + start(Name, []); +start(Opts0) when is_list(Opts0) -> + {Args, Opts} = split_args(Opts0), + gen_server:start(xref, Args, Opts). + +start(Name, Opts0) when is_list(Opts0) -> + {Args, Opts} = split_args(Opts0), + gen_server:start({local, Name}, xref, Args, Opts); +start(Name, Opt) -> + start(Name, [Opt]). + +split_args(Opts) -> + case keysearch(xref_mode, 1, Opts) of + {value, Mode} -> + {[Mode], keydelete(xref_mode, 1, Opts)}; + false -> + {[], Opts} + end. + +stop(Name) -> + gen_server:call(Name, stop, infinity). + +add_release(Name, Dir) -> + gen_server:call(Name, {add_release, Dir}, infinity). + +add_release(Name, Dir, Options) -> + gen_server:call(Name, {add_release, Dir, Options}, infinity). + +add_application(Name, Dir) -> + gen_server:call(Name, {add_application, Dir}, infinity). + +add_application(Name, Dir, Options) -> + gen_server:call(Name, {add_application, Dir, Options}, infinity). + +add_module(Name, File) -> + gen_server:call(Name, {add_module, File}, infinity). + +add_module(Name, File, Options) -> + gen_server:call(Name, {add_module, File, Options}, infinity). + +add_directory(Name, Dir) -> + gen_server:call(Name, {add_directory, Dir}, infinity). + +add_directory(Name, Dir, Options) -> + gen_server:call(Name, {add_directory, Dir, Options}, infinity). + +replace_module(Name, Module, File) -> + gen_server:call(Name, {replace_module, Module, File}, infinity). + +replace_module(Name, Module, File, Options) -> + gen_server:call(Name, {replace_module, Module, File, Options}, infinity). + +replace_application(Name, App, Dir) -> + gen_server:call(Name, {replace_application, App, Dir}, infinity). + +replace_application(Name, App, Dir, Options) -> + gen_server:call(Name, {replace_application, App, Dir, Options}, infinity). + +remove_module(Name, Mod) -> + gen_server:call(Name, {remove_module, Mod}, infinity). + +remove_application(Name, App) -> + gen_server:call(Name, {remove_application, App}, infinity). + +remove_release(Name, Rel) -> + gen_server:call(Name, {remove_release, Rel}, infinity). + +get_library_path(Name) -> + gen_server:call(Name, get_library_path, infinity). + +set_library_path(Name, Path) -> + gen_server:call(Name, {set_library_path, Path}, infinity). + +set_library_path(Name, Path, Options) -> + gen_server:call(Name, {set_library_path, Path, Options}, infinity). + +info(Name) -> + gen_server:call(Name, info, infinity). + +info(Name, What) -> + gen_server:call(Name, {info, What}, infinity). + +info(Name, What, Qual) -> + gen_server:call(Name, {info, What, Qual}, infinity). + +update(Name) -> + gen_server:call(Name, update, infinity). + +update(Name, Options) -> + gen_server:call(Name, {update, Options}, infinity). + +forget(Name) -> + gen_server:call(Name, forget, infinity). + +forget(Name, Variable) -> + gen_server:call(Name, {forget, Variable}, infinity). + +variables(Name) -> + gen_server:call(Name, variables, infinity). + +variables(Name, Options) -> + gen_server:call(Name, {variables, Options}, infinity). + +analyse(Name, What) -> + gen_server:call(Name, {analyze, What}, infinity). + +analyse(Name, What, Options) -> + gen_server:call(Name, {analyze, What, Options}, infinity). + +analyze(Name, What) -> + gen_server:call(Name, {analyze, What}, infinity). + +analyze(Name, What, Options) -> + gen_server:call(Name, {analyze, What, Options}, infinity). + +q(Name, Q) -> + gen_server:call(Name, {qry, Q}, infinity). + +q(Name, Q, Options) -> + gen_server:call(Name, {qry, Q, Options}, infinity). + +get_default(Name) -> + gen_server:call(Name, get_default, infinity). + +get_default(Name, Option) -> + gen_server:call(Name, {get_default, Option}, infinity). + +set_default(Name, OptionValues) -> + gen_server:call(Name, {set_default, OptionValues}, infinity). + +set_default(Name, Option, Value) -> + gen_server:call(Name, {set_default, Option, Value}, infinity). + +format_error({error, Module, Error}) -> + Module:format_error(Error); +format_error(E) -> + io_lib:format("~p~n", [E]). + +%%%---------------------------------------------------------------------- +%%%Callback functions from gen_server +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%---------------------------------------------------------------------- +init(Args) -> + case xref_base:new(Args) of + {ok, S} -> + {ok, S}; + {error, _Module, Reason} -> + {stop, Reason} + end. + +%%---------------------------------------------------------------------- +%% Func: handle_call/3 +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_call(stop, _From, State) -> + {stop, normal, stopped, State}; +handle_call({add_release, Dir}, _From, State) -> + case xref_base:add_release(State, Dir) of + {ok, ReleaseName, NewState} -> + {reply, {ok, ReleaseName}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({add_release, Dir, Options}, _From, State) -> + case xref_base:add_release(State, Dir, Options) of + {ok, ReleaseName, NewState} -> + {reply, {ok, ReleaseName}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({add_application, Dir}, _From, State) -> + case xref_base:add_application(State, Dir) of + {ok, AppName, NewState} -> + {reply, {ok, AppName}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({add_application, Dir, Options}, _From, State) -> + case xref_base:add_application(State, Dir, Options) of + {ok, AppName, NewState} -> + {reply, {ok, AppName}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({add_module, File}, _From, State) -> + case xref_base:add_module(State, File) of + {ok, Module, NewState} -> + {reply, {ok, Module}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({add_module, File, Options}, _From, State) -> + case xref_base:add_module(State, File, Options) of + {ok, Module, NewState} -> + {reply, {ok, Module}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({replace_application, Appl, Dir}, _From, State) -> + case xref_base:replace_application(State, Appl, Dir) of + {ok, AppName, NewState} -> + {reply, {ok, AppName}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({replace_application, Appl, Dir, Opts}, _From, State) -> + case xref_base:replace_application(State, Appl, Dir, Opts) of + {ok, AppName, NewState} -> + {reply, {ok, AppName}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({remove_module, Mod}, _From, State) -> + case xref_base:remove_module(State, Mod) of + {ok, NewState} -> + {reply, ok, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({remove_application, Appl}, _From, State) -> + case xref_base:remove_application(State, Appl) of + {ok, NewState} -> + {reply, ok, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({remove_release, Rel}, _From, State) -> + case xref_base:remove_release(State, Rel) of + {ok, NewState} -> + {reply, ok, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({add_directory, Dir}, _From, State) -> + case xref_base:add_directory(State, Dir) of + {ok, Modules, NewState} -> + {reply, {ok, Modules}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({add_directory, Dir, Options}, _From, State) -> + case xref_base:add_directory(State, Dir, Options) of + {ok, Modules, NewState} -> + {reply, {ok, Modules}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call(get_library_path, _From, State) -> + Path = xref_base:get_library_path(State), + {reply, Path, State}; +handle_call({set_library_path, Path}, _From, State) -> + case xref_base:set_library_path(State, Path) of + {ok, NewState} -> + {reply, ok, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({set_library_path, Path, Options}, _From, State) -> + case xref_base:set_library_path(State, Path, Options) of + {ok, NewState} -> + {reply, ok, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({replace_module, Module, File}, _From, State) -> + case xref_base:replace_module(State, Module, File) of + {ok, Module, NewState} -> + {reply, {ok, Module}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({replace_module, Module, File, Options}, _From, State) -> + case xref_base:replace_module(State, Module, File, Options) of + {ok, Module, NewState} -> + {reply, {ok, Module}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call(info, _From, State) -> + {reply, xref_base:info(State), State}; +handle_call({info, What}, _From, State) -> + {reply, xref_base:info(State, What), State}; +handle_call({info, What, Qual}, _From, State) -> + {reply, xref_base:info(State, What, Qual), State}; +handle_call(update, _From, State) -> + case xref_base:update(State) of + {ok, NewState, Modules} -> + {reply, {ok, Modules}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({update, Options}, _From, State) -> + case xref_base:update(State, Options) of + {ok, NewState, Modules} -> + {reply, {ok, Modules}, NewState}; + Error -> + {reply, Error, State} + end; +handle_call(forget, _From, State) -> + {ok, NewState} = xref_base:forget(State), + {reply, ok, NewState}; +handle_call({forget, Variable}, _From, State) -> + case xref_base:forget(State, Variable) of + {ok, NewState} -> + {reply, ok, NewState}; + Error -> + {reply, Error, State} + end; +handle_call(variables, _From, State) -> + %% The reason the ok-Error pattern is broken for variables, q and + %% analyze is that the state may have been updated even if an + %% error occurs. + {Reply, NewState} = xref_base:variables(State), + {reply, Reply, NewState}; +handle_call({variables, Options}, _From, State) -> + {Reply, NewState} = xref_base:variables(State, Options), + {reply, Reply, NewState}; +handle_call({analyze, What}, _From, State) -> + {Reply, NewState} = xref_base:analyze(State, What), + {reply, unsetify(Reply), NewState}; +handle_call({analyze, What, Options}, _From, State) -> + {Reply, NewState} = xref_base:analyze(State, What, Options), + {reply, unsetify(Reply), NewState}; +handle_call({qry, Q}, _From, State) -> + {Reply, NewState} = xref_base:q(State, Q), + {reply, unsetify(Reply), NewState}; +handle_call({qry, Q, Options}, _From, State) -> + {Reply, NewState} = xref_base:q(State, Q, Options), + {reply, unsetify(Reply), NewState}; +handle_call(get_default, _From, State) -> + Reply = xref_base:get_default(State), + {reply, Reply, State}; +handle_call({get_default, Option}, _From, State) -> + Reply = xref_base:get_default(State, Option), + {reply, Reply, State}; +handle_call({set_default, OptionValues}, _From, State) -> + case xref_base:set_default(State, OptionValues) of + {ok, NewState} -> + {reply, ok, NewState}; + Error -> + {reply, Error, State} + end; +handle_call({set_default, Option, Value}, _From, State) -> + case xref_base:set_default(State, Option, Value) of + {ok, OldValue, NewState} -> + {reply, {ok, OldValue}, NewState}; + Error -> + {reply, Error, State} + end. + +%%---------------------------------------------------------------------- +%% Func: handle_cast/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_cast(_Msg, State) -> {noreply, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_info/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_info(_Info, State) -> + {noreply, State}. + +%%---------------------------------------------------------------------- +%% Func: terminate/2 +%% Purpose: Shutdown the server +%% Returns: any (ignored by gen_server) +%%---------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- + +do_functions_analysis(FFun) -> + {ok, State} = xref_base:new(), + {ok, State1} = xref_base:set_library_path(State, code_path), + {ok, State2} = xref_base:set_default(State1, + [{verbose,false},{warnings,false}]), + State3 = case FFun(State2) of + {ok, _, S} -> S; + Error2 -> throw(Error2) + end, + {Undef, State4} = do_analysis(State3, undefined_function_calls), + {Unused, State5} = do_analysis(State4, locals_not_used), + {Deprecated, _} = do_analysis(State5, deprecated_function_calls), + [{deprecated,to_external(Deprecated)}, + {undefined,to_external(Undef)}, + {unused,to_external(Unused)}]. + +do_modules_analysis(FFun) -> + {ok, State} = xref_base:new({xref_mode, modules}), + {ok, State1} = xref_base:set_library_path(State, code_path), + {ok, State2} = xref_base:set_default(State1, + [{verbose,false},{warnings,false}]), + State3 = case FFun(State2) of + {ok, _, S} -> S; + Error2 -> throw(Error2) + end, + {Undef, State4} = do_analysis(State3, undefined_functions), + {Deprecated, _} = do_analysis(State4, deprecated_functions), + [{deprecated,to_external(Deprecated)}, + {undefined,to_external(Undef)}]. + +do_analysis(State, Analysis) -> + case xref_base:analyze(State, Analysis) of + {{ok, Reply}, NewState} -> + {Reply, NewState}; + {Error, _} -> + throw(Error) + end. + +unsetify(Reply={ok, X}) -> + case is_sofs_set(X) of + true -> {ok, to_external(X)}; + false -> Reply + end; +unsetify(Reply) -> + Reply. |