Skip to content

Commit

Permalink
dialyzer: Expand Dialyzer config file options
Browse files Browse the repository at this point in the history
The Dialyzer config file previously allowed setting fallback modules
to analyse and report warnings for in the absence of explicitly
given modules in the arguments.

This change extends the config file to include warnings to toggle and
modifications to the beginning and end of the path, allowing users
to factor these out of their command line arguments.

This change also fixes apps being in default_warning_apps without
being in default_apps causing an inconsistency. Now all warning
apps are implicitly added to the apps to analyze.

An example Dialyzer config that makes use of the new options:

```erlang
  {incremental,
    {default_apps,[stdlib,kernel,erts]},
    {default_warning_apps,[stdlib]}
  }.
  {warnings, [no_improper_lists]}.
  {add_pathsa,["/users/samwise/potatoes/ebin"]}.
  {add_pathsz,["/users/smeagol/fish/ebin"]}.
```

This config expresses that:
 - If no apps/warnings apps are given explicitly, analyse stdlib, kernel and
     erts, and report warnings for stdlib only.
 - Set the no_improper_lists option to suppress warnings about improper list
     construction
 - Add /users/samwise/potatoes/ebin to the start of the path, and
     /users/smeagol/fish/ebin to the end of it
  • Loading branch information
TD5 authored and bjorng committed Apr 25, 2023
1 parent fb6db59 commit 688f342
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 21 deletions.
26 changes: 26 additions & 0 deletions lib/dialyzer/doc/src/dialyzer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,32 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<p>Currently the only option used is the
<seeerl marker="#error_location"><c>error_location</c></seeerl> option.
</p>

<p><em>Dialyzer configuration file:</em></p>

<p>Dialyzer's configuration file may also be used to augment the default
options and those given directly to the Dialyzer command. It is commonly
used to avoid repeating options which would otherwise need to be given
explicitly to Dialyzer on every invocation.
</p>

<p>The location of the configuration file can be set via the
<c>DIALYZER_CONFIG</c> environment variable, and defaults to
within the <c>user_config</c> from <seemfa marker="stdlib:filename#basedir/3">
<c>filename:basedir/3</c></seemfa>.
</p>

<p>An example configuration file's contents might be:</p>

<code type="none">
{incremental,
{default_apps,[stdlib,kernel,erts]},
{default_warning_apps,[stdlib]}
}.
{warnings, [no_improper_lists]}.
{add_pathsa,["/users/samwise/potatoes/ebin"]}.
{add_pathsz,["/users/smeagol/fish/ebin"]}.
</code>
</section>

