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%% @copyright 2004 Richard Carlsson
16%% @author Richard Carlsson <carlsson.richard@gmail.com>
17%% @doc Provides a better way to start Dialyzer from a script.
18
19-module(dialyzer_options).
20
21-export([build/1, build_warnings/2]).
22
23-include("dialyzer.hrl").
24
25%%-----------------------------------------------------------------------
26
27-spec build(dial_options()) -> #options{} | {'error', string()}.
28
29build(Opts) ->
30  DefaultWarns = [?WARN_RETURN_NO_RETURN,
31		  ?WARN_NOT_CALLED,
32		  ?WARN_NON_PROPER_LIST,
33		  ?WARN_FUN_APP,
34		  ?WARN_MATCHING,
35		  ?WARN_OPAQUE,
36		  ?WARN_CALLGRAPH,
37		  ?WARN_FAILING_CALL,
38		  ?WARN_BIN_CONSTRUCTION,
39		  ?WARN_MAP_CONSTRUCTION,
40		  ?WARN_CONTRACT_RANGE,
41		  ?WARN_CONTRACT_TYPES,
42		  ?WARN_CONTRACT_SYNTAX,
43		  ?WARN_BEHAVIOUR,
44		  ?WARN_UNDEFINED_CALLBACK],
45  DefaultWarns1 = ordsets:from_list(DefaultWarns),
46  InitPlt = dialyzer_plt:get_default_plt(),
47  DefaultOpts = #options{},
48  DefaultOpts1 = DefaultOpts#options{legal_warnings = DefaultWarns1,
49                                     init_plts = [InitPlt]},
50  try
51    Opts1 = preprocess_opts(Opts),
52    NewOpts = build_options(Opts1, DefaultOpts1),
53    postprocess_opts(NewOpts)
54  catch
55    throw:{dialyzer_options_error, Msg} -> {error, Msg}
56  end.
57
58preprocess_opts([]) -> [];
59preprocess_opts([{init_plt, File}|Opts]) ->
60  [{plts, [File]}|preprocess_opts(Opts)];
61preprocess_opts([Opt|Opts]) ->
62  [Opt|preprocess_opts(Opts)].
63
64postprocess_opts(Opts = #options{}) ->
65  check_file_existence(Opts),
66  Opts1 = check_output_plt(Opts),
67  adapt_get_warnings(Opts1).
68
69check_file_existence(#options{analysis_type = plt_remove}) -> ok;
70check_file_existence(#options{files = Files, files_rec = FilesRec}) ->
71  assert_filenames_exist(Files),
72  assert_filenames_exist(FilesRec).
73
74check_output_plt(Opts = #options{analysis_type = Mode, from = From,
75				 output_plt = OutPLT}) ->
76  case is_plt_mode(Mode) of
77    true ->
78      case From =:= byte_code of
79	true -> Opts;
80	false ->
81	  Msg = "Byte code compiled with debug_info is needed to build the PLT",
82	  throw({dialyzer_error, Msg})
83      end;
84    false ->
85      case OutPLT =:= none of
86	true -> Opts;
87	false ->
88	  Msg = io_lib:format("Output PLT cannot be specified "
89			      "in analysis mode ~w", [Mode]),
90	  throw({dialyzer_error, lists:flatten(Msg)})
91      end
92  end.
93
94adapt_get_warnings(Opts = #options{analysis_type = Mode,
95				   get_warnings = Warns}) ->
96  %% Warnings are off by default in plt mode, and on by default in
97  %% success typings mode. User defined warning mode overrides the
98  %% default.
99  case is_plt_mode(Mode) of
100    true ->
101      case Warns =:= maybe of
102	true -> Opts#options{get_warnings = false};
103	false -> Opts
104      end;
105    false ->
106      case Warns =:= maybe of
107	true -> Opts#options{get_warnings = true};
108	false -> Opts
109      end
110  end.
111
112-spec bad_option(string(), term()) -> no_return().
113
114bad_option(String, Term) ->
115  Msg = io_lib:format("~ts: ~tP", [String, Term, 25]),
116  throw({dialyzer_options_error, lists:flatten(Msg)}).
117
118build_options([{OptName, undefined}|Rest], Options) when is_atom(OptName) ->
119  build_options(Rest, Options);
120build_options([{OptionName, Value} = Term|Rest], Options) ->
121  case OptionName of
122    apps ->
123      OldValues = Options#options.files_rec,
124      AppDirs = get_app_dirs(Value),
125      assert_filenames_form(Term, AppDirs),
126      build_options(Rest, Options#options{files_rec = AppDirs ++ OldValues});
127    files ->
128      assert_filenames_form(Term, Value),
129      build_options(Rest, Options#options{files = Value});
130    files_rec ->
131      OldValues = Options#options.files_rec,
132      assert_filenames_form(Term, Value),
133      build_options(Rest, Options#options{files_rec = Value ++ OldValues});
134    analysis_type ->
135      NewOptions =
136	case Value of
137	  succ_typings -> Options#options{analysis_type = Value};
138	  plt_add      -> Options#options{analysis_type = Value};
139	  plt_build    -> Options#options{analysis_type = Value};
140	  plt_check    -> Options#options{analysis_type = Value};
141	  plt_remove   -> Options#options{analysis_type = Value};
142	  dataflow  -> bad_option("Analysis type is no longer supported", Term);
143	  old_style -> bad_option("Analysis type is no longer supported", Term);
144	  Other     -> bad_option("Unknown analysis type", Other)
145	end,
146      assert_plt_op(Options, NewOptions),
147      build_options(Rest, NewOptions);
148    check_plt when is_boolean(Value) ->
149      build_options(Rest, Options#options{check_plt = Value});
150    defines ->
151      assert_defines(Term, Value),
152      OldVal = Options#options.defines,
153      NewVal = ordsets:union(ordsets:from_list(Value), OldVal),
154      build_options(Rest, Options#options{defines = NewVal});
155    from when Value =:= byte_code; Value =:= src_code ->
156      build_options(Rest, Options#options{from = Value});
157    get_warnings ->
158      build_options(Rest, Options#options{get_warnings = Value});
159    plts ->
160      assert_filenames(Term, Value),
161      build_options(Rest, Options#options{init_plts = Value});
162    include_dirs ->
163      assert_filenames(Term, Value),
164      OldVal = Options#options.include_dirs,
165      NewVal = ordsets:union(ordsets:from_list(Value), OldVal),
166      build_options(Rest, Options#options{include_dirs = NewVal});
167    use_spec ->
168      build_options(Rest, Options#options{use_contracts = Value});
169    old_style ->
170      bad_option("Analysis type is no longer supported", old_style);
171    output_file ->
172      assert_filename(Value),
173      build_options(Rest, Options#options{output_file = Value});
174    output_format ->
175      assert_output_format(Value),
176      build_options(Rest, Options#options{output_format = Value});
177    filename_opt ->
178      assert_filename_opt(Value),
179      build_options(Rest, Options#options{filename_opt = Value});
180    output_plt ->
181      assert_filename(Value),
182      build_options(Rest, Options#options{output_plt = Value});
183    report_mode ->
184      build_options(Rest, Options#options{report_mode = Value});
185    erlang_mode ->
186      build_options(Rest, Options#options{erlang_mode = true});
187    warnings ->
188      NewWarnings = build_warnings(Value, Options#options.legal_warnings),
189      build_options(Rest, Options#options{legal_warnings = NewWarnings});
190    callgraph_file ->
191      assert_filename(Value),
192      build_options(Rest, Options#options{callgraph_file = Value});
193    timing ->
194      build_options(Rest, Options#options{timing = Value});
195    solvers ->
196      assert_solvers(Value),
197      build_options(Rest, Options#options{solvers = Value});
198    _ ->
199      bad_option("Unknown dialyzer command line option", Term)
200  end;
201build_options([], Options) ->
202  Options.
203
204get_app_dirs(Apps) when is_list(Apps) ->
205  dialyzer_cl_parse:get_lib_dir([atom_to_list(A) || A <- Apps]);
206get_app_dirs(Apps) ->
207  bad_option("Use a list of otp applications", Apps).
208
209assert_filenames(Term, Files) ->
210  assert_filenames_form(Term, Files),
211  assert_filenames_exist(Files).
212
213assert_filenames_form(Term, [FileName|Left]) when length(FileName) >= 0 ->
214  assert_filenames_form(Term, Left);
215assert_filenames_form(_Term, []) ->
216  ok;
217assert_filenames_form(Term, [_|_]) ->
218  bad_option("Malformed or non-existing filename", Term).
219
220assert_filenames_exist([FileName|Left]) ->
221  case filelib:is_file(FileName) orelse filelib:is_dir(FileName) of
222    true -> ok;
223    false ->
224      bad_option("No such file, directory or application", FileName)
225  end,
226  assert_filenames_exist(Left);
227assert_filenames_exist([]) ->
228  ok.
229
230assert_filename(FileName) when length(FileName) >= 0 ->
231  ok;
232assert_filename(FileName) ->
233  bad_option("Malformed or non-existing filename", FileName).
234
235assert_defines(Term, [{Macro, _Value}|Defs]) when is_atom(Macro) ->
236  assert_defines(Term, Defs);
237assert_defines(_Term, []) ->
238  ok;
239assert_defines(Term, [_|_]) ->
240  bad_option("Malformed define", Term).
241
242assert_output_format(raw) ->
243  ok;
244assert_output_format(formatted) ->
245  ok;
246assert_output_format(Term) ->
247  bad_option("Illegal value for output_format", Term).
248
249assert_filename_opt(basename) ->
250  ok;
251assert_filename_opt(fullpath) ->
252  ok;
253assert_filename_opt(Term) ->
254  bad_option("Illegal value for filename_opt", Term).
255
256assert_plt_op(#options{analysis_type = OldVal},
257	      #options{analysis_type = NewVal}) ->
258  case is_plt_mode(OldVal) andalso is_plt_mode(NewVal) of
259    true -> bad_option("Options cannot be combined", [OldVal, NewVal]);
260    false -> ok
261  end.
262
263is_plt_mode(plt_add)      -> true;
264is_plt_mode(plt_build)    -> true;
265is_plt_mode(plt_remove)   -> true;
266is_plt_mode(plt_check)    -> true;
267is_plt_mode(succ_typings) -> false.
268
269assert_solvers([]) ->
270  ok;
271assert_solvers([v1|Terms]) ->
272  assert_solvers(Terms);
273assert_solvers([v2|Terms]) ->
274  assert_solvers(Terms);
275assert_solvers([Term|_]) ->
276  bad_option("Illegal value for solver", Term).
277
278-spec build_warnings([atom()], dial_warn_tags()) -> dial_warn_tags().
279
280%% The warning options are checked by the code linter.
281%% The function erl_lint:is_module_dialyzer_option/1 must
282%% be updated if options are added or removed.
283build_warnings([Opt|Opts], Warnings) ->
284  NewWarnings =
285    case Opt of
286      no_return ->
287	ordsets:del_element(?WARN_RETURN_NO_RETURN, Warnings);
288      no_unused ->
289	ordsets:del_element(?WARN_NOT_CALLED, Warnings);
290      no_improper_lists ->
291	ordsets:del_element(?WARN_NON_PROPER_LIST, Warnings);
292      no_fun_app ->
293	ordsets:del_element(?WARN_FUN_APP, Warnings);
294      no_match ->
295	ordsets:del_element(?WARN_MATCHING, Warnings);
296      no_opaque ->
297	ordsets:del_element(?WARN_OPAQUE, Warnings);
298      no_fail_call ->
299	ordsets:del_element(?WARN_FAILING_CALL, Warnings);
300      no_contracts ->
301	Warnings1 = ordsets:del_element(?WARN_CONTRACT_SYNTAX, Warnings),
302	ordsets:del_element(?WARN_CONTRACT_TYPES, Warnings1);
303      no_behaviours ->
304	ordsets:del_element(?WARN_BEHAVIOUR, Warnings);
305      no_undefined_callbacks ->
306	ordsets:del_element(?WARN_UNDEFINED_CALLBACK, Warnings);
307      unmatched_returns ->
308	ordsets:add_element(?WARN_UNMATCHED_RETURN, Warnings);
309      error_handling ->
310	ordsets:add_element(?WARN_RETURN_ONLY_EXIT, Warnings);
311      race_conditions ->
312	ordsets:add_element(?WARN_RACE_CONDITION, Warnings);
313      no_missing_calls ->
314        ordsets:del_element(?WARN_CALLGRAPH, Warnings);
315      specdiffs ->
316	S = ordsets:from_list([?WARN_CONTRACT_SUBTYPE,
317			       ?WARN_CONTRACT_SUPERTYPE,
318			       ?WARN_CONTRACT_NOT_EQUAL]),
319	ordsets:union(S, Warnings);
320      overspecs ->
321	ordsets:add_element(?WARN_CONTRACT_SUBTYPE, Warnings);
322      underspecs ->
323	ordsets:add_element(?WARN_CONTRACT_SUPERTYPE, Warnings);
324      unknown ->
325	ordsets:add_element(?WARN_UNKNOWN, Warnings);
326      OtherAtom ->
327	bad_option("Unknown dialyzer warning option", OtherAtom)
328    end,
329  build_warnings(Opts, NewWarnings);
330build_warnings([], Warnings) ->
331  Warnings.
332