1%% -*- erlang-indent-level: 2 -*-
2%%
3%% Licensed under the Apache License, Version 2.0 (the "License");
4%% you may not use this file except in compliance with the License.
5%% You may obtain a copy of the License at
6%%
7%%     http://www.apache.org/licenses/LICENSE-2.0
8%%
9%% Unless required by applicable law or agreed to in writing, software
10%% distributed under the License is distributed on an "AS IS" BASIS,
11%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12%% See the License for the specific language governing permissions and
13%% limitations under the License.
14
15%%%-------------------------------------------------------------------
16%%% File    : dialyzer_plt.erl
17%%% Author  : Tobias Lindahl <tobiasl@it.uu.se>
18%%% Description : Interface to display information in the persistent
19%%%               lookup tables.
20%%%
21%%% Created : 23 Jul 2004 by Tobias Lindahl <tobiasl@it.uu.se>
22%%%-------------------------------------------------------------------
23-module(dialyzer_plt).
24
25-export([check_plt/3,
26	 compute_md5_from_files/1,
27	 contains_mfa/2,
28	 all_modules/1,
29	 delete_list/2,
30	 delete_module/2,
31	 included_files/1,
32	 from_file/1,
33	 get_default_plt/0,
34         get_module_types/2,
35         get_exported_types/1,
36	 insert_list/2,
37	 insert_contract_list/2,
38	 insert_callbacks/2,
39	 insert_types/2,
40         insert_exported_types/2,
41	 lookup/2,
42         is_contract/2,
43	 lookup_contract/2,
44	 lookup_callbacks/2,
45	 lookup_module/2,
46	 merge_plts/1,
47         merge_plts_or_report_conflicts/2,
48	 new/0,
49	 plt_and_info_from_file/1,
50	 get_specs/1,
51	 get_specs/4,
52	 to_file/4,
53         delete/1
54	]).
55
56%% Debug utilities
57-export([pp_non_returning/0, pp_mod/1]).
58
59-export_type([plt/0, plt_info/0]).
60
61-include_lib("stdlib/include/ms_transform.hrl").
62
63%%----------------------------------------------------------------------
64
65-type mod_deps() :: dialyzer_callgraph:mod_deps().
66
67-type deep_string() :: string() | [deep_string()].
68
69%% The following are used for searching the PLT when using the GUI
70%% (e.g. in show or search PLT contents). The user might be searching
71%% with a partial specification, in which case the missing items
72%% default to '_'
73-type arity_patt() :: '_' | arity().
74-type mfa_patt()   :: {module(), atom(), arity_patt()}.
75
76%%----------------------------------------------------------------------
77
78-record(plt, {info      :: ets:tid(), %% {mfa() | integer(), ret_args_types()}
79              types     :: ets:tid(), %% {module(), erl_types:type_table()}
80              contracts :: ets:tid(), %% {mfa(), #contract{}}
81              callbacks :: ets:tid(), %% {module(),
82                                      %%  [{mfa(),
83                                      %%  dialyzer_contracts:file_contract()}]
84              exported_types :: ets:tid() %% {module(), sets:set()}
85             }).
86
87-opaque plt() :: #plt{}.
88
89-include("dialyzer.hrl").
90
91-type file_md5() :: {file:filename(), binary()}.
92-type plt_info() :: {[file_md5()], dict:dict()}.
93
94-record(file_plt, {version = ""                :: string(),
95		   file_md5_list = []          :: [file_md5()],
96		   info = dict:new()           :: dict:dict(),
97		   contracts = dict:new()      :: dict:dict(),
98		   callbacks = dict:new()      :: dict:dict(),
99		   types = dict:new()          :: dict:dict(),
100                   exported_types = sets:new() :: sets:set(),
101		   mod_deps                    :: mod_deps(),
102		   implementation_md5 = []     :: [file_md5()]}).
103
104%%----------------------------------------------------------------------
105
106-spec new() -> plt().
107
108new() ->
109  [ETSInfo, ETSContracts] =
110    [ets:new(Name, [public]) ||
111      Name <- [plt_info, plt_contracts]],
112  [ETSTypes, ETSCallbacks, ETSExpTypes] =
113    [ets:new(Name, [compressed, public]) ||
114      Name <- [plt_types, plt_callbacks, plt_exported_types]],
115  #plt{info = ETSInfo,
116       types = ETSTypes,
117       contracts = ETSContracts,
118       callbacks = ETSCallbacks,
119       exported_types = ETSExpTypes}.
120
121-spec delete_module(plt(), atom()) -> plt().
122
123delete_module(#plt{info = Info, types = Types,
124		   contracts = Contracts,
125		   callbacks = Callbacks,
126                   exported_types = ExpTypes}, Mod) ->
127  #plt{info = table_delete_module(Info, Mod),
128       types = table_delete_module2(Types, Mod),
129       contracts = table_delete_module(Contracts, Mod),
130       callbacks = table_delete_module2(Callbacks, Mod),
131       exported_types = table_delete_module1(ExpTypes, Mod)}.
132
133-spec delete_list(plt(), [mfa() | integer()]) -> plt().
134
135delete_list(#plt{info = Info,
136                 contracts = Contracts}=Plt, List) ->
137  Plt#plt{info = ets_table_delete_list(Info, List),
138          contracts = ets_table_delete_list(Contracts, List)}.
139
140-spec insert_contract_list(plt(), dialyzer_contracts:plt_contracts()) -> plt().
141
142insert_contract_list(#plt{contracts = Contracts} = PLT, List) ->
143  true = ets:insert(Contracts, List),
144  PLT.
145
146-spec insert_callbacks(plt(), dialyzer_codeserver:codeserver()) -> plt().
147
148insert_callbacks(#plt{callbacks = Callbacks} = Plt, Codeserver) ->
149  CallbacksList = dialyzer_codeserver:get_callbacks(Codeserver),
150  CallbacksByModule =
151    [{M, [Cb || {{M1,_,_},_} = Cb <- CallbacksList, M1 =:= M]} ||
152      M <- lists:usort([M || {{M,_,_},_} <- CallbacksList])],
153  true = ets:insert(Callbacks, CallbacksByModule),
154  Plt.
155
156-spec is_contract(plt(), mfa()) -> boolean().
157
158is_contract(#plt{contracts = ETSContracts},
159            {M, F, _} = MFA) when is_atom(M), is_atom(F) ->
160  ets:member(ETSContracts, MFA).
161
162-spec lookup_contract(plt(), mfa_patt()) -> 'none' | {'value', #contract{}}.
163
164lookup_contract(#plt{contracts = ETSContracts},
165		{M, F, _} = MFA) when is_atom(M), is_atom(F) ->
166  ets_table_lookup(ETSContracts, MFA).
167
168-spec lookup_callbacks(plt(), module()) ->
169	 'none' | {'value', [{mfa(), dialyzer_contracts:file_contract()}]}.
170
171lookup_callbacks(#plt{callbacks = ETSCallbacks}, Mod) when is_atom(Mod) ->
172  ets_table_lookup(ETSCallbacks, Mod).
173
174-type ret_args_types() :: {erl_types:erl_type(), [erl_types:erl_type()]}.
175
176-spec insert_list(plt(), [{mfa() | integer(), ret_args_types()}]) -> plt().
177
178insert_list(#plt{info = Info} = PLT, List) ->
179  true = ets:insert(Info, List),
180  PLT.
181
182-spec lookup(plt(), integer() | mfa_patt()) ->
183        'none' | {'value', ret_args_types()}.
184
185lookup(Plt, {M, F, _} = MFA) when is_atom(M), is_atom(F) ->
186  lookup_1(Plt, MFA);
187lookup(Plt, Label) when is_integer(Label) ->
188  lookup_1(Plt, Label).
189
190lookup_1(#plt{info = Info}, MFAorLabel) ->
191  ets_table_lookup(Info, MFAorLabel).
192
193-spec insert_types(plt(), ets:tid()) -> plt().
194
195insert_types(PLT, Records) ->
196  ok = dialyzer_utils:ets_move(Records, PLT#plt.types),
197  PLT.
198
199-spec insert_exported_types(plt(), ets:tid()) -> plt().
200
201insert_exported_types(PLT, ExpTypes) ->
202  ok = dialyzer_utils:ets_move(ExpTypes, PLT#plt.exported_types),
203  PLT.
204
205-spec get_module_types(plt(), atom()) ->
206                          'none' | {'value', erl_types:type_table()}.
207
208get_module_types(#plt{types = Types}, M) when is_atom(M) ->
209  ets_table_lookup(Types, M).
210
211-spec get_exported_types(plt()) -> sets:set().
212
213get_exported_types(#plt{exported_types = ETSExpTypes}) ->
214  sets:from_list([E || {E} <- table_to_list(ETSExpTypes)]).
215
216-type mfa_types() :: {mfa(), erl_types:erl_type(), [erl_types:erl_type()]}.
217
218-spec lookup_module(plt(), atom()) -> 'none' | {'value', [mfa_types()]}.
219
220lookup_module(#plt{info = Info}, M) when is_atom(M) ->
221  table_lookup_module(Info, M).
222
223-spec all_modules(plt()) -> sets:set().
224
225all_modules(#plt{info = Info, contracts = Cs}) ->
226  sets:union(table_all_modules(Info), table_all_modules(Cs)).
227
228-spec contains_mfa(plt(), mfa()) -> boolean().
229
230contains_mfa(#plt{info = Info, contracts = Contracts}, MFA) ->
231  ets:member(Info, MFA) orelse ets:member(Contracts, MFA).
232
233-spec get_default_plt() -> file:filename().
234
235get_default_plt() ->
236  case os:getenv("DIALYZER_PLT") of
237    false ->
238      {ok,[[HomeDir]]} = init:get_argument(home),
239      filename:join(HomeDir, ".dialyzer_plt");
240    UserSpecPlt -> UserSpecPlt
241  end.
242
243-spec plt_and_info_from_file(file:filename()) -> {plt(), plt_info()}.
244
245plt_and_info_from_file(FileName) ->
246  from_file(FileName, true).
247
248-spec from_file(file:filename()) -> plt().
249
250from_file(FileName) ->
251  from_file(FileName, false).
252
253from_file(FileName, ReturnInfo) ->
254  Plt = new(),
255  Fun = fun() -> from_file1(Plt, FileName, ReturnInfo) end,
256  case subproc(Fun) of
257    {ok, Return} ->
258      Return;
259    {error, Msg} ->
260      delete(Plt),
261      plt_error(Msg)
262  end.
263
264from_file1(Plt, FileName, ReturnInfo) ->
265  case get_record_from_file(FileName) of
266    {ok, Rec} ->
267      case check_version(Rec) of
268	error ->
269	  Msg = io_lib:format("Old PLT file ~ts\n", [FileName]),
270          {error, Msg};
271	ok ->
272          #file_plt{info = FileInfo,
273                    contracts = FileContracts,
274                    callbacks = FileCallbacks,
275                    types = FileTypes,
276                    exported_types = FileExpTypes} = Rec,
277          Types = [{Mod, maps:from_list(dict:to_list(Types))} ||
278                    {Mod, Types} <- dict:to_list(FileTypes)],
279          CallbacksList = dict:to_list(FileCallbacks),
280          CallbacksByModule =
281            [{M, [Cb || {{M1,_,_},_} = Cb <- CallbacksList, M1 =:= M]} ||
282              M <- lists:usort([M || {{M,_,_},_} <- CallbacksList])],
283          #plt{info = ETSInfo,
284               types = ETSTypes,
285               contracts = ETSContracts,
286               callbacks = ETSCallbacks,
287               exported_types = ETSExpTypes} = Plt,
288          [true, true, true] =
289            [ets:insert(ETS, Data) ||
290              {ETS, Data} <- [{ETSInfo, dict:to_list(FileInfo)},
291                              {ETSTypes, Types},
292                              {ETSContracts, dict:to_list(FileContracts)}]],
293          true = ets:insert(ETSCallbacks, CallbacksByModule),
294          true = ets:insert(ETSExpTypes, [{ET} ||
295                                           ET <- sets:to_list(FileExpTypes)]),
296	  case ReturnInfo of
297	    false -> {ok, Plt};
298	    true ->
299	      PltInfo = {Rec#file_plt.file_md5_list,
300			 Rec#file_plt.mod_deps},
301	      {ok, {Plt, PltInfo}}
302	  end
303      end;
304    {error, Reason} ->
305      Msg = io_lib:format("Could not read PLT file ~ts: ~p\n",
306			  [FileName, Reason]),
307      {error, Msg}
308  end.
309
310-type err_rsn() :: 'not_valid' | 'no_such_file' | 'read_error'.
311
312-spec included_files(file:filename()) -> {'ok', [file:filename()]}
313				      |  {'error', err_rsn()}.
314
315included_files(FileName) ->
316  Fun = fun() -> included_files1(FileName) end,
317  subproc(Fun).
318
319included_files1(FileName) ->
320  case get_record_from_file(FileName) of
321    {ok, #file_plt{file_md5_list = Md5}} ->
322      {ok, [File || {File, _} <- Md5]};
323    {error, _What} = Error ->
324      Error
325  end.
326
327check_version(#file_plt{version = ?VSN, implementation_md5 = ImplMd5}) ->
328  case compute_new_md5(ImplMd5, [], []) of
329    ok -> ok;
330    {differ, _, _} -> error;
331    {error, _} -> error
332  end;
333check_version(#file_plt{}) -> error.
334
335get_record_from_file(FileName) ->
336  case file:read_file(FileName) of
337    {ok, Bin} ->
338      try binary_to_term(Bin) of
339	  #file_plt{} = FilePLT -> {ok, FilePLT};
340	  _ -> {error, not_valid}
341      catch
342	_:_ -> {error, not_valid}
343      end;
344    {error, enoent} ->
345      {error, no_such_file};
346    {error, _} ->
347      {error, read_error}
348  end.
349
350-spec merge_plts([plt()]) -> plt().
351
352%% One of the PLTs of the list is augmented with the contents of the
353%% other PLTs, and returned. The other PLTs are deleted.
354
355merge_plts(List) ->
356  {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList} =
357    group_fields(List),
358  #plt{info = table_merge(InfoList),
359       types = table_merge(TypesList),
360       exported_types = sets_merge(ExpTypesList),
361       contracts = table_merge(ContractsList),
362       callbacks = table_merge(CallbacksList)
363      }.
364
365-spec merge_disj_plts([plt()]) -> plt().
366
367%% One of the PLTs of the list is augmented with the contents of the
368%% other PLTs, and returned. The other PLTs are deleted.
369%%
370%% The keys are compared when checking for disjointness. Sometimes the
371%% key is a module(), sometimes an mfa(). It boils down to checking if
372%% any module occurs more than once.
373merge_disj_plts(List) ->
374  {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList} =
375    group_fields(List),
376  #plt{info = table_disj_merge(InfoList),
377       types = table_disj_merge(TypesList),
378       exported_types = sets_disj_merge(ExpTypesList),
379       contracts = table_disj_merge(ContractsList),
380       callbacks = table_disj_merge(CallbacksList)
381      }.
382
383group_fields(List) ->
384  InfoList = [Info || #plt{info = Info} <- List],
385  TypesList = [Types || #plt{types = Types} <- List],
386  ExpTypesList = [ExpTypes || #plt{exported_types = ExpTypes} <- List],
387  ContractsList = [Contracts || #plt{contracts = Contracts} <- List],
388  CallbacksList = [Callbacks || #plt{callbacks = Callbacks} <- List],
389  {InfoList, TypesList, ExpTypesList, ContractsList, CallbacksList}.
390
391-spec merge_plts_or_report_conflicts([file:filename()], [plt()]) -> plt().
392
393merge_plts_or_report_conflicts(PltFiles, Plts) ->
394  try
395    merge_disj_plts(Plts)
396  catch throw:{dialyzer_error, not_disjoint_plts} ->
397      IncFiles = lists:append([begin {ok, Fs} = included_files(F), Fs end
398                               || F <- PltFiles]),
399      ConfFiles = find_duplicates(IncFiles),
400      Msg = io_lib:format("Could not merge PLTs since they are not disjoint\n"
401                          "The following files are included in more than one "
402                          "PLTs:\n~tp\n", [ConfFiles]),
403      plt_error(Msg)
404  end.
405
406find_duplicates(List) ->
407  ModList = [filename:basename(E) || E <- List],
408  SortedList = lists:usort(ModList),
409  lists:usort(ModList -- SortedList).
410
411-spec to_file(file:filename(), plt(), mod_deps(), {[file_md5()], mod_deps()}) -> 'ok'.
412
413%% Write the PLT to file, and delete the PLT.
414to_file(FileName, Plt, ModDeps, MD5_OldModDeps) ->
415  Fun = fun() -> to_file1(FileName, Plt, ModDeps, MD5_OldModDeps) end,
416  Return = subproc(Fun),
417  delete(Plt),
418  case Return of
419    ok -> ok;
420    {error, Msg} -> plt_error(Msg)
421  end.
422
423to_file1(FileName,
424	#plt{info = ETSInfo, types = ETSTypes, contracts = ETSContracts,
425	     callbacks = ETSCallbacks, exported_types = ETSExpTypes},
426	ModDeps, {MD5, OldModDeps}) ->
427  NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) ->
428			      ordsets:union(OldVal, NewVal)
429			  end,
430			  OldModDeps, ModDeps),
431  ImplMd5 = compute_implementation_md5(),
432  CallbacksList =
433      [Cb ||
434        {_M, Cbs} <- tab2list(ETSCallbacks),
435        Cb <- Cbs],
436  Callbacks = dict:from_list(CallbacksList),
437  Info = dict:from_list(tab2list(ETSInfo)),
438  Types = tab2list(ETSTypes),
439  Contracts = dict:from_list(tab2list(ETSContracts)),
440  ExpTypes = sets:from_list([E || {E} <- tab2list(ETSExpTypes)]),
441  FileTypes = dict:from_list([{Mod, dict:from_list(maps:to_list(MTypes))} ||
442                               {Mod, MTypes} <- Types]),
443  Record = #file_plt{version = ?VSN,
444		     file_md5_list = MD5,
445		     info = Info,
446		     contracts = Contracts,
447		     callbacks = Callbacks,
448		     types = FileTypes,
449                     exported_types = ExpTypes,
450		     mod_deps = NewModDeps,
451		     implementation_md5 = ImplMd5},
452  Bin = term_to_binary(Record, [compressed]),
453  case file:write_file(FileName, Bin) of
454    ok -> ok;
455    {error, Reason} ->
456      Msg = io_lib:format("Could not write PLT file ~ts: ~w\n",
457			  [FileName, Reason]),
458      {error, Msg}
459  end.
460
461-type md5_diff()    :: [{'differ', atom()} | {'removed', atom()}].
462-type check_error() :: err_rsn() | {'no_file_to_remove', file:filename()}.
463
464-spec check_plt(file:filename(), [file:filename()], [file:filename()]) ->
465	 'ok'
466       | {'error', check_error()}
467       | {'differ', [file_md5()], md5_diff(), mod_deps()}
468       | {'old_version', [file_md5()]}.
469
470check_plt(FileName, RemoveFiles, AddFiles) ->
471  Fun = fun() -> check_plt1(FileName, RemoveFiles, AddFiles) end,
472  subproc(Fun).
473
474check_plt1(FileName, RemoveFiles, AddFiles) ->
475  case get_record_from_file(FileName) of
476    {ok, #file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} ->
477      case check_version(Rec) of
478	ok ->
479	  case compute_new_md5(Md5, RemoveFiles, AddFiles) of
480	    ok -> ok;
481	    {differ, NewMd5, DiffMd5} -> {differ, NewMd5, DiffMd5, ModDeps};
482	    {error, _What} = Err -> Err
483	  end;
484	error ->
485	  case compute_new_md5(Md5, RemoveFiles, AddFiles) of
486	    ok -> {old_version, Md5};
487	    {differ, NewMd5, _DiffMd5} -> {old_version, NewMd5};
488	    {error, _What} = Err -> Err
489	  end
490      end;
491    Error -> Error
492  end.
493
494compute_new_md5(Md5, [], []) ->
495  compute_new_md5_1(Md5, [], []);
496compute_new_md5(Md5, RemoveFiles0, AddFiles0) ->
497  %% Assume that files are first removed and then added. Files that
498  %% are both removed and added will be checked for consistency in the
499  %% normal way. If they have moved, we assume that they differ.
500  RemoveFiles = RemoveFiles0 -- AddFiles0,
501  AddFiles = AddFiles0 -- RemoveFiles0,
502  InitDiffList = init_diff_list(RemoveFiles, AddFiles),
503  case init_md5_list(Md5, RemoveFiles, AddFiles) of
504    {ok, NewMd5} -> compute_new_md5_1(NewMd5, [], InitDiffList);
505    {error, _What} = Error -> Error
506  end.
507
508compute_new_md5_1([{File, Md5} = Entry|Entries], NewList, Diff) ->
509  case compute_md5_from_file(File) of
510    Md5 -> compute_new_md5_1(Entries, [Entry|NewList], Diff);
511    NewMd5 ->
512      ModName = beam_file_to_module(File),
513      compute_new_md5_1(Entries, [{File, NewMd5}|NewList], [{differ, ModName}|Diff])
514  end;
515compute_new_md5_1([], _NewList, []) ->
516  ok;
517compute_new_md5_1([], NewList, Diff) ->
518  {differ, lists:keysort(1, NewList), Diff}.
519
520-spec compute_implementation_md5() -> [file_md5()].
521
522compute_implementation_md5() ->
523  Dir = code:lib_dir(dialyzer),
524  Files1 = ["erl_bif_types.beam", "erl_types.beam"],
525  Files2 = [filename:join([Dir, "ebin", F]) || F <- Files1],
526  compute_md5_from_files(Files2).
527
528-spec compute_md5_from_files([file:filename()]) -> [file_md5()].
529
530compute_md5_from_files(Files) ->
531  lists:keysort(1, [{F, compute_md5_from_file(F)} || F <- Files]).
532
533compute_md5_from_file(File) ->
534  case beam_lib:all_chunks(File) of
535    {ok, _, Chunks} ->
536      %% We cannot use beam_lib:md5 because it does not consider
537      %% the debug_info chunk, where typespecs are likely stored.
538      %% So we consider almost all chunks except the useless ones.
539      Filtered = [[ID, Chunk] || {ID, Chunk} <- Chunks, ID =/= "CInf", ID =/= "Docs"],
540      erlang:md5(lists:sort(Filtered));
541    {error, beam_lib, {file_error, _, enoent}} ->
542      Msg = io_lib:format("File not found: ~ts\n", [File]),
543      plt_error(Msg);
544    {error, beam_lib, _} ->
545      Msg = io_lib:format("Could not compute MD5 for .beam: ~ts\n", [File]),
546      plt_error(Msg)
547  end.
548
549init_diff_list(RemoveFiles, AddFiles) ->
550  RemoveSet0 = sets:from_list([beam_file_to_module(F) || F <- RemoveFiles]),
551  AddSet0 = sets:from_list([beam_file_to_module(F) || F <- AddFiles]),
552  DiffSet = sets:intersection(AddSet0, RemoveSet0),
553  RemoveSet = sets:subtract(RemoveSet0, DiffSet),
554  %% Added files and diff files will appear as diff files from the md5 check.
555  [{removed, F} || F <- sets:to_list(RemoveSet)].
556
557init_md5_list(Md5, RemoveFiles, AddFiles) ->
558  Files = [{remove, F} || F <- RemoveFiles] ++ [{add, F} || F <- AddFiles],
559  DiffFiles = lists:keysort(2, Files),
560  Md5Sorted = lists:keysort(1, Md5),
561  init_md5_list_1(Md5Sorted, DiffFiles, []).
562
563init_md5_list_1([{File, _Md5}|Md5Left], [{remove, File}|DiffLeft], Acc) ->
564  init_md5_list_1(Md5Left, DiffLeft, Acc);
565init_md5_list_1([{File, _Md5} = Entry|Md5Left], [{add, File}|DiffLeft], Acc) ->
566  init_md5_list_1(Md5Left, DiffLeft, [Entry|Acc]);
567init_md5_list_1([{File1, _Md5} = Entry|Md5Left] = Md5List,
568		[{Tag, File2}|DiffLeft] = DiffList, Acc) ->
569  case File1 < File2 of
570    true -> init_md5_list_1(Md5Left, DiffList, [Entry|Acc]);
571    false ->
572      %% Just an assert.
573      true = File1 > File2,
574      case Tag of
575	add -> init_md5_list_1(Md5List, DiffLeft, [{File2, <<>>}|Acc]);
576	remove -> {error, {no_file_to_remove, File2}}
577      end
578  end;
579init_md5_list_1([], DiffList, Acc) ->
580  AddFiles = [{F, <<>>} || {add, F} <- DiffList],
581  {ok, lists:reverse(Acc, AddFiles)};
582init_md5_list_1(Md5List, [], Acc) ->
583  {ok, lists:reverse(Acc, Md5List)}.
584
585-spec delete(plt()) -> 'ok'.
586
587delete(#plt{info = ETSInfo,
588            types = ETSTypes,
589            contracts = ETSContracts,
590            callbacks = ETSCallbacks,
591            exported_types = ETSExpTypes}) ->
592  true = ets:delete(ETSContracts),
593  true = ets:delete(ETSTypes),
594  true = ets:delete(ETSInfo),
595  true = ets:delete(ETSCallbacks),
596  true = ets:delete(ETSExpTypes),
597  ok.
598
599tab2list(Tab) ->
600  dialyzer_utils:ets_tab2list(Tab).
601
602subproc(Fun) ->
603  F = fun() ->
604          exit(try Fun()
605               catch throw:T ->
606                   {thrown, T}
607               end)
608      end,
609  {Pid, Ref} = erlang:spawn_monitor(F),
610  receive {'DOWN', Ref, process, Pid, Return} ->
611    case Return of
612      {thrown, T} -> throw(T);
613      _ -> Return
614    end
615  end.
616
617%%---------------------------------------------------------------------------
618%% Edoc
619
620-spec get_specs(plt()) -> string().
621
622get_specs(#plt{info = Info}) ->
623  %% TODO: Should print contracts as well.
624  L = lists:sort([{MFA, Val} ||
625                   {{_,_,_} = MFA, Val} <- table_to_list(Info)]),
626  lists:flatten(create_specs(L, [])).
627
628beam_file_to_module(Filename) ->
629  list_to_atom(filename:basename(Filename, ".beam")).
630
631-spec get_specs(plt(), atom(), atom(), arity_patt()) -> 'none' | string().
632
633get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) ->
634  MFA = {M, F, A},
635  case ets_table_lookup(Info, MFA) of
636    none -> none;
637    {value, Val} -> lists:flatten(create_specs([{MFA, Val}], []))
638  end.
639
640create_specs([{{M, F, _A}, {Ret, Args}}|Left], M) ->
641  [io_lib:format("-spec ~tw(~ts) -> ~ts\n",
642		 [F, expand_args(Args), erl_types:t_to_string(Ret)])
643   | create_specs(Left, M)];
644create_specs(List = [{{M, _F, _A}, {_Ret, _Args}}| _], _M) ->
645  [io_lib:format("\n\n%% ------- Module: ~w -------\n\n", [M])
646   | create_specs(List, M)];
647create_specs([], _) ->
648  [].
649
650expand_args([]) ->
651  [];
652expand_args([ArgType]) ->
653  case erl_types:t_is_any(ArgType) of
654    true -> ["_"];
655    false -> [erl_types:t_to_string(ArgType)]
656  end;
657expand_args([ArgType|Left]) ->
658  [case erl_types:t_is_any(ArgType) of
659     true -> "_";
660     false -> erl_types:t_to_string(ArgType)
661   end ++
662   ","|expand_args(Left)].
663
664-spec plt_error(deep_string()) -> no_return().
665
666plt_error(Msg) ->
667  throw({dialyzer_error, lists:flatten(Msg)}).
668
669%%---------------------------------------------------------------------------
670%% Ets table
671
672table_to_list(Plt) ->
673  ets:tab2list(Plt).
674
675table_delete_module(Tab, Mod) ->
676  MS = ets:fun2ms(fun({{M, _F, _A}, _Val}) -> M =:= Mod;
677                     ({_, _}) -> false
678                  end),
679  _NumDeleted = ets:select_delete(Tab, MS),
680  Tab.
681
682table_delete_module1(Tab, Mod) ->
683  MS = ets:fun2ms(fun({{M, _F, _A}}) -> M =:= Mod end),
684  _NumDeleted = ets:select_delete(Tab, MS),
685  Tab.
686
687table_delete_module2(Tab, Mod) ->
688  true = ets:delete(Tab, Mod),
689  Tab.
690
691ets_table_delete_list(Tab, [H|T]) ->
692  ets:delete(Tab, H),
693  ets_table_delete_list(Tab, T);
694ets_table_delete_list(Tab, []) ->
695  Tab.
696
697ets_table_lookup(Plt, Obj) ->
698  try ets:lookup_element(Plt, Obj, 2) of
699      Val -> {value, Val}
700  catch
701    _:_ -> none
702  end.
703
704table_lookup_module(Tab, Mod) ->
705  MS = ets:fun2ms(fun({{M, F, A}, V}) when M =:= Mod ->
706                      {{M, F, A}, V} end),
707  List = [begin
708            {V1, V2} = V,
709            {MFA, V1, V2}
710          end || {MFA, V} <- ets:select(Tab, MS)],
711  case List =:= [] of
712    true -> none;
713    false -> {value, List}
714  end.
715
716table_all_modules(Tab) ->
717  Ks = ets:match(Tab, {'$1', '_'}, 100),
718  all_mods(Ks, sets:new()).
719
720all_mods('$end_of_table', S) ->
721  S;
722all_mods({ListsOfKeys, Cont}, S) ->
723  S1 = lists:foldl(fun([{M, _F, _A}], S0) -> sets:add_element(M, S0)
724                   end, S, ListsOfKeys),
725  all_mods(ets:match(Cont), S1).
726
727table_merge([H|T]) ->
728  table_merge(T, H).
729
730table_merge([], Acc) ->
731  Acc;
732table_merge([Plt|Plts], Acc) ->
733  NewAcc = merge_tables(Plt, Acc),
734  table_merge(Plts, NewAcc).
735
736table_disj_merge([H|T]) ->
737  table_disj_merge(T, H).
738
739table_disj_merge([], Acc) ->
740  Acc;
741table_disj_merge([Plt|Plts], Acc) ->
742  case table_is_disjoint(Plt, Acc) of
743    true ->
744      NewAcc = merge_tables(Plt, Acc),
745      table_disj_merge(Plts, NewAcc);
746    false -> throw({dialyzer_error, not_disjoint_plts})
747  end.
748
749sets_merge([H|T]) ->
750  sets_merge(T, H).
751
752sets_merge([], Acc) ->
753  Acc;
754sets_merge([Plt|Plts], Acc) ->
755  NewAcc = merge_tables(Plt, Acc),
756  sets_merge(Plts, NewAcc).
757
758sets_disj_merge([H|T]) ->
759  sets_disj_merge(T, H).
760
761sets_disj_merge([], Acc) ->
762  Acc;
763sets_disj_merge([Plt|Plts], Acc) ->
764  case table_is_disjoint(Plt, Acc) of
765    true ->
766      NewAcc = merge_tables(Plt, Acc),
767      sets_disj_merge(Plts, NewAcc);
768    false -> throw({dialyzer_error, not_disjoint_plts})
769  end.
770
771table_is_disjoint(T1, T2) ->
772  tab_is_disj(ets:first(T1), T1, T2).
773
774tab_is_disj('$end_of_table', _T1, _T2) ->
775  true;
776tab_is_disj(K1, T1, T2) ->
777  case ets:member(T2, K1) of
778    false ->
779      tab_is_disj(ets:next(T1, K1), T1, T2);
780    true ->
781      false
782  end.
783
784merge_tables(T1, T2) ->
785  tab_merge(ets:first(T1), T1, T2).
786
787tab_merge('$end_of_table', T1, T2) ->
788  case ets:first(T1) of % no safe_fixtable()...
789    '$end_of_table' ->
790      true = ets:delete(T1),
791      T2;
792    Key ->
793      tab_merge(Key, T1, T2)
794  end;
795tab_merge(K1, T1, T2) ->
796  Vs = ets:lookup(T1, K1),
797  NextK1 = ets:next(T1, K1),
798  true = ets:delete(T1, K1),
799  true = ets:insert(T2, Vs),
800  tab_merge(NextK1, T1, T2).
801
802%%---------------------------------------------------------------------------
803%% Debug utilities.
804
805-spec pp_non_returning() -> 'ok'.
806
807pp_non_returning() ->
808  PltFile = get_default_plt(),
809  Plt = from_file(PltFile),
810  List = table_to_list(Plt#plt.info),
811  Unit = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List,
812						erl_types:t_is_unit(Ret)],
813  None = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List,
814						erl_types:t_is_none(Ret)],
815  io:format("=========================================\n"),
816  io:format("=                Loops                  =\n"),
817  io:format("=========================================\n\n"),
818  lists:foreach(fun({{M, F, _}, Type}) ->
819		    io:format("~w:~tw~ts.\n",
820			      [M, F, dialyzer_utils:format_sig(Type)])
821		end, lists:sort(Unit)),
822  io:format("\n"),
823  io:format("=========================================\n"),
824  io:format("=                Errors                 =\n"),
825  io:format("=========================================\n\n"),
826  lists:foreach(fun({{M, F, _}, Type}) ->
827		    io:format("~w:~w~s.\n",
828			      [M, F, dialyzer_utils:format_sig(Type)])
829		end, lists:sort(None)),
830  delete(Plt).
831
832-spec pp_mod(atom()) -> 'ok'.
833
834pp_mod(Mod) when is_atom(Mod) ->
835  PltFile = get_default_plt(),
836  Plt = from_file(PltFile),
837  case lookup_module(Plt, Mod) of
838    {value, List} ->
839      lists:foreach(fun({{_, F, _}, Ret, Args}) ->
840			T = erl_types:t_fun(Args, Ret),
841			S = dialyzer_utils:format_sig(T),
842			io:format("-spec ~tw~ts.\n", [F, S])
843		    end, lists:sort(List));
844    none ->
845      io:format("dialyzer: Found no module named '~s' in the PLT\n", [Mod])
846  end,
847  delete(Plt).
848