<section>
Expand Down
9 changes: 5 additions & 4 deletions lib/dialyzer/src/dialyzer_analysis_callgraph.erl
Original file line number Diff line number Diff line change
Expand Up @@ -504,10 +504,11 @@ expand_files(Analysis = #analysis{files = Files, start_from = StartFrom}) ->
case expand_files(Files, Ext, []) of
[] ->
Msg = "No " ++ Ext ++ " files to analyze" ++
case StartFrom of
byte_code -> " (no --src specified?)";
src_code -> ""
end,
case StartFrom of
byte_code -> " (no --src specified?)";
src_code -> ""
end ++
"\nConsider setting some default apps in your dialyzer.config file",
exit({error, Msg});
NewFiles ->
Analysis#analysis{files = NewFiles}
Expand Down
28 changes: 28 additions & 0 deletions lib/dialyzer/src/dialyzer_cl_parse.erl
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,8 @@ Note:
the syntax of defines and includes is the same as that used by \"erlc\".
" ++ warning_options_msg() ++ "
" ++ configuration_file_msg() ++ "
The exit status of the command line version is:
0 - No problems were encountered during the analysis and no
warnings were emitted.
Expand Down Expand Up @@ -654,3 +656,29 @@ They are primarily intended to be used with the -dialyzer attribute:
-Wno_missing_return
Suppress warnings about functions that return values that are not part of the specification.
".

configuration_file_msg() ->
"Configuration file:
Dialyzer's configuration file may also be used to augment the default
options and those given directly to the Dialyzer command. It is commonly
used to avoid repeating options which would otherwise need to be given
explicitly to Dialyzer on every invocation.
The location of the configuration file can be set via the
DIALYZER_CONFIG environment variable, and defaults to
within the user_config location given by filename:basedir/3.
On your system, the location is currently configured as:
" ++ dialyzer_options:get_default_config_filename() ++
"
An example configuration file's contents might be:
{incremental,
{default_apps,[stdlib,kernel,erts]},
{default_warning_apps,[stdlib]}
}.
{warnings, [no_improper_lists]}.
{add_pathsa,[\"/users/samwise/potatoes/ebin\"]}.
{add_pathsz,[\"/users/smeagol/fish/ebin\"]}.
".
78 changes: 62 additions & 16 deletions lib/dialyzer/src/dialyzer_options.erl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

-module(dialyzer_options).

-export([build/1, build_warnings/2]).
-export([build/1, build_warnings/2, get_default_config_filename/0]).

-include("dialyzer.hrl").

Expand Down Expand Up @@ -48,9 +48,12 @@ build(Opts) ->
?WARN_UNDEFINED_CALLBACK,
?WARN_UNKNOWN],
DefaultWarns1 = ordsets:from_list(DefaultWarns),
DefaultOpts = #options{},
DefaultOpts1 = DefaultOpts#options{legal_warnings = DefaultWarns1},
try
WarningsFromConfig = proplists:get_value(warnings, get_config(), []),
update_path_from_config(),
DefaultWarns2 = build_warnings(WarningsFromConfig, DefaultWarns1),
DefaultOpts = #options{},
DefaultOpts1 = DefaultOpts#options{legal_warnings = DefaultWarns2},
Opts1 = preprocess_opts(Opts),
Env = env_default_opts(),
ErrLoc = proplists:get_value(error_location, Env, ?ERROR_LOCATION),
Expand All @@ -61,6 +64,31 @@ build(Opts) ->
throw:{dialyzer_options_error, Msg} -> {error, Msg}
end.

update_path_from_config() ->
Config = get_config(),
PAs = proplists:get_value(add_pathsa, Config, []),
PZs = proplists:get_value(add_pathsz, Config, []),
case is_list(PAs) of
true -> ok;
false -> bad_option("Bad list of paths in config", {add_pathsa, PAs})
end,
case is_list(PZs) of
true -> ok;
false -> bad_option("Bad list of paths in config", {add_pathsz, PZs})
end,
%% Add paths one-by-one so that we can report issues
%% if any path is invalid
%% (code:add_pathsa/1 and code:add_pathsz/1 always return ok)
[ case code:add_patha(PA) of
true -> ok;
{error, _} -> bad_option("Failed to add path from config", {add_patha, PA})
end || PA <- PAs ],
[ case code:add_pathz(PZ) of
true -> ok;
{error, _} -> bad_option("Failed to add path from config", {add_pathz, PZ})
end || PZ <- PZs ],
ok.

preprocess_opts([]) -> [];
preprocess_opts([{init_plt, File}|Opts]) ->
[{plts, [File]}|preprocess_opts(Opts)];
Expand All @@ -79,7 +107,7 @@ postprocess_opts(Opts = #options{}) ->
check_module_lookup_file_validity(Opts1),
Opts2 = check_output_plt(Opts1),
check_init_plt_kind(Opts2),
Opts3 = manage_default_apps(Opts2),
Opts3 = manage_default_incremental_apps(Opts2),
adapt_get_warnings(Opts3).

check_metrics_file_validity(#options{analysis_type = incremental, metrics_file = none}) ->
Expand Down Expand Up @@ -160,31 +188,49 @@ check_init_plt_kind(#options{analysis_type = _NotIncremental, init_plts = InitPl
lists:foreach(RunCheck, InitPlts).

%% If no apps are set explicitly, we fall back to config
manage_default_apps(Opts = #options{analysis_type = incremental, files = [], files_rec = [], warning_files = [], warning_files_rec = []}) ->
DefaultConfig = get_default_config_filename(),
case file:consult(DefaultConfig) of
{ok, [{incremental, {default_apps, DefaultApps}=Term}]} when
manage_default_incremental_apps(Opts = #options{analysis_type = incremental, files = [], files_rec = [], warning_files = [], warning_files_rec = []}) ->
set_default_apps(get_config(), Opts);
manage_default_incremental_apps(Opts) ->
Opts.

set_default_apps([ConfigElem|MoreConfig], Opts) ->
case ConfigElem of
{incremental, {default_apps, DefaultApps}=Term} when
is_list(DefaultApps) ->
AppDirs = get_app_dirs(DefaultApps),
assert_filenames_form(Term, AppDirs),
Opts#options{files_rec = AppDirs};
{ok, [{incremental, {default_apps, DefaultApps}=TermApps,
{default_warning_apps, DefaultWarningApps}=TermWarns}]} when
{incremental, {default_apps, DefaultApps}=TermApps,
{default_warning_apps, DefaultWarningApps}=TermWarns} when
is_list(DefaultApps), is_list(DefaultWarningApps) ->
AppDirs = get_app_dirs(DefaultApps),
AppDirs = get_app_dirs(DefaultApps ++ DefaultWarningApps),
assert_filenames_form(TermApps, AppDirs),
WarningAppDirs = get_app_dirs(DefaultWarningApps),
assert_filenames_form(TermWarns, WarningAppDirs),
Opts#options{files_rec = AppDirs, warning_files_rec = WarningAppDirs};
{ok, _Terms} ->
bad_option("Given Erlang terms could not be understood as Dialyzer config", DefaultConfig);
{error, Reason} ->
bad_option(file:format_error(Reason), DefaultConfig)
_ when element(1, ConfigElem) =:= incremental ->
bad_option("Given Erlang terms in 'incremental' section could not be understood as Dialyzer config", ConfigElem);
_ ->
set_default_apps(MoreConfig, Opts)
end;
manage_default_apps(Opts) ->
set_default_apps([], Opts) ->
Opts.

get_config() ->
DefaultConfig = get_default_config_filename(),
case filelib:is_regular(DefaultConfig) of
true ->
case file:consult(DefaultConfig) of
{ok, Config} when is_list(Config) -> Config;
{error, Reason} ->
bad_option(file:format_error(Reason), DefaultConfig)
end;
false ->
[]
end.

% Intended to work like dialyzer_iplt:get_default_iplt_filename()
-spec get_default_config_filename() -> string().
get_default_config_filename() ->
case os:getenv("DIALYZER_CONFIG") of
false ->
Expand Down
Loading

0 comments on commit 688f342

Please sign in to comment.