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-module(dialyzer_cl_parse).
16
17-export([start/0, get_lib_dir/1]).
18-export([collect_args/1]).	% used also by typer
19
20-include("dialyzer.hrl").
21
22%%-----------------------------------------------------------------------
23
24-type dial_cl_parse_ret() :: {'check_init', #options{}}
25                           | {'plt_info', #options{}}
26                           | {'cl', #options{}}
27                           | {'gui', #options{}}
28                           | {'error', string()}.
29
30-type deep_string() :: string() | [deep_string()].
31
32%%-----------------------------------------------------------------------
33
34-spec start() -> dial_cl_parse_ret().
35
36start() ->
37  init(),
38  Args = init:get_plain_arguments(),
39  try
40    Ret = cl(Args),
41    Ret
42  catch
43    throw:{dialyzer_cl_parse_error, Msg} -> {error, Msg};
44    _:R:S ->
45      Msg = io_lib:format("~tp\n~tp\n", [R, S]),
46      {error, lists:flatten(Msg)}
47  end.
48
49cl(["--add_to_plt"|T]) ->
50  put(dialyzer_options_analysis_type, plt_add),
51  cl(T);
52cl(["--apps"|T]) ->
53  T1 = get_lib_dir(T),
54  {Args, T2} = collect_args(T1),
55  append_var(dialyzer_options_files_rec, Args),
56  cl(T2);
57cl(["--build_plt"|T]) ->
58  put(dialyzer_options_analysis_type, plt_build),
59  cl(T);
60cl(["--check_plt"|T]) ->
61  put(dialyzer_options_analysis_type, plt_check),
62  cl(T);
63cl(["-n"|T]) ->
64  cl(["--no_check_plt"|T]);
65cl(["--no_check_plt"|T]) ->
66  put(dialyzer_options_check_plt, false),
67  cl(T);
68cl(["-nn"|T]) ->
69  cl(["--no_native"|T]);
70cl(["--no_native"|T]) ->
71  put(dialyzer_options_native, false),
72  cl(T);
73cl(["--no_native_cache"|T]) ->
74  put(dialyzer_options_native_cache, false),
75  cl(T);
76cl(["--plt_info"|T]) ->
77  put(dialyzer_options_analysis_type, plt_info),
78  cl(T);
79cl(["--get_warnings"|T]) ->
80  put(dialyzer_options_get_warnings, true),
81  cl(T);
82cl(["-D"|_]) ->
83  cl_error("No defines specified after -D");
84cl(["-D"++Define|T]) ->
85  Def = re:split(Define, "=", [{return, list}, unicode]),
86  append_defines(Def),
87  cl(T);
88cl(["-h"|_]) ->
89  help_message();
90cl(["--help"|_]) ->
91  help_message();
92cl(["-I"]) ->
93  cl_error("no include directory specified after -I");
94cl(["-I", Dir|T]) ->
95  append_include(Dir),
96  cl(T);
97cl(["-I"++Dir|T]) ->
98  append_include(Dir),
99  cl(T);
100cl(["-c"++_|T]) ->
101  NewTail = command_line(T),
102  cl(NewTail);
103cl(["-r"++_|T0]) ->
104  {Args, T} = collect_args(T0),
105  append_var(dialyzer_options_files_rec, Args),
106  cl(T);
107cl(["--remove_from_plt"|T]) ->
108  put(dialyzer_options_analysis_type, plt_remove),
109  cl(T);
110cl(["--com"++_|T]) ->
111  NewTail = command_line(T),
112  cl(NewTail);
113cl(["--output"]) ->
114  cl_error("No outfile specified");
115cl(["-o"]) ->
116  cl_error("No outfile specified");
117cl(["--output",Output|T]) ->
118  put(dialyzer_output, Output),
119  cl(T);
120cl(["--output_plt"]) ->
121  cl_error("No outfile specified for --output_plt");
122cl(["--output_plt",Output|T]) ->
123  put(dialyzer_output_plt, Output),
124  cl(T);
125cl(["-o", Output|T]) ->
126  put(dialyzer_output, Output),
127  cl(T);
128cl(["-o"++Output|T]) ->
129  put(dialyzer_output, Output),
130  cl(T);
131cl(["--raw"|T]) ->
132  put(dialyzer_output_format, raw),
133  cl(T);
134cl(["--fullpath"|T]) ->
135  put(dialyzer_filename_opt, fullpath),
136  cl(T);
137cl(["-pa", Path|T]) ->
138  case code:add_patha(Path) of
139    true -> cl(T);
140    {error, _} -> cl_error("Bad directory for -pa: " ++ Path)
141  end;
142cl(["--plt"]) ->
143  error("No plt specified for --plt");
144cl(["--plt", PLT|T]) ->
145  put(dialyzer_init_plts, [PLT]),
146  cl(T);
147cl(["--plts"]) ->
148  error("No plts specified for --plts");
149cl(["--plts"|T]) ->
150  {PLTs, NewT} = get_plts(T, []),
151  put(dialyzer_init_plts, PLTs),
152  cl(NewT);
153cl(["-q"|T]) ->
154  put(dialyzer_options_report_mode, quiet),
155  cl(T);
156cl(["--quiet"|T]) ->
157  put(dialyzer_options_report_mode, quiet),
158  cl(T);
159cl(["--src"|T]) ->
160  put(dialyzer_options_from, src_code),
161  cl(T);
162cl(["--no_spec"|T]) ->
163  put(dialyzer_options_use_contracts, false),
164  cl(T);
165cl(["--statistics"|T]) ->
166  put(dialyzer_timing, true),
167  cl(T);
168cl(["--resources"|T]) ->
169  put(dialyzer_options_report_mode, quiet),
170  put(dialyzer_timing, debug),
171  cl(T);
172cl(["-v"|_]) ->
173  io:format("Dialyzer version "++?VSN++"\n"),
174  erlang:halt(?RET_NOTHING_SUSPICIOUS);
175cl(["--version"|_]) ->
176  io:format("Dialyzer version "++?VSN++"\n"),
177  erlang:halt(?RET_NOTHING_SUSPICIOUS);
178cl(["--verbose"|T]) ->
179  put(dialyzer_options_report_mode, verbose),
180  cl(T);
181cl(["-W"|_]) ->
182  cl_error("-W given without warning");
183cl(["-Whelp"|_]) ->
184  help_warnings();
185cl(["-W"++Warn|T]) ->
186  append_var(dialyzer_warnings, [list_to_atom(Warn)]),
187  cl(T);
188cl(["--dump_callgraph"]) ->
189  cl_error("No outfile specified for --dump_callgraph");
190cl(["--dump_callgraph", File|T]) ->
191  put(dialyzer_callgraph_file, File),
192  cl(T);
193cl(["--gui"|T]) ->
194  put(dialyzer_options_mode, gui),
195  cl(T);
196cl(["--solver", Solver|T]) -> % not documented
197  append_var(dialyzer_solvers, [list_to_atom(Solver)]),
198  cl(T);
199cl([H|_] = L) ->
200  case filelib:is_file(H) orelse filelib:is_dir(H) of
201    true ->
202      NewTail = command_line(L),
203      cl(NewTail);
204    false ->
205      cl_error("Unknown option: " ++ H)
206  end;
207cl([]) ->
208  {RetTag, Opts} =
209    case get(dialyzer_options_analysis_type) =:= plt_info of
210      true ->
211	put(dialyzer_options_analysis_type, plt_check),
212	{plt_info, cl_options()};
213      false ->
214	case get(dialyzer_options_mode) of
215	  gui -> {gui, common_options()};
216	  cl ->
217	    case get(dialyzer_options_analysis_type) =:= plt_check of
218	      true  -> {check_init, cl_options()};
219	      false -> {cl, cl_options()}
220	    end
221	end
222    end,
223  case dialyzer_options:build(Opts) of
224    {error, Msg} -> cl_error(Msg);
225    OptsRecord -> {RetTag, OptsRecord}
226  end.
227
228%%-----------------------------------------------------------------------
229
230command_line(T0) ->
231  {Args, T} = collect_args(T0),
232  append_var(dialyzer_options_files, Args),
233  %% if all files specified are ".erl" files, set the 'src' flag automatically
234  case lists:all(fun(F) -> filename:extension(F) =:= ".erl" end, Args) of
235    true -> put(dialyzer_options_from, src_code);
236    false -> ok
237  end,
238  T.
239
240-spec cl_error(deep_string()) -> no_return().
241
242cl_error(Str) ->
243  Msg = lists:flatten(Str),
244  throw({dialyzer_cl_parse_error, Msg}).
245
246init() ->
247  put(dialyzer_options_mode, cl),
248  put(dialyzer_options_files_rec, []),
249  put(dialyzer_options_report_mode, normal),
250  put(dialyzer_warnings, []),
251  DefaultOpts = #options{},
252  put(dialyzer_include,           DefaultOpts#options.include_dirs),
253  put(dialyzer_options_defines,   DefaultOpts#options.defines),
254  put(dialyzer_options_files,     DefaultOpts#options.files),
255  put(dialyzer_output_format,     formatted),
256  put(dialyzer_filename_opt,      basename),
257  put(dialyzer_options_check_plt, DefaultOpts#options.check_plt),
258  put(dialyzer_timing,            DefaultOpts#options.timing),
259  put(dialyzer_solvers,           DefaultOpts#options.solvers),
260  ok.
261
262append_defines([Def, Val]) ->
263  {ok, Tokens, _} = erl_scan:string(Val++"."),
264  {ok, ErlVal} = erl_parse:parse_term(Tokens),
265  append_var(dialyzer_options_defines, [{list_to_atom(Def), ErlVal}]);
266append_defines([Def]) ->
267  append_var(dialyzer_options_defines, [{list_to_atom(Def), true}]).
268
269append_include(Dir) ->
270  append_var(dialyzer_include, [Dir]).
271
272append_var(Var, List) when is_list(List) ->
273  put(Var, get(Var) ++ List),
274  ok.
275
276%%-----------------------------------------------------------------------
277
278-spec collect_args([string()]) -> {[string()], [string()]}.
279
280collect_args(List) ->
281  collect_args_1(List, []).
282
283collect_args_1(["-"++_|_] = L, Acc) ->
284  {lists:reverse(Acc), L};
285collect_args_1([Arg|T], Acc) ->
286  collect_args_1(T, [Arg|Acc]);
287collect_args_1([], Acc) ->
288  {lists:reverse(Acc), []}.
289
290%%-----------------------------------------------------------------------
291
292cl_options() ->
293  [{files, get(dialyzer_options_files)},
294   {files_rec, get(dialyzer_options_files_rec)},
295   {output_file, get(dialyzer_output)},
296   {output_format, get(dialyzer_output_format)},
297   {filename_opt, get(dialyzer_filename_opt)},
298   {analysis_type, get(dialyzer_options_analysis_type)},
299   {get_warnings, get(dialyzer_options_get_warnings)},
300   {timing, get(dialyzer_timing)},
301   {callgraph_file, get(dialyzer_callgraph_file)}
302   |common_options()].
303
304common_options() ->
305  [{defines, get(dialyzer_options_defines)},
306   {from, get(dialyzer_options_from)},
307   {include_dirs, get(dialyzer_include)},
308   {plts, get(dialyzer_init_plts)},
309   {output_plt, get(dialyzer_output_plt)},
310   {report_mode, get(dialyzer_options_report_mode)},
311   {use_spec, get(dialyzer_options_use_contracts)},
312   {warnings, get(dialyzer_warnings)},
313   {check_plt, get(dialyzer_options_check_plt)},
314   {solvers, get(dialyzer_solvers)}].
315
316%%-----------------------------------------------------------------------
317
318-spec get_lib_dir([string()]) -> [string()].
319
320get_lib_dir(Apps) ->
321  get_lib_dir(Apps, []).
322
323get_lib_dir([H|T], Acc) ->
324  NewElem =
325    case code:lib_dir(list_to_atom(H)) of
326      {error, bad_name} ->
327	case H =:= "erts" of % hack for including erts in an un-installed system
328	  true -> filename:join(code:root_dir(), "erts/preloaded/ebin");
329	  false -> H
330	end;
331      LibDir -> LibDir ++ "/ebin"
332    end,
333  get_lib_dir(T, [NewElem|Acc]);
334get_lib_dir([], Acc) ->
335  lists:reverse(Acc).
336
337%%-----------------------------------------------------------------------
338
339get_plts(["--"|T], Acc) -> {lists:reverse(Acc), T};
340get_plts(["-"++_Opt = H|T], Acc) -> {lists:reverse(Acc), [H|T]};
341get_plts([H|T], Acc) -> get_plts(T, [H|Acc]);
342get_plts([], Acc) -> {lists:reverse(Acc), []}.
343
344%%-----------------------------------------------------------------------
345
346-spec help_warnings() -> no_return().
347
348help_warnings() ->
349  S = warning_options_msg(),
350  io:put_chars(S),
351  erlang:halt(?RET_NOTHING_SUSPICIOUS).
352
353-spec help_message() -> no_return().
354
355help_message() ->
356  S = "Usage: dialyzer [--help] [--version] [--shell] [--quiet] [--verbose]
357		[-pa dir]* [--plt plt] [--plts plt*] [-Ddefine]*
358                [-I include_dir]* [--output_plt file] [-Wwarn]* [--raw]
359                [--src] [--gui] [files_or_dirs] [-r dirs]
360                [--apps applications] [-o outfile]
361		[--build_plt] [--add_to_plt] [--remove_from_plt]
362		[--check_plt] [--no_check_plt] [--plt_info] [--get_warnings]
363                [--dump_callgraph file] [--no_native] [--fullpath]
364                [--statistics] [--no_native_cache]
365Options:
366  files_or_dirs (for backwards compatibility also as: -c files_or_dirs)
367      Use Dialyzer from the command line to detect defects in the
368      specified files or directories containing .erl or .beam files,
369      depending on the type of the analysis.
370  -r dirs
371      Same as the previous but the specified directories are searched
372      recursively for subdirectories containing .erl or .beam files in
373      them, depending on the type of analysis.
374  --apps applications
375      Option typically used when building or modifying a plt as in:
376        dialyzer --build_plt --apps erts kernel stdlib mnesia ...
377      to conveniently refer to library applications corresponding to the
378      Erlang/OTP installation. However, the option is general and can also
379      be used during analysis in order to refer to Erlang/OTP applications.
380      In addition, file or directory names can also be included, as in:
381        dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam
382  -o outfile (or --output outfile)
383      When using Dialyzer from the command line, send the analysis
384      results to the specified outfile rather than to stdout.
385  --raw
386      When using Dialyzer from the command line, output the raw analysis
387      results (Erlang terms) instead of the formatted result.
388      The raw format is easier to post-process (for instance, to filter
389      warnings or to output HTML pages).
390  --src
391      Override the default, which is to analyze BEAM files, and
392      analyze starting from Erlang source code instead.
393  -Dname (or -Dname=value)
394      When analyzing from source, pass the define to Dialyzer. (**)
395  -I include_dir
396      When analyzing from source, pass the include_dir to Dialyzer. (**)
397  -pa dir
398      Include dir in the path for Erlang (useful when analyzing files
399      that have '-include_lib()' directives).
400  --output_plt file
401      Store the plt at the specified file after building it.
402  --plt plt
403      Use the specified plt as the initial plt (if the plt was built
404      during setup the files will be checked for consistency).
405  --plts plt*
406      Merge the specified plts to create the initial plt -- requires
407      that the plts are disjoint (i.e., do not have any module
408      appearing in more than one plt).
409      The plts are created in the usual way:
410        dialyzer --build_plt --output_plt plt_1 files_to_include
411        ...
412        dialyzer --build_plt --output_plt plt_n files_to_include
413      and then can be used in either of the following ways:
414        dialyzer files_to_analyze --plts plt_1 ... plt_n
415      or:
416        dialyzer --plts plt_1 ... plt_n -- files_to_analyze
417      (Note the -- delimiter in the second case)
418  -Wwarn
419      A family of options which selectively turn on/off warnings
420      (for help on the names of warnings use dialyzer -Whelp).
421  --shell
422      Do not disable the Erlang shell while running the GUI.
423  --version (or -v)
424      Print the Dialyzer version and some more information and exit.
425  --help (or -h)
426      Print this message and exit.
427  --quiet (or -q)
428      Make Dialyzer a bit more quiet.
429  --verbose
430      Make Dialyzer a bit more verbose.
431  --statistics
432      Prints information about the progress of execution (analysis phases,
433      time spent in each and size of the relative input).
434  --build_plt
435      The analysis starts from an empty plt and creates a new one from the
436      files specified with -c and -r. Only works for beam files.
437      Use --plt(s) or --output_plt to override the default plt location.
438  --add_to_plt
439      The plt is extended to also include the files specified with -c and -r.
440      Use --plt(s) to specify which plt to start from, and --output_plt to
441      specify where to put the plt. Note that the analysis might include
442      files from the plt if they depend on the new files.
443      This option only works with beam files.
444  --remove_from_plt
445      The information from the files specified with -c and -r is removed
446      from the plt. Note that this may cause a re-analysis of the remaining
447      dependent files.
448  --check_plt
449      Check the plt for consistency and rebuild it if it is not up-to-date.
450      Actually, this option is of rare use as it is on by default.
451  --no_check_plt (or -n)
452      Skip the plt check when running Dialyzer. Useful when working with
453      installed plts that never change.
454  --plt_info
455      Make Dialyzer print information about the plt and then quit. The plt
456      can be specified with --plt(s).
457  --get_warnings
458      Make Dialyzer emit warnings even when manipulating the plt. Warnings
459      are only emitted for files that are actually analyzed.
460  --dump_callgraph file
461      Dump the call graph into the specified file whose format is determined
462      by the file name extension. Supported extensions are: raw, dot, and ps.
463      If something else is used as file name extension, default format '.raw'
464      will be used.
465  --no_native (or -nn)
466      Bypass the native code compilation of some key files that Dialyzer
467      heuristically performs when dialyzing many files; this avoids the
468      compilation time but it may result in (much) longer analysis time.
469  --no_native_cache
470      By default, Dialyzer caches the results of native compilation in the
471      $XDG_CACHE_HOME/erlang/dialyzer_hipe_cache directory.
472      XDG_CACHE_HOME defaults to $HOME/.cache.  Use this option to disable
473      caching.
474  --fullpath
475      Display the full path names of files for which warnings are emitted.
476  --gui
477      Use the GUI.
478
479Note:
480  * denotes that multiple occurrences of these options are possible.
481 ** options -D and -I work both from command-line and in the Dialyzer GUI;
482    the syntax of defines and includes is the same as that used by \"erlc\".
483
484" ++ warning_options_msg() ++ "
485The exit status of the command line version is:
486  0 - No problems were encountered during the analysis and no
487      warnings were emitted.
488  1 - Problems were encountered during the analysis.
489  2 - No problems were encountered, but warnings were emitted.
490",
491  io:put_chars(S),
492  erlang:halt(?RET_NOTHING_SUSPICIOUS).
493
494warning_options_msg() ->
495  "Warning options:
496  -Wno_return
497     Suppress warnings for functions that will never return a value.
498  -Wno_unused
499     Suppress warnings for unused functions.
500  -Wno_improper_lists
501     Suppress warnings for construction of improper lists.
502  -Wno_fun_app
503     Suppress warnings for fun applications that will fail.
504  -Wno_match
505     Suppress warnings for patterns that are unused or cannot match.
506  -Wno_opaque
507     Suppress warnings for violations of opacity of data types.
508  -Wno_fail_call
509     Suppress warnings for failing calls.
510  -Wno_contracts
511     Suppress warnings about invalid contracts.
512  -Wno_behaviours
513     Suppress warnings about behaviour callbacks which drift from the published
514     recommended interfaces.
515  -Wno_missing_calls
516     Suppress warnings about calls to missing functions.
517  -Wno_undefined_callbacks
518     Suppress warnings about behaviours that have no -callback attributes for
519     their callbacks.
520  -Wunmatched_returns ***
521     Include warnings for function calls which ignore a structured return
522     value or do not match against one of many possible return value(s).
523  -Werror_handling ***
524     Include warnings for functions that only return by means of an exception.
525  -Wrace_conditions ***
526     Include warnings for possible race conditions.
527  -Wunderspecs ***
528     Warn about underspecified functions
529     (those whose -spec is strictly more allowing than the success typing).
530  -Wunknown ***
531     Let warnings about unknown functions and types affect the
532     exit status of the command line version. The default is to ignore
533     warnings about unknown functions and types when setting the exit
534     status. When using the Dialyzer from Erlang, warnings about unknown
535     functions and types are returned; the default is not to return
536     such warnings.
537
538The following options are also available but their use is not recommended:
539(they are mostly for Dialyzer developers and internal debugging)
540  -Woverspecs ***
541     Warn about overspecified functions
542     (those whose -spec is strictly less allowing than the success typing).
543  -Wspecdiffs ***
544     Warn when the -spec is different than the success typing.
545
546*** Identifies options that turn on warnings rather than turning them off.
547".
548