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_cl.erl 17%%% Authors : Tobias Lindahl <tobiasl@it.uu.se> 18%%% Kostis Sagonas <kostis@it.uu.se> 19%%% Description : The command line interface for the Dialyzer tool. 20%%% 21%%% Created : 27 Apr 2004 by Tobias Lindahl <tobiasl@it.uu.se> 22%%%------------------------------------------------------------------- 23 24-module(dialyzer_cl). 25 26-export([start/1]). 27 28-include("dialyzer.hrl"). 29-include_lib("kernel/include/file.hrl"). % needed for #file_info{} 30 31-record(cl_state, 32 {backend_pid :: pid() | 'undefined', 33 code_server = none :: 'none' 34 | dialyzer_codeserver:codeserver(), 35 erlang_mode = false :: boolean(), 36 external_calls = [] :: [mfa()], 37 external_types = [] :: [mfa()], 38 legal_warnings = ordsets:new() :: [dial_warn_tag()], 39 mod_deps = dict:new() :: dialyzer_callgraph:mod_deps(), 40 output = standard_io :: io:device(), 41 output_format = formatted :: format(), 42 filename_opt = basename :: fopt(), 43 indent_opt = ?INDENT_OPT :: iopt(), 44 output_plt = none :: 'none' | file:filename(), 45 plt_info = none :: 'none' | dialyzer_plt:plt_info(), 46 report_mode = normal :: rep_mode(), 47 return_status= ?RET_NOTHING_SUSPICIOUS :: dial_ret(), 48 stored_warnings = [] :: [raw_warning()] 49 }). 50 51%%-------------------------------------------------------------------- 52 53-spec start(#options{}) -> {dial_ret(), [dial_warning()]}. 54 55start(#options{analysis_type = AnalysisType} = Options) -> 56 process_flag(trap_exit, true), 57 case AnalysisType of 58 plt_check -> check_plt(Options); 59 plt_build -> build_plt(Options); 60 plt_add -> add_to_plt(Options); 61 plt_remove -> remove_from_plt(Options); 62 succ_typings -> do_analysis(Options) 63 end. 64 65%%-------------------------------------------------------------------- 66 67build_plt(Opts) -> 68 Opts1 = init_opts_for_build(Opts), 69 Files = get_files_from_opts(Opts1), 70 Md5 = dialyzer_plt:compute_md5_from_files(Files), 71 PltInfo = {Md5, dict:new()}, 72 do_analysis(Files, Opts1, dialyzer_plt:new(), PltInfo). 73 74init_opts_for_build(Opts) -> 75 case Opts#options.output_plt =:= none of 76 true -> 77 case Opts#options.init_plts of 78 [] -> Opts#options{output_plt = get_default_output_plt()}; 79 [Plt] -> Opts#options{init_plts = [], output_plt = Plt}; 80 Plts -> 81 Msg = io_lib:format("Could not build multiple PLT files: ~ts\n", 82 [format_plts(Plts)]), 83 cl_error(Msg) 84 end; 85 false -> Opts#options{init_plts = []} 86 end. 87 88%%-------------------------------------------------------------------- 89 90add_to_plt(Opts) -> 91 Opts1 = init_opts_for_add(Opts), 92 AddFiles = get_files_from_opts(Opts1), 93 plt_common(Opts1, [], AddFiles). 94 95init_opts_for_add(Opts) -> 96 case Opts#options.output_plt =:= none of 97 true -> 98 case Opts#options.init_plts of 99 [] -> Opts#options{output_plt = get_default_output_plt(), 100 init_plts = get_default_init_plt()}; 101 [Plt] -> Opts#options{output_plt = Plt}; 102 Plts -> 103 Msg = io_lib:format("Could not add to multiple PLT files: ~ts\n", 104 [format_plts(Plts)]), 105 cl_error(Msg) 106 end; 107 false -> 108 case Opts#options.init_plts =:= [] of 109 true -> Opts#options{init_plts = get_default_init_plt()}; 110 false -> Opts 111 end 112 end. 113 114%%-------------------------------------------------------------------- 115 116check_plt(#options{init_plts = []} = Opts) -> 117 Opts1 = init_opts_for_check(Opts), 118 report_check(Opts1), 119 plt_common(Opts1, [], []); 120check_plt(#options{init_plts = Plts} = Opts) -> 121 check_plt_aux(Plts, Opts). 122 123check_plt_aux([_] = Plt, Opts) -> 124 Opts1 = Opts#options{init_plts = Plt}, 125 Opts2 = init_opts_for_check(Opts1), 126 report_check(Opts2), 127 plt_common(Opts2, [], []); 128check_plt_aux([Plt|Plts], Opts) -> 129 case check_plt_aux([Plt], Opts) of 130 {?RET_NOTHING_SUSPICIOUS, []} -> check_plt_aux(Plts, Opts); 131 {?RET_DISCREPANCIES, Warns} -> 132 {_RET, MoreWarns} = check_plt_aux(Plts, Opts), 133 {?RET_DISCREPANCIES, Warns ++ MoreWarns} 134 end. 135 136init_opts_for_check(Opts) -> 137 InitPlt = 138 case Opts#options.init_plts of 139 []-> get_default_init_plt(); 140 Plt -> Plt 141 end, 142 [OutputPlt] = InitPlt, 143 Opts#options{files = [], 144 files_rec = [], 145 analysis_type = plt_check, 146 defines = [], 147 from = byte_code, 148 init_plts = InitPlt, 149 include_dirs = [], 150 output_plt = OutputPlt, 151 use_contracts = true 152 }. 153 154%%-------------------------------------------------------------------- 155 156remove_from_plt(Opts) -> 157 Opts1 = init_opts_for_remove(Opts), 158 Files = get_files_from_opts(Opts1), 159 plt_common(Opts1, Files, []). 160 161init_opts_for_remove(Opts) -> 162 case Opts#options.output_plt =:= none of 163 true -> 164 case Opts#options.init_plts of 165 [] -> Opts#options{output_plt = get_default_output_plt(), 166 init_plts = get_default_init_plt()}; 167 [Plt] -> Opts#options{output_plt = Plt}; 168 Plts -> 169 Msg = io_lib:format("Could not remove from multiple PLT files: ~ts\n", 170 [format_plts(Plts)]), 171 cl_error(Msg) 172 end; 173 false -> 174 case Opts#options.init_plts =:= [] of 175 true -> Opts#options{init_plts = get_default_init_plt()}; 176 false -> Opts 177 end 178 end. 179 180%%-------------------------------------------------------------------- 181 182plt_common(#options{init_plts = [InitPlt]} = Opts, RemoveFiles, AddFiles) -> 183 case check_plt(Opts, RemoveFiles, AddFiles) of 184 ok -> 185 case Opts#options.output_plt of 186 none -> ok; 187 InitPlt -> ok; 188 OutPlt -> 189 {ok, Binary} = file:read_file(InitPlt), 190 ok = file:write_file(OutPlt, Binary) 191 end, 192 case Opts#options.report_mode of 193 quiet -> ok; 194 _ -> io:put_chars(" yes\n") 195 end, 196 {?RET_NOTHING_SUSPICIOUS, []}; 197 {old_version, Md5} -> 198 PltInfo = {Md5, dict:new()}, 199 Files = [F || {F, _} <- Md5], 200 do_analysis(Files, Opts, dialyzer_plt:new(), PltInfo); 201 {differ, Md5, DiffMd5, ModDeps} -> 202 report_failed_plt_check(Opts, DiffMd5), 203 {AnalFiles, RemovedMods, ModDeps1} = 204 expand_dependent_modules(Md5, DiffMd5, ModDeps), 205 Plt = clean_plt(InitPlt, RemovedMods), 206 case AnalFiles =:= [] of 207 true -> 208 %% Only removed stuff. Just write the PLT. 209 dialyzer_plt:to_file(Opts#options.output_plt, Plt, ModDeps, 210 {Md5, ModDeps}), 211 {?RET_NOTHING_SUSPICIOUS, []}; 212 false -> 213 do_analysis(AnalFiles, Opts, Plt, {Md5, ModDeps1}) 214 end; 215 {error, no_such_file} -> 216 Msg = io_lib:format("Could not find the PLT: ~ts\n~s", 217 [InitPlt, default_plt_error_msg()]), 218 cl_error(Msg); 219 {error, not_valid} -> 220 Msg = io_lib:format("The file: ~ts is not a valid PLT file\n~s", 221 [InitPlt, default_plt_error_msg()]), 222 cl_error(Msg); 223 {error, read_error} -> 224 Msg = io_lib:format("Could not read the PLT: ~ts\n~s", 225 [InitPlt, default_plt_error_msg()]), 226 cl_error(Msg); 227 {error, {no_file_to_remove, F}} -> 228 Msg = io_lib:format("Could not remove the file ~ts from the PLT: ~ts\n", 229 [F, InitPlt]), 230 cl_error(Msg) 231 end. 232 233default_plt_error_msg() -> 234 "Use the options:\n" 235 " --build_plt to build a new PLT; or\n" 236 " --add_to_plt to add to an existing PLT\n" 237 "\n" 238 "For example, use a command like the following:\n" 239 " dialyzer --build_plt --apps erts kernel stdlib mnesia\n" 240 "Note that building a PLT such as the above may take 20 mins or so\n" 241 "\n" 242 "If you later need information about other applications, say crypto,\n" 243 "you can extend the PLT by the command:\n" 244 " dialyzer --add_to_plt --apps crypto\n" 245 "For applications that are not in Erlang/OTP use an absolute file name.\n". 246 247%%-------------------------------------------------------------------- 248 249check_plt(#options{init_plts = [Plt]} = Opts, RemoveFiles, AddFiles) -> 250 case dialyzer_plt:check_plt(Plt, RemoveFiles, AddFiles) of 251 {old_version, _MD5} = OldVersion -> 252 report_old_version(Opts), 253 OldVersion; 254 {differ, _MD5, _DiffMd5, _ModDeps} = Differ -> 255 Differ; 256 ok -> 257 ok; 258 {error, _Reason} = Error -> 259 Error 260 end. 261 262%%-------------------------------------------------------------------- 263 264report_check(#options{report_mode = ReportMode, init_plts = [InitPlt]}) -> 265 case ReportMode of 266 quiet -> ok; 267 _ -> 268 io:format(" Checking whether the PLT ~ts is up-to-date...", [InitPlt]) 269 end. 270 271report_old_version(#options{report_mode = ReportMode, init_plts = [InitPlt]}) -> 272 case ReportMode of 273 quiet -> ok; 274 _ -> 275 io:put_chars(" no\n"), 276 io:format(" (the PLT ~ts was built with an old version of Dialyzer)\n", 277 [InitPlt]) 278 end. 279 280report_failed_plt_check(#options{analysis_type = AnalType, 281 report_mode = ReportMode}, DiffMd5) -> 282 case AnalType =:= plt_check of 283 true -> 284 case ReportMode of 285 quiet -> ok; 286 normal -> io:format(" no\n", []); 287 verbose -> report_md5_diff(DiffMd5) 288 end; 289 false -> ok 290 end. 291 292report_analysis_start(#options{analysis_type = Type, 293 report_mode = ReportMode, 294 init_plts = InitPlts, 295 output_plt = OutputPlt}) -> 296 case ReportMode of 297 quiet -> ok; 298 _ -> 299 io:format(" "), 300 case Type of 301 plt_add -> 302 [InitPlt] = InitPlts, 303 case InitPlt =:= OutputPlt of 304 true -> io:format("Adding information to ~ts...", [OutputPlt]); 305 false -> io:format("Adding information from ~ts to ~ts...", 306 [InitPlt, OutputPlt]) 307 end; 308 plt_build -> 309 io:format("Creating PLT ~ts ...", [OutputPlt]); 310 plt_check -> 311 io:format("Rebuilding the information in ~ts...", [OutputPlt]); 312 plt_remove -> 313 [InitPlt] = InitPlts, 314 case InitPlt =:= OutputPlt of 315 true -> io:format("Removing information from ~ts...", [OutputPlt]); 316 false -> io:format("Removing information from ~ts to ~ts...", 317 [InitPlt, OutputPlt]) 318 end; 319 succ_typings -> io:format("Proceeding with analysis...") 320 end 321 end. 322 323report_elapsed_time(T1, T2, #options{report_mode = ReportMode}) -> 324 case ReportMode of 325 quiet -> ok; 326 _ -> 327 ElapsedTime = T2 - T1, 328 Mins = ElapsedTime div 60000, 329 Secs = (ElapsedTime rem 60000) / 1000, 330 io:format(" done in ~wm~.2fs\n", [Mins, Secs]) 331 end. 332 333report_md5_diff(List) -> 334 io:format(" The PLT information is not up to date:\n", []), 335 case [Mod || {removed, Mod} <- List] of 336 [] -> ok; 337 RemovedMods -> io:format(" Removed modules: ~p\n", [RemovedMods]) 338 end, 339 case [Mod || {differ, Mod} <- List] of 340 [] -> ok; 341 ChangedMods -> io:format(" Changed modules: ~p\n", [ChangedMods]) 342 end. 343 344%%-------------------------------------------------------------------- 345 346get_default_init_plt() -> 347 [dialyzer_plt:get_default_plt()]. 348 349get_default_output_plt() -> 350 dialyzer_plt:get_default_plt(). 351 352%%-------------------------------------------------------------------- 353 354format_plts([Plt]) -> Plt; 355format_plts([Plt|Plts]) -> 356 Plt ++ ", " ++ format_plts(Plts). 357 358%%-------------------------------------------------------------------- 359 360do_analysis(Options) -> 361 Files = get_files_from_opts(Options), 362 case Options#options.init_plts of 363 [] -> do_analysis(Files, Options, dialyzer_plt:new(), none); 364 PltFiles -> 365 Plts = [dialyzer_plt:from_file(F) || F <- PltFiles], 366 Plt = dialyzer_plt:merge_plts_or_report_conflicts(PltFiles, Plts), 367 do_analysis(Files, Options, Plt, none) 368 end. 369 370do_analysis(Files, Options, Plt, PltInfo) -> 371 assert_writable(Options#options.output_plt), 372 report_analysis_start(Options), 373 State0 = new_state(), 374 State1 = init_output(State0, Options), 375 State2 = State1#cl_state{legal_warnings = Options#options.legal_warnings, 376 output_plt = Options#options.output_plt, 377 plt_info = PltInfo, 378 erlang_mode = Options#options.erlang_mode, 379 report_mode = Options#options.report_mode}, 380 AnalysisType = convert_analysis_type(Options#options.analysis_type, 381 Options#options.get_warnings), 382 InitAnalysis = #analysis{type = AnalysisType, 383 defines = Options#options.defines, 384 include_dirs = Options#options.include_dirs, 385 files = Files, 386 start_from = Options#options.from, 387 timing = Options#options.timing, 388 plt = Plt, 389 use_contracts = Options#options.use_contracts, 390 callgraph_file = Options#options.callgraph_file, 391 solvers = Options#options.solvers}, 392 State3 = start_analysis(State2, InitAnalysis), 393 {T1, _} = statistics(wall_clock), 394 Return = cl_loop(State3), 395 {T2, _} = statistics(wall_clock), 396 report_elapsed_time(T1, T2, Options), 397 Return. 398 399convert_analysis_type(plt_check, true) -> succ_typings; 400convert_analysis_type(plt_check, false) -> plt_build; 401convert_analysis_type(plt_add, true) -> succ_typings; 402convert_analysis_type(plt_add, false) -> plt_build; 403convert_analysis_type(plt_build, true) -> succ_typings; 404convert_analysis_type(plt_build, false) -> plt_build; 405convert_analysis_type(plt_remove, true) -> succ_typings; 406convert_analysis_type(plt_remove, false) -> plt_build; 407convert_analysis_type(succ_typings, _) -> succ_typings. 408 409%%-------------------------------------------------------------------- 410 411assert_writable(none) -> 412 ok; 413assert_writable(PltFile) -> 414 case check_if_writable(PltFile) of 415 true -> ok; 416 false -> 417 Msg = io_lib:format(" The PLT file ~ts is not writable", [PltFile]), 418 cl_error(Msg) 419 end. 420 421check_if_writable(PltFile) -> 422 case filelib:is_regular(PltFile) of 423 true -> is_writable_file_or_dir(PltFile); 424 false -> 425 case filelib:is_dir(PltFile) of 426 true -> false; 427 false -> 428 DirName = filename:dirname(PltFile), 429 filelib:is_dir(DirName) andalso is_writable_file_or_dir(DirName) 430 end 431 end. 432 433is_writable_file_or_dir(PltFile) -> 434 case file:read_file_info(PltFile) of 435 {ok, #file_info{access = A}} -> 436 (A =:= write) orelse (A =:= read_write); 437 {error, _} -> 438 false 439 end. 440 441%%-------------------------------------------------------------------- 442 443clean_plt(PltFile, RemovedMods) -> 444 %% Clean the plt from the removed modules. 445 Plt = dialyzer_plt:from_file(PltFile), 446 sets:fold(fun(M, AccPlt) -> dialyzer_plt:delete_module(AccPlt, M) end, 447 Plt, RemovedMods). 448 449expand_dependent_modules(Md5, DiffMd5, ModDeps) -> 450 ChangedMods = sets:from_list([M || {differ, M} <- DiffMd5]), 451 RemovedMods = sets:from_list([M || {removed, M} <- DiffMd5]), 452 BigSet = sets:union(ChangedMods, RemovedMods), 453 BigList = sets:to_list(BigSet), 454 ExpandedSet = expand_dependent_modules_1(BigList, BigSet, ModDeps), 455 NewModDeps = dialyzer_callgraph:strip_module_deps(ModDeps, BigSet), 456 AnalyzeMods = sets:subtract(ExpandedSet, RemovedMods), 457 FilterFun = fun(File) -> 458 Mod = list_to_atom(filename:basename(File, ".beam")), 459 sets:is_element(Mod, AnalyzeMods) 460 end, 461 {[F || {F, _} <- Md5, FilterFun(F)], BigSet, NewModDeps}. 462 463expand_dependent_modules_1([Mod|Mods], Included, ModDeps) -> 464 case dict:find(Mod, ModDeps) of 465 {ok, Deps} -> 466 NewDeps = sets:subtract(sets:from_list(Deps), Included), 467 case sets:size(NewDeps) =:= 0 of 468 true -> expand_dependent_modules_1(Mods, Included, ModDeps); 469 false -> 470 NewIncluded = sets:union(Included, NewDeps), 471 expand_dependent_modules_1(sets:to_list(NewDeps) ++ Mods, 472 NewIncluded, ModDeps) 473 end; 474 error -> 475 expand_dependent_modules_1(Mods, Included, ModDeps) 476 end; 477expand_dependent_modules_1([], Included, _ModDeps) -> 478 Included. 479 480new_state() -> 481 #cl_state{}. 482 483init_output(State0, #options{output_file = OutFile, 484 output_format = OutFormat, 485 filename_opt = FOpt, 486 indent_opt = IOpt}) -> 487 State = State0#cl_state{output_format = OutFormat, 488 filename_opt = FOpt, 489 indent_opt = IOpt}, 490 case OutFile =:= none of 491 true -> 492 State; 493 false -> 494 case file:open(OutFile, [write]) of 495 {ok, File} -> 496 %% Warnings and errors can include Unicode characters. 497 ok = io:setopts(File, [{encoding, unicode}]), 498 State#cl_state{output = File}; 499 {error, Reason} -> 500 Msg = io_lib:format("Could not open output file ~tp, Reason: ~p\n", 501 [OutFile, Reason]), 502 cl_error(State, lists:flatten(Msg)) 503 end 504 end. 505 506-spec maybe_close_output_file(#cl_state{}) -> 'ok'. 507 508maybe_close_output_file(State) -> 509 case State#cl_state.output of 510 standard_io -> ok; 511 File -> ok = file:close(File) 512 end. 513 514%% ---------------------------------------------------------------- 515%% 516%% Main Loop 517%% 518 519-define(LOG_CACHE_SIZE, 10). 520 521%%-spec cl_loop(#cl_state{}) -> 522cl_loop(State) -> 523 cl_loop(State, []). 524 525cl_loop(State, LogCache) -> 526 BackendPid = State#cl_state.backend_pid, 527 receive 528 {BackendPid, log, LogMsg} -> 529 %%io:format(State#cl_state.output ,"Log: ~s\n", [LogMsg]), 530 cl_loop(State, lists:sublist([LogMsg|LogCache], ?LOG_CACHE_SIZE)); 531 {BackendPid, warnings, Warnings} -> 532 NewState = store_warnings(State, Warnings), 533 cl_loop(NewState, LogCache); 534 {BackendPid, cserver, CodeServer, _Plt} -> % Plt is ignored 535 NewState = State#cl_state{code_server = CodeServer}, 536 cl_loop(NewState, LogCache); 537 {BackendPid, done, NewPlt, _NewDocPlt} -> 538 return_value(State, NewPlt); 539 {BackendPid, ext_calls, ExtCalls} -> 540 cl_loop(State#cl_state{external_calls = ExtCalls}, LogCache); 541 {BackendPid, ext_types, ExtTypes} -> 542 cl_loop(State#cl_state{external_types = ExtTypes}, LogCache); 543 {BackendPid, mod_deps, ModDeps} -> 544 NewState = State#cl_state{mod_deps = ModDeps}, 545 cl_loop(NewState, LogCache); 546 {'EXIT', BackendPid, {error, Reason}} -> 547 Msg = failed_anal_msg(Reason, LogCache), 548 cl_error(State, Msg); 549 {'EXIT', BackendPid, Reason} when Reason =/= 'normal' -> 550 Msg = failed_anal_msg(io_lib:format("~p", [Reason]), LogCache), 551 cl_error(State, Msg); 552 _Other -> 553 %% io:format("Received ~p\n", [_Other]), 554 cl_loop(State, LogCache) 555 end. 556 557-spec failed_anal_msg(string(), [_]) -> nonempty_string(). 558 559failed_anal_msg(Reason, LogCache) -> 560 Msg = "Analysis failed with error:\n" ++ lists:flatten(Reason) ++ "\n", 561 case LogCache =:= [] of 562 true -> Msg; 563 false -> 564 Msg ++ "Last messages in the log cache:\n " ++ format_log_cache(LogCache) 565 end. 566 567%% 568%% formats the log cache (treating it as a string) for pretty-printing 569%% 570format_log_cache(LogCache) -> 571 Str = lists:append(lists:reverse(LogCache)), 572 lists:join("\n ", string:lexemes(Str, "\n")). 573 574-spec store_warnings(#cl_state{}, [raw_warning()]) -> #cl_state{}. 575 576store_warnings(#cl_state{stored_warnings = StoredWarnings} = St, Warnings) -> 577 St#cl_state{stored_warnings = StoredWarnings ++ Warnings}. 578 579-spec cl_error(string()) -> no_return(). 580 581cl_error(Msg) -> 582 throw({dialyzer_error, lists:flatten(Msg)}). 583 584-spec cl_error(#cl_state{}, string()) -> no_return(). 585 586cl_error(State, Msg) -> 587 case State#cl_state.output of 588 standard_io -> ok; 589 Outfile -> io:format(Outfile, "\n~ts\n", [Msg]) 590 end, 591 maybe_close_output_file(State), 592 throw({dialyzer_error, lists:flatten(Msg)}). 593 594return_value(State = #cl_state{code_server = CodeServer, 595 erlang_mode = ErlangMode, 596 mod_deps = ModDeps, 597 output_plt = OutputPlt, 598 plt_info = PltInfo, 599 stored_warnings = StoredWarnings}, 600 Plt) -> 601 %% Just for now: 602 case CodeServer =:= none of 603 true -> 604 ok; 605 false -> 606 dialyzer_codeserver:delete(CodeServer) 607 end, 608 case OutputPlt =:= none of 609 true -> 610 dialyzer_plt:delete(Plt); 611 false -> 612 dialyzer_plt:to_file(OutputPlt, Plt, ModDeps, PltInfo) 613 end, 614 UnknownWarnings = unknown_warnings(State), 615 RetValue = 616 case StoredWarnings =:= [] andalso UnknownWarnings =:= [] of 617 true -> ?RET_NOTHING_SUSPICIOUS; 618 false -> ?RET_DISCREPANCIES 619 end, 620 case ErlangMode of 621 false -> 622 print_warnings(State), 623 print_ext_calls(State), 624 print_ext_types(State), 625 maybe_close_output_file(State), 626 {RetValue, []}; 627 true -> 628 AllWarnings = 629 UnknownWarnings ++ process_warnings(StoredWarnings), 630 {RetValue, set_warning_id(AllWarnings)} 631 end. 632 633unknown_warnings(State = #cl_state{legal_warnings = LegalWarnings}) -> 634 Unknown = case ordsets:is_element(?WARN_UNKNOWN, LegalWarnings) of 635 true -> 636 unknown_functions(State) ++ 637 unknown_types(State); 638 false -> [] 639 end, 640 WarningInfo = {_Filename = "", _Line = 0, _MorMFA = ''}, 641 [{?WARN_UNKNOWN, WarningInfo, W} || W <- Unknown]. 642 643unknown_functions(#cl_state{external_calls = Calls}) -> 644 [{unknown_function, MFA} || MFA <- Calls]. 645 646set_warning_id(Warnings) -> 647 lists:map(fun({Tag, {File, Line, _MorMFA}, Msg}) -> 648 {Tag, {File, Line}, Msg} 649 end, Warnings). 650 651print_ext_calls(#cl_state{report_mode = quiet}) -> 652 ok; 653print_ext_calls(#cl_state{output = Output, 654 external_calls = Calls, 655 stored_warnings = Warnings, 656 output_format = Format}) -> 657 case Calls =:= [] of 658 true -> ok; 659 false -> 660 case Warnings =:= [] of 661 true -> io:nl(Output); %% Need to do a newline first 662 false -> ok 663 end, 664 case Format of 665 formatted -> 666 io:put_chars(Output, "Unknown functions:\n"), 667 do_print_ext_calls(Output, Calls, " "); 668 raw -> 669 io:put_chars(Output, "%% Unknown functions:\n"), 670 do_print_ext_calls(Output, Calls, "%% ") 671 end 672 end. 673 674do_print_ext_calls(Output, [{M,F,A}|T], Before) -> 675 io:format(Output, "~s~tp:~tp/~p\n", [Before,M,F,A]), 676 do_print_ext_calls(Output, T, Before); 677do_print_ext_calls(_, [], _) -> 678 ok. 679 680unknown_types(#cl_state{external_types = Types}) -> 681 [{unknown_type, MFA} || MFA <- Types]. 682 683print_ext_types(#cl_state{report_mode = quiet}) -> 684 ok; 685print_ext_types(#cl_state{output = Output, 686 external_calls = Calls, 687 external_types = Types, 688 stored_warnings = Warnings, 689 output_format = Format}) -> 690 case Types =:= [] of 691 true -> ok; 692 false -> 693 case Warnings =:= [] andalso Calls =:= [] of 694 true -> io:nl(Output); %% Need to do a newline first 695 false -> ok 696 end, 697 case Format of 698 formatted -> 699 io:put_chars(Output, "Unknown types:\n"), 700 do_print_ext_types(Output, Types, " "); 701 raw -> 702 io:put_chars(Output, "%% Unknown types:\n"), 703 do_print_ext_types(Output, Types, "%% ") 704 end 705 end. 706 707do_print_ext_types(Output, [{M,F,A}|T], Before) -> 708 io:format(Output, "~s~tp:~tp/~p\n", [Before,M,F,A]), 709 do_print_ext_types(Output, T, Before); 710do_print_ext_types(_, [], _) -> 711 ok. 712 713print_warnings(#cl_state{stored_warnings = []}) -> 714 ok; 715print_warnings(#cl_state{output = Output, 716 output_format = Format, 717 filename_opt = FOpt, 718 indent_opt = IOpt, 719 stored_warnings = Warnings}) -> 720 PrWarnings = process_warnings(Warnings), 721 case PrWarnings of 722 [] -> ok; 723 [_|_] -> 724 S = case Format of 725 formatted -> 726 Opts = [{filename_opt, FOpt}, {indent_opt, IOpt}], 727 [dialyzer:format_warning(W, Opts) || W <- PrWarnings]; 728 raw -> 729 [io_lib:format("~tp. \n", 730 [W]) || W <- set_warning_id(PrWarnings)] 731 end, 732 io:format(Output, "\n~ts", [S]) 733 end. 734 735-spec process_warnings([raw_warning()]) -> [raw_warning()]. 736 737process_warnings(Warnings) -> 738 Warnings1 = lists:keysort(2, Warnings), %% Sort on file/line (and m/mfa..) 739 remove_duplicate_warnings(Warnings1, []). 740 741remove_duplicate_warnings([Duplicate, Duplicate|Left], Acc) -> 742 remove_duplicate_warnings([Duplicate|Left], Acc); 743remove_duplicate_warnings([NotDuplicate|Left], Acc) -> 744 remove_duplicate_warnings(Left, [NotDuplicate|Acc]); 745remove_duplicate_warnings([], Acc) -> 746 lists:reverse(Acc). 747 748get_files_from_opts(Options) -> 749 From = Options#options.from, 750 Files1 = add_files(Options#options.files, From), 751 Files2 = add_files_rec(Options#options.files_rec, From), 752 ordsets:union(Files1, Files2). 753 754add_files_rec(Files, From) -> 755 add_files(Files, From, true). 756 757add_files(Files, From) -> 758 add_files(Files, From, false). 759 760add_files(Files, From, Rec) -> 761 Files1 = [filename:absname(F) || F <- Files], 762 Files2 = ordsets:from_list(Files1), 763 Dirs = ordsets:filter(fun(X) -> filelib:is_dir(X) end, Files2), 764 Files3 = ordsets:subtract(Files2, Dirs), 765 Extension = case From of 766 byte_code -> ".beam"; 767 src_code -> ".erl" 768 end, 769 Fun = add_file_fun(Extension), 770 lists:foldl(fun(Dir, Acc) -> 771 filelib:fold_files(Dir, Extension, Rec, Fun, Acc) 772 end, Files3, Dirs). 773 774add_file_fun(Extension) -> 775 fun(File, AccFiles) -> 776 case filename:extension(File) =:= Extension of 777 true -> 778 AbsName = filename:absname(File), 779 ordsets:add_element(AbsName, AccFiles); 780 false -> AccFiles 781 end 782 end. 783 784-spec start_analysis(#cl_state{}, #analysis{}) -> #cl_state{}. 785 786start_analysis(State, Analysis) -> 787 Self = self(), 788 LegalWarnings = State#cl_state.legal_warnings, 789 Fun = fun() -> 790 dialyzer_analysis_callgraph:start(Self, LegalWarnings, Analysis) 791 end, 792 BackendPid = spawn_link(Fun), 793 State#cl_state{backend_pid = BackendPid}. 794 795