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 deletes 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 Thrown -> throw(Thrown) 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 {dialyzer_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(hipe), 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 throw({dialyzer_error, Msg}); 544 {error, beam_lib, _} -> 545 Msg = io_lib:format("Could not compute MD5 for .beam: ~ts\n", [File]), 546 throw({dialyzer_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() -> exit(Fun()) end, 604 {Pid, Ref} = erlang:spawn_monitor(F), 605 receive {'DOWN', Ref, process, Pid, Return} -> 606 Return 607 end. 608 609%%--------------------------------------------------------------------------- 610%% Edoc 611 612-spec get_specs(plt()) -> string(). 613 614get_specs(#plt{info = Info}) -> 615 %% TODO: Should print contracts as well. 616 L = lists:sort([{MFA, Val} || 617 {{_,_,_} = MFA, Val} <- table_to_list(Info)]), 618 lists:flatten(create_specs(L, [])). 619 620beam_file_to_module(Filename) -> 621 list_to_atom(filename:basename(Filename, ".beam")). 622 623-spec get_specs(plt(), atom(), atom(), arity_patt()) -> 'none' | string(). 624 625get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) -> 626 MFA = {M, F, A}, 627 case ets_table_lookup(Info, MFA) of 628 none -> none; 629 {value, Val} -> lists:flatten(create_specs([{MFA, Val}], [])) 630 end. 631 632create_specs([{{M, F, _A}, {Ret, Args}}|Left], M) -> 633 [io_lib:format("-spec ~tw(~ts) -> ~ts\n", 634 [F, expand_args(Args), erl_types:t_to_string(Ret)]) 635 | create_specs(Left, M)]; 636create_specs(List = [{{M, _F, _A}, {_Ret, _Args}}| _], _M) -> 637 [io_lib:format("\n\n%% ------- Module: ~w -------\n\n", [M]) 638 | create_specs(List, M)]; 639create_specs([], _) -> 640 []. 641 642expand_args([]) -> 643 []; 644expand_args([ArgType]) -> 645 case erl_types:t_is_any(ArgType) of 646 true -> ["_"]; 647 false -> [erl_types:t_to_string(ArgType)] 648 end; 649expand_args([ArgType|Left]) -> 650 [case erl_types:t_is_any(ArgType) of 651 true -> "_"; 652 false -> erl_types:t_to_string(ArgType) 653 end ++ 654 ","|expand_args(Left)]. 655 656-spec plt_error(deep_string()) -> no_return(). 657 658plt_error(Msg) -> 659 throw({dialyzer_error, lists:flatten(Msg)}). 660 661%%--------------------------------------------------------------------------- 662%% Ets table 663 664table_to_list(Plt) -> 665 ets:tab2list(Plt). 666 667table_delete_module(Tab, Mod) -> 668 MS = ets:fun2ms(fun({{M, _F, _A}, _Val}) -> M =:= Mod; 669 ({_, _}) -> false 670 end), 671 _NumDeleted = ets:select_delete(Tab, MS), 672 Tab. 673 674table_delete_module1(Tab, Mod) -> 675 MS = ets:fun2ms(fun({{M, _F, _A}}) -> M =:= Mod end), 676 _NumDeleted = ets:select_delete(Tab, MS), 677 Tab. 678 679table_delete_module2(Tab, Mod) -> 680 true = ets:delete(Tab, Mod), 681 Tab. 682 683ets_table_delete_list(Tab, [H|T]) -> 684 ets:delete(Tab, H), 685 ets_table_delete_list(Tab, T); 686ets_table_delete_list(Tab, []) -> 687 Tab. 688 689ets_table_lookup(Plt, Obj) -> 690 try ets:lookup_element(Plt, Obj, 2) of 691 Val -> {value, Val} 692 catch 693 _:_ -> none 694 end. 695 696table_lookup_module(Tab, Mod) -> 697 MS = ets:fun2ms(fun({{M, F, A}, V}) when M =:= Mod -> 698 {{M, F, A}, V} end), 699 List = [begin 700 {V1, V2} = V, 701 {MFA, V1, V2} 702 end || {MFA, V} <- ets:select(Tab, MS)], 703 case List =:= [] of 704 true -> none; 705 false -> {value, List} 706 end. 707 708table_all_modules(Tab) -> 709 Ks = ets:match(Tab, {'$1', '_'}, 100), 710 all_mods(Ks, sets:new()). 711 712all_mods('$end_of_table', S) -> 713 S; 714all_mods({ListsOfKeys, Cont}, S) -> 715 S1 = lists:foldl(fun([{M, _F, _A}], S0) -> sets:add_element(M, S0) 716 end, S, ListsOfKeys), 717 all_mods(ets:match(Cont), S1). 718 719table_merge([H|T]) -> 720 table_merge(T, H). 721 722table_merge([], Acc) -> 723 Acc; 724table_merge([Plt|Plts], Acc) -> 725 NewAcc = merge_tables(Plt, Acc), 726 table_merge(Plts, NewAcc). 727 728table_disj_merge([H|T]) -> 729 table_disj_merge(T, H). 730 731table_disj_merge([], Acc) -> 732 Acc; 733table_disj_merge([Plt|Plts], Acc) -> 734 case table_is_disjoint(Plt, Acc) of 735 true -> 736 NewAcc = merge_tables(Plt, Acc), 737 table_disj_merge(Plts, NewAcc); 738 false -> throw({dialyzer_error, not_disjoint_plts}) 739 end. 740 741sets_merge([H|T]) -> 742 sets_merge(T, H). 743 744sets_merge([], Acc) -> 745 Acc; 746sets_merge([Plt|Plts], Acc) -> 747 NewAcc = merge_tables(Plt, Acc), 748 sets_merge(Plts, NewAcc). 749 750sets_disj_merge([H|T]) -> 751 sets_disj_merge(T, H). 752 753sets_disj_merge([], Acc) -> 754 Acc; 755sets_disj_merge([Plt|Plts], Acc) -> 756 case table_is_disjoint(Plt, Acc) of 757 true -> 758 NewAcc = merge_tables(Plt, Acc), 759 sets_disj_merge(Plts, NewAcc); 760 false -> throw({dialyzer_error, not_disjoint_plts}) 761 end. 762 763table_is_disjoint(T1, T2) -> 764 tab_is_disj(ets:first(T1), T1, T2). 765 766tab_is_disj('$end_of_table', _T1, _T2) -> 767 true; 768tab_is_disj(K1, T1, T2) -> 769 case ets:member(T2, K1) of 770 false -> 771 tab_is_disj(ets:next(T1, K1), T1, T2); 772 true -> 773 false 774 end. 775 776merge_tables(T1, T2) -> 777 tab_merge(ets:first(T1), T1, T2). 778 779tab_merge('$end_of_table', T1, T2) -> 780 case ets:first(T1) of % no safe_fixtable()... 781 '$end_of_table' -> 782 true = ets:delete(T1), 783 T2; 784 Key -> 785 tab_merge(Key, T1, T2) 786 end; 787tab_merge(K1, T1, T2) -> 788 Vs = ets:lookup(T1, K1), 789 NextK1 = ets:next(T1, K1), 790 true = ets:delete(T1, K1), 791 true = ets:insert(T2, Vs), 792 tab_merge(NextK1, T1, T2). 793 794%%--------------------------------------------------------------------------- 795%% Debug utilities. 796 797-spec pp_non_returning() -> 'ok'. 798 799pp_non_returning() -> 800 PltFile = get_default_plt(), 801 Plt = from_file(PltFile), 802 List = table_to_list(Plt#plt.info), 803 Unit = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List, 804 erl_types:t_is_unit(Ret)], 805 None = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List, 806 erl_types:t_is_none(Ret)], 807 io:format("=========================================\n"), 808 io:format("= Loops =\n"), 809 io:format("=========================================\n\n"), 810 lists:foreach(fun({{M, F, _}, Type}) -> 811 io:format("~w:~tw~ts.\n", 812 [M, F, dialyzer_utils:format_sig(Type)]) 813 end, lists:sort(Unit)), 814 io:format("\n"), 815 io:format("=========================================\n"), 816 io:format("= Errors =\n"), 817 io:format("=========================================\n\n"), 818 lists:foreach(fun({{M, F, _}, Type}) -> 819 io:format("~w:~w~s.\n", 820 [M, F, dialyzer_utils:format_sig(Type)]) 821 end, lists:sort(None)), 822 delete(Plt). 823 824-spec pp_mod(atom()) -> 'ok'. 825 826pp_mod(Mod) when is_atom(Mod) -> 827 PltFile = get_default_plt(), 828 Plt = from_file(PltFile), 829 case lookup_module(Plt, Mod) of 830 {value, List} -> 831 lists:foreach(fun({{_, F, _}, Ret, Args}) -> 832 T = erl_types:t_fun(Args, Ret), 833 S = dialyzer_utils:format_sig(T), 834 io:format("-spec ~tw~ts.\n", [F, S]) 835 end, lists:sort(List)); 836 none -> 837 io:format("dialyzer: Found no module named '~s' in the PLT\n", [Mod]) 838 end, 839 delete(Plt). 840