aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dialyzer/src/dialyzer_plt.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dialyzer/src/dialyzer_plt.erl')
-rw-r--r--lib/dialyzer/src/dialyzer_plt.erl576
1 files changed, 576 insertions, 0 deletions
diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl
new file mode 100644
index 0000000000..f2e0fe1e97
--- /dev/null
+++ b/lib/dialyzer/src/dialyzer_plt.erl
@@ -0,0 +1,576 @@
+%% -*- erlang-indent-level: 2 -*-
+%%----------------------------------------------------------------------
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File : dialyzer_plt.erl
+%%% Author : Tobias Lindahl <[email protected]>
+%%% Description : Interface to display information in the persistent
+%%% lookup tables.
+%%%
+%%% Created : 23 Jul 2004 by Tobias Lindahl <[email protected]>
+%%%-------------------------------------------------------------------
+-module(dialyzer_plt).
+
+-export([check_plt/3,
+ compute_md5_from_files/1,
+ contains_mfa/2,
+ contains_module/2,
+ delete_contract_list/2,
+ delete_list/2,
+ delete_module/2,
+ included_files/1,
+ from_file/1,
+ get_default_plt/0,
+ get_types/1,
+ %% insert/3,
+ insert_list/2,
+ insert_contract_list/2,
+ insert_types/2,
+ lookup/2,
+ lookup_contract/2,
+ lookup_module/2,
+ merge_plts/1,
+ new/0,
+ plt_and_info_from_file/1,
+ get_specs/1,
+ get_specs/4,
+ to_file/4
+ ]).
+
+%% Debug utilities
+-export([pp_non_returning/0, pp_mod/1]).
+
+%%----------------------------------------------------------------------
+
+-type mod_deps() :: dict().
+
+%% The following are used for searching the PLT when using the GUI
+%% (e.g. in show or search PLT contents). The user might be searching
+%% with a partial specification, in which case the missing items
+%% default to '_'
+-type arity_patt() :: '_' | arity().
+-type mfa_patt() :: {module(), atom(), arity_patt()}.
+
+%%----------------------------------------------------------------------
+
+-record(dialyzer_plt, {info = table_new() :: dict(),
+ types = table_new() :: dict(),
+ contracts = table_new() :: dict()}).
+-opaque plt() :: #dialyzer_plt{}.
+
+-include("dialyzer.hrl").
+
+-type file_md5() :: {file:filename(), binary()}.
+-type plt_info() :: {[file_md5()], dict()}.
+
+-record(dialyzer_file_plt, {version = "" :: string(),
+ file_md5_list = [] :: [file_md5()],
+ info = dict:new() :: dict(),
+ contracts = dict:new() :: dict(),
+ types = dict:new() :: dict(),
+ mod_deps :: mod_deps(),
+ implementation_md5 = [] :: [file_md5()]
+ }).
+
+%%----------------------------------------------------------------------
+
+-spec new() -> plt().
+
+new() ->
+ #dialyzer_plt{}.
+
+-spec delete_module(plt(), module()) -> plt().
+
+delete_module(#dialyzer_plt{info = Info, types = Types, contracts = Contracts},
+ Mod) ->
+ #dialyzer_plt{info = table_delete_module(Info, Mod),
+ types = table_delete_module2(Types, Mod),
+ contracts = table_delete_module(Contracts, Mod)}.
+
+-spec delete_list(plt(), [mfa() | integer()]) -> plt().
+
+delete_list(#dialyzer_plt{info = Info, types = Types, contracts = Contracts},
+ List) ->
+ #dialyzer_plt{info = table_delete_list(Info, List),
+ types = Types,
+ contracts = table_delete_list(Contracts, List)}.
+
+-spec insert_contract_list(plt(), dialyzer_contracts:plt_contracts()) -> plt().
+
+insert_contract_list(#dialyzer_plt{contracts = Contracts} = PLT, List) ->
+ PLT#dialyzer_plt{contracts = table_insert_list(Contracts, List)}.
+
+-spec lookup_contract(plt(), mfa_patt()) -> 'none' | {'value', #contract{}}.
+
+lookup_contract(#dialyzer_plt{contracts = Contracts},
+ {M, F, _} = MFA) when is_atom(M), is_atom(F) ->
+ table_lookup(Contracts, MFA).
+
+-spec delete_contract_list(plt(), [mfa()]) -> plt().
+
+delete_contract_list(#dialyzer_plt{contracts = Contracts} = PLT, List) ->
+ PLT#dialyzer_plt{contracts = table_delete_list(Contracts, List)}.
+
+%% -spec insert(plt(), mfa() | integer(), {_, _}) -> plt().
+%%
+%% insert(#dialyzer_plt{info = Info} = PLT, Id, Types) ->
+%% PLT#dialyzer_plt{info = table_insert(Info, Id, Types)}.
+
+-type ret_args_types() :: {erl_types:erl_type(), [erl_types:erl_type()]}.
+
+-spec insert_list(plt(), [{mfa() | integer(), ret_args_types()}]) -> plt().
+
+insert_list(#dialyzer_plt{info = Info} = PLT, List) ->
+ PLT#dialyzer_plt{info = table_insert_list(Info, List)}.
+
+-spec lookup(plt(), integer() | mfa_patt()) ->
+ 'none' | {'value', ret_args_types()}.
+
+lookup(#dialyzer_plt{info = Info}, {M, F, _} = MFA) when is_atom(M), is_atom(F) ->
+ table_lookup(Info, MFA);
+lookup(#dialyzer_plt{info = Info}, Label) when is_integer(Label) ->
+ table_lookup(Info, Label).
+
+-spec insert_types(plt(), dict()) -> plt().
+
+insert_types(PLT, Rec) ->
+ PLT#dialyzer_plt{types = Rec}.
+
+-spec get_types(plt()) -> dict().
+
+get_types(#dialyzer_plt{types = Types}) ->
+ Types.
+
+-type mfa_types() :: {mfa(), erl_types:erl_type(), [erl_types:erl_type()]}.
+
+-spec lookup_module(plt(), module()) -> 'none' | {'value', [mfa_types()]}.
+
+lookup_module(#dialyzer_plt{info = Info}, M) when is_atom(M) ->
+ table_lookup_module(Info, M).
+
+-spec contains_module(plt(), module()) -> boolean().
+
+contains_module(#dialyzer_plt{info = Info, contracts = Cs}, M) when is_atom(M) ->
+ table_contains_module(Info, M) orelse table_contains_module(Cs, M).
+
+-spec contains_mfa(plt(), mfa()) -> boolean().
+
+contains_mfa(#dialyzer_plt{info = Info, contracts = Contracts}, MFA) ->
+ (table_lookup(Info, MFA) =/= none)
+ orelse (table_lookup(Contracts, MFA) =/= none).
+
+-spec get_default_plt() -> file:filename().
+
+get_default_plt() ->
+ case os:getenv("DIALYZER_PLT") of
+ false ->
+ case os:getenv("HOME") of
+ false ->
+ error("The HOME environment variable needs to be set " ++
+ "so that Dialyzer knows where to find the default PLT");
+ HomeDir -> filename:join(HomeDir, ".dialyzer_plt")
+ end;
+ UserSpecPlt -> UserSpecPlt
+ end.
+
+-spec plt_and_info_from_file(file:filename()) -> {plt(), plt_info()}.
+
+plt_and_info_from_file(FileName) ->
+ from_file(FileName, true).
+
+-spec from_file(file:filename()) -> plt().
+
+from_file(FileName) ->
+ from_file(FileName, false).
+
+from_file(FileName, ReturnInfo) ->
+ case get_record_from_file(FileName) of
+ {ok, Rec} ->
+ case check_version(Rec) of
+ error ->
+ Msg = io_lib:format("Old PLT file ~s\n", [FileName]),
+ error(Msg);
+ ok ->
+ Plt = #dialyzer_plt{info = Rec#dialyzer_file_plt.info,
+ types = Rec#dialyzer_file_plt.types,
+ contracts = Rec#dialyzer_file_plt.contracts},
+ case ReturnInfo of
+ false -> Plt;
+ true ->
+ PltInfo = {Rec#dialyzer_file_plt.file_md5_list,
+ Rec#dialyzer_file_plt.mod_deps},
+ {Plt, PltInfo}
+ end
+ end;
+ {error, Reason} ->
+ error(io_lib:format("Could not read PLT file ~s: ~p\n",
+ [FileName, Reason]))
+ end.
+
+-type inc_file_err_rsn() :: 'no_such_file' | 'read_error'.
+-spec included_files(file:filename()) -> {'ok', [file:filename()]}
+ | {'error', inc_file_err_rsn()}.
+
+included_files(FileName) ->
+ case get_record_from_file(FileName) of
+ {ok, #dialyzer_file_plt{file_md5_list = Md5}} ->
+ {ok, [File || {File, _} <- Md5]};
+ {error, _What} = Error ->
+ Error
+ end.
+
+check_version(#dialyzer_file_plt{version=?VSN, implementation_md5=ImplMd5}) ->
+ case compute_new_md5(ImplMd5, [], []) of
+ ok -> ok;
+ {differ, _, _} -> error;
+ {error, _} -> error
+ end;
+check_version(#dialyzer_file_plt{}) -> error.
+
+get_record_from_file(FileName) ->
+ case file:read_file(FileName) of
+ {ok, Bin} ->
+ try binary_to_term(Bin) of
+ #dialyzer_file_plt{} = FilePLT -> {ok, FilePLT};
+ _ -> {error, not_valid}
+ catch
+ _:_ -> {error, not_valid}
+ end;
+ {error, enoent} ->
+ {error, no_such_file};
+ {error, _} ->
+ {error, read_error}
+ end.
+
+-spec merge_plts([plt()]) -> plt().
+
+merge_plts(List) ->
+ InfoList = [Info || #dialyzer_plt{info = Info} <- List],
+ TypesList = [Types || #dialyzer_plt{types = Types} <- List],
+ ContractsList = [Contracts || #dialyzer_plt{contracts = Contracts} <- List],
+ #dialyzer_plt{info = table_merge(InfoList),
+ types = table_merge(TypesList),
+ contracts = table_merge(ContractsList)}.
+
+-spec to_file(file:filename(), plt(), mod_deps(), {[file_md5()], mod_deps()}) -> 'ok'.
+
+to_file(FileName,
+ #dialyzer_plt{info = Info, types = Types, contracts = Contracts},
+ ModDeps, {MD5, OldModDeps}) ->
+ NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) ->
+ ordsets:union(OldVal, NewVal)
+ end,
+ OldModDeps, ModDeps),
+ ImplMd5 = compute_implementation_md5(),
+ Record = #dialyzer_file_plt{version = ?VSN,
+ file_md5_list = MD5,
+ info = Info,
+ contracts = Contracts,
+ types = Types,
+ mod_deps = NewModDeps,
+ implementation_md5 = ImplMd5},
+ Bin = term_to_binary(Record, [compressed]),
+ case file:write_file(FileName, Bin) of
+ ok -> ok;
+ {error, Reason} ->
+ Msg = io_lib:format("Could not write PLT file ~s: ~w\n",
+ [FileName, Reason]),
+ throw({dialyzer_error, Msg})
+ end.
+
+-type md5_diff() :: [{'differ', atom()} | {'removed', atom()}].
+-type check_error() :: 'not_valid' | 'no_such_file' | 'read_error'
+ | {'no_file_to_remove', file:filename()}.
+
+-spec check_plt(file:filename(), [file:filename()], [file:filename()]) ->
+ 'ok'
+ | {'error', check_error()}
+ | {'differ', [file_md5()], md5_diff(), mod_deps()}
+ | {'old_version', [file_md5()]}.
+
+check_plt(FileName, RemoveFiles, AddFiles) ->
+ case get_record_from_file(FileName) of
+ {ok, #dialyzer_file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} ->
+ case check_version(Rec) of
+ ok ->
+ case compute_new_md5(Md5, RemoveFiles, AddFiles) of
+ ok -> ok;
+ {differ, NewMd5, DiffMd5} -> {differ, NewMd5, DiffMd5, ModDeps};
+ {error, _What} = Err -> Err
+ end;
+ error ->
+ case compute_new_md5(Md5, RemoveFiles, AddFiles) of
+ ok -> {old_version, Md5};
+ {differ, NewMd5, _DiffMd5} -> {old_version, NewMd5};
+ {error, _What} = Err -> Err
+ end
+ end;
+ Error -> Error
+ end.
+
+compute_new_md5(Md5, [], []) ->
+ compute_new_md5_1(Md5, [], []);
+compute_new_md5(Md5, RemoveFiles0, AddFiles0) ->
+ %% Assume that files are first removed and then added. Files that
+ %% are both removed and added will be checked for consistency in the
+ %% normal way. If they have moved, we assume that they differ.
+ RemoveFiles = RemoveFiles0 -- AddFiles0,
+ AddFiles = AddFiles0 -- RemoveFiles0,
+ InitDiffList = init_diff_list(RemoveFiles, AddFiles),
+ case init_md5_list(Md5, RemoveFiles, AddFiles) of
+ {ok, NewMd5} -> compute_new_md5_1(NewMd5, [], InitDiffList);
+ {error, _What} = Error -> Error
+ end.
+
+compute_new_md5_1([{File, Md5} = Entry|Entries], NewList, Diff) ->
+ case compute_md5_from_file(File) of
+ Md5 -> compute_new_md5_1(Entries, [Entry|NewList], Diff);
+ NewMd5 ->
+ ModName = beam_file_to_module(File),
+ compute_new_md5_1(Entries, [{File, NewMd5}|NewList], [{differ, ModName}|Diff])
+ end;
+compute_new_md5_1([], _NewList, []) ->
+ ok;
+compute_new_md5_1([], NewList, Diff) ->
+ {differ, lists:keysort(1, NewList), Diff}.
+
+-spec compute_implementation_md5() -> [file_md5()].
+
+compute_implementation_md5() ->
+ Dir = code:lib_dir(hipe),
+ Files1 = ["erl_bif_types.beam", "erl_types.beam"],
+ Files2 = [filename:join([Dir, "ebin", F]) || F <- Files1],
+ compute_md5_from_files(Files2).
+
+-spec compute_md5_from_files([file:filename()]) -> [file_md5()].
+
+compute_md5_from_files(Files) ->
+ lists:keysort(1, [{F, compute_md5_from_file(F)} || F <- Files]).
+
+compute_md5_from_file(File) ->
+ case filelib:is_regular(File) of
+ false ->
+ Msg = io_lib:format("Not a regular file: ~s\n", [File]),
+ throw({dialyzer_error, Msg});
+ true ->
+ case dialyzer_utils:get_abstract_code_from_beam(File) of
+ error ->
+ Msg = io_lib:format("Could not get abstract code for file: ~s (please recompile it with +debug_info)\n", [File]),
+ throw({dialyzer_error, Msg});
+ {ok, Abs} ->
+ erlang:md5(term_to_binary(Abs))
+ end
+ end.
+
+init_diff_list(RemoveFiles, AddFiles) ->
+ RemoveSet0 = sets:from_list([beam_file_to_module(F) || F <- RemoveFiles]),
+ AddSet0 = sets:from_list([beam_file_to_module(F) || F <- AddFiles]),
+ DiffSet = sets:intersection(AddSet0, RemoveSet0),
+ RemoveSet = sets:subtract(RemoveSet0, DiffSet),
+ %% Added files and diff files will appear as diff files from the md5 check.
+ [{removed, F} || F <- sets:to_list(RemoveSet)].
+
+init_md5_list(Md5, RemoveFiles, AddFiles) ->
+ Files = [{remove, F} || F <- RemoveFiles] ++ [{add, F} || F <- AddFiles],
+ DiffFiles = lists:keysort(2, Files),
+ Md5Sorted = lists:keysort(1, Md5),
+ init_md5_list_1(Md5Sorted, DiffFiles, []).
+
+init_md5_list_1([{File, _Md5}|Md5Left], [{remove, File}|DiffLeft], Acc) ->
+ init_md5_list_1(Md5Left, DiffLeft, Acc);
+init_md5_list_1([{File, _Md5} = Entry|Md5Left], [{add, File}|DiffLeft], Acc) ->
+ init_md5_list_1(Md5Left, DiffLeft, [Entry|Acc]);
+init_md5_list_1([{File1, _Md5} = Entry|Md5Left] = Md5List,
+ [{Tag, File2}|DiffLeft] = DiffList, Acc) ->
+ case File1 < File2 of
+ true -> init_md5_list_1(Md5Left, DiffList, [Entry|Acc]);
+ false ->
+ %% Just an assert.
+ true = File1 > File2,
+ case Tag of
+ add -> init_md5_list_1(Md5List, DiffLeft, [{File2, <<>>}|Acc]);
+ remove -> {error, {no_file_to_remove, File2}}
+ end
+ end;
+init_md5_list_1([], DiffList, Acc) ->
+ AddFiles = [{F, <<>>} || {add, F} <- DiffList],
+ {ok, lists:reverse(Acc, AddFiles)};
+init_md5_list_1(Md5List, [], Acc) ->
+ {ok, lists:reverse(Acc, Md5List)}.
+
+%%---------------------------------------------------------------------------
+%% Edoc
+
+-spec get_specs(plt()) -> string().
+
+get_specs(#dialyzer_plt{info = Info}) ->
+ %% TODO: Should print contracts as well.
+ List =
+ lists:sort([{MFA, Val} || {MFA = {_,_,_}, Val} <- table_to_list(Info)]),
+ lists:flatten(create_specs(List, [])).
+
+beam_file_to_module(Filename) ->
+ list_to_atom(filename:basename(Filename, ".beam")).
+
+-spec get_specs(plt(), module(), atom(), arity_patt()) -> 'none' | string().
+
+get_specs(#dialyzer_plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) ->
+ MFA = {M, F, A},
+ case table_lookup(Info, MFA) of
+ none -> none;
+ {value, Val} -> lists:flatten(create_specs([{MFA, Val}], []))
+ end.
+
+create_specs([{{M, F, _A}, {Ret, Args}}|Left], M) ->
+ [io_lib:format("-spec ~w(~s) -> ~s\n",
+ [F, expand_args(Args), erl_types:t_to_string(Ret)])
+ | create_specs(Left, M)];
+create_specs(List = [{{M, _F, _A}, {_Ret, _Args}}| _], _M) ->
+ [io_lib:format("\n\n%% ------- Module: ~w -------\n\n", [M])
+ | create_specs(List, M)];
+create_specs([], _) ->
+ [].
+
+expand_args([]) ->
+ [];
+expand_args([ArgType]) ->
+ case erl_types:t_is_any(ArgType) of
+ true -> ["_"];
+ false -> [erl_types:t_to_string(ArgType)]
+ end;
+expand_args([ArgType|Left]) ->
+ [case erl_types:t_is_any(ArgType) of
+ true -> "_";
+ false -> erl_types:t_to_string(ArgType)
+ end ++
+ ","|expand_args(Left)].
+
+error(Msg) ->
+ throw({dialyzer_error, lists:flatten(Msg)}).
+
+%%---------------------------------------------------------------------------
+%% Ets table
+
+table_new() ->
+ dict:new().
+
+table_to_list(Plt) ->
+ dict:to_list(Plt).
+
+table_delete_module(Plt, Mod) ->
+ dict:filter(fun({M, _F, _A}, _Val) -> M =/= Mod;
+ (_, _) -> true
+ end, Plt).
+
+table_delete_module2(Plt, Mod) ->
+ dict:filter(fun(M, _Val) -> M =/= Mod end, Plt).
+
+table_delete_list(Plt, [H|T]) ->
+ table_delete_list(dict:erase(H, Plt), T);
+table_delete_list(Plt, []) ->
+ Plt.
+
+table_insert_list(Plt, [{Key, Val}|Left]) ->
+ table_insert_list(table_insert(Plt, Key, Val), Left);
+table_insert_list(Plt, []) ->
+ Plt.
+
+table_insert(Plt, Key, {_Ret, _Arg} = Obj) ->
+ dict:store(Key, Obj, Plt);
+table_insert(Plt, Key, #contract{} = C) ->
+ dict:store(Key, C, Plt).
+
+table_lookup(Plt, Obj) ->
+ case dict:find(Obj, Plt) of
+ error -> none;
+ {ok, Val} -> {value, Val}
+ end.
+
+table_lookup_module(Plt, Mod) ->
+ List = dict:fold(fun(Key, Val, Acc) ->
+ case Key of
+ {Mod, _F, _A} -> [{Key, element(1, Val),
+ element(2, Val)}|Acc];
+ _ -> Acc
+ end
+ end, [], Plt),
+ case List =:= [] of
+ true -> none;
+ false -> {value, List}
+ end.
+
+table_contains_module(Plt, Mod) ->
+ dict:fold(fun({M, _F, _A}, _Val, _Acc) when M =:= Mod -> true;
+ (_, _, Acc) -> Acc
+ end, false, Plt).
+
+table_merge([H|T]) ->
+ table_merge(T, H).
+
+table_merge([], Acc) ->
+ Acc;
+table_merge([Plt|Left], Acc) ->
+ NewAcc = dict:merge(fun(_Key, Val, Val) -> Val end, Plt, Acc),
+ table_merge(Left, NewAcc).
+
+%%---------------------------------------------------------------------------
+%% Debug utilities.
+
+-spec pp_non_returning() -> 'ok'.
+
+pp_non_returning() ->
+ PltFile = get_default_plt(),
+ Plt = from_file(PltFile),
+ List = table_to_list(Plt#dialyzer_plt.info),
+ Unit = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List,
+ erl_types:t_is_unit(Ret)],
+ None = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List,
+ erl_types:t_is_none(Ret)],
+ io:format("=========================================\n"),
+ io:format("= Loops =\n"),
+ io:format("=========================================\n\n"),
+ lists:foreach(fun({{M, F, _}, Type}) ->
+ io:format("~w:~w~s.\n",
+ [M, F, dialyzer_utils:format_sig(Type)])
+ end, lists:sort(Unit)),
+ io:format("\n"),
+ io:format("=========================================\n"),
+ io:format("= Errors =\n"),
+ io:format("=========================================\n\n"),
+ lists:foreach(fun({{M, F, _}, Type}) ->
+ io:format("~w:~w~s.\n",
+ [M, F, dialyzer_utils:format_sig(Type)])
+ end, lists:sort(None)).
+
+-spec pp_mod(module()) -> 'ok'.
+
+pp_mod(Mod) when is_atom(Mod) ->
+ PltFile = get_default_plt(),
+ Plt = from_file(PltFile),
+ case lookup_module(Plt, Mod) of
+ {value, List} ->
+ lists:foreach(fun({{_, F, _}, Ret, Args}) ->
+ T = erl_types:t_fun(Args, Ret),
+ S = dialyzer_utils:format_sig(T),
+ io:format("-spec ~w~s.\n", [F, S])
+ end, lists:sort(List));
+ none ->
+ io:format("dialyzer: Found no module named '~s' in the PLT\n", [Mod])
+ end.