aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dialyzer
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dialyzer')
-rw-r--r--lib/dialyzer/doc/src/Makefile2
-rw-r--r--lib/dialyzer/doc/src/dialyzer.xml7
-rw-r--r--lib/dialyzer/doc/src/ref_man.xml1
-rw-r--r--lib/dialyzer/doc/src/typer.xml157
-rw-r--r--lib/dialyzer/src/Makefile6
-rw-r--r--lib/dialyzer/src/dialyzer.app.src3
-rw-r--r--lib/dialyzer/src/dialyzer_analysis_callgraph.erl86
-rw-r--r--lib/dialyzer/src/dialyzer_options.erl3
-rw-r--r--lib/dialyzer/src/dialyzer_plt.erl11
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl298
-rw-r--r--lib/dialyzer/src/typer.erl1106
-rw-r--r--lib/dialyzer/test/Makefile3
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args3
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_missing_callbacks1
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/results/vars_in_beh_spec2
-rw-r--r--lib/dialyzer/test/plt_SUITE.erl37
-rw-r--r--lib/dialyzer/test/typer_SUITE.erl158
17 files changed, 1646 insertions, 238 deletions
diff --git a/lib/dialyzer/doc/src/Makefile b/lib/dialyzer/doc/src/Makefile
index 77d0a6fc68..8fe6cd30eb 100644
--- a/lib/dialyzer/doc/src/Makefile
+++ b/lib/dialyzer/doc/src/Makefile
@@ -34,7 +34,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
-XML_REF3_FILES = dialyzer.xml
+XML_REF3_FILES = dialyzer.xml typer.xml
XML_PART_FILES = part.xml part_notes.xml
XML_CHAPTER_FILES = dialyzer_chapter.xml notes.xml
diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml
index 4b7eb4ad68..e34ffd6def 100644
--- a/lib/dialyzer/doc/src/dialyzer.xml
+++ b/lib/dialyzer/doc/src/dialyzer.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2006</year><year>2016</year>
+ <year>2006</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -457,11 +457,6 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
<c>gui/1</c></seealso> below (<c>WarnOpts</c>).</p>
<note>
- <p>Attribute <c>-dialyzer()</c> is not checked by the Erlang
- compiler, but by Dialyzer itself.</p>
- </note>
-
- <note>
<p>Warning option <c>-Wrace_conditions</c> has no effect when
set in source files.</p>
</note>
diff --git a/lib/dialyzer/doc/src/ref_man.xml b/lib/dialyzer/doc/src/ref_man.xml
index ddac047f2e..d820fc5e00 100644
--- a/lib/dialyzer/doc/src/ref_man.xml
+++ b/lib/dialyzer/doc/src/ref_man.xml
@@ -31,5 +31,6 @@
<description>
</description>
<xi:include href="dialyzer.xml"/>
+ <xi:include href="typer.xml"/>
</application>
diff --git a/lib/dialyzer/doc/src/typer.xml b/lib/dialyzer/doc/src/typer.xml
new file mode 100644
index 0000000000..abd7f07ccf
--- /dev/null
+++ b/lib/dialyzer/doc/src/typer.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2006</year><year>2016</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ </legalnotice>
+
+ <title>typer</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date>2017-04-13</date>
+ <rev></rev>
+ <file>type.xml</file>
+ </header>
+ <module>typer</module>
+ <modulesummary>Typer, a Type annotator for ERlang programs.
+ </modulesummary>
+ <description>
+ <p>TypEr shows type information for Erlang modules to the user.
+ Additionally, it can annotate the code of files with such type
+ information.</p>
+ </description>
+
+ <section>
+ <marker id="command_line"></marker>
+ <title>Using TypEr from the Command Line</title>
+ <p>TypEr is used from the command-line. This section provides a
+ brief description of the options. The same information can be
+ obtained by writing the following in a shell:</p>
+
+ <code type="none">
+typer --help</code>
+
+ <p><em>Usage:</em></p>
+
+ <code type="none">
+typer [--help] [--version] [--plt PLT] [--edoc]
+ [--show | --show-exported | --annotate | --annotate-inc-files]
+ [-Ddefine]* [-I include_dir]* [-pa dir]* [-pz dir]*
+ [-T application]* [-r] file*</code>
+
+ <note>
+ <p>* denotes that multiple occurrences of the option are possible.</p>
+ </note>
+
+ <p><em>Options:</em></p>
+
+ <taglist>
+
+ <tag><c>--r</c></tag>
+ <item>
+ <p>Search directories recursively for .erl files below them.</p>
+ </item>
+ <tag><c>--show</c></tag>
+ <item>
+ <p>Print type specifications for all functions on stdout.
+ (This is the default behaviour; this option is not really
+ needed.)</p>
+ </item>
+
+ <tag><c>--show-exported</c> (or <c>show_exported</c>)</tag>
+ <item>
+ <p>Same as <c>--show</c>, but print specifications for
+ exported functions only. Specs are displayed sorted
+ alphabetically on the function's name.</p>
+ </item>
+
+ <tag><c>--annotate</c></tag>
+ <item>
+ <p>Annotate the specified files with type specifications.</p>
+ </item>
+
+ <tag><c>--annotate-inc-files</c></tag>
+ <item>
+ <p>Same as <c>--annotate</c> but annotates all
+ <c>-include()</c> files as well as all .erl files. (Use this
+ option with caution - it has not been tested much).</p>
+ </item>
+
+ <tag><c>--edoc</c></tag>
+ <item>
+ <p>Print type information as Edoc <c>@spec</c> comments, not
+ as type specs.</p>
+ </item>
+
+ <tag><c>--plt</c></tag>
+ <item>
+ <p>Use the specified dialyzer PLT file rather than the default one.</p>
+ </item>
+
+ <tag><c>-T file*</c></tag>
+ <item>
+ <p>The specified file(s) already contain type specifications
+ and these are to be trusted in order to print specs for the
+ rest of the files. (Multiple files or dirs, separated by
+ spaces, can be specified.)</p>
+ </item>
+
+ <tag><c>-Dname</c> (or <c>-Dname=value</c>)</tag>
+ <item>
+ <p>Pass the defined name(s) to TypEr. (**)</p>
+ </item>
+
+ <tag><c>-I</c></tag>
+ <item>
+ <p>Pass the include_dir to TypEr. (**)</p>
+ </item>
+
+ <tag><c>-pa dir</c></tag>
+ <item>
+ <p>Include <c>dir</c> in the path for Erlang. This is useful
+ when analyzing files that have <c>-include_lib()</c>
+ directives or use parse transforms.</p>
+ </item>
+
+ <tag><c>-pz dir</c></tag>
+ <item>
+ <p>Include <c>dir</c> in the path for Erlang. This is useful
+ when analyzing files that have <c>-include_lib()</c>
+ directives or use parse transforms.</p>
+ </item>
+
+ <tag><c>--version</c> (or <c>-v</c>)</tag>
+ <item>
+ <p>Print the TypEr version and some more information and
+ exit.</p>
+ </item>
+
+ </taglist>
+
+ <note>
+ <p>** options <c>-D</c> and <c>-I</c> work both
+ from the command line and in the TypEr GUI; the syntax of
+ defines and includes is the same as that used by
+ <seealso marker="erts:erlc">erlc(1)</seealso>.</p>
+ </note>
+
+ </section>
+
+</erlref>
diff --git a/lib/dialyzer/src/Makefile b/lib/dialyzer/src/Makefile
index 256f20f549..28f74ed441 100644
--- a/lib/dialyzer/src/Makefile
+++ b/lib/dialyzer/src/Makefile
@@ -68,7 +68,8 @@ MODULES = \
dialyzer_typesig \
dialyzer_coordinator \
dialyzer_worker \
- dialyzer_utils
+ dialyzer_utils \
+ typer
HRL_FILES= dialyzer.hrl dialyzer_gui_wx.hrl
ERL_FILES= $(MODULES:%=%.erl)
@@ -117,6 +118,9 @@ $(EBIN)/dialyzer_plt.$(EMULATOR): dialyzer_plt.erl ../vsn.mk
$(EBIN)/dialyzer_gui_wx.$(EMULATOR): dialyzer_gui_wx.erl ../vsn.mk
$(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_gui_wx.erl
+$(EBIN)/typer.$(EMULATOR): typer.erl ../vsn.mk
+ $(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) typer.erl
+
$(APP_TARGET): $(APP_SRC) ../vsn.mk
$(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@
diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src
index f517c51ec1..5f803875b0 100644
--- a/lib/dialyzer/src/dialyzer.app.src
+++ b/lib/dialyzer/src/dialyzer.app.src
@@ -43,7 +43,8 @@
dialyzer_typesig,
dialyzer_utils,
dialyzer_timing,
- dialyzer_worker]},
+ dialyzer_worker,
+ typer]},
{registered, []},
{applications, [compiler, hipe, kernel, stdlib, wx]},
{env, []},
diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
index aeeb895a0c..051b700231 100644
--- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
+++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
@@ -385,68 +385,46 @@ compile_src(File, Includes, Defines, Callgraph, CServer, UseContracts,
DefaultIncludes = default_includes(filename:dirname(File)),
SrcCompOpts = dialyzer_utils:src_compiler_opts(),
CompOpts = SrcCompOpts ++ Includes ++ Defines ++ DefaultIncludes,
- case dialyzer_utils:get_abstract_code_from_src(File, CompOpts) of
+ case dialyzer_utils:get_core_from_src(File, CompOpts) of
{error, _Msg} = Error -> Error;
- {ok, AbstrCode} ->
- compile_common(File, AbstrCode, CompOpts, Callgraph, CServer,
- UseContracts, LegalWarnings)
+ {ok, Core} ->
+ compile_common(Core, Callgraph, CServer, UseContracts, LegalWarnings)
end.
compile_byte(File, Callgraph, CServer, UseContracts, LegalWarnings) ->
- case dialyzer_utils:get_abstract_code_from_beam(File) of
- error ->
- {error, " Could not get abstract code for: " ++ File ++ "\n" ++
- " Recompile with +debug_info or analyze starting from source code"};
- {ok, AbstrCode} ->
- compile_byte(File, AbstrCode, Callgraph, CServer, UseContracts,
- LegalWarnings)
- end.
-
-compile_byte(File, AbstrCode, Callgraph, CServer, UseContracts,
- LegalWarnings) ->
- case dialyzer_utils:get_compile_options_from_beam(File) of
- error ->
- {error, " Could not get compile options for: " ++ File ++ "\n" ++
- " Recompile or analyze starting from source code"};
- {ok, CompOpts} ->
- compile_common(File, AbstrCode, CompOpts, Callgraph, CServer,
- UseContracts, LegalWarnings)
+ case dialyzer_utils:get_core_from_beam(File) of
+ {error, _} = Error -> Error;
+ {ok, Core} ->
+ compile_common(Core, Callgraph, CServer, UseContracts, LegalWarnings)
end.
-compile_common(File, AbstrCode, CompOpts, Callgraph, CServer,
- UseContracts, LegalWarnings) ->
- case dialyzer_utils:get_core_from_abstract_code(AbstrCode, CompOpts) of
- error -> {error, " Could not get core Erlang code for: " ++ File};
- {ok, Core} ->
- Mod = cerl:concrete(cerl:module_name(Core)),
- case dialyzer_utils:get_record_and_type_info(AbstrCode) of
+compile_common(Core, Callgraph, CServer, UseContracts, LegalWarnings) ->
+ Mod = cerl:concrete(cerl:module_name(Core)),
+ case dialyzer_utils:get_record_and_type_info(Core) of
+ {error, _} = Error -> Error;
+ {ok, RecInfo} ->
+ CServer1 =
+ dialyzer_codeserver:store_temp_records(Mod, RecInfo, CServer),
+ case dialyzer_utils:get_fun_meta_info(Mod, Core, LegalWarnings) of
{error, _} = Error -> Error;
- {ok, RecInfo} ->
- CServer1 =
- dialyzer_codeserver:store_temp_records(Mod, RecInfo, CServer),
- case
- dialyzer_utils:get_fun_meta_info(Mod, AbstrCode, LegalWarnings)
- of
- {error, _} = Error -> Error;
- MetaFunInfo ->
- CServer2 =
- dialyzer_codeserver:insert_fun_meta_info(MetaFunInfo, CServer1),
- case UseContracts of
- true ->
- case dialyzer_utils:get_spec_info(Mod, AbstrCode, RecInfo) of
- {error, _} = Error -> Error;
- {ok, SpecInfo, CallbackInfo} ->
- CServer3 =
- dialyzer_codeserver:store_temp_contracts(Mod, SpecInfo,
- CallbackInfo,
- CServer2),
- store_core(Mod, Core, Callgraph, CServer3)
- end;
- false ->
- store_core(Mod, Core, Callgraph, CServer2)
- end
- end
+ MetaFunInfo ->
+ CServer2 =
+ dialyzer_codeserver:insert_fun_meta_info(MetaFunInfo, CServer1),
+ case UseContracts of
+ true ->
+ case dialyzer_utils:get_spec_info(Mod, Core, RecInfo) of
+ {error, _} = Error -> Error;
+ {ok, SpecInfo, CallbackInfo} ->
+ CServer3 =
+ dialyzer_codeserver:store_temp_contracts(Mod, SpecInfo,
+ CallbackInfo,
+ CServer2),
+ store_core(Mod, Core, Callgraph, CServer3)
+ end;
+ false ->
+ store_core(Mod, Core, Callgraph, CServer2)
end
+ end
end.
store_core(Mod, Core, Callgraph, CServer) ->
diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl
index 616e8834f5..ec3f41311d 100644
--- a/lib/dialyzer/src/dialyzer_options.erl
+++ b/lib/dialyzer/src/dialyzer_options.erl
@@ -277,6 +277,9 @@ assert_solvers([Term|_]) ->
-spec build_warnings([atom()], dial_warn_tags()) -> dial_warn_tags().
+%% The warning options are checked by the code linter.
+%% The function erl_lint:is_module_dialyzer_option/1 must
+%% be updated if options are added or removed.
build_warnings([Opt|Opts], Warnings) ->
NewWarnings =
case Opt of
diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl
index bfd3f84fc5..847faab2f4 100644
--- a/lib/dialyzer/src/dialyzer_plt.erl
+++ b/lib/dialyzer/src/dialyzer_plt.erl
@@ -470,12 +470,11 @@ compute_md5_from_file(File) ->
Msg = io_lib:format("Not a regular file: ~s\n", [File]),
throw({dialyzer_error, Msg});
true ->
- case dialyzer_utils:get_abstract_code_from_beam(File) of
- error ->
- Msg = io_lib:format("Could not get abstract code for file: ~s (please recompile it with +debug_info)\n", [File]),
- throw({dialyzer_error, Msg});
- {ok, Abs} ->
- erlang:md5(term_to_binary(Abs))
+ case dialyzer_utils:get_core_from_beam(File) of
+ {error, Error} ->
+ throw({dialyzer_error, Error});
+ {ok, Core} ->
+ erlang:md5(term_to_binary(Core))
end
end.
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index 9eaf95c1a2..e0497159b3 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -24,14 +24,10 @@
-export([
format_sig/1,
format_sig/2,
- get_abstract_code_from_beam/1,
- get_compile_options_from_beam/1,
- get_abstract_code_from_src/1,
- get_abstract_code_from_src/2,
- get_core_from_abstract_code/1,
- get_core_from_abstract_code/2,
get_core_from_src/1,
get_core_from_src/2,
+ get_core_from_beam/1,
+ get_core_from_beam/2,
get_record_and_type_info/1,
get_spec_info/3,
get_fun_meta_info/3,
@@ -77,9 +73,7 @@ print_types1([{record, _Name} = Key|T], RecDict) ->
%% ----------------------------------------------------------------------------
--type abstract_code() :: [erl_parse:abstract_form()].
-type comp_options() :: [compile:option()].
--type mod_or_fname() :: module() | file:filename().
-type fa() :: {atom(), arity()}.
-type codeserver() :: dialyzer_codeserver:codeserver().
@@ -89,63 +83,82 @@ print_types1([{record, _Name} = Key|T], RecDict) ->
%%
%% ============================================================================
--spec get_abstract_code_from_src(mod_or_fname()) ->
- {'ok', abstract_code()} | {'error', [string()]}.
+-type get_core_from_src_ret() :: {'ok', cerl:c_module()} | {'error', string()}.
+
+-spec get_core_from_src(file:filename()) -> get_core_from_src_ret().
-get_abstract_code_from_src(File) ->
- get_abstract_code_from_src(File, src_compiler_opts()).
+get_core_from_src(File) ->
+ get_core_from_src(File, []).
--spec get_abstract_code_from_src(mod_or_fname(), comp_options()) ->
- {'ok', abstract_code()} | {'error', [string()]}.
+-spec get_core_from_src(file:filename(), comp_options()) -> get_core_from_src_ret().
-get_abstract_code_from_src(File, Opts) ->
- case compile:noenv_file(File, [to_pp, binary|Opts]) of
+get_core_from_src(File, Opts) ->
+ case compile:noenv_file(File, Opts ++ src_compiler_opts()) of
error -> {error, []};
{error, Errors, _} -> {error, format_errors(Errors)};
- {ok, _, AbstrCode} -> {ok, AbstrCode}
+ {ok, _, Core} -> {ok, Core}
end.
--type get_core_from_src_ret() :: {'ok', cerl:c_module()} | {'error', string()}.
+-type get_core_from_beam_ret() :: {'ok', cerl:c_module()} | {'error', string()}.
--spec get_core_from_src(file:filename()) -> get_core_from_src_ret().
+-spec get_core_from_beam(file:filename()) -> get_core_from_beam_ret().
-get_core_from_src(File) ->
- get_core_from_src(File, []).
+get_core_from_beam(File) ->
+ get_core_from_beam(File, []).
--spec get_core_from_src(file:filename(), comp_options()) -> get_core_from_src_ret().
+-spec get_core_from_beam(file:filename(), comp_options()) -> get_core_from_beam_ret().
-get_core_from_src(File, Opts) ->
- case get_abstract_code_from_src(File, Opts) of
- {error, _} = Error -> Error;
+get_core_from_beam(File, Opts) ->
+ case beam_lib:chunks(File, [debug_info]) of
+ {ok, {Module, [{debug_info, {debug_info_v1, Backend, Metadata}}]}} ->
+ case Backend:debug_info(core_v1, Module, Metadata, Opts ++ src_compiler_opts()) of
+ {ok, Core} ->
+ {ok, Core};
+ {error, _} ->
+ {error, " Could not get Core Erlang code for: " ++ File ++ "\n"}
+ end;
+ _ ->
+ deprecated_get_core_from_beam(File, Opts)
+ end.
+
+deprecated_get_core_from_beam(File, Opts) ->
+ case get_abstract_code_from_beam(File) of
+ error ->
+ {error, " Could not get abstract code for: " ++ File ++ "\n" ++
+ " Recompile with +debug_info or analyze starting from source code"};
{ok, AbstrCode} ->
- case get_core_from_abstract_code(AbstrCode, Opts) of
- error -> {error, " Could not get Core Erlang code from abstract code"};
- {ok, _Core} = C -> C
+ case get_compile_options_from_beam(File) of
+ error ->
+ {error, " Could not get compile options for: " ++ File ++ "\n" ++
+ " Recompile or analyze starting from source code"};
+ {ok, CompOpts} ->
+ case get_core_from_abstract_code(AbstrCode, Opts ++ CompOpts) of
+ error ->
+ {error, " Could not get core Erlang code for: " ++ File};
+ {ok, _} = Core ->
+ Core
+ end
end
end.
--spec get_abstract_code_from_beam(file:filename()) -> 'error' | {'ok', abstract_code()}.
-
get_abstract_code_from_beam(File) ->
case beam_lib:chunks(File, [abstract_code]) of
{ok, {_, List}} ->
case lists:keyfind(abstract_code, 1, List) of
- {abstract_code, {raw_abstract_v1, Abstr}} -> {ok, Abstr};
- _ -> error
+ {abstract_code, {raw_abstract_v1, Abstr}} -> {ok, Abstr};
+ _ -> error
end;
_ ->
%% No or unsuitable abstract code.
error
end.
--spec get_compile_options_from_beam(file:filename()) -> 'error' | {'ok', [compile:option()]}.
-
get_compile_options_from_beam(File) ->
case beam_lib:chunks(File, [compile_info]) of
{ok, {_, List}} ->
case lists:keyfind(compile_info, 1, List) of
- {compile_info, CompInfo} -> compile_info_to_options(CompInfo);
- _ -> error
+ {compile_info, CompInfo} -> compile_info_to_options(CompInfo);
+ _ -> error
end;
_ ->
%% No or unsuitable compile info.
@@ -158,15 +171,6 @@ compile_info_to_options(CompInfo) ->
_ -> error
end.
--type get_core_from_abs_ret() :: {'ok', cerl:c_module()} | 'error'.
-
--spec get_core_from_abstract_code(abstract_code()) -> get_core_from_abs_ret().
-
-get_core_from_abstract_code(AbstrCode) ->
- get_core_from_abstract_code(AbstrCode, []).
-
--spec get_core_from_abstract_code(abstract_code(), comp_options()) -> get_core_from_abs_ret().
-
get_core_from_abstract_code(AbstrCode, Opts) ->
%% We do not want the parse_transforms around since we already
%% performed them. In some cases we end up in trouble when
@@ -175,12 +179,31 @@ get_core_from_abstract_code(AbstrCode, Opts) ->
%% Remove parse_transforms (and other options) from compile options.
Opts2 = cleanup_compile_options(Opts),
try compile:noenv_forms(AbstrCode1, Opts2 ++ src_compiler_opts()) of
- {ok, _, Core} -> {ok, Core};
- _What -> error
+ {ok, _, Core} -> {ok, Core};
+ _What -> error
catch
error:_ -> error
end.
+cleanup_parse_transforms([{attribute, _, compile, {parse_transform, _}}|Left]) ->
+ cleanup_parse_transforms(Left);
+cleanup_parse_transforms([Other|Left]) ->
+ [Other|cleanup_parse_transforms(Left)];
+cleanup_parse_transforms([]) ->
+ [].
+
+cleanup_compile_options(Opts) ->
+ lists:filter(fun keep_compile_option/1, Opts).
+
+%% Using abstract, not asm or core.
+keep_compile_option(from_asm) -> false;
+keep_compile_option(from_core) -> false;
+%% The parse transform will already have been applied, may cause
+%% problems if it is re-applied.
+keep_compile_option({parse_transform, _}) -> false;
+keep_compile_option(warnings_as_errors) -> false;
+keep_compile_option(_) -> true.
+
%% ============================================================================
%%
%% Typed Records
@@ -189,55 +212,50 @@ get_core_from_abstract_code(AbstrCode, Opts) ->
-type type_table() :: erl_types:type_table().
--spec get_record_and_type_info(abstract_code()) ->
- {'ok', type_table()} | {'error', string()}.
+-spec get_record_and_type_info(cerl:module()) ->
+ {'ok', type_table()} | {'error', string()}.
-get_record_and_type_info(AbstractCode) ->
- Module = get_module(AbstractCode),
- get_record_and_type_info(AbstractCode, Module, maps:new()).
+get_record_and_type_info(Core) ->
+ Module = cerl:concrete(cerl:module_name(Core)),
+ Tuples = core_to_attr_tuples(Core),
+ get_record_and_type_info(Tuples, Module, maps:new(), "nofile").
--spec get_record_and_type_info(abstract_code(), module(), type_table()) ->
- {'ok', type_table()} | {'error', string()}.
-
-get_record_and_type_info(AbstractCode, Module, RecDict) ->
- get_record_and_type_info(AbstractCode, Module, RecDict, "nofile").
-
-get_record_and_type_info([{attribute, A, record, {Name, Fields0}}|Left],
+get_record_and_type_info([{record, Line, [{Name, Fields0}]}|Left],
Module, RecDict, File) ->
{ok, Fields} = get_record_fields(Fields0, RecDict),
Arity = length(Fields),
- FN = {File, erl_anno:line(A)},
+ FN = {File, Line},
NewRecDict = maps:put({record, Name}, {FN, [{Arity,Fields}]}, RecDict),
get_record_and_type_info(Left, Module, NewRecDict, File);
-get_record_and_type_info([{attribute, A, type, {{record, Name}, Fields0, []}}
+get_record_and_type_info([{type, Line, [{{record, Name}, Fields0, []}]}
|Left], Module, RecDict, File) ->
%% This overrides the original record declaration.
{ok, Fields} = get_record_fields(Fields0, RecDict),
Arity = length(Fields),
- FN = {File, erl_anno:line(A)},
+ FN = {File, Line},
NewRecDict = maps:put({record, Name}, {FN, [{Arity, Fields}]}, RecDict),
get_record_and_type_info(Left, Module, NewRecDict, File);
-get_record_and_type_info([{attribute, A, Attr, {Name, TypeForm}}|Left],
+get_record_and_type_info([{Attr, Line, [{Name, TypeForm}]}|Left],
Module, RecDict, File)
when Attr =:= 'type'; Attr =:= 'opaque' ->
- FN = {File, erl_anno:line(A)},
+ FN = {File, Line},
try add_new_type(Attr, Name, TypeForm, [], Module, FN, RecDict) of
NewRecDict ->
get_record_and_type_info(Left, Module, NewRecDict, File)
catch
throw:{error, _} = Error -> Error
end;
-get_record_and_type_info([{attribute, A, Attr, {Name, TypeForm, Args}}|Left],
+get_record_and_type_info([{Attr, Line, [{Name, TypeForm, Args}]}|Left],
Module, RecDict, File)
when Attr =:= 'type'; Attr =:= 'opaque' ->
- FN = {File, erl_anno:line(A)},
+ FN = {File, Line},
try add_new_type(Attr, Name, TypeForm, Args, Module, FN, RecDict) of
NewRecDict ->
get_record_and_type_info(Left, Module, NewRecDict, File)
catch
throw:{error, _} = Error -> Error
end;
-get_record_and_type_info([{attribute, _, file, {IncludeFile, _}}|Left],
+get_record_and_type_info([{file, _, [{IncludeFile, _}]}|Left],
Module, RecDict, _File) ->
get_record_and_type_info(Left, Module, RecDict, IncludeFile);
get_record_and_type_info([_Other|Left], Module, RecDict, File) ->
@@ -460,23 +478,18 @@ merge_types(CServer, Plt) ->
-type spec_map() :: dialyzer_codeserver:contracts().
-type callback_map() :: dialyzer_codeserver:contracts().
--spec get_spec_info(module(), abstract_code(), type_table()) ->
+-spec get_spec_info(module(), cerl:module(), type_table()) ->
{'ok', spec_map(), callback_map()} | {'error', string()}.
-get_spec_info(ModName, AbstractCode, RecordsMap) ->
- OptionalCallbacks0 = get_optional_callbacks(AbstractCode, ModName),
+get_spec_info(ModName, Core, RecordsMap) ->
+ Tuples = core_to_attr_tuples(Core),
+ OptionalCallbacks0 = get_optional_callbacks(Tuples, ModName),
OptionalCallbacks = gb_sets:from_list(OptionalCallbacks0),
- get_spec_info(AbstractCode, maps:new(), maps:new(),
+ get_spec_info(Tuples, maps:new(), maps:new(),
RecordsMap, ModName, OptionalCallbacks, "nofile").
-get_optional_callbacks(Abs, ModName) ->
- [{ModName, F, A} || {F, A} <- get_optional_callbacks(Abs)].
-
-get_optional_callbacks(Abs) ->
- L = [O ||
- {attribute, _, optional_callbacks, O} <- Abs,
- is_fa_list(O)],
- lists:append(L).
+get_optional_callbacks(Tuples, ModName) ->
+ [{ModName, F, A} || {optional_callbacks, _, O} <- Tuples, is_fa_list(O), {F, A} <- O].
%% TypeSpec is a list of conditional contracts for a function.
%% Each contract is of the form {[Argument], Range, [Constraint]} where
@@ -484,11 +497,10 @@ get_optional_callbacks(Abs) ->
%% - Constraint is of the form {subtype, T1, T2} where T1 and T2
%% are erl_types:erl_type()
-get_spec_info([{attribute, Anno, Contract, {Id, TypeSpec}}|Left],
+get_spec_info([{Contract, Ln, [{Id, TypeSpec}]}|Left],
SpecMap, CallbackMap, RecordsMap, ModName, OptCb, File)
when ((Contract =:= 'spec') or (Contract =:= 'callback')),
is_list(TypeSpec) ->
- Ln = erl_anno:line(Anno),
MFA = case Id of
{_, _, _} = T -> T;
{F, A} -> {ModName, F, A}
@@ -523,7 +535,7 @@ get_spec_info([{attribute, Anno, Contract, {Id, TypeSpec}}|Left],
{error, flat_format(" Error while parsing contract in line ~w: ~s\n",
[Ln, Error])}
end;
-get_spec_info([{attribute, _, file, {IncludeFile, _}}|Left],
+get_spec_info([{file, _, [{IncludeFile, _}]}|Left],
SpecMap, CallbackMap, RecordsMap, ModName, OptCb, _File) ->
get_spec_info(Left, SpecMap, CallbackMap,
RecordsMap, ModName, OptCb, IncludeFile);
@@ -535,15 +547,25 @@ get_spec_info([], SpecMap, CallbackMap,
_RecordsMap, _ModName, _OptCb, _File) ->
{ok, SpecMap, CallbackMap}.
--spec get_fun_meta_info(module(), abstract_code(), [dial_warn_tag()]) ->
+core_to_attr_tuples(Core) ->
+ [{cerl:concrete(Key), get_core_line(cerl:get_ann(Key)), cerl:concrete(Value)} ||
+ {Key, Value} <- cerl:module_attrs(Core)].
+
+get_core_line([L | _As]) when is_integer(L) -> L;
+get_core_line([_ | As]) -> get_core_line(As);
+get_core_line([]) -> undefined.
+
+-spec get_fun_meta_info(module(), cerl:module(), [dial_warn_tag()]) ->
dialyzer_codeserver:fun_meta_info() | {'error', string()}.
-get_fun_meta_info(M, Abs, LegalWarnings) ->
+get_fun_meta_info(M, Core, LegalWarnings) ->
+ Functions = lists:map(fun cerl:var_name/1, cerl:module_vars(Core)),
try
- {get_nowarn_unused_function(M, Abs), get_func_suppressions(M, Abs)}
+ {get_nowarn_unused_function(M, Core, Functions),
+ get_func_suppressions(M, Core, Functions)}
of
{NoWarn, FuncSupp} ->
- Warnings0 = get_options(Abs, LegalWarnings),
+ Warnings0 = get_options(Core, LegalWarnings),
Warnings = ordsets:to_list(Warnings0),
ModuleWarnings = [{M, W} || W <- Warnings],
RawProps = lists:append([NoWarn, FuncSupp, ModuleWarnings]),
@@ -569,49 +591,45 @@ process_options([{{_M, _F, _A}=MFA, Opts}|Left], Warnings) ->
end;
process_options([], _Warnings) -> [].
--spec get_nowarn_unused_function(module(), abstract_code()) ->
+-spec get_nowarn_unused_function(module(), cerl:module(), [fa()]) ->
[{mfa(), 'no_unused'}].
-get_nowarn_unused_function(M, Abs) ->
- Opts = get_options_with_tag(compile, Abs),
+get_nowarn_unused_function(M, Core, Functions) ->
+ Opts = get_options_with_tag(compile, Core),
Warn = erl_lint:bool_option(warn_unused_function, nowarn_unused_function,
true, Opts),
- Functions = [{F, A} || {function, _, F, A, _} <- Abs],
- AttrFile = collect_attribute(Abs, compile),
+ AttrFile = collect_attribute(Core, compile),
TagsFaList = check_fa_list(AttrFile, nowarn_unused_function, Functions),
FAs = case Warn of
false -> Functions;
true ->
- [FA || {{nowarn_unused_function,_L,_File}, FA} <- TagsFaList]
+ [FA || {{[nowarn_unused_function],_L,_File}, FA} <- TagsFaList]
end,
[{{M, F, A}, no_unused} || {F, A} <- FAs].
--spec get_func_suppressions(module(), abstract_code()) ->
+-spec get_func_suppressions(module(), cerl:module(), [fa()]) ->
[{mfa(), 'nowarn_function' | dial_warn_tag()}].
-get_func_suppressions(M, Abs) ->
- Functions = [{F, A} || {function, _, F, A, _} <- Abs],
- AttrFile = collect_attribute(Abs, dialyzer),
+get_func_suppressions(M, Core, Functions) ->
+ AttrFile = collect_attribute(Core, dialyzer),
TagsFAs = check_fa_list(AttrFile, '*', Functions),
%% Check the options:
- Fun = fun({{nowarn_function, _L, _File}, _FA}) -> ok;
+ Fun = fun({{[nowarn_function], _L, _File}, _FA}) -> ok;
({OptLFile, _FA}) ->
_ = get_options1([OptLFile], ordsets:new())
end,
lists:foreach(Fun, TagsFAs),
- [{{M, F, A}, W} || {{W, _L, _File}, {F, A}} <- TagsFAs].
+ [{{M, F, A}, W} || {{Warnings, _L, _File}, {F, A}} <- TagsFAs, W <- Warnings].
--spec get_options(abstract_code(), [dial_warn_tag()]) ->
+-spec get_options(cerl:module(), [dial_warn_tag()]) ->
ordsets:ordset(dial_warn_tag()).
-get_options(Abs, LegalWarnings) ->
- AttrFile = collect_attribute(Abs, dialyzer),
+get_options(Core, LegalWarnings) ->
+ AttrFile = collect_attribute(Core, dialyzer),
get_options1(AttrFile, LegalWarnings).
get_options1([{Args, L, File}|Left], Warnings) ->
- Opts = [O ||
- O <- lists:flatten([Args]),
- is_atom(O)],
+ Opts = [O || O <- Args, is_atom(O)],
try dialyzer_options:build_warnings(Opts, Warnings) of
NewWarnings ->
get_options1(Left, NewWarnings)
@@ -624,19 +642,24 @@ get_options1([], Warnings) ->
Warnings.
-type collected_attribute() ::
- {Args :: term(), erl_anno:line(), file:filename()}.
-
-collect_attribute(Abs, Tag) ->
- collect_attribute(Abs, Tag, "nofile").
-
-collect_attribute([{attribute, L, Tag, Args}|Left], Tag, File) ->
- CollAttr = {Args, L, File},
- [CollAttr | collect_attribute(Left, Tag, File)];
-collect_attribute([{attribute, _, file, {IncludeFile, _}}|Left], Tag, _) ->
- collect_attribute(Left, Tag, IncludeFile);
-collect_attribute([_Other|Left], Tag, File) ->
- collect_attribute(Left, Tag, File);
-collect_attribute([], _Tag, _File) -> [].
+ {Args :: [term()], erl_anno:line(), file:filename()}.
+
+collect_attribute(Core, Tag) ->
+ collect_attribute(cerl:module_attrs(Core), Tag, "nofile").
+
+collect_attribute([{Key, Value}|T], Tag, File) ->
+ case cerl:concrete(Key) of
+ Tag ->
+ [{cerl:concrete(Value), get_core_line(cerl:get_ann(Key)), File} |
+ collect_attribute(T, Tag, File)];
+ file ->
+ [{IncludeFile, _}] = cerl:concrete(Value),
+ collect_attribute(T, Tag, IncludeFile);
+ _ ->
+ collect_attribute(T, Tag, File)
+ end;
+collect_attribute([], _Tag, _File) ->
+ [].
-spec is_suppressed_fun(mfa(), codeserver()) -> boolean().
@@ -687,35 +710,6 @@ src_compiler_opts() ->
no_inline, strict_record_tests, strict_record_updates,
dialyzer].
--spec get_module(abstract_code()) -> module().
-
-get_module([{attribute, _, module, {M, _As}} | _]) -> M;
-get_module([{attribute, _, module, M} | _]) -> M;
-get_module([_ | Rest]) -> get_module(Rest).
-
--spec cleanup_parse_transforms(abstract_code()) -> abstract_code().
-
-cleanup_parse_transforms([{attribute, _, compile, {parse_transform, _}}|Left]) ->
- cleanup_parse_transforms(Left);
-cleanup_parse_transforms([Other|Left]) ->
- [Other|cleanup_parse_transforms(Left)];
-cleanup_parse_transforms([]) ->
- [].
-
--spec cleanup_compile_options([compile:option()]) -> [compile:option()].
-
-cleanup_compile_options(Opts) ->
- lists:filter(fun keep_compile_option/1, Opts).
-
-%% Using abstract, not asm or core.
-keep_compile_option(from_asm) -> false;
-keep_compile_option(from_core) -> false;
-%% The parse transform will already have been applied, may cause
-%% problems if it is re-applied.
-keep_compile_option({parse_transform, _}) -> false;
-keep_compile_option(warnings_as_errors) -> false;
-keep_compile_option(_) -> true.
-
-spec format_errors([{module(), string()}]) -> [string()].
format_errors([{Mod, Errors}|Left]) ->
@@ -741,10 +735,12 @@ format_sig(Type, RecDict) ->
flat_format(Fmt, Lst) ->
lists:flatten(io_lib:format(Fmt, Lst)).
--spec get_options_with_tag(atom(), abstract_code()) -> [term()].
+-spec get_options_with_tag(atom(), cerl:module()) -> [term()].
-get_options_with_tag(Tag, Abs) ->
- lists:flatten([O || {attribute, _, Tag0, O} <- Abs, Tag =:= Tag0]).
+get_options_with_tag(Tag, Core) ->
+ [O || {Key, Value} <- cerl:module_attrs(Core),
+ cerl:concrete(Key) =:= Tag,
+ O <- cerl:concrete(Value)].
%% Check F/A, and collect (unchecked) warning tags with line and file.
-spec check_fa_list([collected_attribute()], atom(), [fa()]) ->
@@ -755,8 +751,8 @@ check_fa_list(AttrFile, Tag, Functions) ->
check_fa_list1(AttrFile, Tag, FuncTab).
check_fa_list1([{Args, L, File}|Left], Tag, Funcs) ->
- TermsL = [{{Tag0, L, File}, Term} ||
- {Tags, Terms0} <- lists:flatten([Args]),
+ TermsL = [{{[Tag0], L, File}, Term} ||
+ {Tags, Terms0} <- Args,
Tag0 <- lists:flatten([Tags]),
Tag =:= '*' orelse Tag =:= Tag0,
Term <- lists:flatten([Terms0])],
diff --git a/lib/dialyzer/src/typer.erl b/lib/dialyzer/src/typer.erl
new file mode 100644
index 0000000000..86c7c62f98
--- /dev/null
+++ b/lib/dialyzer/src/typer.erl
@@ -0,0 +1,1106 @@
+%% -*- erlang-indent-level: 2 -*-
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+
+%%-----------------------------------------------------------------------
+%% File : typer.erl
+%% Author(s) : The first version of typer was written by Bingwen He
+%% with guidance from Kostis Sagonas and Tobias Lindahl.
+%% Since June 2008 typer is maintained by Kostis Sagonas.
+%% Description : An Erlang/OTP application that shows type information
+%% for Erlang modules to the user. Additionally, it can
+%% annotate the code of files with such type information.
+%%-----------------------------------------------------------------------
+
+-module(typer).
+
+-export([start/0]).
+
+%%-----------------------------------------------------------------------
+
+-define(SHOW, show).
+-define(SHOW_EXPORTED, show_exported).
+-define(ANNOTATE, annotate).
+-define(ANNOTATE_INC_FILES, annotate_inc_files).
+
+-type mode() :: ?SHOW | ?SHOW_EXPORTED | ?ANNOTATE | ?ANNOTATE_INC_FILES.
+
+%%-----------------------------------------------------------------------
+
+-type files() :: [file:filename()].
+-type callgraph() :: dialyzer_callgraph:callgraph().
+-type codeserver() :: dialyzer_codeserver:codeserver().
+-type plt() :: dialyzer_plt:plt().
+
+-record(analysis,
+ {mode :: mode() | 'undefined',
+ macros = [] :: [{atom(), term()}],
+ includes = [] :: files(),
+ codeserver = dialyzer_codeserver:new():: codeserver(),
+ callgraph = dialyzer_callgraph:new() :: callgraph(),
+ files = [] :: files(), % absolute names
+ plt = none :: 'none' | file:filename(),
+ no_spec = false :: boolean(),
+ show_succ = false :: boolean(),
+ %% For choosing between specs or edoc @spec comments
+ edoc = false :: boolean(),
+ %% Files in 'fms' are compilable with option 'to_pp'; we keep them
+ %% as {FileName, ModuleName} in case the ModuleName is different
+ fms = [] :: [{file:filename(), module()}],
+ ex_func = map__new() :: map_dict(),
+ record = map__new() :: map_dict(),
+ func = map__new() :: map_dict(),
+ inc_func = map__new() :: map_dict(),
+ trust_plt = dialyzer_plt:new() :: plt()}).
+-type analysis() :: #analysis{}.
+
+-record(args, {files = [] :: files(),
+ files_r = [] :: files(),
+ trusted = [] :: files()}).
+-type args() :: #args{}.
+
+%%--------------------------------------------------------------------
+
+-spec start() -> no_return().
+
+start() ->
+ {Args, Analysis} = process_cl_args(),
+ %% io:format("Args: ~p\n", [Args]),
+ %% io:format("Analysis: ~p\n", [Analysis]),
+ Timer = dialyzer_timing:init(false),
+ TrustedFiles = filter_fd(Args#args.trusted, [], fun is_erl_file/1),
+ Analysis2 = extract(Analysis, TrustedFiles),
+ All_Files = get_all_files(Args),
+ %% io:format("All_Files: ~p\n", [All_Files]),
+ Analysis3 = Analysis2#analysis{files = All_Files},
+ Analysis4 = collect_info(Analysis3),
+ %% io:format("Final: ~p\n", [Analysis4#analysis.fms]),
+ TypeInfo = get_type_info(Analysis4),
+ dialyzer_timing:stop(Timer),
+ show_or_annotate(TypeInfo),
+ %% io:format("\nTyper analysis finished\n"),
+ erlang:halt(0).
+
+%%--------------------------------------------------------------------
+
+-spec extract(analysis(), files()) -> analysis().
+
+extract(#analysis{macros = Macros,
+ includes = Includes,
+ trust_plt = TrustPLT} = Analysis, TrustedFiles) ->
+ %% io:format("--- Extracting trusted typer_info... "),
+ Ds = [{d, Name, Value} || {Name, Value} <- Macros],
+ CodeServer = dialyzer_codeserver:new(),
+ Fun =
+ fun(File, CS) ->
+ %% We include one more dir; the one above the one we are trusting
+ %% E.g, for /home/tests/typer_ann/test.ann.erl, we should include
+ %% /home/tests/ rather than /home/tests/typer_ann/
+ AllIncludes = [filename:dirname(filename:dirname(File)) | Includes],
+ Is = [{i, Dir} || Dir <- AllIncludes],
+ CompOpts = dialyzer_utils:src_compiler_opts() ++ Is ++ Ds,
+ case dialyzer_utils:get_core_from_src(File, CompOpts) of
+ {ok, Core} ->
+ case dialyzer_utils:get_record_and_type_info(Core) of
+ {ok, RecDict} ->
+ Mod = list_to_atom(filename:basename(File, ".erl")),
+ case dialyzer_utils:get_spec_info(Mod, Core, RecDict) of
+ {ok, SpecDict, CbDict} ->
+ CS1 = dialyzer_codeserver:store_temp_records(Mod, RecDict, CS),
+ dialyzer_codeserver:store_temp_contracts(Mod, SpecDict, CbDict, CS1);
+ {error, Reason} -> compile_error([Reason])
+ end;
+ {error, Reason} -> compile_error([Reason])
+ end;
+ {error, Reason} -> compile_error(Reason)
+ end
+ end,
+ CodeServer1 = lists:foldl(Fun, CodeServer, TrustedFiles),
+ %% Process remote types
+ NewCodeServer =
+ try
+ CodeServer2 =
+ dialyzer_utils:merge_types(CodeServer1,
+ TrustPLT), % XXX change to the PLT?
+ NewExpTypes = dialyzer_codeserver:get_temp_exported_types(CodeServer1),
+ case sets:size(NewExpTypes) of 0 -> ok end,
+ CodeServer3 = dialyzer_codeserver:finalize_exported_types(NewExpTypes, CodeServer2),
+ CodeServer4 = dialyzer_utils:process_record_remote_types(CodeServer3),
+ dialyzer_contracts:process_contract_remote_types(CodeServer4)
+ catch
+ throw:{error, ErrorMsg} ->
+ compile_error(ErrorMsg)
+ end,
+ %% Create TrustPLT
+ ContractsDict = dialyzer_codeserver:get_contracts(NewCodeServer),
+ Contracts = orddict:from_list(dict:to_list(ContractsDict)),
+ NewTrustPLT = dialyzer_plt:insert_contract_list(TrustPLT, Contracts),
+ Analysis#analysis{trust_plt = NewTrustPLT}.
+
+%%--------------------------------------------------------------------
+
+-spec get_type_info(analysis()) -> analysis().
+
+get_type_info(#analysis{callgraph = CallGraph,
+ trust_plt = TrustPLT,
+ codeserver = CodeServer} = Analysis) ->
+ StrippedCallGraph = remove_external(CallGraph, TrustPLT),
+ %% io:format("--- Analyzing callgraph... "),
+ try
+ NewMiniPlt = dialyzer_succ_typings:analyze_callgraph(StrippedCallGraph,
+ TrustPLT,
+ CodeServer),
+ NewPlt = dialyzer_plt:restore_full_plt(NewMiniPlt),
+ Analysis#analysis{callgraph = StrippedCallGraph, trust_plt = NewPlt}
+ catch
+ error:What ->
+ fatal_error(io_lib:format("Analysis failed with message: ~p",
+ [{What, erlang:get_stacktrace()}]));
+ throw:{dialyzer_succ_typing_error, Msg} ->
+ fatal_error(io_lib:format("Analysis failed with message: ~s", [Msg]))
+ end.
+
+-spec remove_external(callgraph(), plt()) -> callgraph().
+
+remove_external(CallGraph, PLT) ->
+ {StrippedCG0, Ext} = dialyzer_callgraph:remove_external(CallGraph),
+ case get_external(Ext, PLT) of
+ [] -> ok;
+ Externals ->
+ msg(io_lib:format(" Unknown functions: ~p\n", [lists:usort(Externals)])),
+ ExtTypes = rcv_ext_types(),
+ case ExtTypes of
+ [] -> ok;
+ _ -> msg(io_lib:format(" Unknown types: ~p\n", [ExtTypes]))
+ end
+ end,
+ StrippedCG0.
+
+-spec get_external([{mfa(), mfa()}], plt()) -> [mfa()].
+
+get_external(Exts, Plt) ->
+ Fun = fun ({_From, To = {M, F, A}}, Acc) ->
+ case dialyzer_plt:contains_mfa(Plt, To) of
+ false ->
+ case erl_bif_types:is_known(M, F, A) of
+ true -> Acc;
+ false -> [To|Acc]
+ end;
+ true -> Acc
+ end
+ end,
+ lists:foldl(Fun, [], Exts).
+
+%%--------------------------------------------------------------------
+%% Showing type information or annotating files with such information.
+%%--------------------------------------------------------------------
+
+-define(TYPER_ANN_DIR, "typer_ann").
+
+-type line() :: non_neg_integer().
+-type fa() :: {atom(), arity()}.
+-type func_info() :: {line(), atom(), arity()}.
+
+-record(info, {records = maps:new() :: erl_types:type_table(),
+ functions = [] :: [func_info()],
+ types = map__new() :: map_dict(),
+ edoc = false :: boolean()}).
+-record(inc, {map = map__new() :: map_dict(), filter = [] :: files()}).
+-type inc() :: #inc{}.
+
+-spec show_or_annotate(analysis()) -> 'ok'.
+
+show_or_annotate(#analysis{mode = Mode, fms = Files} = Analysis) ->
+ case Mode of
+ ?SHOW -> show(Analysis);
+ ?SHOW_EXPORTED -> show(Analysis);
+ ?ANNOTATE ->
+ Fun = fun ({File, Module}) ->
+ Info = get_final_info(File, Module, Analysis),
+ write_typed_file(File, Info)
+ end,
+ lists:foreach(Fun, Files);
+ ?ANNOTATE_INC_FILES ->
+ IncInfo = write_and_collect_inc_info(Analysis),
+ write_inc_files(IncInfo)
+ end.
+
+write_and_collect_inc_info(Analysis) ->
+ Fun = fun ({File, Module}, Inc) ->
+ Info = get_final_info(File, Module, Analysis),
+ write_typed_file(File, Info),
+ IncFuns = get_functions(File, Analysis),
+ collect_imported_functions(IncFuns, Info#info.types, Inc)
+ end,
+ NewInc = lists:foldl(Fun, #inc{}, Analysis#analysis.fms),
+ clean_inc(NewInc).
+
+write_inc_files(Inc) ->
+ Fun =
+ fun (File) ->
+ Val = map__lookup(File, Inc#inc.map),
+ %% Val is function with its type info
+ %% in form [{{Line,F,A},Type}]
+ Functions = [Key || {Key, _} <- Val],
+ Val1 = [{{F,A},Type} || {{_Line,F,A},Type} <- Val],
+ Info = #info{types = map__from_list(Val1),
+ records = maps:new(),
+ %% Note we need to sort functions here!
+ functions = lists:keysort(1, Functions)},
+ %% io:format("Types ~p\n", [Info#info.types]),
+ %% io:format("Functions ~p\n", [Info#info.functions]),
+ %% io:format("Records ~p\n", [Info#info.records]),
+ write_typed_file(File, Info)
+ end,
+ lists:foreach(Fun, dict:fetch_keys(Inc#inc.map)).
+
+show(Analysis) ->
+ Fun = fun ({File, Module}) ->
+ Info = get_final_info(File, Module, Analysis),
+ show_type_info(File, Info)
+ end,
+ lists:foreach(Fun, Analysis#analysis.fms).
+
+get_final_info(File, Module, Analysis) ->
+ Records = get_records(File, Analysis),
+ Types = get_types(Module, Analysis, Records),
+ Functions = get_functions(File, Analysis),
+ Edoc = Analysis#analysis.edoc,
+ #info{records = Records, functions = Functions, types = Types, edoc = Edoc}.
+
+collect_imported_functions(Functions, Types, Inc) ->
+ %% Coming from other sourses, including:
+ %% FIXME: How to deal with yecc-generated file????
+ %% --.yrl (yecc-generated file)???
+ %% -- yeccpre.hrl (yecc-generated file)???
+ %% -- other cases
+ Fun = fun ({File, _} = Obj, I) ->
+ case is_yecc_gen(File, I) of
+ {true, NewI} -> NewI;
+ {false, NewI} ->
+ check_imported_functions(Obj, NewI, Types)
+ end
+ end,
+ lists:foldl(Fun, Inc, Functions).
+
+-spec is_yecc_gen(file:filename(), inc()) -> {boolean(), inc()}.
+
+is_yecc_gen(File, #inc{filter = Fs} = Inc) ->
+ case lists:member(File, Fs) of
+ true -> {true, Inc};
+ false ->
+ case filename:extension(File) of
+ ".yrl" ->
+ Rootname = filename:rootname(File, ".yrl"),
+ Obj = Rootname ++ ".erl",
+ case lists:member(Obj, Fs) of
+ true -> {true, Inc};
+ false ->
+ NewInc = Inc#inc{filter = [Obj|Fs]},
+ {true, NewInc}
+ end;
+ _ ->
+ case filename:basename(File) of
+ "yeccpre.hrl" -> {true, Inc};
+ _ -> {false, Inc}
+ end
+ end
+ end.
+
+check_imported_functions({File, {Line, F, A}}, Inc, Types) ->
+ IncMap = Inc#inc.map,
+ FA = {F, A},
+ Type = get_type_info(FA, Types),
+ case map__lookup(File, IncMap) of
+ none -> %% File is not added. Add it
+ Obj = {File,[{FA, {Line, Type}}]},
+ NewMap = map__insert(Obj, IncMap),
+ Inc#inc{map = NewMap};
+ Val -> %% File is already in. Check.
+ case lists:keyfind(FA, 1, Val) of
+ false ->
+ %% Function is not in; add it
+ Obj = {File, Val ++ [{FA, {Line, Type}}]},
+ NewMap = map__insert(Obj, IncMap),
+ Inc#inc{map = NewMap};
+ Type ->
+ %% Function is in and with same type
+ Inc;
+ _ ->
+ %% Function is in but with diff type
+ inc_warning(FA, File),
+ Elem = lists:keydelete(FA, 1, Val),
+ NewMap = case Elem of
+ [] -> map__remove(File, IncMap);
+ _ -> map__insert({File, Elem}, IncMap)
+ end,
+ Inc#inc{map = NewMap}
+ end
+ end.
+
+inc_warning({F, A}, File) ->
+ io:format(" ***Warning: Skip function ~p/~p ", [F, A]),
+ io:format("in file ~p because of inconsistent type\n", [File]).
+
+clean_inc(Inc) ->
+ Inc1 = remove_yecc_generated_file(Inc),
+ normalize_obj(Inc1).
+
+remove_yecc_generated_file(#inc{filter = Filter} = Inc) ->
+ Fun = fun (Key, #inc{map = Map} = I) ->
+ I#inc{map = map__remove(Key, Map)}
+ end,
+ lists:foldl(Fun, Inc, Filter).
+
+normalize_obj(TmpInc) ->
+ Fun = fun (Key, Val, Inc) ->
+ NewVal = [{{Line,F,A},Type} || {{F,A},{Line,Type}} <- Val],
+ map__insert({Key, NewVal}, Inc)
+ end,
+ TmpInc#inc{map = map__fold(Fun, map__new(), TmpInc#inc.map)}.
+
+get_records(File, Analysis) ->
+ map__lookup(File, Analysis#analysis.record).
+
+get_types(Module, Analysis, Records) ->
+ TypeInfoPlt = Analysis#analysis.trust_plt,
+ TypeInfo =
+ case dialyzer_plt:lookup_module(TypeInfoPlt, Module) of
+ none -> [];
+ {value, List} -> List
+ end,
+ CodeServer = Analysis#analysis.codeserver,
+ TypeInfoList =
+ case Analysis#analysis.show_succ of
+ true ->
+ [convert_type_info(I) || I <- TypeInfo];
+ false ->
+ [get_type(I, CodeServer, Records) || I <- TypeInfo]
+ end,
+ map__from_list(TypeInfoList).
+
+convert_type_info({{_M, F, A}, Range, Arg}) ->
+ {{F, A}, {Range, Arg}}.
+
+get_type({{M, F, A} = MFA, Range, Arg}, CodeServer, Records) ->
+ case dialyzer_codeserver:lookup_mfa_contract(MFA, CodeServer) of
+ error ->
+ {{F, A}, {Range, Arg}};
+ {ok, {_FileLine, Contract, _Xtra}} ->
+ Sig = erl_types:t_fun(Arg, Range),
+ case dialyzer_contracts:check_contract(Contract, Sig) of
+ ok -> {{F, A}, {contract, Contract}};
+ {error, {extra_range, _, _}} ->
+ {{F, A}, {contract, Contract}};
+ {error, {overlapping_contract, []}} ->
+ {{F, A}, {contract, Contract}};
+ {error, invalid_contract} ->
+ CString = dialyzer_contracts:contract_to_string(Contract),
+ SigString = dialyzer_utils:format_sig(Sig, Records),
+ Msg = io_lib:format("Error in contract of function ~w:~w/~w\n"
+ "\t The contract is: " ++ CString ++ "\n" ++
+ "\t but the inferred signature is: ~s",
+ [M, F, A, SigString]),
+ fatal_error(Msg);
+ {error, ErrorStr} when is_list(ErrorStr) -> % ErrorStr is a string()
+ Msg = io_lib:format("Error in contract of function ~w:~w/~w: ~s",
+ [M, F, A, ErrorStr]),
+ fatal_error(Msg)
+ end
+ end.
+
+get_functions(File, Analysis) ->
+ case Analysis#analysis.mode of
+ ?SHOW ->
+ Funcs = map__lookup(File, Analysis#analysis.func),
+ Inc_Funcs = map__lookup(File, Analysis#analysis.inc_func),
+ remove_module_info(Funcs) ++ normalize_incFuncs(Inc_Funcs);
+ ?SHOW_EXPORTED ->
+ Ex_Funcs = map__lookup(File, Analysis#analysis.ex_func),
+ remove_module_info(Ex_Funcs);
+ ?ANNOTATE ->
+ Funcs = map__lookup(File, Analysis#analysis.func),
+ remove_module_info(Funcs);
+ ?ANNOTATE_INC_FILES ->
+ map__lookup(File, Analysis#analysis.inc_func)
+ end.
+
+normalize_incFuncs(Functions) ->
+ [FunInfo || {_FileName, FunInfo} <- Functions].
+
+-spec remove_module_info([func_info()]) -> [func_info()].
+
+remove_module_info(FunInfoList) ->
+ F = fun ({_,module_info,0}) -> false;
+ ({_,module_info,1}) -> false;
+ ({Line,F,A}) when is_integer(Line), is_atom(F), is_integer(A) -> true
+ end,
+ lists:filter(F, FunInfoList).
+
+write_typed_file(File, Info) ->
+ io:format(" Processing file: ~p\n", [File]),
+ Dir = filename:dirname(File),
+ RootName = filename:basename(filename:rootname(File)),
+ Ext = filename:extension(File),
+ TyperAnnDir = filename:join(Dir, ?TYPER_ANN_DIR),
+ TmpNewFilename = lists:concat([RootName, ".ann", Ext]),
+ NewFileName = filename:join(TyperAnnDir, TmpNewFilename),
+ case file:make_dir(TyperAnnDir) of
+ {error, Reason} ->
+ case Reason of
+ eexist -> %% TypEr dir exists; remove old typer files if they exist
+ case file:delete(NewFileName) of
+ ok -> ok;
+ {error, enoent} -> ok;
+ {error, _} ->
+ Msg = io_lib:format("Error in deleting file ~s\n", [NewFileName]),
+ fatal_error(Msg)
+ end,
+ write_typed_file(File, Info, NewFileName);
+ enospc ->
+ Msg = io_lib:format("Not enough space in ~p\n", [Dir]),
+ fatal_error(Msg);
+ eacces ->
+ Msg = io_lib:format("No write permission in ~p\n", [Dir]),
+ fatal_error(Msg);
+ _ ->
+ Msg = io_lib:format("Unhandled error ~s when writing ~p\n",
+ [Reason, Dir]),
+ fatal_error(Msg)
+ end;
+ ok -> %% Typer dir does NOT exist
+ write_typed_file(File, Info, NewFileName)
+ end.
+
+write_typed_file(File, Info, NewFileName) ->
+ {ok, Binary} = file:read_file(File),
+ Chars = binary_to_list(Binary),
+ write_typed_file(Chars, NewFileName, Info, 1, []),
+ io:format(" Saved as: ~p\n", [NewFileName]).
+
+write_typed_file(Chars, File, #info{functions = []}, _LNo, _Acc) ->
+ ok = file:write_file(File, list_to_binary(Chars), [append]);
+write_typed_file([Ch|Chs] = Chars, File, Info, LineNo, Acc) ->
+ [{Line,F,A}|RestFuncs] = Info#info.functions,
+ case Line of
+ 1 -> %% This will happen only for inc files
+ ok = raw_write(F, A, Info, File, []),
+ NewInfo = Info#info{functions = RestFuncs},
+ NewAcc = [],
+ write_typed_file(Chars, File, NewInfo, Line, NewAcc);
+ _ ->
+ case Ch of
+ 10 ->
+ NewLineNo = LineNo + 1,
+ {NewInfo, NewAcc} =
+ case NewLineNo of
+ Line ->
+ ok = raw_write(F, A, Info, File, [Ch|Acc]),
+ {Info#info{functions = RestFuncs}, []};
+ _ ->
+ {Info, [Ch|Acc]}
+ end,
+ write_typed_file(Chs, File, NewInfo, NewLineNo, NewAcc);
+ _ ->
+ write_typed_file(Chs, File, Info, LineNo, [Ch|Acc])
+ end
+ end.
+
+raw_write(F, A, Info, File, Content) ->
+ TypeInfo = get_type_string(F, A, Info, file),
+ ContentList = lists:reverse(Content) ++ TypeInfo ++ "\n",
+ ContentBin = list_to_binary(ContentList),
+ file:write_file(File, ContentBin, [append]).
+
+get_type_string(F, A, Info, Mode) ->
+ Type = get_type_info({F,A}, Info#info.types),
+ TypeStr =
+ case Type of
+ {contract, C} ->
+ dialyzer_contracts:contract_to_string(C);
+ {RetType, ArgType} ->
+ Sig = erl_types:t_fun(ArgType, RetType),
+ dialyzer_utils:format_sig(Sig, Info#info.records)
+ end,
+ case Info#info.edoc of
+ false ->
+ case {Mode, Type} of
+ {file, {contract, _}} -> "";
+ _ ->
+ Prefix = lists:concat(["-spec ", erl_types:atom_to_string(F)]),
+ lists:concat([Prefix, TypeStr, "."])
+ end;
+ true ->
+ Prefix = lists:concat(["%% @spec ", F]),
+ lists:concat([Prefix, TypeStr, "."])
+ end.
+
+show_type_info(File, Info) ->
+ io:format("\n%% File: ~p\n%% ", [File]),
+ OutputString = lists:concat(["~.", length(File)+8, "c~n"]),
+ io:fwrite(OutputString, [$-]),
+ Fun = fun ({_LineNo, F, A}) ->
+ TypeInfo = get_type_string(F, A, Info, show),
+ io:format("~s\n", [TypeInfo])
+ end,
+ lists:foreach(Fun, Info#info.functions).
+
+get_type_info(Func, Types) ->
+ case map__lookup(Func, Types) of
+ none ->
+ %% Note: Typeinfo of any function should exist in
+ %% the result offered by dialyzer, otherwise there
+ %% *must* be something wrong with the analysis
+ Msg = io_lib:format("No type info for function: ~p\n", [Func]),
+ fatal_error(Msg);
+ {contract, _Fun} = C -> C;
+ {_RetType, _ArgType} = RA -> RA
+ end.
+
+%%--------------------------------------------------------------------
+%% Processing of command-line options and arguments.
+%%--------------------------------------------------------------------
+
+-spec process_cl_args() -> {args(), analysis()}.
+
+process_cl_args() ->
+ ArgList = init:get_plain_arguments(),
+ %% io:format("Args is ~p\n", [ArgList]),
+ {Args, Analysis} = analyze_args(ArgList, #args{}, #analysis{}),
+ %% if the mode has not been set, set it to the default mode (show)
+ {Args, case Analysis#analysis.mode of
+ undefined -> Analysis#analysis{mode = ?SHOW};
+ Mode when is_atom(Mode) -> Analysis
+ end}.
+
+analyze_args([], Args, Analysis) ->
+ {Args, Analysis};
+analyze_args(ArgList, Args, Analysis) ->
+ {Result, Rest} = cl(ArgList),
+ {NewArgs, NewAnalysis} = analyze_result(Result, Args, Analysis),
+ analyze_args(Rest, NewArgs, NewAnalysis).
+
+cl(["-h"|_]) -> help_message();
+cl(["--help"|_]) -> help_message();
+cl(["-v"|_]) -> version_message();
+cl(["--version"|_]) -> version_message();
+cl(["--edoc"|Opts]) -> {edoc, Opts};
+cl(["--show"|Opts]) -> {{mode, ?SHOW}, Opts};
+cl(["--show_exported"|Opts]) -> {{mode, ?SHOW_EXPORTED}, Opts};
+cl(["--show-exported"|Opts]) -> {{mode, ?SHOW_EXPORTED}, Opts};
+cl(["--show_success_typings"|Opts]) -> {show_succ, Opts};
+cl(["--show-success-typings"|Opts]) -> {show_succ, Opts};
+cl(["--annotate"|Opts]) -> {{mode, ?ANNOTATE}, Opts};
+cl(["--annotate-inc-files"|Opts]) -> {{mode, ?ANNOTATE_INC_FILES}, Opts};
+cl(["--no_spec"|Opts]) -> {no_spec, Opts};
+cl(["--plt",Plt|Opts]) -> {{plt, Plt}, Opts};
+cl(["-D"++Def|Opts]) ->
+ case Def of
+ "" -> fatal_error("no variable name specified after -D");
+ _ ->
+ DefPair = process_def_list(re:split(Def, "=", [{return, list}])),
+ {{def, DefPair}, Opts}
+ end;
+cl(["-I",Dir|Opts]) -> {{inc, Dir}, Opts};
+cl(["-I"++Dir|Opts]) ->
+ case Dir of
+ "" -> fatal_error("no include directory specified after -I");
+ _ -> {{inc, Dir}, Opts}
+ end;
+cl(["-T"|Opts]) ->
+ {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts),
+ case Files of
+ [] -> fatal_error("no file or directory specified after -T");
+ [_|_] -> {{trusted, Files}, RestOpts}
+ end;
+cl(["-r"|Opts]) ->
+ {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts),
+ {{files_r, Files}, RestOpts};
+cl(["-pa",Dir|Opts]) -> {{pa,Dir}, Opts};
+cl(["-pz",Dir|Opts]) -> {{pz,Dir}, Opts};
+cl(["-"++H|_]) -> fatal_error("unknown option -"++H);
+cl(Opts) ->
+ {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts),
+ {{files, Files}, RestOpts}.
+
+process_def_list(L) ->
+ case L of
+ [Name, Value] ->
+ {ok, Tokens, _} = erl_scan:string(Value ++ "."),
+ {ok, ErlValue} = erl_parse:parse_term(Tokens),
+ {list_to_atom(Name), ErlValue};
+ [Name] ->
+ {list_to_atom(Name), true}
+ end.
+
+%% Get information about files that the user trusts and wants to analyze
+analyze_result({files, Val}, Args, Analysis) ->
+ NewVal = Args#args.files ++ Val,
+ {Args#args{files = NewVal}, Analysis};
+analyze_result({files_r, Val}, Args, Analysis) ->
+ NewVal = Args#args.files_r ++ Val,
+ {Args#args{files_r = NewVal}, Analysis};
+analyze_result({trusted, Val}, Args, Analysis) ->
+ NewVal = Args#args.trusted ++ Val,
+ {Args#args{trusted = NewVal}, Analysis};
+analyze_result(edoc, Args, Analysis) ->
+ {Args, Analysis#analysis{edoc = true}};
+%% Get useful information for actual analysis
+analyze_result({mode, Mode}, Args, Analysis) ->
+ case Analysis#analysis.mode of
+ undefined -> {Args, Analysis#analysis{mode = Mode}};
+ OldMode -> mode_error(OldMode, Mode)
+ end;
+analyze_result({def, Val}, Args, Analysis) ->
+ NewVal = Analysis#analysis.macros ++ [Val],
+ {Args, Analysis#analysis{macros = NewVal}};
+analyze_result({inc, Val}, Args, Analysis) ->
+ NewVal = Analysis#analysis.includes ++ [Val],
+ {Args, Analysis#analysis{includes = NewVal}};
+analyze_result({plt, Plt}, Args, Analysis) ->
+ {Args, Analysis#analysis{plt = Plt}};
+analyze_result(show_succ, Args, Analysis) ->
+ {Args, Analysis#analysis{show_succ = true}};
+analyze_result(no_spec, Args, Analysis) ->
+ {Args, Analysis#analysis{no_spec = true}};
+analyze_result({pa, Dir}, Args, Analysis) ->
+ true = code:add_patha(Dir),
+ {Args, Analysis};
+analyze_result({pz, Dir}, Args, Analysis) ->
+ true = code:add_pathz(Dir),
+ {Args, Analysis}.
+
+%%--------------------------------------------------------------------
+%% File processing.
+%%--------------------------------------------------------------------
+
+-spec get_all_files(args()) -> [file:filename(),...].
+
+get_all_files(#args{files = Fs, files_r = Ds}) ->
+ case filter_fd(Fs, Ds, fun test_erl_file_exclude_ann/1) of
+ [] -> fatal_error("no file(s) to analyze");
+ AllFiles -> AllFiles
+ end.
+
+-spec test_erl_file_exclude_ann(file:filename()) -> boolean().
+
+test_erl_file_exclude_ann(File) ->
+ case is_erl_file(File) of
+ true -> %% Exclude files ending with ".ann.erl"
+ case re:run(File, "[\.]ann[\.]erl$") of
+ {match, _} -> false;
+ nomatch -> true
+ end;
+ false -> false
+ end.
+
+-spec is_erl_file(file:filename()) -> boolean().
+
+is_erl_file(File) ->
+ filename:extension(File) =:= ".erl".
+
+-type test_file_fun() :: fun((file:filename()) -> boolean()).
+
+-spec filter_fd(files(), files(), test_file_fun()) -> files().
+
+filter_fd(File_Dir, Dir_R, Fun) ->
+ All_File_1 = process_file_and_dir(File_Dir, Fun),
+ All_File_2 = process_dir_rec(Dir_R, Fun),
+ remove_dup(All_File_1 ++ All_File_2).
+
+-spec process_file_and_dir(files(), test_file_fun()) -> files().
+
+process_file_and_dir(File_Dir, TestFun) ->
+ Fun =
+ fun (Elem, Acc) ->
+ case filelib:is_regular(Elem) of
+ true -> process_file(Elem, TestFun, Acc);
+ false -> check_dir(Elem, false, Acc, TestFun)
+ end
+ end,
+ lists:foldl(Fun, [], File_Dir).
+
+-spec process_dir_rec(files(), test_file_fun()) -> files().
+
+process_dir_rec(Dirs, TestFun) ->
+ Fun = fun (Dir, Acc) -> check_dir(Dir, true, Acc, TestFun) end,
+ lists:foldl(Fun, [], Dirs).
+
+-spec check_dir(file:filename(), boolean(), files(), test_file_fun()) -> files().
+
+check_dir(Dir, Recursive, Acc, Fun) ->
+ case file:list_dir(Dir) of
+ {ok, Files} ->
+ {TmpDirs, TmpFiles} = split_dirs_and_files(Files, Dir),
+ case Recursive of
+ false ->
+ FinalFiles = process_file_and_dir(TmpFiles, Fun),
+ Acc ++ FinalFiles;
+ true ->
+ TmpAcc1 = process_file_and_dir(TmpFiles, Fun),
+ TmpAcc2 = process_dir_rec(TmpDirs, Fun),
+ Acc ++ TmpAcc1 ++ TmpAcc2
+ end;
+ {error, eacces} ->
+ fatal_error("no access permission to dir \""++Dir++"\"");
+ {error, enoent} ->
+ fatal_error("cannot access "++Dir++": No such file or directory");
+ {error, _Reason} ->
+ fatal_error("error involving a use of file:list_dir/1")
+ end.
+
+%% Same order as the input list
+-spec process_file(file:filename(), test_file_fun(), files()) -> files().
+
+process_file(File, TestFun, Acc) ->
+ case TestFun(File) of
+ true -> Acc ++ [File];
+ false -> Acc
+ end.
+
+%% Same order as the input list
+-spec split_dirs_and_files(files(), file:filename()) -> {files(), files()}.
+
+split_dirs_and_files(Elems, Dir) ->
+ Test_Fun =
+ fun (Elem, {DirAcc, FileAcc}) ->
+ File = filename:join(Dir, Elem),
+ case filelib:is_regular(File) of
+ false -> {[File|DirAcc], FileAcc};
+ true -> {DirAcc, [File|FileAcc]}
+ end
+ end,
+ {Dirs, Files} = lists:foldl(Test_Fun, {[], []}, Elems),
+ {lists:reverse(Dirs), lists:reverse(Files)}.
+
+%% Removes duplicate filenames but keeps the order of the input list
+-spec remove_dup(files()) -> files().
+
+remove_dup(Files) ->
+ Test_Dup = fun (File, Acc) ->
+ case lists:member(File, Acc) of
+ true -> Acc;
+ false -> [File|Acc]
+ end
+ end,
+ Reversed_Elems = lists:foldl(Test_Dup, [], Files),
+ lists:reverse(Reversed_Elems).
+
+%%--------------------------------------------------------------------
+%% Collect information.
+%%--------------------------------------------------------------------
+
+-type inc_file_info() :: {file:filename(), func_info()}.
+
+-record(tmpAcc, {file :: file:filename(),
+ module :: atom(),
+ funcAcc = [] :: [func_info()],
+ incFuncAcc = [] :: [inc_file_info()],
+ dialyzerObj = [] :: [{mfa(), {_, _}}]}).
+
+-spec collect_info(analysis()) -> analysis().
+
+collect_info(Analysis) ->
+ NewPlt =
+ try get_dialyzer_plt(Analysis) of
+ DialyzerPlt ->
+ dialyzer_plt:merge_plts([Analysis#analysis.trust_plt, DialyzerPlt])
+ catch
+ throw:{dialyzer_error,_Reason} ->
+ fatal_error("Dialyzer's PLT is missing or is not up-to-date; please (re)create it")
+ end,
+ NewAnalysis = lists:foldl(fun collect_one_file_info/2,
+ Analysis#analysis{trust_plt = NewPlt},
+ Analysis#analysis.files),
+ %% Process Remote Types
+ TmpCServer = NewAnalysis#analysis.codeserver,
+ NewCServer =
+ try
+ TmpCServer1 = dialyzer_utils:merge_types(TmpCServer, NewPlt),
+ NewExpTypes = dialyzer_codeserver:get_temp_exported_types(TmpCServer),
+ OldExpTypes = dialyzer_plt:get_exported_types(NewPlt),
+ MergedExpTypes = sets:union(NewExpTypes, OldExpTypes),
+ TmpCServer2 =
+ dialyzer_codeserver:finalize_exported_types(MergedExpTypes, TmpCServer1),
+ TmpCServer3 = dialyzer_utils:process_record_remote_types(TmpCServer2),
+ dialyzer_contracts:process_contract_remote_types(TmpCServer3)
+ catch
+ throw:{error, ErrorMsg} ->
+ fatal_error(ErrorMsg)
+ end,
+ NewAnalysis#analysis{codeserver = NewCServer}.
+
+collect_one_file_info(File, Analysis) ->
+ Ds = [{d,Name,Val} || {Name,Val} <- Analysis#analysis.macros],
+ %% Current directory should also be included in "Includes".
+ Includes = [filename:dirname(File)|Analysis#analysis.includes],
+ Is = [{i,Dir} || Dir <- Includes],
+ Options = dialyzer_utils:src_compiler_opts() ++ Is ++ Ds,
+ case dialyzer_utils:get_core_from_src(File, Options) of
+ {error, Reason} ->
+ %% io:format("File=~p\n,Options=~p\n,Error=~p\n", [File,Options,Reason]),
+ compile_error(Reason);
+ {ok, Core} ->
+ case dialyzer_utils:get_record_and_type_info(Core) of
+ {error, Reason} -> compile_error([Reason]);
+ {ok, Records} ->
+ Mod = cerl:concrete(cerl:module_name(Core)),
+ case dialyzer_utils:get_spec_info(Mod, Core, Records) of
+ {error, Reason} -> compile_error([Reason]);
+ {ok, SpecInfo, CbInfo} ->
+ ExpTypes = get_exported_types_from_core(Core),
+ analyze_core_tree(Core, Records, SpecInfo, CbInfo,
+ ExpTypes, Analysis, File)
+ end
+ end
+ end.
+
+analyze_core_tree(Core, Records, SpecInfo, CbInfo, ExpTypes, Analysis, File) ->
+ Module = cerl:concrete(cerl:module_name(Core)),
+ TmpTree = cerl:from_records(Core),
+ CS1 = Analysis#analysis.codeserver,
+ NextLabel = dialyzer_codeserver:get_next_core_label(CS1),
+ {Tree, NewLabel} = cerl_trees:label(TmpTree, NextLabel),
+ CS2 = dialyzer_codeserver:insert(Module, Tree, CS1),
+ CS3 = dialyzer_codeserver:set_next_core_label(NewLabel, CS2),
+ CS4 = dialyzer_codeserver:store_temp_records(Module, Records, CS3),
+ CS5 =
+ case Analysis#analysis.no_spec of
+ true -> CS4;
+ false ->
+ dialyzer_codeserver:store_temp_contracts(Module, SpecInfo, CbInfo, CS4)
+ end,
+ OldExpTypes = dialyzer_codeserver:get_temp_exported_types(CS5),
+ MergedExpTypes = sets:union(ExpTypes, OldExpTypes),
+ CS6 = dialyzer_codeserver:insert_temp_exported_types(MergedExpTypes, CS5),
+ Ex_Funcs = [{0,F,A} || {_,_,{F,A}} <- cerl:module_exports(Tree)],
+ CG = Analysis#analysis.callgraph,
+ {V, E} = dialyzer_callgraph:scan_core_tree(Tree, CG),
+ dialyzer_callgraph:add_edges(E, V, CG),
+ Fun = fun analyze_one_function/2,
+ All_Defs = cerl:module_defs(Tree),
+ Acc = lists:foldl(Fun, #tmpAcc{file = File, module = Module}, All_Defs),
+ Exported_FuncMap = map__insert({File, Ex_Funcs}, Analysis#analysis.ex_func),
+ %% we must sort all functions in the file which
+ %% originate from this file by *numerical order* of lineNo
+ Sorted_Functions = lists:keysort(1, Acc#tmpAcc.funcAcc),
+ FuncMap = map__insert({File, Sorted_Functions}, Analysis#analysis.func),
+ %% we do not need to sort functions which are imported from included files
+ IncFuncMap = map__insert({File, Acc#tmpAcc.incFuncAcc},
+ Analysis#analysis.inc_func),
+ FMs = Analysis#analysis.fms ++ [{File, Module}],
+ RecordMap = map__insert({File, Records}, Analysis#analysis.record),
+ Analysis#analysis{fms = FMs,
+ callgraph = CG,
+ codeserver = CS6,
+ ex_func = Exported_FuncMap,
+ inc_func = IncFuncMap,
+ record = RecordMap,
+ func = FuncMap}.
+
+analyze_one_function({Var, FunBody} = Function, Acc) ->
+ F = cerl:fname_id(Var),
+ A = cerl:fname_arity(Var),
+ TmpDialyzerObj = {{Acc#tmpAcc.module, F, A}, Function},
+ NewDialyzerObj = Acc#tmpAcc.dialyzerObj ++ [TmpDialyzerObj],
+ Anno = cerl:get_ann(FunBody),
+ LineNo = get_line(Anno),
+ FileName = get_file(Anno),
+ BaseName = filename:basename(FileName),
+ FuncInfo = {LineNo, F, A},
+ OriginalName = Acc#tmpAcc.file,
+ {FuncAcc, IncFuncAcc} =
+ case (FileName =:= OriginalName) orelse (BaseName =:= OriginalName) of
+ true -> %% Coming from original file
+ %% io:format("Added function ~p\n", [{LineNo, F, A}]),
+ {Acc#tmpAcc.funcAcc ++ [FuncInfo], Acc#tmpAcc.incFuncAcc};
+ false ->
+ %% Coming from other sourses, including:
+ %% -- .yrl (yecc-generated file)
+ %% -- yeccpre.hrl (yecc-generated file)
+ %% -- other cases
+ {Acc#tmpAcc.funcAcc, Acc#tmpAcc.incFuncAcc ++ [{FileName, FuncInfo}]}
+ end,
+ Acc#tmpAcc{funcAcc = FuncAcc,
+ incFuncAcc = IncFuncAcc,
+ dialyzerObj = NewDialyzerObj}.
+
+get_line([Line|_]) when is_integer(Line) -> Line;
+get_line([_|T]) -> get_line(T);
+get_line([]) -> none.
+
+get_file([{file,File}|_]) -> File;
+get_file([_|T]) -> get_file(T);
+get_file([]) -> "no_file". % should not happen
+
+-spec get_dialyzer_plt(analysis()) -> plt().
+
+get_dialyzer_plt(#analysis{plt = PltFile0}) ->
+ PltFile =
+ case PltFile0 =:= none of
+ true -> dialyzer_plt:get_default_plt();
+ false -> PltFile0
+ end,
+ dialyzer_plt:from_file(PltFile).
+
+%% Exported Types
+
+get_exported_types_from_core(Core) ->
+ Attrs = cerl:module_attrs(Core),
+ ExpTypes1 = [cerl:concrete(L2) || {L1, L2} <- Attrs,
+ cerl:is_literal(L1),
+ cerl:is_literal(L2),
+ cerl:concrete(L1) =:= 'export_type'],
+ ExpTypes2 = lists:flatten(ExpTypes1),
+ M = cerl:atom_val(cerl:module_name(Core)),
+ sets:from_list([{M, F, A} || {F, A} <- ExpTypes2]).
+
+%%--------------------------------------------------------------------
+%% Utilities for error reporting.
+%%--------------------------------------------------------------------
+
+-spec fatal_error(string()) -> no_return().
+
+fatal_error(Slogan) ->
+ msg(io_lib:format("typer: ~s\n", [Slogan])),
+ erlang:halt(1).
+
+-spec mode_error(mode(), mode()) -> no_return().
+
+mode_error(OldMode, NewMode) ->
+ Msg = io_lib:format("Mode was previously set to '~s'; "
+ "can not set it to '~s' now",
+ [OldMode, NewMode]),
+ fatal_error(Msg).
+
+-spec compile_error([string()]) -> no_return().
+
+compile_error(Reason) ->
+ JoinedString = lists:flatten([X ++ "\n" || X <- Reason]),
+ Msg = "Analysis failed with error report:\n" ++ JoinedString,
+ fatal_error(Msg).
+
+-spec msg(string()) -> 'ok'.
+
+msg(Msg) ->
+ io:format(standard_error, "~s", [Msg]).
+
+%%--------------------------------------------------------------------
+%% Version and help messages.
+%%--------------------------------------------------------------------
+
+-spec version_message() -> no_return().
+
+version_message() ->
+ io:format("TypEr version "++?VSN++"\n"),
+ erlang:halt(0).
+
+-spec help_message() -> no_return().
+
+help_message() ->
+ S = <<" Usage: typer [--help] [--version] [--plt PLT] [--edoc]
+ [--show | --show-exported | --annotate | --annotate-inc-files]
+ [-Ddefine]* [-I include_dir]* [-pa dir]* [-pz dir]*
+ [-T application]* [-r] file*
+
+ Options:
+ -r dir*
+ search directories recursively for .erl files below them
+ --show
+ Prints type specifications for all functions on stdout.
+ (this is the default behaviour; this option is not really needed)
+ --show-exported (or --show_exported)
+ Same as --show, but prints specifications for exported functions only
+ Specs are displayed sorted alphabetically on the function's name
+ --annotate
+ Annotates the specified files with type specifications
+ --annotate-inc-files
+ Same as --annotate but annotates all -include() files as well as
+ all .erl files (use this option with caution - has not been tested much)
+ --edoc
+ Prints type information as Edoc @spec comments, not as type specs
+ --plt PLT
+ Use the specified dialyzer PLT file rather than the default one
+ -T file*
+ The specified file(s) already contain type specifications and these
+ are to be trusted in order to print specs for the rest of the files
+ (Multiple files or dirs, separated by spaces, can be specified.)
+ -Dname (or -Dname=value)
+ pass the defined name(s) to TypEr
+ (The syntax of defines is the same as that used by \"erlc\".)
+ -I include_dir
+ pass the include_dir to TypEr
+ (The syntax of includes is the same as that used by \"erlc\".)
+ -pa dir
+ -pz dir
+ Set code path options to TypEr
+ (This is useful for files that use parse tranforms.)
+ --version (or -v)
+ prints the Typer version and exits
+ --help (or -h)
+ prints this message and exits
+
+ Note:
+ * denotes that multiple occurrences of these options are possible.
+">>,
+ io:put_chars(S),
+ erlang:halt(0).
+
+%%--------------------------------------------------------------------
+%% Handle messages.
+%%--------------------------------------------------------------------
+
+rcv_ext_types() ->
+ Self = self(),
+ Self ! {Self, done},
+ rcv_ext_types(Self, []).
+
+rcv_ext_types(Self, ExtTypes) ->
+ receive
+ {Self, ext_types, ExtType} ->
+ rcv_ext_types(Self, [ExtType|ExtTypes]);
+ {Self, done} ->
+ lists:usort(ExtTypes)
+ end.
+
+%%--------------------------------------------------------------------
+%% A convenient abstraction of a Key-Value mapping data structure
+%% specialized for the uses in this module
+%%--------------------------------------------------------------------
+
+-type map_dict() :: dict:dict().
+
+-spec map__new() -> map_dict().
+map__new() ->
+ dict:new().
+
+-spec map__insert({term(), term()}, map_dict()) -> map_dict().
+map__insert(Object, Map) ->
+ {Key, Value} = Object,
+ dict:store(Key, Value, Map).
+
+-spec map__lookup(term(), map_dict()) -> term().
+map__lookup(Key, Map) ->
+ try dict:fetch(Key, Map) catch error:_ -> none end.
+
+-spec map__from_list([{fa(), term()}]) -> map_dict().
+map__from_list(List) ->
+ dict:from_list(List).
+
+-spec map__remove(term(), map_dict()) -> map_dict().
+map__remove(Key, Dict) ->
+ dict:erase(Key, Dict).
+
+-spec map__fold(fun((term(), term(), term()) -> map_dict()), map_dict(), map_dict()) -> map_dict().
+map__fold(Fun, Acc0, Dict) ->
+ dict:fold(Fun, Acc0, Dict).
diff --git a/lib/dialyzer/test/Makefile b/lib/dialyzer/test/Makefile
index 0d8fba438c..43c8a61ce1 100644
--- a/lib/dialyzer/test/Makefile
+++ b/lib/dialyzer/test/Makefile
@@ -13,7 +13,8 @@ AUXILIARY_FILES=\
file_utils.erl\
dialyzer_SUITE.erl\
abstract_SUITE.erl\
- plt_SUITE.erl
+ plt_SUITE.erl\
+ typer_SUITE.erl
# ----------------------------------------------------
# Release directory specification
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args
index 3e98da785f..2f504d3c72 100644
--- a/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args
+++ b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_incorrect_args
@@ -1,8 +1,5 @@
-gen_server_incorrect_args.erl:3: Undefined callback function code_change/3 (behaviour 'gen_server')
gen_server_incorrect_args.erl:3: Undefined callback function handle_cast/2 (behaviour 'gen_server')
-gen_server_incorrect_args.erl:3: Undefined callback function handle_info/2 (behaviour 'gen_server')
gen_server_incorrect_args.erl:3: Undefined callback function init/1 (behaviour 'gen_server')
-gen_server_incorrect_args.erl:3: Undefined callback function terminate/2 (behaviour 'gen_server')
gen_server_incorrect_args.erl:7: The inferred return type of handle_call/3 ({'no'} | {'ok'}) has nothing in common with {'noreply',_} | {'noreply',_,'hibernate' | 'infinity' | non_neg_integer()} | {'reply',_,_} | {'stop',_,_} | {'reply',_,_,'hibernate' | 'infinity' | non_neg_integer()} | {'stop',_,_,_}, which is the expected return type for the callback of gen_server behaviour
gen_server_incorrect_args.erl:7: The inferred type for the 2nd argument of handle_call/3 ('boo' | 'foo') is not a supertype of {pid(),_}, which is expected type for this argument in the callback of the gen_server behaviour
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_missing_callbacks b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_missing_callbacks
index 5e0ed5fd27..0a7642a9b5 100644
--- a/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_missing_callbacks
+++ b/lib/dialyzer/test/behaviour_SUITE_data/results/gen_server_missing_callbacks
@@ -1,3 +1,2 @@
gen_server_missing_callbacks.erl:3: Undefined callback function handle_cast/2 (behaviour 'gen_server')
-gen_server_missing_callbacks.erl:3: Undefined callback function handle_info/2 (behaviour 'gen_server')
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/results/vars_in_beh_spec b/lib/dialyzer/test/behaviour_SUITE_data/results/vars_in_beh_spec
index 5284e412f0..512dcdd758 100644
--- a/lib/dialyzer/test/behaviour_SUITE_data/results/vars_in_beh_spec
+++ b/lib/dialyzer/test/behaviour_SUITE_data/results/vars_in_beh_spec
@@ -1,6 +1,4 @@
vars_in_beh_spec.erl:3: Undefined callback function handle_call/3 (behaviour 'gen_server')
vars_in_beh_spec.erl:3: Undefined callback function handle_cast/2 (behaviour 'gen_server')
-vars_in_beh_spec.erl:3: Undefined callback function handle_info/2 (behaviour 'gen_server')
vars_in_beh_spec.erl:3: Undefined callback function init/1 (behaviour 'gen_server')
-vars_in_beh_spec.erl:3: Undefined callback function terminate/2 (behaviour 'gen_server')
diff --git a/lib/dialyzer/test/plt_SUITE.erl b/lib/dialyzer/test/plt_SUITE.erl
index ba153c1c27..92c63bdb0c 100644
--- a/lib/dialyzer/test/plt_SUITE.erl
+++ b/lib/dialyzer/test/plt_SUITE.erl
@@ -259,26 +259,41 @@ remove_plt(Config) ->
{init_plt, Plt}] ++ Opts),
ok.
+%% ERL-283, OTP-13979. As of OTP-14323 this test no longer does what
+%% it is designed to do--the linter stops every attempt to run the
+%% checks of Dialyzer's on bad dialyzer attributes. For the time
+%% being, the linter's error message are checked instead. The test
+%% needs to be updated when/if the Dialyzer can analyze Core Erlang
+%% without compiling abstract code.
bad_dialyzer_attr(Config) ->
PrivDir = ?config(priv_dir, Config),
- Plt = filename:join(PrivDir, "plt_bad_dialyzer_attr.plt"),
+ Source = lists:concat([dial, ".erl"]),
+ Filename = filename:join(PrivDir, Source),
+ ok = dialyzer_common:check_plt(PrivDir),
+ PltFilename = dialyzer_common:plt_file(PrivDir),
+ Opts = [{files, [Filename]},
+ {check_plt, false},
+ {from, src_code},
+ {init_plt, PltFilename}],
+
Prog1 = <<"-module(dial).
-dialyzer({no_return, [undef/0]}).">>,
- {ok, Beam1} = compile(Config, Prog1, dial, []),
+ ok = file:write_file(Filename, Prog1),
{dialyzer_error,
- "Analysis failed with error:\n"
- "Could not scan the following file(s):\n"
- " Unknown function undef/0 in line " ++ _} =
- (catch run_dialyzer(plt_build, [Beam1], [{output_plt, Plt}])),
+ "Analysis failed with error:\n" ++ Str1} =
+ (catch dialyzer:run(Opts)),
+ P1 = string:str(Str1, "dial.erl:2: function undef/0 undefined"),
+ true = P1 > 0,
Prog2 = <<"-module(dial).
-dialyzer({no_return, [{undef,1,2}]}).">>,
- {ok, Beam2} = compile(Config, Prog2, dial, []),
+ ok = file:write_file(Filename, Prog2),
{dialyzer_error,
- "Analysis failed with error:\n"
- "Could not scan the following file(s):\n"
- " Bad function {undef,1,2} in line " ++ _} =
- (catch run_dialyzer(plt_build, [Beam2], [{output_plt, Plt}])),
+ "Analysis failed with error:\n" ++ Str2} =
+ (catch dialyzer:run(Opts)),
+ P2 = string:str(Str2, "dial.erl:2: badly formed dialyzer "
+ "attribute: {no_return,{undef,1,2}}"),
+ true = P2 > 0,
ok.
diff --git a/lib/dialyzer/test/typer_SUITE.erl b/lib/dialyzer/test/typer_SUITE.erl
new file mode 100644
index 0000000000..da5b961643
--- /dev/null
+++ b/lib/dialyzer/test/typer_SUITE.erl
@@ -0,0 +1,158 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(typer_SUITE).
+
+-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ smoke/1]).
+
+-include_lib("common_test/include/ct.hrl").
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [smoke].
+
+groups() ->
+ [].
+
+init_per_suite(Config) ->
+ OutDir = proplists:get_value(priv_dir, Config),
+ case dialyzer_common:check_plt(OutDir) of
+ fail -> {skip, "Plt creation/check failed."};
+ ok -> [{dialyzer_options, []}|Config]
+ end.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+smoke(Config) ->
+ Code = <<"-module(typer_test_module).
+ -compile([export_all,nowarn_export_all]).
+ a(L) ->
+ L ++ [1,2,3].">>,
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Src = filename:join(PrivDir, "typer_test_module.erl"),
+ ok = file:write_file(Src, Code),
+ Args = "--plt " ++ PrivDir ++ "dialyzer_plt",
+ Res = ["^$",
+ "^%% File:",
+ "^%% ----",
+ "^-spec a",
+ "^_OK_"],
+ run(Config, Args, Src, Res),
+ ok.
+
+typer() ->
+ case os:find_executable("typer") of
+ false ->
+ ct:fail("Can't find typer");
+ Typer ->
+ Typer
+ end.
+
+%% Runs a command.
+
+run(Config, Args0, Name, Expect) ->
+ Args = Args0 ++ " " ++ Name,
+ Result = run_command(Config, Args),
+ verify_result(Result, Expect).
+
+verify_result(Result, Expect) ->
+ Messages = split(Result, [], []),
+ io:format("Result: ~p", [Messages]),
+ io:format("Expected: ~p", [Expect]),
+ match_messages(Messages, Expect).
+
+split([$\n|Rest], Current, Lines) ->
+ split(Rest, [], [lists:reverse(Current)|Lines]);
+split([$\r|Rest], Current, Lines) ->
+ split(Rest, Current, Lines);
+split([Char|Rest], Current, Lines) ->
+ split(Rest, [Char|Current], Lines);
+split([], [], Lines) ->
+ lists:reverse(Lines);
+split([], Current, Lines) ->
+ split([], [], [lists:reverse(Current)|Lines]).
+
+match_messages([Msg|Rest1], [Regexp|Rest2]) ->
+ case re:run(Msg, Regexp, [{capture,none}, unicode]) of
+ match ->
+ ok;
+ nomatch ->
+ io:format("Not matching: ~s\n", [Msg]),
+ io:format("Regexp : ~s\n", [Regexp]),
+ ct:fail(message_mismatch)
+ end,
+ match_messages(Rest1, Rest2);
+match_messages([], [Expect|Rest]) ->
+ ct:fail({too_few_messages, [Expect|Rest]});
+match_messages([Msg|Rest], []) ->
+ ct:fail({too_many_messages, [Msg|Rest]});
+match_messages([], []) ->
+ ok.
+
+%% Runs the command using os:cmd/1.
+%%
+%% Returns the output from the command (as a list of characters with
+%% embedded newlines). The very last line will indicate the
+%% exit status of the command, where _OK_ means zero, and _ERROR_
+%% a non-zero exit status.
+
+run_command(Config, Args) ->
+ TmpDir = filename:join(proplists:get_value(priv_dir, Config), "tmp"),
+ file:make_dir(TmpDir),
+ {RunFile, Run, Script} = run_command(TmpDir, os:type(), Args),
+ ok = file:write_file(filename:join(TmpDir, RunFile),
+ unicode:characters_to_binary(Script)),
+ io:format("~ts\n", [Script]),
+ os:cmd(Run).
+
+run_command(Dir, {win32, _}, Args) ->
+ BatchFile = filename:join(Dir, "run.bat"),
+ Run = re:replace(filename:rootname(BatchFile), "/", "\\",
+ [global,{return,list}]),
+ Typer = typer(),
+ {BatchFile,
+ Run,
+ ["@echo off\r\n",
+ "\"",Typer,"\" ",Args, "\r\n",
+ "if errorlevel 1 echo _ERROR_\r\n",
+ "if not errorlevel 1 echo _OK_\r\n"]};
+run_command(Dir, {unix, _}, Args) ->
+ TyperDir = filename:dirname(typer()),
+ Name = filename:join(Dir, "run"),
+ {Name,
+ "/bin/sh " ++ Name,
+ ["#!/bin/sh\n",
+ "PATH=\"",TyperDir,":$PATH\"\n",
+ "typer ",Args,"\n",
+ "case $? in\n",
+ " 0) echo '_OK_';;\n",
+ " *) echo '_ERROR_';;\n",
+ "esac\n"]};
+run_command(_Dir, Other, _Args) ->
+ ct:fail("Don't know how to test exit code for ~p", [Other]).