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