diff options
Diffstat (limited to 'lib/dialyzer/src/dialyzer_plt.erl')
-rw-r--r-- | lib/dialyzer/src/dialyzer_plt.erl | 516 |
1 files changed, 29 insertions, 487 deletions
diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl index 409d1f65c5..f58c84da25 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -16,21 +16,17 @@ %%% File : dialyzer_plt.erl %%% Author : Tobias Lindahl <tobiasl@it.uu.se> %%% Description : Interface to display information in the persistent -%%% lookup tables. +%%% lookup tables stored in memory, and other commonality +%%% between the various kinds of persisted PLT files. %%% %%% Created : 23 Jul 2004 by Tobias Lindahl <tobiasl@it.uu.se> %%%------------------------------------------------------------------- -module(dialyzer_plt). --export([check_plt/3, - compute_md5_from_files/1, - contains_mfa/2, +-export([contains_mfa/2, all_modules/1, delete_list/2, delete_module/2, - included_files/1, - from_file/1, - get_default_plt/0, get_module_types/2, get_exported_types/1, insert_list/2, @@ -43,32 +39,23 @@ lookup_contract/2, lookup_callbacks/2, lookup_module/2, - merge_plts/1, - merge_plts_or_report_conflicts/2, + merge_plts/1, new/0, - plt_and_info_from_file/1, get_specs/1, get_specs/4, - to_file/4, - delete/1, - get_all_types/1, - get_all_contracts/1, - get_all_callbacks/1 + delete/1, + get_all_types/1, + get_all_contracts/1, + get_all_callbacks/1, + plt_kind/1 ]). -%% Debug utilities --export([pp_non_returning/0, pp_mod/1]). - --export_type([plt/0, file_md5/0]). +-export_type([plt/0]). -include_lib("stdlib/include/ms_transform.hrl"). %%---------------------------------------------------------------------- --type mod_deps() :: dialyzer_callgraph:mod_deps(). - --type deep_string() :: string() | [deep_string()]. - %% 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 @@ -78,30 +65,9 @@ %%---------------------------------------------------------------------- --record(plt, {info :: ets:tid(), %% {mfa() | integer(), ret_args_types()} - types :: ets:tid(), %% {module(), erl_types:type_table()} - contracts :: ets:tid(), %% {mfa(), #contract{}} - callbacks :: ets:tid(), %% {module(), - %% [{mfa(), - %% dialyzer_contracts:file_contract()}] - exported_types :: ets:tid() %% {module(), sets:set()} - }). - --opaque plt() :: #plt{}. - -include("dialyzer.hrl"). --type file_md5() :: {file:filename(), binary()}. - --record(file_plt, {version = "" :: string(), - file_md5_list = [] :: [file_md5()], - info = dict:new() :: dict:dict(), - contracts = dict:new() :: dict:dict(), - callbacks = dict:new() :: dict:dict(), - types = dict:new() :: dict:dict(), - exported_types = sets:new() :: sets:set(), - mod_deps :: mod_deps(), - implementation_md5 = [] :: [file_md5()]}). +-type plt() :: #plt{}. %%---------------------------------------------------------------------- @@ -232,123 +198,6 @@ all_modules(#plt{info = Info, contracts = Cs}) -> contains_mfa(#plt{info = Info, contracts = Contracts}, MFA) -> ets:member(Info, MFA) orelse ets:member(Contracts, MFA). --spec get_default_plt() -> file:filename(). - -get_default_plt() -> - case os:getenv("DIALYZER_PLT") of - false -> - CacheDir = filename:basedir(user_cache, "erlang"), - filename:join(CacheDir, ".dialyzer_plt"); - 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) -> - Plt = new(), - Fun = fun() -> from_file1(Plt, FileName, ReturnInfo) end, - case subproc(Fun) of - {ok, Return} -> - Return; - {error, Msg} -> - delete(Plt), - plt_error(Msg) - end. - -from_file1(Plt, FileName, ReturnInfo) -> - case get_record_from_file(FileName) of - {ok, Rec} -> - case check_version(Rec) of - error -> - Msg = io_lib:format("Old PLT file ~ts\n", [FileName]), - {error, Msg}; - ok -> - #file_plt{info = FileInfo, - contracts = FileContracts, - callbacks = FileCallbacks, - types = FileTypes, - exported_types = FileExpTypes} = Rec, - Types = [{Mod, maps:from_list(dict:to_list(Types))} || - {Mod, Types} <- dict:to_list(FileTypes)], - CallbacksList = dict:to_list(FileCallbacks), - CallbacksByModule = - [{M, [Cb || {{M1,_,_},_} = Cb <- CallbacksList, M1 =:= M]} || - M <- lists:usort([M || {{M,_,_},_} <- CallbacksList])], - #plt{info = ETSInfo, - types = ETSTypes, - contracts = ETSContracts, - callbacks = ETSCallbacks, - exported_types = ETSExpTypes} = Plt, - [true, true, true] = - [ets:insert(ETS, Data) || - {ETS, Data} <- [{ETSInfo, dict:to_list(FileInfo)}, - {ETSTypes, Types}, - {ETSContracts, dict:to_list(FileContracts)}]], - true = ets:insert(ETSCallbacks, CallbacksByModule), - true = ets:insert(ETSExpTypes, [{ET} || - ET <- sets:to_list(FileExpTypes)]), - case ReturnInfo of - false -> {ok, Plt}; - true -> - PltInfo = #plt_info{files = Rec#file_plt.file_md5_list, - mod_deps = Rec#file_plt.mod_deps}, - {ok, {Plt, PltInfo}} - end - end; - {error, Reason} -> - Msg = io_lib:format("Could not read PLT file ~ts: ~p\n", - [FileName, Reason]), - {error, Msg} - end. - --type err_rsn() :: 'not_valid' | 'no_such_file' | 'read_error'. - --spec included_files(file:filename()) -> {'ok', [file:filename()]} - | {'error', err_rsn()}. - -included_files(FileName) -> - Fun = fun() -> included_files1(FileName) end, - subproc(Fun). - -included_files1(FileName) -> - case get_record_from_file(FileName) of - {ok, #file_plt{file_md5_list = Md5}} -> - {ok, [File || {File, _} <- Md5]}; - {error, _What} = Error -> - Error - end. - -check_version(#file_plt{version = ?VSN, implementation_md5 = ImplMd5}) -> - case compute_new_md5(ImplMd5, [], []) of - ok -> ok; - {differ, _, _} -> error; - {error, _} -> error - end; -check_version(#file_plt{}) -> error. - -get_record_from_file(FileName) -> - case file:read_file(FileName) of - {ok, Bin} -> - try binary_to_term(Bin) of - #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(). %% One of the PLTs of the list is augmented with the contents of the @@ -364,23 +213,6 @@ merge_plts(List) -> callbacks = table_merge(CallbacksList) }. --spec merge_disj_plts([plt()]) -> plt(). - -%% One of the PLTs of the list is augmented with the contents of the -%% other PLTs, and returned. The other PLTs are deleted. -%% -%% The keys are compared when checking for disjointness. Sometimes the -%% key is a module(), sometimes an mfa(). It boils down to checking if -%% any module occurs more than once. -merge_disj_plts(List) -> - {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList} = - group_fields(List), - #plt{info = table_disj_merge(InfoList), - types = table_disj_merge(TypesList), - exported_types = sets_disj_merge(ExpTypesList), - contracts = table_disj_merge(ContractsList), - callbacks = table_disj_merge(CallbacksList) - }. group_fields(List) -> InfoList = [Info || #plt{info = Info} <- List], @@ -390,199 +222,6 @@ group_fields(List) -> CallbacksList = [Callbacks || #plt{callbacks = Callbacks} <- List], {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList}. --spec merge_plts_or_report_conflicts([file:filename()], [plt()]) -> plt(). - -merge_plts_or_report_conflicts(PltFiles, Plts) -> - try - merge_disj_plts(Plts) - catch throw:{dialyzer_error, not_disjoint_plts} -> - IncFiles = lists:append([begin {ok, Fs} = included_files(F), Fs end - || F <- PltFiles]), - ConfFiles = find_duplicates(IncFiles), - Msg = io_lib:format("Could not merge PLTs since they are not disjoint\n" - "The following files are included in more than one " - "PLTs:\n~tp\n", [ConfFiles]), - plt_error(Msg) - end. - -find_duplicates(List) -> - ModList = [filename:basename(E) || E <- List], - SortedList = lists:usort(ModList), - lists:usort(ModList -- SortedList). - --spec to_file(file:filename(), plt(), mod_deps(), #plt_info{}) -> 'ok'. - -%% Write the PLT to file, and delete the PLT. -to_file(FileName, Plt, ModDeps, MD5_OldModDeps) -> - Fun = fun() -> to_file1(FileName, Plt, ModDeps, MD5_OldModDeps) end, - Return = subproc(Fun), - delete(Plt), - case Return of - ok -> ok; - {error, Msg} -> plt_error(Msg) - end. - -to_file1(FileName, - #plt{info = ETSInfo, types = ETSTypes, contracts = ETSContracts, - callbacks = ETSCallbacks, exported_types = ETSExpTypes}, - ModDeps, #plt_info{files = MD5, mod_deps = OldModDeps}) -> - NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) -> - ordsets:union(OldVal, NewVal) - end, - OldModDeps, ModDeps), - ImplMd5 = compute_implementation_md5(), - CallbacksList = - [Cb || - {_M, Cbs} <- tab2list(ETSCallbacks), - Cb <- Cbs], - Callbacks = dict:from_list(CallbacksList), - Info = dict:from_list(tab2list(ETSInfo)), - Types = tab2list(ETSTypes), - Contracts = dict:from_list(tab2list(ETSContracts)), - ExpTypes = sets:from_list([E || {E} <- tab2list(ETSExpTypes)]), - FileTypes = dict:from_list([{Mod, dict:from_list(maps:to_list(MTypes))} || - {Mod, MTypes} <- Types]), - Record = #file_plt{version = ?VSN, - file_md5_list = MD5, - info = Info, - contracts = Contracts, - callbacks = Callbacks, - types = FileTypes, - exported_types = ExpTypes, - 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 ~ts: ~w\n", - [FileName, Reason]), - {error, Msg} - end. - --type md5_diff() :: [{'differ', atom()} | {'removed', atom()}]. --type check_error() :: err_rsn() | {'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) -> - Fun = fun() -> check_plt1(FileName, RemoveFiles, AddFiles) end, - subproc(Fun). - -check_plt1(FileName, RemoveFiles, AddFiles) -> - case get_record_from_file(FileName) of - {ok, #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(dialyzer), - 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 beam_lib:all_chunks(File) of - {ok, _, Chunks} -> - %% We cannot use beam_lib:md5 because it does not consider - %% the debug_info chunk, where typespecs are likely stored. - %% So we consider almost all chunks except the useless ones. - Filtered = [[ID, Chunk] || {ID, Chunk} <- Chunks, ID =/= "CInf", ID =/= "Docs"], - erlang:md5(lists:sort(Filtered)); - {error, beam_lib, {file_error, _, enoent}} -> - Msg = io_lib:format("File not found: ~ts\n", [File]), - plt_error(Msg); - {error, beam_lib, _} -> - Msg = io_lib:format("Could not compute MD5 for .beam: ~ts\n", [File]), - plt_error(Msg) - 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)}. -spec delete(plt()) -> 'ok'. @@ -598,24 +237,6 @@ delete(#plt{info = ETSInfo, true = ets:delete(ETSExpTypes), ok. -tab2list(Tab) -> - dialyzer_utils:ets_tab2list(Tab). - -subproc(Fun) -> - F = fun() -> - exit(try Fun() - catch throw:T -> - {thrown, T} - end) - end, - {Pid, Ref} = erlang:spawn_monitor(F), - receive {'DOWN', Ref, process, Pid, Return} -> - case Return of - {thrown, T} -> throw(T); - _ -> Return - end - end. - %%--------------------------------------------------------------------------- %% Edoc @@ -627,9 +248,6 @@ get_specs(#plt{info = Info}) -> {{_,_,_} = MFA, Val} <- table_to_list(Info)]), lists:flatten(create_specs(L, [])). -beam_file_to_module(Filename) -> - list_to_atom(filename:basename(Filename, ".beam")). - -spec get_specs(plt(), atom(), atom(), arity_patt()) -> 'none' | string(). get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) -> @@ -663,10 +281,6 @@ expand_args([ArgType|Left]) -> end ++ ","|expand_args(Left)]. --spec plt_error(deep_string()) -> no_return(). - -plt_error(Msg) -> - throw({dialyzer_error, lists:flatten(Msg)}). %%--------------------------------------------------------------------------- %% Ets table @@ -735,19 +349,6 @@ table_merge([Plt|Plts], Acc) -> NewAcc = merge_tables(Plt, Acc), table_merge(Plts, NewAcc). -table_disj_merge([H|T]) -> - table_disj_merge(T, H). - -table_disj_merge([], Acc) -> - Acc; -table_disj_merge([Plt|Plts], Acc) -> - case table_is_disjoint(Plt, Acc) of - true -> - NewAcc = merge_tables(Plt, Acc), - table_disj_merge(Plts, NewAcc); - false -> throw({dialyzer_error, not_disjoint_plts}) - end. - sets_merge([H|T]) -> sets_merge(T, H). @@ -757,31 +358,6 @@ sets_merge([Plt|Plts], Acc) -> NewAcc = merge_tables(Plt, Acc), sets_merge(Plts, NewAcc). -sets_disj_merge([H|T]) -> - sets_disj_merge(T, H). - -sets_disj_merge([], Acc) -> - Acc; -sets_disj_merge([Plt|Plts], Acc) -> - case table_is_disjoint(Plt, Acc) of - true -> - NewAcc = merge_tables(Plt, Acc), - sets_disj_merge(Plts, NewAcc); - false -> throw({dialyzer_error, not_disjoint_plts}) - end. - -table_is_disjoint(T1, T2) -> - tab_is_disj(ets:first(T1), T1, T2). - -tab_is_disj('$end_of_table', _T1, _T2) -> - true; -tab_is_disj(K1, T1, T2) -> - case ets:member(T2, K1) of - false -> - tab_is_disj(ets:next(T1, K1), T1, T2); - true -> - false - end. merge_tables(T1, T2) -> tab_merge(ets:first(T1), T1, T2). @@ -801,53 +377,6 @@ tab_merge(K1, T1, T2) -> true = ets:insert(T2, Vs), tab_merge(NextK1, T1, T2). -%%--------------------------------------------------------------------------- -%% Debug utilities. - --spec pp_non_returning() -> 'ok'. - -pp_non_returning() -> - PltFile = get_default_plt(), - Plt = from_file(PltFile), - List = table_to_list(Plt#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:~tw~ts.\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)), - delete(Plt). - --spec pp_mod(atom()) -> '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 ~tw~ts.\n", [F, S]) - end, lists:sort(List)); - none -> - io:format("dialyzer: Found no module named '~s' in the PLT\n", [Mod]) - end, - delete(Plt). - %% Returns all contracts stored in the PLT -spec get_all_contracts(plt()) -> #{mfa() => #contract{}}. @@ -857,14 +386,27 @@ get_all_contracts(#plt{contracts = ETSContracts}) -> %% Returns all callbacks stored in the PLT -spec get_all_callbacks(plt()) -> #{mfa() => #contract{}}. get_all_callbacks(#plt{callbacks = ETSCallbacks}) -> - CallbacksList = - [Cb || - {_M, Cbs} <- ets:tab2list(ETSCallbacks), - Cb <- Cbs], - maps:from_list(CallbacksList). + #{K => V || + {_M, Cbs} <- ets:tab2list(ETSCallbacks), + {K, V} <- Cbs}. %% Returns all types stored in the PLT -spec get_all_types(plt()) -> #{module() => erl_types:type_table()}. get_all_types(#plt{types = ETSTypes}) -> Types = ets:tab2list(ETSTypes), maps:from_list(Types). + +-spec plt_kind(file:filename()) -> 'iplt' | 'cplt' | 'bad_file' | 'no_file'. +plt_kind(FileName) -> + case filelib:is_regular(FileName) of + true -> + case dialyzer_iplt:is_iplt(FileName) of + true -> iplt; + false -> + case dialyzer_cplt:is_cplt(FileName) of + true -> cplt; + false -> bad_file + end + end; + false -> no_file + end. |