aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dialyzer/src
diff options
context:
space:
mode:
authorSverker Eriksson <[email protected]>2017-08-30 20:55:08 +0200
committerSverker Eriksson <[email protected]>2017-08-30 20:55:08 +0200
commit7c67bbddb53c364086f66260701bc54a61c9659c (patch)
tree92ab0d4b91d5e2f6e7a3f9d61ea25089e8a71fe0 /lib/dialyzer/src
parent97dc5e7f396129222419811c173edc7fa767b0f8 (diff)
parent3b7a6ffddc819bf305353a593904cea9e932e7dc (diff)
downloadotp-7c67bbddb53c364086f66260701bc54a61c9659c.tar.gz
otp-7c67bbddb53c364086f66260701bc54a61c9659c.tar.bz2
otp-7c67bbddb53c364086f66260701bc54a61c9659c.zip
Merge tag 'OTP-19.0' into sverker/19/binary_to_atom-utf8-crash/ERL-474/OTP-14590
Diffstat (limited to 'lib/dialyzer/src')
-rw-r--r--lib/dialyzer/src/Makefile30
-rw-r--r--lib/dialyzer/src/dialyzer.app.src35
-rw-r--r--lib/dialyzer/src/dialyzer.appup.src32
-rw-r--r--lib/dialyzer/src/dialyzer.erl109
-rw-r--r--lib/dialyzer/src/dialyzer.hrl54
-rw-r--r--lib/dialyzer/src/dialyzer_analysis_callgraph.erl284
-rw-r--r--lib/dialyzer/src/dialyzer_behaviours.erl55
-rw-r--r--lib/dialyzer/src/dialyzer_callgraph.erl229
-rw-r--r--lib/dialyzer/src/dialyzer_cl.erl185
-rw-r--r--lib/dialyzer/src/dialyzer_cl_parse.erl66
-rw-r--r--lib/dialyzer/src/dialyzer_codeserver.erl104
-rw-r--r--lib/dialyzer/src/dialyzer_contracts.erl570
-rw-r--r--lib/dialyzer/src/dialyzer_coordinator.erl59
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl1926
-rw-r--r--lib/dialyzer/src/dialyzer_dep.erl139
-rw-r--r--lib/dialyzer/src/dialyzer_explanation.erl23
-rw-r--r--lib/dialyzer/src/dialyzer_gui.erl1381
-rw-r--r--lib/dialyzer/src/dialyzer_gui_wx.erl63
-rw-r--r--lib/dialyzer/src/dialyzer_options.erl67
-rw-r--r--lib/dialyzer/src/dialyzer_plt.erl65
-rw-r--r--lib/dialyzer/src/dialyzer_race_data_server.erl134
-rw-r--r--lib/dialyzer/src/dialyzer_races.erl115
-rw-r--r--lib/dialyzer/src/dialyzer_succ_typings.erl145
-rw-r--r--lib/dialyzer/src/dialyzer_timing.erl31
-rw-r--r--lib/dialyzer/src/dialyzer_typesig.erl1008
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl671
-rw-r--r--lib/dialyzer/src/dialyzer_worker.erl80
27 files changed, 3803 insertions, 3857 deletions
diff --git a/lib/dialyzer/src/Makefile b/lib/dialyzer/src/Makefile
index bb2edd419a..256f20f549 100644
--- a/lib/dialyzer/src/Makefile
+++ b/lib/dialyzer/src/Makefile
@@ -1,18 +1,19 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2006-2012. All Rights Reserved.
+# Copyright Ericsson AB 2006-2016. All Rights Reserved.
#
-# The contents of this file are subject to the Erlang Public License,
-# Version 1.1, (the "License"); you may not use this file except in
-# compliance with the License. You should have received a copy of the
-# Erlang Public License along with this software. If not, it can be
-# retrieved online at http://www.erlang.org/.
+# 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
#
-# Software distributed under the License is distributed on an "AS IS"
-# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-# the License for the specific language governing rights and limitations
-# under the License.
+# 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%
#
@@ -57,10 +58,10 @@ MODULES = \
dialyzer_dataflow \
dialyzer_dep \
dialyzer_explanation \
- dialyzer_gui \
dialyzer_gui_wx \
dialyzer_options \
dialyzer_plt \
+ dialyzer_race_data_server \
dialyzer_races \
dialyzer_succ_typings \
dialyzer_timing \
@@ -89,7 +90,7 @@ APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
ifeq ($(NATIVE_LIBS_ENABLED),yes)
ERL_COMPILE_FLAGS += +native
endif
-ERL_COMPILE_FLAGS += +warn_exported_vars +warn_unused_import +warn_untyped_record +warn_missing_spec +warnings_as_errors
+ERL_COMPILE_FLAGS += +warn_export_vars +warn_unused_import +warn_untyped_record +warn_missing_spec +warnings_as_errors
# ----------------------------------------------------
# Targets
@@ -113,9 +114,6 @@ $(EBIN)/dialyzer_cl_parse.$(EMULATOR): dialyzer_cl_parse.erl ../vsn.mk
$(EBIN)/dialyzer_plt.$(EMULATOR): dialyzer_plt.erl ../vsn.mk
$(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_plt.erl
-$(EBIN)/dialyzer_gui.$(EMULATOR): dialyzer_gui.erl ../vsn.mk
- $(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_gui.erl
-
$(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
@@ -140,10 +138,10 @@ $(EBIN)/dialyzer_contracts.beam: dialyzer.hrl
$(EBIN)/dialyzer_dataflow.beam: dialyzer.hrl
$(EBIN)/dialyzer_dep.beam: dialyzer.hrl
$(EBIN)/dialyzer_explanation.beam: dialyzer.hrl
-$(EBIN)/dialyzer_gui.beam: dialyzer.hrl
$(EBIN)/dialyzer_gui_wx.beam: dialyzer.hrl dialyzer_gui_wx.hrl
$(EBIN)/dialyzer_options.beam: dialyzer.hrl
$(EBIN)/dialyzer_plt.beam: dialyzer.hrl
+$(EBIN)/dialyzer_race_data_server.beam: dialyzer.hrl
$(EBIN)/dialyzer_races.beam: dialyzer.hrl
$(EBIN)/dialyzer_succ_typings.beam: dialyzer.hrl
$(EBIN)/dialyzer_typesig.beam: dialyzer.hrl
diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src
index 9222a28a77..5b28f7ae86 100644
--- a/lib/dialyzer/src/dialyzer.app.src
+++ b/lib/dialyzer/src/dialyzer.app.src
@@ -2,18 +2,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -29,17 +30,23 @@
dialyzer_cl_parse,
dialyzer_codeserver,
dialyzer_contracts,
+ dialyzer_coordinator,
dialyzer_dataflow,
dialyzer_dep,
dialyzer_explanation,
- dialyzer_gui,
dialyzer_gui_wx,
dialyzer_options,
dialyzer_plt,
+ dialyzer_race_data_server,
dialyzer_races,
dialyzer_succ_typings,
dialyzer_typesig,
- dialyzer_utils]},
+ dialyzer_utils,
+ dialyzer_timing,
+ dialyzer_worker]},
{registered, []},
- {applications, [compiler, gs, hipe, kernel, stdlib, wx]},
- {env, []}]}.
+ {applications, [compiler, hipe, kernel, stdlib, wx]},
+ {env, []},
+ {runtime_dependencies, ["wx-1.2","syntax_tools-2.0","stdlib-3.0",
+ "kernel-5.0","hipe-3.15.1","erts-8.0",
+ "compiler-7.0"]}]}.
diff --git a/lib/dialyzer/src/dialyzer.appup.src b/lib/dialyzer/src/dialyzer.appup.src
index 26d14ee8f4..d81ff641d7 100644
--- a/lib/dialyzer/src/dialyzer.appup.src
+++ b/lib/dialyzer/src/dialyzer.appup.src
@@ -1,20 +1,22 @@
-%%
+%% -*- erlang -*-
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
-%%
-
-{"%VSN%",[],[]}.
+{"%VSN%",
+ [{<<".*">>,[{restart_application, dialyzer}]}],
+ [{<<".*">>,[{restart_application, dialyzer}]}]
+}.
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl
index 822aa0826a..bcac8afe64 100644
--- a/lib/dialyzer/src/dialyzer.erl
+++ b/lib/dialyzer/src/dialyzer.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -62,18 +63,18 @@ plain_cl() ->
cl_halt(cl_check_init(Opts), Opts);
{plt_info, Opts} ->
cl_halt(cl_print_plt_info(Opts), Opts);
- {{gui, Type}, Opts} ->
+ {gui, Opts} ->
try check_gui_options(Opts)
catch throw:{dialyzer_error, Msg} -> cl_error(Msg)
end,
case Opts#options.check_plt of
true ->
case cl_check_init(Opts#options{get_warnings = false}) of
- {ok, _} -> gui_halt(internal_gui(Type, Opts), Opts);
+ {ok, _} -> gui_halt(internal_gui(Opts), Opts);
{error, _} = Error -> cl_halt(Error, Opts)
end;
false ->
- gui_halt(internal_gui(Type, Opts), Opts)
+ gui_halt(internal_gui(Opts), Opts)
end;
{cl, Opts} ->
case Opts#options.check_plt of
@@ -162,29 +163,29 @@ run(Opts) ->
{error, Msg} ->
throw({dialyzer_error, Msg});
OptsRecord ->
- case OptsRecord#options.check_plt of
- true ->
- case cl_check_init(OptsRecord) of
- {ok, ?RET_NOTHING_SUSPICIOUS} -> ok;
- {error, ErrorMsg1} -> throw({dialyzer_error, ErrorMsg1})
- end;
- false -> ok
- end,
+ ok = check_init(OptsRecord),
case dialyzer_cl:start(OptsRecord) of
{?RET_DISCREPANCIES, Warnings} -> Warnings;
- {?RET_NOTHING_SUSPICIOUS, []} -> []
+ {?RET_NOTHING_SUSPICIOUS, _} -> []
end
catch
throw:{dialyzer_error, ErrorMsg} ->
erlang:error({dialyzer_error, lists:flatten(ErrorMsg)})
end.
-internal_gui(Type, OptsRecord) ->
+check_init(#options{analysis_type = plt_check}) ->
+ ok;
+check_init(#options{check_plt = true} = OptsRecord) ->
+ case cl_check_init(OptsRecord) of
+ {ok, _} -> ok;
+ {error, Msg} -> throw({dialyzer_error, Msg})
+ end;
+check_init(#options{check_plt = false}) ->
+ ok.
+
+internal_gui(OptsRecord) ->
F = fun() ->
- case Type of
- gs -> dialyzer_gui:start(OptsRecord);
- wx -> dialyzer_gui_wx:start(OptsRecord)
- end,
+ dialyzer_gui_wx:start(OptsRecord),
?RET_NOTHING_SUSPICIOUS
end,
doit(F).
@@ -202,17 +203,13 @@ gui(Opts) ->
throw({dialyzer_error, Msg});
OptsRecord ->
ok = check_gui_options(OptsRecord),
- case cl_check_init(OptsRecord) of
- {ok, ?RET_NOTHING_SUSPICIOUS} ->
- F = fun() ->
- dialyzer_gui:start(OptsRecord)
- end,
- case doit(F) of
- {ok, _} -> ok;
- {error, Msg} -> throw({dialyzer_error, Msg})
- end;
- {error, ErrorMsg1} ->
- throw({dialyzer_error, ErrorMsg1})
+ ok = check_init(OptsRecord),
+ F = fun() ->
+ dialyzer_gui_wx:start(OptsRecord)
+ end,
+ case doit(F) of
+ {ok, _} -> ok;
+ {error, Msg} -> throw({dialyzer_error, Msg})
end
catch
throw:{dialyzer_error, ErrorMsg} ->
@@ -285,15 +282,17 @@ cl_check_log(none) ->
cl_check_log(Output) ->
io:format(" Check output file `~s' for details\n", [Output]).
--spec format_warning(dial_warning()) -> string().
+-spec format_warning(raw_warning()) -> string().
format_warning(W) ->
format_warning(W, basename).
--spec format_warning(dial_warning(), fopt()) -> string().
+-spec format_warning(raw_warning() | dial_warning(), fopt()) -> string().
+format_warning({Tag, {File, Line, _MFA}, Msg}, FOpt) ->
+ format_warning({Tag, {File, Line}, Msg}, FOpt);
format_warning({_Tag, {File, Line}, Msg}, FOpt) when is_list(File),
- is_integer(Line) ->
+ is_integer(Line) ->
F = case FOpt of
fullpath -> File;
basename -> filename:basename(File)
@@ -337,6 +336,9 @@ message_to_string({guard_fail, []}) ->
"Clause guard cannot succeed.\n";
message_to_string({guard_fail, [Arg1, Infix, Arg2]}) ->
io_lib:format("Guard test ~s ~s ~s can never succeed\n", [Arg1, Infix, Arg2]);
+message_to_string({map_update, [Type, Key]}) ->
+ io_lib:format("A key of type ~s cannot exist "
+ "in a map of type ~s\n", [Key, Type]);
message_to_string({neg_guard_fail, [Arg1, Infix, Arg2]}) ->
io_lib:format("Guard test not(~s ~s ~s) can never succeed\n",
[Arg1, Infix, Arg2]);
@@ -426,6 +428,9 @@ message_to_string({call_without_opaque, [M, F, Args, ExpectedTriples]}) ->
message_to_string({opaque_eq, [Type, _Op, OpaqueType]}) ->
io_lib:format("Attempt to test for equality between a term of type ~s"
" and a term of opaque type ~s\n", [Type, OpaqueType]);
+message_to_string({opaque_guard, [Arg1, Infix, Arg2, ArgNs]}) ->
+ io_lib:format("Guard test ~s ~s ~s contains ~s\n",
+ [Arg1, Infix, Arg2, form_positions(ArgNs)]);
message_to_string({opaque_guard, [Guard, Args]}) ->
io_lib:format("Guard test ~w~s breaks the opaqueness of its argument\n",
[Guard, Args]);
@@ -438,8 +443,15 @@ message_to_string({opaque_match, [Pat, OpaqueType, OpaqueTerm]}) ->
message_to_string({opaque_neq, [Type, _Op, OpaqueType]}) ->
io_lib:format("Attempt to test for inequality between a term of type ~s"
" and a term of opaque type ~s\n", [Type, OpaqueType]);
-message_to_string({opaque_type_test, [Fun, Opaque]}) ->
- io_lib:format("The type test ~s(~s) breaks the opaqueness of the term ~s\n", [Fun, Opaque, Opaque]);
+message_to_string({opaque_type_test, [Fun, Args, Arg, ArgType]}) ->
+ io_lib:format("The type test ~s~s breaks the opaqueness of the term ~s~s\n",
+ [Fun, Args, Arg, ArgType]);
+message_to_string({opaque_size, [SizeType, Size]}) ->
+ io_lib:format("The size ~s breaks the opaqueness of ~s\n",
+ [SizeType, Size]);
+message_to_string({opaque_call, [M, F, Args, Culprit, OpaqueType]}) ->
+ io_lib:format("The call ~s:~s~s breaks the opaqueness of the term ~s :: ~s\n",
+ [M, F, Args, Culprit, OpaqueType]);
%%----- Warnings for concurrency errors --------------------
message_to_string({race_condition, [M, F, Args, Reason]}) ->
io_lib:format("The call ~w:~w~s ~s\n", [M, F, Args, Reason]);
@@ -466,7 +478,14 @@ message_to_string({callback_missing, [B, F, A]}) ->
io_lib:format("Undefined callback function ~w/~w (behaviour '~w')\n",
[F, A, B]);
message_to_string({callback_info_missing, [B]}) ->
- io_lib:format("Callback info about the ~w behaviour is not available\n", [B]).
+ io_lib:format("Callback info about the ~w behaviour is not available\n", [B]);
+%%----- Warnings for unknown functions, types, and behaviours -------------
+message_to_string({unknown_type, {M, F, A}}) ->
+ io_lib:format("Unknown type ~w:~w/~w", [M, F, A]);
+message_to_string({unknown_function, {M, F, A}}) ->
+ io_lib:format("Unknown function ~w:~w/~w", [M, F, A]);
+message_to_string({unknown_behaviour, B}) ->
+ io_lib:format("Unknown behaviour ~w", [B]).
%%-----------------------------------------------------------------------------
%% Auxiliary functions below
@@ -549,4 +568,4 @@ form_position_string(ArgNs) ->
ordinal(1) -> "1st";
ordinal(2) -> "2nd";
ordinal(3) -> "3rd";
-ordinal(N) when is_integer(N) -> io_lib:format("~wth",[N]).
+ordinal(N) when is_integer(N) -> io_lib:format("~wth", [N]).
diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl
index 105a174e31..ea6a71217c 100644
--- a/lib/dialyzer/src/dialyzer.hrl
+++ b/lib/dialyzer/src/dialyzer.hrl
@@ -2,18 +2,19 @@
%%%
%%% %CopyrightBegin%
%%%
-%%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%%% Copyright Ericsson AB 2006-2015. All Rights Reserved.
%%%
-%%% The contents of this file are subject to the Erlang Public License,
-%%% Version 1.1, (the "License"); you may not use this file except in
-%%% compliance with the License. You should have received a copy of the
-%%% Erlang Public License along with this software. If not, it can be
-%%% retrieved online at http://www.erlang.org/.
+%%% 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
%%%
-%%% Software distributed under the License is distributed on an "AS IS"
-%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%%% the License for the specific language governing rights and limitations
-%%% under the License.
+%%% 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%
%%%
@@ -58,6 +59,8 @@
-define(WARN_RACE_CONDITION, warn_race_condition).
-define(WARN_BEHAVIOUR, warn_behaviour).
-define(WARN_UNDEFINED_CALLBACK, warn_undefined_callbacks).
+-define(WARN_UNKNOWN, warn_unknown).
+-define(WARN_MAP_CONSTRUCTION, warn_map_construction).
%%
%% The following type has double role:
@@ -73,7 +76,8 @@
| ?WARN_CONTRACT_SUPERTYPE | ?WARN_CALLGRAPH
| ?WARN_UNMATCHED_RETURN | ?WARN_RACE_CONDITION
| ?WARN_BEHAVIOUR | ?WARN_CONTRACT_RANGE
- | ?WARN_UNDEFINED_CALLBACK.
+ | ?WARN_UNDEFINED_CALLBACK | ?WARN_UNKNOWN
+ | ?WARN_MAP_CONSTRUCTION.
%%
%% This is the representation of each warning as they will be returned
@@ -83,17 +87,20 @@
-type dial_warning() :: {dial_warn_tag(), file_line(), {atom(), [term()]}}.
%%
+%% This is the representation of each warning before suppressions have
+%% been applied
+%%
+-type m_or_mfa() :: module() % warnings not associated with any function
+ | mfa().
+-type warning_info() :: {file:filename(), non_neg_integer(), m_or_mfa()}.
+-type raw_warning() :: {dial_warn_tag(), warning_info(), {atom(), [term()]}}.
+
+%%
%% This is the representation of dialyzer's internal errors
%%
-type dial_error() :: any(). %% XXX: underspecified
%%--------------------------------------------------------------------
-%% THIS TYPE SHOULD ONE DAY DISAPPEAR -- IT DOES NOT BELONG HERE
-%%--------------------------------------------------------------------
-
--type ordset(T) :: [T] . %% XXX: temporarily
-
-%%--------------------------------------------------------------------
%% Basic types used either in the record definitions below or in other
%% parts of the application
%%--------------------------------------------------------------------
@@ -108,6 +115,7 @@
-type fopt() :: 'basename' | 'fullpath'.
-type format() :: 'formatted' | 'raw'.
-type label() :: non_neg_integer().
+-type dial_warn_tags():: ordsets:ordset(dial_warn_tag()).
-type rep_mode() :: 'quiet' | 'normal' | 'verbose'.
-type start_from() :: 'byte_code' | 'src_code'.
-type mfa_or_funlbl() :: label() | mfa().
@@ -117,10 +125,12 @@
%% Record declarations used by various files
%%--------------------------------------------------------------------
--record(analysis, {analysis_pid :: pid(),
+-type doc_plt() :: 'undefined' | dialyzer_plt:plt().
+
+-record(analysis, {analysis_pid :: pid() | 'undefined',
type = succ_typings :: anal_type(),
defines = [] :: [dial_define()],
- doc_plt :: dialyzer_plt:plt(),
+ doc_plt :: doc_plt(),
files = [] :: [file:filename()],
include_dirs = [] :: [file:filename()],
start_from = byte_code :: start_from(),
@@ -129,7 +139,7 @@
race_detection = false :: boolean(),
behaviours_chk = false :: boolean(),
timing = false :: boolean() | 'debug',
- timing_server :: dialyzer_timing:timing_server(),
+ timing_server = none :: dialyzer_timing:timing_server(),
callgraph_file = "" :: file:filename(),
solvers :: [solver()]}).
@@ -143,7 +153,7 @@
init_plts = [] :: [file:filename()],
include_dirs = [] :: [file:filename()],
output_plt = none :: 'none' | file:filename(),
- legal_warnings = ordsets:new() :: ordset(dial_warn_tag()),
+ legal_warnings = ordsets:new() :: dial_warn_tags(),
report_mode = normal :: rep_mode(),
erlang_mode = false :: boolean(),
use_contracts = true :: boolean(),
@@ -167,4 +177,4 @@
dialyzer_timing:end_stamp(Server),
Var
end).
--define(timing(Server, Msg, Expr),?timing(Server, Msg, _T, Expr)).
+-define(timing(Server, Msg, Expr), ?timing(Server, Msg, _T, Expr)).
diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
index 0fbaf1d47c..50fc1d8471 100644
--- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
+++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
@@ -2,18 +2,19 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -30,14 +31,17 @@
-export([start/3]).
+%%% Export for dialyzer_coordinator...
-export([compile_init_result/0,
- add_to_result/4,
- start_compilation/2,
+ add_to_result/4]).
+%%% ... and export for dialyzer_worker.
+-export([start_compilation/2,
continue_compilation/2]).
-export_type([compile_init_data/0,
- one_file_result/0,
- compile_result/0]).
+ one_file_mid_error/0,
+ one_file_result_ok/0,
+ compile_result/0]).
-include("dialyzer.hrl").
@@ -48,8 +52,9 @@
defines = [] :: [dial_define()],
doc_plt :: dialyzer_plt:plt(),
include_dirs = [] :: [file:filename()],
- no_warn_unused :: set(),
parent :: pid(),
+ legal_warnings :: % command line options
+ [dial_warn_tag()],
plt :: dialyzer_plt:plt(),
start_from = byte_code :: start_from(),
use_contracts = true :: boolean(),
@@ -57,7 +62,10 @@
solvers :: [solver()]
}).
--record(server_state, {parent :: pid(), legal_warnings :: [dial_warn_tag()]}).
+-record(server_state,
+ {
+ parent :: pid()
+ }).
%%--------------------------------------------------------------------
%% Main
@@ -71,48 +79,36 @@ start(Parent, LegalWarnings, Analysis) ->
Analysis0 =
Analysis#analysis{race_detection = RacesOn, timing_server = TimingServer},
Analysis1 = expand_files(Analysis0),
- Analysis2 = run_analysis(Analysis1),
- State = #server_state{parent = Parent, legal_warnings = LegalWarnings},
+ Analysis2 = run_analysis(Analysis1, LegalWarnings),
+ State = #server_state{parent = Parent},
loop(State, Analysis2, none),
dialyzer_timing:stop(TimingServer).
-run_analysis(Analysis) ->
+run_analysis(Analysis, LegalWarnings) ->
Self = self(),
- Fun = fun() -> analysis_start(Self, Analysis) end,
+ Fun = fun() -> analysis_start(Self, Analysis, LegalWarnings) end,
Analysis#analysis{analysis_pid = spawn_link(Fun)}.
-loop(#server_state{parent = Parent, legal_warnings = LegalWarnings} = State,
+loop(#server_state{parent = Parent} = State,
#analysis{analysis_pid = AnalPid} = Analysis, ExtCalls) ->
receive
{AnalPid, log, LogMsg} ->
send_log(Parent, LogMsg),
loop(State, Analysis, ExtCalls);
{AnalPid, warnings, Warnings} ->
- case filter_warnings(LegalWarnings, Warnings) of
- [] -> ok;
- SendWarnings ->
- send_warnings(Parent, SendWarnings)
- end,
+ send_warnings(Parent, Warnings),
loop(State, Analysis, ExtCalls);
{AnalPid, cserver, CServer, Plt} ->
send_codeserver_plt(Parent, CServer, Plt),
loop(State, Analysis, ExtCalls);
{AnalPid, done, Plt, DocPlt} ->
- case ExtCalls =:= none of
- true ->
- send_analysis_done(Parent, Plt, DocPlt);
- false ->
- send_ext_calls(Parent, ExtCalls),
- send_analysis_done(Parent, Plt, DocPlt)
- end;
+ send_ext_calls(Parent, ExtCalls),
+ send_analysis_done(Parent, Plt, DocPlt);
{AnalPid, ext_calls, NewExtCalls} ->
loop(State, Analysis, NewExtCalls);
{AnalPid, ext_types, ExtTypes} ->
send_ext_types(Parent, ExtTypes),
loop(State, Analysis, ExtCalls);
- {AnalPid, unknown_behaviours, UnknownBehaviour} ->
- send_unknown_behaviours(Parent, UnknownBehaviour),
- loop(State, Analysis, ExtCalls);
{AnalPid, mod_deps, ModDeps} ->
send_mod_deps(Parent, ModDeps),
loop(State, Analysis, ExtCalls);
@@ -125,7 +121,7 @@ loop(#server_state{parent = Parent, legal_warnings = LegalWarnings} = State,
%% The Analysis
%%--------------------------------------------------------------------
-analysis_start(Parent, Analysis) ->
+analysis_start(Parent, Analysis, LegalWarnings) ->
CServer = dialyzer_codeserver:new(),
Plt = Analysis#analysis.plt,
State = #analysis_state{codeserver = CServer,
@@ -135,13 +131,14 @@ analysis_start(Parent, Analysis) ->
include_dirs = Analysis#analysis.include_dirs,
plt = Plt,
parent = Parent,
+ legal_warnings = LegalWarnings,
start_from = Analysis#analysis.start_from,
use_contracts = Analysis#analysis.use_contracts,
timing_server = Analysis#analysis.timing_server,
solvers = Analysis#analysis.solvers
},
Files = ordsets:from_list(Analysis#analysis.files),
- {Callgraph, NoWarn, TmpCServer0} = compile_and_store(Files, State),
+ {Callgraph, TmpCServer0} = compile_and_store(Files, State),
%% Remote type postprocessing
NewCServer =
try
@@ -159,21 +156,22 @@ analysis_start(Parent, Analysis) ->
MergedExpTypes = sets:union(NewExpTypes, OldExpTypes1),
TmpCServer1 = dialyzer_codeserver:set_temp_records(MergedRecords, TmpCServer0),
TmpCServer2 =
- dialyzer_codeserver:insert_temp_exported_types(MergedExpTypes,
- TmpCServer1),
- TmpCServer3 = dialyzer_utils:process_record_remote_types(TmpCServer2),
+ dialyzer_codeserver:finalize_exported_types(MergedExpTypes, TmpCServer1),
?timing(State#analysis_state.timing_server, "remote",
- dialyzer_contracts:process_contract_remote_types(TmpCServer3))
+ begin
+ TmpCServer3 =
+ dialyzer_utils:process_record_remote_types(TmpCServer2),
+ dialyzer_contracts:process_contract_remote_types(TmpCServer3)
+ end)
catch
throw:{error, _ErrorMsg} = Error -> exit(Error)
end,
NewPlt0 = dialyzer_plt:insert_types(Plt, dialyzer_codeserver:get_records(NewCServer)),
- ExpTypes = dialyzer_codeserver:get_exported_types(NewCServer),
+ ExpTypes = dialyzer_codeserver:get_exported_types(NewCServer),
NewPlt1 = dialyzer_plt:insert_exported_types(NewPlt0, ExpTypes),
State0 = State#analysis_state{plt = NewPlt1},
dump_callgraph(Callgraph, State0, Analysis),
State1 = State0#analysis_state{codeserver = NewCServer},
- State2 = State1#analysis_state{no_warn_unused = NoWarn},
%% Remove all old versions of the files being analyzed
AllNodes = dialyzer_callgraph:all_nodes(Callgraph),
Plt1 = dialyzer_plt:delete_list(NewPlt1, AllNodes),
@@ -183,14 +181,14 @@ analysis_start(Parent, Analysis) ->
true -> dialyzer_callgraph:put_race_detection(true, Callgraph);
false -> Callgraph
end,
- State3 = analyze_callgraph(NewCallgraph, State2#analysis_state{plt = Plt1}),
+ State2 = analyze_callgraph(NewCallgraph, State1#analysis_state{plt = Plt1}),
dialyzer_callgraph:dispose_race_server(NewCallgraph),
rcv_and_send_ext_types(Parent),
NonExports = sets:subtract(sets:from_list(AllNodes), Exports),
NonExportsList = sets:to_list(NonExports),
- Plt2 = dialyzer_plt:delete_list(State3#analysis_state.plt, NonExportsList),
- send_codeserver_plt(Parent, CServer, State3#analysis_state.plt),
- send_analysis_done(Parent, Plt2, State3#analysis_state.doc_plt).
+ Plt2 = dialyzer_plt:delete_list(State2#analysis_state.plt, NonExportsList),
+ send_codeserver_plt(Parent, CServer, State2#analysis_state.plt),
+ send_analysis_done(Parent, Plt2, State2#analysis_state.doc_plt).
analyze_callgraph(Callgraph, #analysis_state{codeserver = Codeserver,
doc_plt = DocPlt,
@@ -206,11 +204,11 @@ analyze_callgraph(Callgraph, #analysis_state{codeserver = Codeserver,
TimingServer, Solvers, Parent),
{NewPlt0, DocPlt};
succ_typings ->
- NoWarn = State#analysis_state.no_warn_unused,
{Warnings, NewPlt0, NewDocPlt0} =
dialyzer_succ_typings:get_warnings(Callgraph, Plt, DocPlt, Codeserver,
- NoWarn, TimingServer, Solvers, Parent),
- send_warnings(State#analysis_state.parent, Warnings),
+ TimingServer, Solvers, Parent),
+ Warnings1 = filter_warnings(Warnings, Codeserver),
+ send_warnings(State#analysis_state.parent, Warnings1),
{NewPlt0, NewDocPlt0}
end,
dialyzer_callgraph:delete(Callgraph),
@@ -226,29 +224,36 @@ analyze_callgraph(Callgraph, #analysis_state{codeserver = Codeserver,
defines = [] :: [dial_define()],
include_dirs = [] :: [file:filename()],
start_from = byte_code :: start_from(),
- use_contracts = true :: boolean()
+ use_contracts = true :: boolean(),
+ legal_warnings :: [dial_warn_tag()]
}).
make_compile_init(#analysis_state{codeserver = Codeserver,
defines = Defs,
include_dirs = Dirs,
use_contracts = UseContracts,
+ legal_warnings = LegalWarnings,
start_from = StartFrom}, Callgraph) ->
#compile_init{callgraph = Callgraph,
codeserver = Codeserver,
defines = [{d, Macro, Val} || {Macro, Val} <- Defs],
include_dirs = [{i, D} || D <- Dirs],
use_contracts = UseContracts,
+ legal_warnings = LegalWarnings,
start_from = StartFrom}.
compile_and_store(Files, #analysis_state{codeserver = CServer,
timing_server = Timing,
parent = Parent} = State) ->
send_log(Parent, "Reading files and computing callgraph... "),
- {T1, _} = statistics(runtime),
+ {T1, _} = statistics(wall_clock),
Callgraph = dialyzer_callgraph:new(),
CompileInit = make_compile_init(State, Callgraph),
- {{Failed, NoWarn, Modules}, NextLabel} =
+ %% Spawn a worker per file - where each worker calls
+ %% start_compilation on its file, asks next label to coordinator and
+ %% calls continue_compilation - and let coordinator aggregate
+ %% results using (compile_init_result and) add_to_result.
+ {{Failed, Modules}, NextLabel} =
?timing(Timing, "compile", _C1,
dialyzer_coordinator:parallel_job(compile, Files,
CompileInit, Timing)),
@@ -268,43 +273,45 @@ compile_and_store(Files, #analysis_state{codeserver = CServer,
[[Reason || {_Filename, Reason} <- Failed]]),
exit({error, Msg})
end,
- {T2, _} = statistics(runtime),
+ {T2, _} = statistics(wall_clock),
Msg1 = io_lib:format("done in ~.2f secs\nRemoving edges... ", [(T2-T1)/1000]),
send_log(Parent, Msg1),
Callgraph =
?timing(Timing, "clean", _C2,
cleanup_callgraph(State, CServer2, Callgraph, Modules)),
- {T3, _} = statistics(runtime),
+ {T3, _} = statistics(wall_clock),
Msg2 = io_lib:format("done in ~.2f secs\n", [(T3-T2)/1000]),
send_log(Parent, Msg2),
- {Callgraph, sets:from_list(NoWarn), CServer2}.
-
--type compile_init_data() :: #compile_init{}.
--type error_reason() :: string().
--type compile_result() :: {[{file:filename(), error_reason()}], [mfa()],
- [module()]}. %%opaque
--type one_file_result() :: {error, error_reason()} |
- {ok, [dialyzer_callgraph:callgraph_edge()],
- [mfa_or_funlbl()], [mfa()], module()}. %%opaque
--type compile_mid_data() :: {module(), cerl:cerl(), [mfa()],
- dialyzer_callgraph:callgraph(),
- dialyzer_codeserver:codeserver()}.
+ {Callgraph, CServer2}.
+
+-opaque compile_init_data() :: #compile_init{}.
+-type error_reason() :: string().
+-opaque compile_result() :: {[{file:filename(), error_reason()}],
+ [module()]}.
+-type one_file_mid_error() :: {error, error_reason()}.
+-opaque one_file_result_ok() :: {ok, [dialyzer_callgraph:callgraph_edge()],
+ [mfa_or_funlbl()], module()}.
+-type one_file_result() :: one_file_mid_error() |
+ one_file_result_ok().
+-type compile_mid_data() :: {module(), cerl:cerl(),
+ dialyzer_callgraph:callgraph(),
+ dialyzer_codeserver:codeserver()}.
-spec compile_init_result() -> compile_result().
-compile_init_result() -> {[], [], []}.
+compile_init_result() -> {[], []}.
-spec add_to_result(file:filename(), one_file_result(), compile_result(),
compile_init_data()) -> compile_result().
-add_to_result(File, NewData, {Failed, NoWarn, Mods}, InitData) ->
+add_to_result(File, NewData, {Failed, Mods}, InitData) ->
case NewData of
{error, Reason} ->
- {[{File, Reason}|Failed], NoWarn, Mods};
- {ok, V, E, NewNoWarn, Mod} ->
+ {[{File, Reason}|Failed], Mods};
+ {ok, V, E, Mod} ->
Callgraph = InitData#compile_init.callgraph,
dialyzer_callgraph:add_edges(E, V, Callgraph),
- {Failed, NewNoWarn ++ NoWarn, [Mod|Mods]}
+ {Failed, [Mod|Mods]}
end.
-spec start_compilation(file:filename(), compile_init_data()) ->
@@ -314,12 +321,14 @@ start_compilation(File,
#compile_init{callgraph = Callgraph, codeserver = Codeserver,
defines = Defines, include_dirs = IncludeD,
use_contracts = UseContracts,
+ legal_warnings = LegalWarnings,
start_from = StartFrom}) ->
case StartFrom of
src_code ->
- compile_src(File, IncludeD, Defines, Callgraph, Codeserver, UseContracts);
+ compile_src(File, IncludeD, Defines, Callgraph, Codeserver,
+ UseContracts, LegalWarnings);
byte_code ->
- compile_byte(File, Callgraph, Codeserver, UseContracts)
+ compile_byte(File, Callgraph, Codeserver, UseContracts, LegalWarnings)
end.
cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent,
@@ -353,82 +362,87 @@ cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent,
end,
Callgraph1.
-compile_src(File, Includes, Defines, Callgraph, CServer, UseContracts) ->
+compile_src(File, Includes, Defines, Callgraph, CServer, UseContracts,
+ LegalWarnings) ->
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
{error, _Msg} = Error -> Error;
{ok, AbstrCode} ->
- compile_common(File, AbstrCode, CompOpts, Callgraph, CServer, UseContracts)
+ compile_common(File, AbstrCode, CompOpts, Callgraph, CServer,
+ UseContracts, LegalWarnings)
end.
-compile_byte(File, Callgraph, CServer, UseContracts) ->
+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_common(File, AbstrCode, [], Callgraph, CServer, UseContracts)
+ compile_byte(File, AbstrCode, Callgraph, CServer, UseContracts,
+ LegalWarnings)
end.
-compile_common(File, AbstrCode, CompOpts, Callgraph, CServer, UseContracts) ->
+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)
+ 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)),
- NoWarn = abs_get_nowarn(AbstrCode, Mod),
case dialyzer_utils:get_record_and_type_info(AbstrCode) of
{error, _} = Error -> Error;
{ok, RecInfo} ->
CServer1 =
dialyzer_codeserver:store_temp_records(Mod, RecInfo, CServer),
+ MetaFunInfo =
+ dialyzer_utils:get_fun_meta_info(Mod, AbstrCode, LegalWarnings),
+ 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} ->
- CServer2 =
+ CServer3 =
dialyzer_codeserver:store_temp_contracts(Mod, SpecInfo,
CallbackInfo,
- CServer1),
- store_core(Mod, Core, NoWarn, Callgraph, CServer2)
+ CServer2),
+ store_core(Mod, Core, Callgraph, CServer3)
end;
false ->
- store_core(Mod, Core, NoWarn, Callgraph, CServer1)
+ store_core(Mod, Core, Callgraph, CServer2)
end
end
end.
-store_core(Mod, Core, NoWarn, Callgraph, CServer) ->
+store_core(Mod, Core, Callgraph, CServer) ->
Exp = get_exports_from_core(Core),
ExpTypes = get_exported_types_from_core(Core),
CServer = dialyzer_codeserver:insert_exports(Exp, CServer),
CServer = dialyzer_codeserver:insert_temp_exported_types(ExpTypes, CServer),
CoreTree = cerl:from_records(Core),
- {ok, cerl_trees:size(CoreTree), {Mod, CoreTree, NoWarn, Callgraph, CServer}}.
+ CoreSize = cerl_trees:size(CoreTree),
+ {ok, CoreSize, {Mod, CoreTree, Callgraph, CServer}}.
--spec continue_compilation(integer(), compile_mid_data()) -> one_file_result().
+-spec continue_compilation(integer(), compile_mid_data()) ->
+ one_file_result_ok().
-continue_compilation(NextLabel, {Mod, CoreTree, NoWarn, Callgraph, CServer}) ->
+continue_compilation(NextLabel, {Mod, CoreTree, Callgraph, CServer}) ->
{LabeledTree, _NewNextLabel} = cerl_trees:label(CoreTree, NextLabel),
LabeledCore = cerl:to_records(LabeledTree),
- store_code_and_build_callgraph(Mod, LabeledCore, Callgraph, NoWarn, CServer).
-
-abs_get_nowarn(Abs, M) ->
- Opts = lists:flatten([C || {attribute, _, compile, C} <- Abs]),
- Warn = erl_lint:bool_option(warn_unused_function, nowarn_unused_function,
- true, Opts),
- case Warn of
- false ->
- [{M, F, A} || {function, _, F, A, _} <- Abs]; % all functions
- true ->
- OnLoad =
- lists:flatten([{M, F, A} || {attribute, _, on_load, {F, A}} <- Abs]),
- OnLoad ++ [{M, F, A} ||
- {nowarn_unused_function, FAs} <- Opts,
- {F, A} <- lists:flatten([FAs])]
- end.
+ store_code_and_build_callgraph(Mod, LabeledCore, Callgraph, CServer).
get_exported_types_from_core(Core) ->
Attrs = cerl:module_attrs(Core),
@@ -446,11 +460,11 @@ get_exports_from_core(Core) ->
M = cerl:atom_val(cerl:module_name(Tree)),
[{M, F, A} || {F, A} <- Exports2].
-store_code_and_build_callgraph(Mod, Core, Callgraph, NoWarn, CServer) ->
+store_code_and_build_callgraph(Mod, Core, Callgraph, CServer) ->
CoreTree = cerl:from_records(Core),
{Vertices, Edges} = dialyzer_callgraph:scan_core_tree(CoreTree, Callgraph),
CServer = dialyzer_codeserver:insert(Mod, CoreTree, CServer),
- {ok, Vertices, Edges, NoWarn, Mod}.
+ {ok, Vertices, Edges, Mod}.
%%--------------------------------------------------------------------
%% Utilities
@@ -538,14 +552,25 @@ send_warnings(Parent, Warnings) ->
Parent ! {self(), warnings, Warnings},
ok.
-filter_warnings(LegalWarnings, Warnings) ->
- [TIW || {Tag, _Id, _Warning} = TIW <- Warnings,
- ordsets:is_element(Tag, LegalWarnings)].
+filter_warnings(Warnings, Codeserver) ->
+ [TWW || {Tag, WarningInfo, _Warning} = TWW <- Warnings,
+ is_ok_fun(WarningInfo, Codeserver),
+ is_ok_tag(Tag, WarningInfo, Codeserver)].
+
+is_ok_fun({_F, _L, Module}, _Codeserver) when is_atom(Module) ->
+ true;
+is_ok_fun({_Filename, _Line, {_M, _F, _A} = MFA}, Codeserver) ->
+ not dialyzer_utils:is_suppressed_fun(MFA, Codeserver).
+is_ok_tag(Tag, {_F, _L, MorMFA}, Codeserver) ->
+ not dialyzer_utils:is_suppressed_tag(MorMFA, Tag, Codeserver).
+
send_analysis_done(Parent, Plt, DocPlt) ->
Parent ! {self(), done, Plt, DocPlt},
ok.
+send_ext_calls(_Parent, none) ->
+ ok;
send_ext_calls(Parent, ExtCalls) ->
Parent ! {self(), ext_calls, ExtCalls},
ok.
@@ -554,16 +579,14 @@ send_ext_types(Parent, ExtTypes) ->
Parent ! {self(), ext_types, ExtTypes},
ok.
-send_unknown_behaviours(Parent, UnknownBehaviours) ->
- Parent ! {self(), unknown_behaviours, UnknownBehaviours},
- ok.
-
send_codeserver_plt(Parent, CServer, Plt ) ->
Parent ! {self(), cserver, CServer, Plt},
ok.
send_bad_calls(Parent, BadCalls, CodeServer) ->
- send_warnings(Parent, format_bad_calls(BadCalls, CodeServer, [])).
+ FormatedBadCalls = format_bad_calls(BadCalls, CodeServer, []),
+ Warnings = filter_warnings(FormatedBadCalls, CodeServer),
+ send_warnings(Parent, Warnings).
send_mod_deps(Parent, ModuleDeps) ->
Parent ! {self(), mod_deps, ModuleDeps},
@@ -575,8 +598,9 @@ format_bad_calls([{{_, _, _}, {_, module_info, A}}|Left], CodeServer, Acc)
format_bad_calls([{FromMFA, {M, F, A} = To}|Left], CodeServer, Acc) ->
{_Var, FunCode} = dialyzer_codeserver:lookup_mfa_code(FromMFA, CodeServer),
Msg = {call_to_missing, [M, F, A]},
- FileLine = find_call_file_and_line(FunCode, To),
- NewAcc = [{?WARN_CALLGRAPH, FileLine, Msg}|Acc],
+ {File, Line} = find_call_file_and_line(FunCode, To),
+ WarningInfo = {File, Line, FromMFA},
+ NewAcc = [{?WARN_CALLGRAPH, WarningInfo, Msg}|Acc],
format_bad_calls(Left, CodeServer, NewAcc);
format_bad_calls([], _CodeServer, Acc) ->
Acc.
@@ -595,6 +619,28 @@ find_call_file_and_line(Tree, MFA) ->
MFA ->
Ann = cerl:get_ann(SubTree),
[{get_file(Ann), get_line(Ann)}|Acc];
+ {erlang, make_fun, 3} ->
+ [CA1, CA2, CA3] = cerl:call_args(SubTree),
+ case
+ cerl:is_c_atom(CA1) andalso
+ cerl:is_c_atom(CA2) andalso
+ cerl:is_c_int(CA3)
+ of
+ true ->
+ case
+ {cerl:concrete(CA1),
+ cerl:concrete(CA2),
+ cerl:concrete(CA3)}
+ of
+ MFA ->
+ Ann = cerl:get_ann(SubTree),
+ [{get_file(Ann), get_line(Ann)}|Acc];
+ _ ->
+ Acc
+ end;
+ false ->
+ Acc
+ end;
_ -> Acc
end;
false -> Acc
@@ -619,9 +665,9 @@ dump_callgraph(CallGraph, State, #analysis{callgraph_file = File} = Analysis) ->
Extension = filename:extension(File),
Start_Msg = io_lib:format("Dumping the callgraph... ", []),
send_log(State#analysis_state.parent, Start_Msg),
- {T1, _} = statistics(runtime),
+ {T1, _} = statistics(wall_clock),
dump_callgraph(CallGraph, State, Analysis, Extension),
- {T2, _} = statistics(runtime),
+ {T2, _} = statistics(wall_clock),
Finish_Msg = io_lib:format("done in ~2f secs\n", [(T2-T1)/1000]),
send_log(State#analysis_state.parent, Finish_Msg),
ok.
diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl
index 2b9a69ce77..5623929a43 100644
--- a/lib/dialyzer/src/dialyzer_behaviours.erl
+++ b/lib/dialyzer/src/dialyzer_behaviours.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -40,17 +41,19 @@
-type behaviour() :: atom().
+-type rectab() :: erl_types:type_table().
+
-record(state, {plt :: dialyzer_plt:plt(),
codeserver :: dialyzer_codeserver:codeserver(),
filename :: file:filename(),
behlines :: [{behaviour(), non_neg_integer()}],
- records :: dict()}).
+ records :: rectab()}).
%%--------------------------------------------------------------------
--spec check_callbacks(module(), [{cerl:cerl(), cerl:cerl()}], dict(),
+-spec check_callbacks(module(), [{cerl:cerl(), cerl:cerl()}], rectab(),
dialyzer_plt:plt(),
- dialyzer_codeserver:codeserver()) -> [dial_warning()].
+ dialyzer_codeserver:codeserver()) -> [raw_warning()].
check_callbacks(Module, Attrs, Records, Plt, Codeserver) ->
{Behaviours, BehLines} = get_behaviours(Attrs),
@@ -63,7 +66,7 @@ check_callbacks(Module, Attrs, Records, Plt, Codeserver) ->
State = #state{plt = Plt, filename = File, behlines = BehLines,
codeserver = Codeserver, records = Records},
Warnings = get_warnings(Module, Behaviours, State),
- [add_tag_file_line(Module, W, State) || W <- Warnings]
+ [add_tag_warning_info(Module, W, State) || W <- Warnings]
end.
%%--------------------------------------------------------------------
@@ -100,14 +103,18 @@ check_all_callbacks(Module, Behaviour, [Cb|Rest],
#state{plt = Plt, codeserver = Codeserver,
records = Records} = State, Acc) ->
{{Behaviour, Function, Arity},
- {{_BehFile, _BehLine}, Callback}} = Cb,
+ {{_BehFile, _BehLine}, Callback, Xtra}} = Cb,
CbMFA = {Module, Function, Arity},
CbReturnType = dialyzer_contracts:get_contract_return(Callback),
CbArgTypes = dialyzer_contracts:get_contract_args(Callback),
Acc0 = Acc,
Acc1 =
case dialyzer_plt:lookup(Plt, CbMFA) of
- 'none' -> [{callback_missing, [Behaviour, Function, Arity]}|Acc0];
+ 'none' ->
+ case lists:member(optional_callback, Xtra) of
+ true -> Acc0;
+ false -> [{callback_missing, [Behaviour, Function, Arity]}|Acc0]
+ end;
{'value', RetArgTypes} ->
Acc00 = Acc0,
{ReturnType, ArgTypes} = RetArgTypes,
@@ -135,7 +142,7 @@ check_all_callbacks(Module, Behaviour, [Cb|Rest],
Acc2 =
case dialyzer_codeserver:lookup_mfa_contract(CbMFA, Codeserver) of
'error' -> Acc1;
- {ok, {{File, Line}, Contract}} ->
+ {ok, {{File, Line}, Contract, _Xtra}} ->
Acc10 = Acc1,
SpecReturnType0 = dialyzer_contracts:get_contract_return(Contract),
SpecArgTypes0 = dialyzer_contracts:get_contract_args(Contract),
@@ -187,7 +194,7 @@ find_mismatching_args(Kind, [Type|Rest], [CbType|CbRest], Behaviour,
Arity, Records, N+1, NewAcc)
end.
-add_tag_file_line(_Module, {Tag, [B|_R]} = Warn, State)
+add_tag_warning_info(Module, {Tag, [B|_R]} = Warn, State)
when Tag =:= callback_missing;
Tag =:= callback_info_missing ->
{B, Line} = lists:keyfind(B, 1, State#state.behlines),
@@ -196,18 +203,18 @@ add_tag_file_line(_Module, {Tag, [B|_R]} = Warn, State)
callback_missing -> ?WARN_BEHAVIOUR;
callback_info_missing -> ?WARN_UNDEFINED_CALLBACK
end,
- {Category, {State#state.filename, Line}, Warn};
-add_tag_file_line(_Module, {Tag, [File, Line|R]}, _State)
+ {Category, {State#state.filename, Line, Module}, Warn};
+add_tag_warning_info(Module, {Tag, [File, Line|R]}, _State)
when Tag =:= callback_spec_type_mismatch;
Tag =:= callback_spec_arg_type_mismatch ->
- {?WARN_BEHAVIOUR, {File, Line}, {Tag, R}};
-add_tag_file_line(Module, {_Tag, [_B, Fun, Arity|_R]} = Warn, State) ->
+ {?WARN_BEHAVIOUR, {File, Line, Module}, {Tag, R}};
+add_tag_warning_info(Module, {_Tag, [_B, Fun, Arity|_R]} = Warn, State) ->
{_A, FunCode} =
dialyzer_codeserver:lookup_mfa_code({Module, Fun, Arity},
State#state.codeserver),
Anns = cerl:get_ann(FunCode),
- FileLine = {get_file(Anns), get_line(Anns)},
- {?WARN_BEHAVIOUR, FileLine, Warn}.
+ WarningInfo = {get_file(Anns), get_line(Anns), {Module, Fun, Arity}},
+ {?WARN_BEHAVIOUR, WarningInfo, Warn}.
get_line([Line|_]) when is_integer(Line) -> Line;
get_line([_|Tail]) -> get_line(Tail);
diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl
index b9ad3f857d..50abb22009 100644
--- a/lib/dialyzer/src/dialyzer_callgraph.erl
+++ b/lib/dialyzer/src/dialyzer_callgraph.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -35,6 +36,7 @@
is_escaping/2,
is_self_rec/2,
non_local_calls/1,
+ lookup_letrec/2,
lookup_rec_var/2,
lookup_call_site/2,
lookup_label/2,
@@ -63,14 +65,16 @@
put_named_tables/2, put_public_tables/2, put_behaviour_api_calls/2,
get_behaviour_api_calls/1, dispose_race_server/1, duplicate/1]).
--export_type([callgraph/0, mfa_or_funlbl/0, callgraph_edge/0]).
+-export_type([callgraph/0, mfa_or_funlbl/0, callgraph_edge/0, mod_deps/0]).
-include("dialyzer.hrl").
%%----------------------------------------------------------------------
-type scc() :: [mfa_or_funlbl()].
--type mfa_calls() :: [{mfa_or_funlbl(), mfa_or_funlbl()}].
+-type mfa_call() :: {mfa_or_funlbl(), mfa_or_funlbl()}.
+-type mfa_calls() :: [mfa_call()].
+-type mod_deps() :: dict:dict(module(), [module()]).
%%-----------------------------------------------------------------------------
%% A callgraph is a directed graph where the nodes are functions and a
@@ -81,6 +85,8 @@
%% digraph - A digraph representing the callgraph.
%% Nodes are represented as MFAs or labels.
%% esc - A set of all escaping functions as reported by dialyzer_dep.
+%% letrec_map - A dict mapping from letrec bound labels to function labels.
+%% Includes all functions.
%% name_map - A mapping from label to MFA.
%% rev_name_map - A reverse mapping of the name_map.
%% rec_var_map - A dict mapping from letrec bound labels to function names.
@@ -90,38 +96,42 @@
%% whenever applicable.
%%-----------------------------------------------------------------------------
--record(callgraph, {digraph = digraph:new() :: digraph(),
- active_digraph :: active_digraph(),
- esc :: ets:tid(),
+%% Types with comment 'race' are due to dialyzer_races.erl.
+-record(callgraph, {digraph = digraph:new() :: digraph:graph(),
+ active_digraph :: active_digraph()
+ | 'undefined', % race
+ esc :: ets:tid()
+ | 'undefined', % race
+ letrec_map :: ets:tid()
+ | 'undefined', % race
name_map :: ets:tid(),
rev_name_map :: ets:tid(),
- rec_var_map :: ets:tid(),
- self_rec :: ets:tid(),
- calls :: ets:tid(),
+ rec_var_map :: ets:tid()
+ | 'undefined', % race
+ self_rec :: ets:tid()
+ | 'undefined', % race
+ calls :: ets:tid()
+ | 'undefined', % race
race_detection = false :: boolean(),
- race_data_server = new_race_data_server() :: pid()}).
-
--record(race_data_state, {race_code = dict:new() :: dict(),
- public_tables = [] :: [label()],
- named_tables = [] :: [string()],
- beh_api_calls = [] :: [{mfa(), mfa()}]}).
+ race_data_server = dialyzer_race_data_server:new() :: pid()}).
%% Exported Types
--type callgraph() :: #callgraph{}.
+-opaque callgraph() :: #callgraph{}.
--type active_digraph() :: {'d', digraph()} | {'e', ets:tid(), ets:tid()}.
+-type active_digraph() :: {'d', digraph:graph()} | {'e', ets:tid(), ets:tid()}.
%%----------------------------------------------------------------------
-spec new() -> callgraph().
new() ->
- [ETSEsc, ETSNameMap, ETSRevNameMap, ETSRecVarMap, ETSSelfRec, ETSCalls] =
+ [ETSEsc, ETSNameMap, ETSRevNameMap, ETSRecVarMap, ETSLetrecMap, ETSSelfRec, ETSCalls] =
[ets:new(N,[public, {read_concurrency, true}]) ||
N <- [callgraph_esc, callgraph_name_map, callgraph_rev_name_map,
- callgraph_rec_var_map, callgraph_self_rec, callgraph_calls]],
+ callgraph_rec_var_map, callgraph_letrec_map, callgraph_self_rec, callgraph_calls]],
#callgraph{esc = ETSEsc,
+ letrec_map = ETSLetrecMap,
name_map = ETSNameMap,
rev_name_map = ETSRevNameMap,
rec_var_map = ETSRecVarMap,
@@ -144,6 +154,12 @@ lookup_rec_var(Label, #callgraph{rec_var_map = RecVarMap})
when is_integer(Label) ->
ets_lookup_dict(Label, RecVarMap).
+-spec lookup_letrec(label(), callgraph()) -> 'error' | {'ok', label()}.
+
+lookup_letrec(Label, #callgraph{letrec_map = LetrecMap})
+ when is_integer(Label) ->
+ ets_lookup_dict(Label, LetrecMap).
+
-spec lookup_call_site(label(), callgraph()) -> 'error' | {'ok', [_]}. % XXX: refine
lookup_call_site(Label, #callgraph{calls = Calls})
@@ -211,7 +227,10 @@ non_local_calls(#callgraph{digraph = DG}) ->
Edges = digraph_edges(DG),
find_non_local_calls(Edges, sets:new()).
--spec find_non_local_calls([{mfa_or_funlbl(), mfa_or_funlbl()}], set()) -> mfa_calls().
+-type call_tab() :: sets:set(mfa_call()).
+
+-spec find_non_local_calls([{mfa_or_funlbl(), mfa_or_funlbl()}], call_tab()) ->
+ mfa_calls().
find_non_local_calls([{{M,_,_}, {M,_,_}}|Left], Set) ->
find_non_local_calls(Left, Set);
@@ -256,7 +275,7 @@ get_required_by(SCC, #callgraph{active_digraph = {'d', DG}}) ->
modules(#callgraph{digraph = DG}) ->
ordsets:from_list([M || {M,_F,_A} <- digraph_vertices(DG)]).
--spec module_postorder(callgraph()) -> {[module()], {'d', digraph()}}.
+-spec module_postorder(callgraph()) -> {[module()], {'d', digraph:graph()}}.
module_postorder(#callgraph{digraph = DG}) ->
Edges = lists:foldl(fun edge_fold/2, sets:new(), digraph_edges(DG)),
@@ -276,7 +295,7 @@ edge_fold(_, Set) -> Set.
%% The module deps of a module are modules that depend on the module
--spec module_deps(callgraph()) -> dict().
+-spec module_deps(callgraph()) -> mod_deps().
module_deps(#callgraph{digraph = DG}) ->
Edges = lists:foldl(fun edge_fold/2, sets:new(), digraph_edges(DG)),
@@ -290,7 +309,7 @@ module_deps(#callgraph{digraph = DG}) ->
digraph_delete(MDG),
dict:from_list(Deps).
--spec strip_module_deps(dict(), set()) -> dict().
+-spec strip_module_deps(mod_deps(), sets:set(module())) -> mod_deps().
strip_module_deps(ModDeps, StripSet) ->
FilterFun1 = fun(Val) -> not sets:is_element(Val, StripSet) end,
@@ -348,16 +367,18 @@ ets_lookup_set(Key, Table) ->
scan_core_tree(Tree, #callgraph{calls = ETSCalls,
esc = ETSEsc,
+ letrec_map = ETSLetrecMap,
name_map = ETSNameMap,
rec_var_map = ETSRecVarMap,
rev_name_map = ETSRevNameMap,
self_rec = ETSSelfRec}) ->
%% Build name map and recursion variable maps.
- build_maps(Tree, ETSRecVarMap, ETSNameMap, ETSRevNameMap),
+ build_maps(Tree, ETSRecVarMap, ETSNameMap, ETSRevNameMap, ETSLetrecMap),
%% First find the module-local dependencies.
- {Deps0, EscapingFuns, Calls} = dialyzer_dep:analyze(Tree),
+ {Deps0, EscapingFuns, Calls, Letrecs} = dialyzer_dep:analyze(Tree),
true = ets:insert(ETSCalls, dict:to_list(Calls)),
+ true = ets:insert(ETSLetrecMap, dict:to_list(Letrecs)),
true = ets:insert(ETSEsc, [{E} || E <- EscapingFuns]),
LabelEdges = get_edges_from_deps(Deps0),
@@ -394,7 +415,7 @@ scan_core_tree(Tree, #callgraph{calls = ETSCalls,
NamedEdges3 = NewNamedEdges1 ++ NewNamedEdges2,
{Names3, NamedEdges3}.
-build_maps(Tree, ETSRecVarMap, ETSNameMap, ETSRevNameMap) ->
+build_maps(Tree, ETSRecVarMap, ETSNameMap, ETSRevNameMap, ETSLetrecMap) ->
%% We only care about the named (top level) functions. The anonymous
%% functions will be analysed together with their parents.
Defs = cerl:module_defs(Tree),
@@ -406,6 +427,7 @@ build_maps(Tree, ETSRecVarMap, ETSNameMap, ETSRevNameMap) ->
MFA = {Mod, FunName, Arity},
FunLabel = get_label(Function),
VarLabel = get_label(Var),
+ true = ets:insert(ETSLetrecMap, {VarLabel, FunLabel}),
true = ets:insert(ETSNameMap, {FunLabel, MFA}),
true = ets:insert(ETSRevNameMap, {MFA, FunLabel}),
true = ets:insert(ETSRecVarMap, {VarLabel, MFA})
@@ -458,14 +480,37 @@ scan_one_core_fun(TopTree, FunName) ->
call ->
CalleeM = cerl:call_module(Tree),
CalleeF = cerl:call_name(Tree),
- A = length(cerl:call_args(Tree)),
+ CalleeArgs = cerl:call_args(Tree),
+ A = length(CalleeArgs),
case (cerl:is_c_atom(CalleeM) andalso
cerl:is_c_atom(CalleeF)) of
true ->
M = cerl:atom_val(CalleeM),
F = cerl:atom_val(CalleeF),
case erl_bif_types:is_known(M, F, A) of
- true -> Acc;
+ true ->
+ case {M, F, A} of
+ {erlang, make_fun, 3} ->
+ [CA1, CA2, CA3] = CalleeArgs,
+ case
+ cerl:is_c_atom(CA1) andalso
+ cerl:is_c_atom(CA2) andalso
+ cerl:is_c_int(CA3)
+ of
+ true ->
+ MM = cerl:atom_val(CA1),
+ FF = cerl:atom_val(CA2),
+ AA = cerl:int_val(CA3),
+ case erl_bif_types:is_known(MM, FF, AA) of
+ true -> Acc;
+ false -> [{FunName, {MM, FF, AA}}|Acc]
+ end;
+ false ->
+ Acc
+ end;
+ _ ->
+ Acc
+ end;
false -> [{FunName, {M, F, A}}|Acc]
end;
false ->
@@ -561,50 +606,35 @@ digraph_reaching_subgraph(Funs, DG) ->
%% Races
%%----------------------------------------------------------------------
--spec renew_race_info(callgraph(), dict(), [label()], [string()]) ->
+-spec renew_race_info(callgraph(), dict:dict(), [label()], [string()]) ->
callgraph().
renew_race_info(#callgraph{race_data_server = RaceDataServer} = CG,
RaceCode, PublicTables, NamedTables) ->
- ok = race_data_server_cast(
+ ok = dialyzer_race_data_server:cast(
{renew_race_info, {RaceCode, PublicTables, NamedTables}},
RaceDataServer),
CG.
-renew_race_info({RaceCode, PublicTables, NamedTables},
- #race_data_state{} = State) ->
- State#race_data_state{race_code = RaceCode,
- public_tables = PublicTables,
- named_tables = NamedTables}.
-
-spec renew_race_code(dialyzer_races:races(), callgraph()) -> callgraph().
renew_race_code(Races, #callgraph{race_data_server = RaceDataServer} = CG) ->
Fun = dialyzer_races:get_curr_fun(Races),
FunArgs = dialyzer_races:get_curr_fun_args(Races),
Code = lists:reverse(dialyzer_races:get_race_list(Races)),
- ok = race_data_server_cast(
+ ok = dialyzer_race_data_server:cast(
{renew_race_code, {Fun, FunArgs, Code}},
RaceDataServer),
CG.
-renew_race_code_handler({Fun, FunArgs, Code},
- #race_data_state{race_code = RaceCode} = State) ->
- State#race_data_state{race_code = dict:store(Fun, [FunArgs, Code], RaceCode)}.
-
-spec renew_race_public_tables(label(), callgraph()) -> callgraph().
renew_race_public_tables(VarLabel,
#callgraph{race_data_server = RaceDataServer} = CG) ->
ok =
- race_data_server_cast({renew_race_public_tables, VarLabel}, RaceDataServer),
+ dialyzer_race_data_server:cast({renew_race_public_tables, VarLabel}, RaceDataServer),
CG.
-renew_race_public_tables_handler(VarLabel,
- #race_data_state{public_tables = PT}
- = State) ->
- State#race_data_state{public_tables = ordsets:add_element(VarLabel, PT)}.
-
-spec cleanup(callgraph()) -> callgraph().
cleanup(#callgraph{digraph = Digraph,
@@ -614,20 +644,20 @@ cleanup(#callgraph{digraph = Digraph,
#callgraph{digraph = Digraph,
name_map = NameMap,
rev_name_map = RevNameMap,
- race_data_server = race_data_server_call(dup, RaceDataServer)}.
+ race_data_server = dialyzer_race_data_server:duplicate(RaceDataServer)}.
-spec duplicate(callgraph()) -> callgraph().
duplicate(#callgraph{race_data_server = RaceDataServer} = Callgraph) ->
Callgraph#callgraph{
- race_data_server = race_data_server_call(dup, RaceDataServer)}.
+ race_data_server = dialyzer_race_data_server:duplicate(RaceDataServer)}.
-spec dispose_race_server(callgraph()) -> ok.
dispose_race_server(#callgraph{race_data_server = RaceDataServer}) ->
- race_data_server_cast(stop, RaceDataServer).
+ dialyzer_race_data_server:stop(RaceDataServer).
--spec get_digraph(callgraph()) -> digraph().
+-spec get_digraph(callgraph()) -> digraph:graph().
get_digraph(#callgraph{digraph = Digraph}) ->
Digraph.
@@ -635,17 +665,17 @@ get_digraph(#callgraph{digraph = Digraph}) ->
-spec get_named_tables(callgraph()) -> [string()].
get_named_tables(#callgraph{race_data_server = RaceDataServer}) ->
- race_data_server_call(get_named_tables, RaceDataServer).
+ dialyzer_race_data_server:call(get_named_tables, RaceDataServer).
-spec get_public_tables(callgraph()) -> [label()].
get_public_tables(#callgraph{race_data_server = RaceDataServer}) ->
- race_data_server_call(get_public_tables, RaceDataServer).
+ dialyzer_race_data_server:call(get_public_tables, RaceDataServer).
--spec get_race_code(callgraph()) -> dict().
+-spec get_race_code(callgraph()) -> dict:dict().
get_race_code(#callgraph{race_data_server = RaceDataServer}) ->
- race_data_server_call(get_race_code, RaceDataServer).
+ dialyzer_race_data_server:call(get_race_code, RaceDataServer).
-spec get_race_detection(callgraph()) -> boolean().
@@ -655,23 +685,23 @@ get_race_detection(#callgraph{race_detection = RD}) ->
-spec get_behaviour_api_calls(callgraph()) -> [{mfa(), mfa()}].
get_behaviour_api_calls(#callgraph{race_data_server = RaceDataServer}) ->
- race_data_server_call(get_behaviour_api_calls, RaceDataServer).
+ dialyzer_race_data_server:call(get_behaviour_api_calls, RaceDataServer).
-spec race_code_new(callgraph()) -> callgraph().
race_code_new(#callgraph{race_data_server = RaceDataServer} = CG) ->
- ok = race_data_server_cast(race_code_new, RaceDataServer),
+ ok = dialyzer_race_data_server:cast(race_code_new, RaceDataServer),
CG.
--spec put_digraph(digraph(), callgraph()) -> callgraph().
+-spec put_digraph(digraph:graph(), callgraph()) -> callgraph().
put_digraph(Digraph, Callgraph) ->
Callgraph#callgraph{digraph = Digraph}.
--spec put_race_code(dict(), callgraph()) -> callgraph().
+-spec put_race_code(dict:dict(), callgraph()) -> callgraph().
put_race_code(RaceCode, #callgraph{race_data_server = RaceDataServer} = CG) ->
- ok = race_data_server_cast({put_race_code, RaceCode}, RaceDataServer),
+ ok = dialyzer_race_data_server:cast({put_race_code, RaceCode}, RaceDataServer),
CG.
-spec put_race_detection(boolean(), callgraph()) -> callgraph().
@@ -683,78 +713,23 @@ put_race_detection(RaceDetection, Callgraph) ->
put_named_tables(NamedTables,
#callgraph{race_data_server = RaceDataServer} = CG) ->
- ok = race_data_server_cast({put_named_tables, NamedTables}, RaceDataServer),
+ ok = dialyzer_race_data_server:cast({put_named_tables, NamedTables}, RaceDataServer),
CG.
-spec put_public_tables([label()], callgraph()) -> callgraph().
put_public_tables(PublicTables,
#callgraph{race_data_server = RaceDataServer} = CG) ->
- ok = race_data_server_cast({put_public_tables, PublicTables}, RaceDataServer),
+ ok = dialyzer_race_data_server:cast({put_public_tables, PublicTables}, RaceDataServer),
CG.
-spec put_behaviour_api_calls([{mfa(), mfa()}], callgraph()) -> callgraph().
put_behaviour_api_calls(Calls,
#callgraph{race_data_server = RaceDataServer} = CG) ->
- ok = race_data_server_cast({put_behaviour_api_calls, Calls}, RaceDataServer),
+ ok = dialyzer_race_data_server:cast({put_behaviour_api_calls, Calls}, RaceDataServer),
CG.
-
-new_race_data_server() ->
- spawn_link(fun() -> race_data_server_loop(#race_data_state{}) end).
-
-race_data_server_loop(State) ->
- receive
- {call, From, Ref, Query} ->
- Reply = race_data_server_handle_call(Query, State),
- From ! {Ref, Reply},
- race_data_server_loop(State);
- {cast, stop} ->
- ok;
- {cast, Message} ->
- NewState = race_data_server_handle_cast(Message, State),
- race_data_server_loop(NewState)
- end.
-
-race_data_server_call(Query, Server) ->
- Ref = make_ref(),
- Server ! {call, self(), Ref, Query},
- receive
- {Ref, Reply} -> Reply
- end.
-
-race_data_server_cast(Message, Server) ->
- Server ! {cast, Message},
- ok.
-
-race_data_server_handle_cast(race_code_new, State) ->
- State#race_data_state{race_code = dict:new()};
-race_data_server_handle_cast({Tag, Data}, State) ->
- case Tag of
- renew_race_info -> renew_race_info(Data, State);
- renew_race_code -> renew_race_code_handler(Data, State);
- renew_race_public_tables -> renew_race_public_tables_handler(Data, State);
- put_race_code -> State#race_data_state{race_code = Data};
- put_public_tables -> State#race_data_state{public_tables = Data};
- put_named_tables -> State#race_data_state{named_tables = Data};
- put_behaviour_api_calls -> State#race_data_state{beh_api_calls = Data}
- end.
-
-race_data_server_handle_call(Query,
- #race_data_state{race_code = RaceCode,
- public_tables = PublicTables,
- named_tables = NamedTables,
- beh_api_calls = BehApiCalls}
- = State) ->
- case Query of
- dup -> spawn_link(fun() -> race_data_server_loop(State) end);
- get_race_code -> RaceCode;
- get_public_tables -> PublicTables;
- get_named_tables -> NamedTables;
- get_behaviour_api_calls -> BehApiCalls
- end.
-
%%=============================================================================
%% Utilities for 'dot'
%%=============================================================================
diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl
index 365c0b36d4..fc56693ea3 100644
--- a/lib/dialyzer/src/dialyzer_cl.erl
+++ b/lib/dialyzer/src/dialyzer_cl.erl
@@ -2,18 +2,19 @@
%%-------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -35,12 +36,12 @@
-include_lib("kernel/include/file.hrl"). % needed for #file_info{}
-record(cl_state,
- {backend_pid :: pid(),
+ {backend_pid :: pid() | 'undefined',
erlang_mode = false :: boolean(),
external_calls = [] :: [mfa()],
external_types = [] :: [mfa()],
legal_warnings = ordsets:new() :: [dial_warn_tag()],
- mod_deps = dict:new() :: dict(),
+ mod_deps = dict:new() :: dialyzer_callgraph:mod_deps(),
output = standard_io :: io:device(),
output_format = formatted :: format(),
filename_opt = basename :: fopt(),
@@ -48,8 +49,7 @@
plt_info = none :: 'none' | dialyzer_plt:plt_info(),
report_mode = normal :: rep_mode(),
return_status= ?RET_NOTHING_SUSPICIOUS :: dial_ret(),
- stored_warnings = [] :: [dial_warning()],
- unknown_behaviours = [] :: [dialyzer_behaviours:behaviour()]
+ stored_warnings = [] :: [raw_warning()]
}).
%%--------------------------------------------------------------------
@@ -469,7 +469,7 @@ expand_dependent_modules(Md5, DiffMd5, ModDeps) ->
Mod = list_to_atom(filename:basename(File, ".beam")),
sets:is_element(Mod, AnalyzeMods)
end,
- {[F || {F, _} <- Md5, FilterFun(F)], RemovedMods, NewModDeps}.
+ {[F || {F, _} <- Md5, FilterFun(F)], BigSet, NewModDeps}.
expand_dependent_modules_1([Mod|Mods], Included, ModDeps) ->
case dict:find(Mod, ModDeps) of
@@ -504,7 +504,7 @@ hipe_compile(Files, #options{erlang_mode = ErlangMode} = Options) ->
_ ->
Mods = [lists, dict, digraph, digraph_utils, ets,
gb_sets, gb_trees, ordsets, sets, sofs,
- cerl, cerl_trees, erl_types, erl_bif_types,
+ cerl, erl_types, cerl_trees, erl_bif_types,
dialyzer_analysis_callgraph, dialyzer, dialyzer_behaviours,
dialyzer_codeserver, dialyzer_contracts,
dialyzer_coordinator, dialyzer_dataflow, dialyzer_dep,
@@ -512,32 +512,82 @@ hipe_compile(Files, #options{erlang_mode = ErlangMode} = Options) ->
dialyzer_worker],
report_native_comp(Options),
{T1, _} = statistics(wall_clock),
- native_compile(Mods),
+ Cache = (get(dialyzer_options_native_cache) =/= false),
+ native_compile(Mods, Cache),
{T2, _} = statistics(wall_clock),
report_elapsed_time(T1, T2, Options)
end
end.
-native_compile(Mods) ->
+native_compile(Mods, Cache) ->
case dialyzer_utils:parallelism() > ?MIN_PARALLELISM of
true ->
Parent = self(),
- Pids = [spawn(fun () -> Parent ! {self(), hc(M)} end) || M <- Mods],
+ Pids = [spawn(fun () -> Parent ! {self(), hc(M, Cache)} end) || M <- Mods],
lists:foreach(fun (Pid) -> receive {Pid, Res} -> Res end end, Pids);
false ->
- lists:foreach(fun (Mod) -> hc(Mod) end, Mods)
+ lists:foreach(fun (Mod) -> hc(Mod, Cache) end, Mods)
end.
-hc(Mod) ->
+hc(Mod, Cache) ->
{module, Mod} = code:ensure_loaded(Mod),
case code:is_module_native(Mod) of
true -> ok;
false ->
- %% io:format(" ~s", [Mod]),
- {ok, Mod} = hipe:c(Mod),
- ok
+ %% io:format(" ~w", [Mod]),
+ case Cache of
+ false ->
+ {ok, Mod} = hipe:c(Mod),
+ ok;
+ true ->
+ hc_cache(Mod)
+ end
end.
+hc_cache(Mod) ->
+ CacheBase = cache_base_dir(),
+ %% Use HiPE architecture, version and erts checksum in directory name,
+ %% to avoid clashes between incompatible binaries.
+ HipeArchVersion =
+ lists:concat(
+ [erlang:system_info(hipe_architecture), "-",
+ hipe:version(), "-",
+ hipe:erts_checksum()]),
+ CacheDir = filename:join(CacheBase, HipeArchVersion),
+ OrigBeamFile = code:which(Mod),
+ {ok, {Mod, <<Checksum:128>>}} = beam_lib:md5(OrigBeamFile),
+ CachedBeamFile = filename:join(CacheDir, lists:concat([Mod, "-", Checksum, ".beam"])),
+ ok = filelib:ensure_dir(CachedBeamFile),
+ ModBin =
+ case filelib:is_file(CachedBeamFile) of
+ true ->
+ {ok, BinFromFile} = file:read_file(CachedBeamFile),
+ BinFromFile;
+ false ->
+ {ok, Mod, CompiledBin} = compile:file(OrigBeamFile, [from_beam, native, binary]),
+ ok = file:write_file(CachedBeamFile, CompiledBin),
+ CompiledBin
+ end,
+ code:unstick_dir(filename:dirname(OrigBeamFile)),
+ {module, Mod} = code:load_binary(Mod, CachedBeamFile, ModBin),
+ true = code:is_module_native(Mod),
+ ok.
+
+cache_base_dir() ->
+ %% http://standards.freedesktop.org/basedir-spec/basedir-spec-0.7.html
+ %% If XDG_CACHE_HOME is set to an absolute path, use it as base.
+ XdgCacheHome = os:getenv("XDG_CACHE_HOME"),
+ CacheHome =
+ case is_list(XdgCacheHome) andalso filename:pathtype(XdgCacheHome) =:= absolute of
+ true ->
+ XdgCacheHome;
+ false ->
+ %% Otherwise, the default is $HOME/.cache.
+ {ok, [[Home]]} = init:get_argument(home),
+ filename:join(Home, ".cache")
+ end,
+ filename:join([CacheHome, "dialyzer_hipe_cache"]).
+
new_state() ->
#cl_state{}.
@@ -587,9 +637,6 @@ cl_loop(State, LogCache) ->
{BackendPid, warnings, Warnings} ->
NewState = store_warnings(State, Warnings),
cl_loop(NewState, LogCache);
- {BackendPid, unknown_behaviours, Behaviours} ->
- NewState = store_unknown_behaviours(State, Behaviours),
- cl_loop(NewState, LogCache);
{BackendPid, done, NewPlt, _NewDocPlt} ->
return_value(State, NewPlt);
{BackendPid, ext_calls, ExtCalls} ->
@@ -603,7 +650,7 @@ cl_loop(State, LogCache) ->
Msg = failed_anal_msg(Reason, LogCache),
cl_error(State, Msg);
{'EXIT', BackendPid, Reason} when Reason =/= 'normal' ->
- Msg = failed_anal_msg(io_lib:format("~P", [Reason, 12]), LogCache),
+ Msg = failed_anal_msg(io_lib:format("~p", [Reason]), LogCache),
cl_error(State, Msg);
_Other ->
%% io:format("Received ~p\n", [_Other]),
@@ -613,7 +660,7 @@ cl_loop(State, LogCache) ->
-spec failed_anal_msg(string(), [_]) -> nonempty_string().
failed_anal_msg(Reason, LogCache) ->
- Msg = "Analysis failed with error:\n" ++ Reason ++ "\n",
+ Msg = "Analysis failed with error:\n" ++ lists:flatten(Reason) ++ "\n",
case LogCache =:= [] of
true -> Msg;
false ->
@@ -627,20 +674,15 @@ format_log_cache(LogCache) ->
Str = lists:append(lists:reverse(LogCache)),
string:join(string:tokens(Str, "\n"), "\n ").
--spec store_warnings(#cl_state{}, [dial_warning()]) -> #cl_state{}.
+-spec store_warnings(#cl_state{}, [raw_warning()]) -> #cl_state{}.
store_warnings(#cl_state{stored_warnings = StoredWarnings} = St, Warnings) ->
St#cl_state{stored_warnings = StoredWarnings ++ Warnings}.
--spec store_unknown_behaviours(#cl_state{}, [dialyzer_behaviours:behaviour()]) -> #cl_state{}.
-
-store_unknown_behaviours(#cl_state{unknown_behaviours = Behs} = St, Beh) ->
- St#cl_state{unknown_behaviours = Beh ++ Behs}.
-
-spec cl_error(string()) -> no_return().
cl_error(Msg) ->
- throw({dialyzer_error, Msg}).
+ throw({dialyzer_error, lists:flatten(Msg)}).
-spec cl_error(#cl_state{}, string()) -> no_return().
@@ -650,7 +692,7 @@ cl_error(State, Msg) ->
Outfile -> io:format(Outfile, "\n~s\n", [Msg])
end,
maybe_close_output_file(State),
- throw({dialyzer_error, Msg}).
+ throw({dialyzer_error, lists:flatten(Msg)}).
return_value(State = #cl_state{erlang_mode = ErlangMode,
mod_deps = ModDeps,
@@ -662,8 +704,9 @@ return_value(State = #cl_state{erlang_mode = ErlangMode,
true -> ok;
false -> dialyzer_plt:to_file(OutputPlt, Plt, ModDeps, PltInfo)
end,
+ UnknownWarnings = unknown_warnings(State),
RetValue =
- case StoredWarnings =:= [] of
+ case StoredWarnings =:= [] andalso UnknownWarnings =:= [] of
true -> ?RET_NOTHING_SUSPICIOUS;
false -> ?RET_DISCREPANCIES
end,
@@ -672,13 +715,32 @@ return_value(State = #cl_state{erlang_mode = ErlangMode,
print_warnings(State),
print_ext_calls(State),
print_ext_types(State),
- print_unknown_behaviours(State),
maybe_close_output_file(State),
{RetValue, []};
true ->
- {RetValue, process_warnings(StoredWarnings)}
+ AllWarnings =
+ UnknownWarnings ++ process_warnings(StoredWarnings),
+ {RetValue, set_warning_id(AllWarnings)}
end.
+unknown_warnings(State = #cl_state{legal_warnings = LegalWarnings}) ->
+ Unknown = case ordsets:is_element(?WARN_UNKNOWN, LegalWarnings) of
+ true ->
+ unknown_functions(State) ++
+ unknown_types(State);
+ false -> []
+ end,
+ WarningInfo = {_Filename = "", _Line = 0, _MorMFA = ''},
+ [{?WARN_UNKNOWN, WarningInfo, W} || W <- Unknown].
+
+unknown_functions(#cl_state{external_calls = Calls}) ->
+ [{unknown_function, MFA} || MFA <- Calls].
+
+set_warning_id(Warnings) ->
+ lists:map(fun({Tag, {File, Line, _MorMFA}, Msg}) ->
+ {Tag, {File, Line}, Msg}
+ end, Warnings).
+
print_ext_calls(#cl_state{report_mode = quiet}) ->
ok;
print_ext_calls(#cl_state{output = Output,
@@ -708,6 +770,9 @@ do_print_ext_calls(Output, [{M,F,A}|T], Before) ->
do_print_ext_calls(_, [], _) ->
ok.
+unknown_types(#cl_state{external_types = Types}) ->
+ [{unknown_type, MFA} || MFA <- Types].
+
print_ext_types(#cl_state{report_mode = quiet}) ->
ok;
print_ext_types(#cl_state{output = Output,
@@ -738,39 +803,6 @@ do_print_ext_types(Output, [{M,F,A}|T], Before) ->
do_print_ext_types(_, [], _) ->
ok.
-%%print_unknown_behaviours(#cl_state{report_mode = quiet}) ->
-%% ok;
-print_unknown_behaviours(#cl_state{output = Output,
- external_calls = Calls,
- external_types = Types,
- stored_warnings = Warnings,
- unknown_behaviours = DupBehaviours,
- legal_warnings = LegalWarnings,
- output_format = Format}) ->
- case ordsets:is_element(?WARN_BEHAVIOUR, LegalWarnings)
- andalso DupBehaviours =/= [] of
- false -> ok;
- true ->
- Behaviours = lists:usort(DupBehaviours),
- case Warnings =:= [] andalso Calls =:= [] andalso Types =:= [] of
- true -> io:nl(Output); %% Need to do a newline first
- false -> ok
- end,
- {Prompt, Prefix} =
- case Format of
- formatted -> {"Unknown behaviours:\n"," "};
- raw -> {"%% Unknown behaviours:\n","%% "}
- end,
- io:put_chars(Output, Prompt),
- do_print_unknown_behaviours(Output, Behaviours, Prefix)
- end.
-
-do_print_unknown_behaviours(Output, [B|T], Before) ->
- io:format(Output, "~s~p\n", [Before,B]),
- do_print_unknown_behaviours(Output, T, Before);
-do_print_unknown_behaviours(_, [], _) ->
- ok.
-
print_warnings(#cl_state{stored_warnings = []}) ->
ok;
print_warnings(#cl_state{output = Output,
@@ -785,15 +817,16 @@ print_warnings(#cl_state{output = Output,
formatted ->
[dialyzer:format_warning(W, FOpt) || W <- PrWarnings];
raw ->
- [io_lib:format("~p. \n", [W]) || W <- PrWarnings]
+ [io_lib:format("~p. \n",
+ [W]) || W <- set_warning_id(PrWarnings)]
end,
io:format(Output, "\n~s", [S])
end.
--spec process_warnings([dial_warning()]) -> [dial_warning()].
+-spec process_warnings([raw_warning()]) -> [raw_warning()].
process_warnings(Warnings) ->
- Warnings1 = lists:keysort(2, Warnings), %% Sort on file/line
+ Warnings1 = lists:keysort(2, Warnings), %% Sort on file/line (and m/mfa..)
remove_duplicate_warnings(Warnings1, []).
remove_duplicate_warnings([Duplicate, Duplicate|Left], Acc) ->
diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl
index 2ea3d3af5a..934351aeeb 100644
--- a/lib/dialyzer/src/dialyzer_cl_parse.erl
+++ b/lib/dialyzer/src/dialyzer_cl_parse.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -30,7 +31,7 @@
-type dial_cl_parse_ret() :: {'check_init', #options{}}
| {'plt_info', #options{}}
| {'cl', #options{}}
- | {{'gui', 'gs' | 'wx'}, #options{}}
+ | {'gui', #options{}}
| {'error', string()}.
-type deep_string() :: string() | [deep_string()].
@@ -75,6 +76,9 @@ cl(["-nn"|T]) ->
cl(["--no_native"|T]) ->
put(dialyzer_options_native, false),
cl(T);
+cl(["--no_native_cache"|T]) ->
+ put(dialyzer_options_native_cache, false),
+ cl(T);
cl(["--plt_info"|T]) ->
put(dialyzer_options_analysis_type, plt_info),
cl(T);
@@ -193,12 +197,9 @@ cl(["--dump_callgraph", File|T]) ->
put(dialyzer_callgraph_file, File),
cl(T);
cl(["--gui"|T]) ->
- put(dialyzer_options_mode, {gui, gs}),
- cl(T);
-cl(["--wx"|T]) ->
- put(dialyzer_options_mode, {gui, wx}),
+ put(dialyzer_options_mode, gui),
cl(T);
-cl(["--solver",Solver|T]) -> % not documented
+cl(["--solver", Solver|T]) -> % not documented
append_var(dialyzer_solvers, [list_to_atom(Solver)]),
cl(T);
cl([H|_] = L) ->
@@ -217,7 +218,7 @@ cl([]) ->
{plt_info, cl_options()};
false ->
case get(dialyzer_options_mode) of
- {gui, _} = GUI -> {GUI, common_options()};
+ gui -> {gui, common_options()};
cl ->
case get(dialyzer_options_analysis_type) =:= plt_check of
true -> {check_init, cl_options()};
@@ -360,12 +361,13 @@ help_warnings() ->
help_message() ->
S = "Usage: dialyzer [--help] [--version] [--shell] [--quiet] [--verbose]
[-pa dir]* [--plt plt] [--plts plt*] [-Ddefine]*
- [-I include_dir]* [--output_plt file] [-Wwarn]*
- [--src] [--gui | --wx] [files_or_dirs] [-r dirs]
+ [-I include_dir]* [--output_plt file] [-Wwarn]* [--raw]
+ [--src] [--gui] [files_or_dirs] [-r dirs]
[--apps applications] [-o outfile]
[--build_plt] [--add_to_plt] [--remove_from_plt]
[--check_plt] [--no_check_plt] [--plt_info] [--get_warnings]
- [--no_native] [--fullpath] [--statistics]
+ [--dump_callgraph file] [--no_native] [--fullpath]
+ [--statistics] [--no_native_cache]
Options:
files_or_dirs (for backwards compatibility also as: -c files_or_dirs)
Use Dialyzer from the command line to detect defects in the
@@ -470,12 +472,15 @@ Options:
Bypass the native code compilation of some key files that Dialyzer
heuristically performs when dialyzing many files; this avoids the
compilation time but it may result in (much) longer analysis time.
+ --no_native_cache
+ By default, Dialyzer caches the results of native compilation in the
+ $XDG_CACHE_HOME/erlang/dialyzer_hipe_cache directory.
+ XDG_CACHE_HOME defaults to $HOME/.cache. Use this option to disable
+ caching.
--fullpath
Display the full path names of files for which warnings are emitted.
--gui
- Use the gs-based GUI.
- --wx
- Use the wx-based GUI.
+ Use the GUI.
Note:
* denotes that multiple occurrences of these options are possible.
@@ -500,17 +505,21 @@ warning_options_msg() ->
Suppress warnings for unused functions.
-Wno_improper_lists
Suppress warnings for construction of improper lists.
- -Wno_tuple_as_fun
- Suppress warnings for using tuples instead of funs.
-Wno_fun_app
Suppress warnings for fun applications that will fail.
-Wno_match
Suppress warnings for patterns that are unused or cannot match.
-Wno_opaque
Suppress warnings for violations of opaqueness of data types.
+ -Wno_fail_call
+ Suppress warnings for failing calls.
+ -Wno_contracts
+ Suppress warnings about invalid contracts.
-Wno_behaviours
Suppress warnings about behaviour callbacks which drift from the published
recommended interfaces.
+ -Wno_missing_calls
+ Suppress warnings about calls to missing functions.
-Wno_undefined_callbacks
Suppress warnings about behaviours that have no -callback attributes for
their callbacks.
@@ -524,6 +533,13 @@ warning_options_msg() ->
-Wunderspecs ***
Warn about underspecified functions
(those whose -spec is strictly more allowing than the success typing).
+ -Wunknown ***
+ Let warnings about unknown functions and types affect the
+ exit status of the command line version. The default is to ignore
+ warnings about unknown functions and types when setting the exit
+ status. When using the Dialyzer from Erlang, warnings about unknown
+ functions and types are returned; the default is not to return
+ such warnings.
The following options are also available but their use is not recommended:
(they are mostly for Dialyzer developers and internal debugging)
diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl
index 216e06219c..03cd9671af 100644
--- a/lib/dialyzer/src/dialyzer_codeserver.erl
+++ b/lib/dialyzer/src/dialyzer_codeserver.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -43,19 +44,21 @@
insert/3,
insert_exports/2,
insert_temp_exported_types/2,
+ insert_fun_meta_info/2,
is_exported/2,
lookup_mod_code/2,
lookup_mfa_code/2,
lookup_mod_records/2,
lookup_mod_contracts/2,
lookup_mfa_contract/2,
+ lookup_meta_info/2,
new/0,
set_next_core_label/2,
set_temp_records/2,
store_temp_records/3,
store_temp_contracts/4]).
--export_type([codeserver/0]).
+-export_type([codeserver/0, fun_meta_info/0]).
-include("dialyzer.hrl").
@@ -64,12 +67,25 @@
-type dict_ets() :: ets:tid().
-type set_ets() :: ets:tid().
+-type types() :: erl_types:type_table().
+-type mod_records() :: dict:dict(module(), types()).
+
+-type contracts() :: dict:dict(mfa(),dialyzer_contracts:file_contract()).
+-type mod_contracts() :: dict:dict(module(), contracts()).
+
+%% A property-list of data compiled from -compile and -dialyzer attributes.
+-type meta_info() :: [{{'nowarn_function' | dial_warn_tag()},
+ 'mod' | 'func'}].
+-type fun_meta_info() :: [{mfa(), meta_info()}
+ | {module(), [dial_warn_tag()]}].
+
-record(codeserver, {next_core_label = 0 :: label(),
code :: dict_ets(),
- exported_types :: set_ets(), % set(mfa())
- records :: dict_ets(),
- contracts :: dict_ets(),
- callbacks :: dict_ets(),
+ exported_types :: set_ets() | 'undefined', % set(mfa())
+ records :: dict_ets() | 'undefined',
+ contracts :: dict_ets() | 'undefined',
+ callbacks :: dict_ets() | 'undefined',
+ fun_meta_info :: dict_ets(), % {mfa(), meta_info()}
exports :: 'clean' | set_ets(), % set(mfa())
temp_exported_types :: 'clean' | set_ets(), % set(mfa())
temp_records :: 'clean' | dict_ets(),
@@ -123,14 +139,17 @@ new() ->
CodeOptions = [compressed, public, {read_concurrency, true}],
Code = ets:new(dialyzer_codeserver_code, CodeOptions),
TempOptions = [public, {write_concurrency, true}],
- [Exports, TempExportedTypes, TempRecords, TempContracts, TempCallbacks] =
+ [Exports, FunMetaInfo, TempExportedTypes, TempRecords, TempContracts,
+ TempCallbacks] =
[ets:new(Name, TempOptions) ||
Name <-
- [dialyzer_codeserver_exports, dialyzer_codeserver_temp_exported_types,
+ [dialyzer_codeserver_exports, dialyzer_codeserver_fun_meta_info,
+ dialyzer_codeserver_temp_exported_types,
dialyzer_codeserver_temp_records, dialyzer_codeserver_temp_contracts,
dialyzer_codeserver_temp_callbacks]],
#codeserver{code = Code,
exports = Exports,
+ fun_meta_info = FunMetaInfo,
temp_exported_types = TempExportedTypes,
temp_records = TempRecords,
temp_contracts = TempContracts,
@@ -160,12 +179,12 @@ insert(Mod, ModCode, CS) ->
true = ets:insert(CS#codeserver.code, [ModEntry|Funs]),
CS.
--spec get_temp_exported_types(codeserver()) -> set().
+-spec get_temp_exported_types(codeserver()) -> sets:set(mfa()).
get_temp_exported_types(#codeserver{temp_exported_types = TempExpTypes}) ->
ets_set_to_set(TempExpTypes).
--spec insert_temp_exported_types(set(), codeserver()) -> codeserver().
+-spec insert_temp_exported_types(sets:set(mfa()), codeserver()) -> codeserver().
insert_temp_exported_types(Set, CS) ->
TempExportedTypes = CS#codeserver.temp_exported_types,
@@ -178,22 +197,28 @@ insert_exports(List, #codeserver{exports = Exports} = CS) ->
true = ets_set_insert_list(List, Exports),
CS.
+-spec insert_fun_meta_info(fun_meta_info(), codeserver()) -> codeserver().
+
+insert_fun_meta_info(List, #codeserver{fun_meta_info = FunMetaInfo} = CS) ->
+ true = ets:insert(FunMetaInfo, List),
+ CS.
+
-spec is_exported(mfa(), codeserver()) -> boolean().
is_exported(MFA, #codeserver{exports = Exports}) ->
ets_set_is_element(MFA, Exports).
--spec get_exported_types(codeserver()) -> set(). % set(mfa())
+-spec get_exported_types(codeserver()) -> sets:set(mfa()).
get_exported_types(#codeserver{exported_types = ExpTypes}) ->
ets_set_to_set(ExpTypes).
--spec get_exports(codeserver()) -> set(). % set(mfa())
+-spec get_exports(codeserver()) -> sets:set(mfa()).
get_exports(#codeserver{exports = Exports}) ->
ets_set_to_set(Exports).
--spec finalize_exported_types(set(), codeserver()) -> codeserver().
+-spec finalize_exported_types(sets:set(mfa()), codeserver()) -> codeserver().
finalize_exported_types(Set, CS) ->
ExportedTypes = ets_read_concurrent_table(dialyzer_codeserver_exported_types),
@@ -222,7 +247,7 @@ get_next_core_label(#codeserver{next_core_label = NCL}) ->
set_next_core_label(NCL, CS) ->
CS#codeserver{next_core_label = NCL}.
--spec lookup_mod_records(atom(), codeserver()) -> dict().
+-spec lookup_mod_records(atom(), codeserver()) -> types().
lookup_mod_records(Mod, #codeserver{records = RecDict}) when is_atom(Mod) ->
case ets_dict_find(Mod, RecDict) of
@@ -230,12 +255,12 @@ lookup_mod_records(Mod, #codeserver{records = RecDict}) when is_atom(Mod) ->
{ok, Dict} -> Dict
end.
--spec get_records(codeserver()) -> dict().
+-spec get_records(codeserver()) -> mod_records().
get_records(#codeserver{records = RecDict}) ->
ets_dict_to_dict(RecDict).
--spec store_temp_records(atom(), dict(), codeserver()) -> codeserver().
+-spec store_temp_records(module(), types(), codeserver()) -> codeserver().
store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS)
when is_atom(Mod) ->
@@ -244,12 +269,12 @@ store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS)
false -> CS#codeserver{temp_records = ets_dict_store(Mod, Dict, TempRecDict)}
end.
--spec get_temp_records(codeserver()) -> dict().
+-spec get_temp_records(codeserver()) -> mod_records().
get_temp_records(#codeserver{temp_records = TempRecDict}) ->
ets_dict_to_dict(TempRecDict).
--spec set_temp_records(dict(), codeserver()) -> codeserver().
+-spec set_temp_records(mod_records(), codeserver()) -> codeserver().
set_temp_records(Dict, CS) ->
true = ets:delete(CS#codeserver.temp_records),
@@ -257,7 +282,7 @@ set_temp_records(Dict, CS) ->
true = ets_dict_store_dict(Dict, TempRecords),
CS#codeserver{temp_records = TempRecords}.
--spec finalize_records(dict(), codeserver()) -> codeserver().
+-spec finalize_records(mod_records(), codeserver()) -> codeserver().
finalize_records(Dict, CS) ->
true = ets:delete(CS#codeserver.temp_records),
@@ -265,17 +290,17 @@ finalize_records(Dict, CS) ->
true = ets_dict_store_dict(Dict, Records),
CS#codeserver{records = Records, temp_records = clean}.
--spec lookup_mod_contracts(atom(), codeserver()) -> dict().
+-spec lookup_mod_contracts(atom(), codeserver()) -> contracts().
lookup_mod_contracts(Mod, #codeserver{contracts = ContDict})
when is_atom(Mod) ->
case ets_dict_find(Mod, ContDict) of
error -> dict:new();
{ok, Keys} ->
- dict:from_list([get_contract_pair(Key, ContDict)|| Key <- Keys])
+ dict:from_list([get_file_contract(Key, ContDict)|| Key <- Keys])
end.
-get_contract_pair(Key, ContDict) ->
+get_file_contract(Key, ContDict) ->
{Key, ets:lookup_element(ContDict, Key, 2)}.
-spec lookup_mfa_contract(mfa(), codeserver()) ->
@@ -284,7 +309,15 @@ get_contract_pair(Key, ContDict) ->
lookup_mfa_contract(MFA, #codeserver{contracts = ContDict}) ->
ets_dict_find(MFA, ContDict).
--spec get_contracts(codeserver()) -> dict().
+-spec lookup_meta_info(module() | mfa(), codeserver()) -> meta_info().
+
+lookup_meta_info(MorMFA, #codeserver{fun_meta_info = FunMetaInfo}) ->
+ case ets_dict_find(MorMFA, FunMetaInfo) of
+ error -> [];
+ {ok, PropList} -> PropList
+ end.
+
+-spec get_contracts(codeserver()) -> mod_contracts().
get_contracts(#codeserver{contracts = ContDict}) ->
ets_dict_to_dict(ContDict).
@@ -294,7 +327,7 @@ get_contracts(#codeserver{contracts = ContDict}) ->
get_callbacks(#codeserver{callbacks = CallbDict}) ->
ets:tab2list(CallbDict).
--spec store_temp_contracts(atom(), dict(), dict(), codeserver()) ->
+-spec store_temp_contracts(module(), contracts(), contracts(), codeserver()) ->
codeserver().
store_temp_contracts(Mod, SpecDict, CallbackDict,
@@ -313,13 +346,14 @@ store_temp_contracts(Mod, SpecDict, CallbackDict,
CS1#codeserver{temp_callbacks = ets_dict_store(Mod, CallbackDict, Cb)}
end.
--spec get_temp_contracts(codeserver()) -> {dict(), dict()}.
+-spec get_temp_contracts(codeserver()) -> {mod_contracts(), mod_contracts()}.
get_temp_contracts(#codeserver{temp_contracts = TempContDict,
temp_callbacks = TempCallDict}) ->
{ets_dict_to_dict(TempContDict), ets_dict_to_dict(TempCallDict)}.
--spec finalize_contracts(dict(), dict(), codeserver()) -> codeserver().
+-spec finalize_contracts(mod_contracts(), mod_contracts(), codeserver()) ->
+ codeserver().
finalize_contracts(SpecDict, CallbackDict, CS) ->
Contracts = ets_read_concurrent_table(dialyzer_codeserver_contracts),
diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl
index 332a326b0d..976a2b8955 100644
--- a/lib/dialyzer/src/dialyzer_contracts.erl
+++ b/lib/dialyzer/src/dialyzer_contracts.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -21,10 +22,10 @@
-module(dialyzer_contracts).
-export([check_contract/2,
- check_contracts/3,
+ check_contracts/4,
contracts_without_fun/3,
contract_to_string/1,
- get_invalid_contract_warnings/3,
+ get_invalid_contract_warnings/4,
get_contract_args/1,
get_contract_return/1,
get_contract_return/2,
@@ -43,16 +44,18 @@
%% Types used in other parts of the system below
%%-----------------------------------------------------------------------
--type file_contract() :: {file_line(), #contract{}}.
+-type file_contract() :: {file_line(), #contract{}, Extra :: [_]}.
--type plt_contracts() :: [{mfa(), #contract{}}]. % actually, an orddict()
+-type plt_contracts() :: orddict:orddict(mfa(), #contract{}).
%%-----------------------------------------------------------------------
%% Internal record for contracts whose components have not been processed
%% to expand records and/or remote types that they might contain.
%%-----------------------------------------------------------------------
--type tmp_contract_fun() :: fun((set(), dict()) -> contract_pair()).
+-type cache() :: ets:tid().
+-type tmp_contract_fun() ::
+ fun((sets:set(mfa()), types(), cache()) -> contract_pair()).
-record(tmp_contract, {contract_funs = [] :: [tmp_contract_fun()],
forms = [] :: [{_, _}]}).
@@ -125,13 +128,19 @@ butlast([H|T]) -> [H|butlast(T)].
constraints_to_string([]) ->
"";
-constraints_to_string([{type, _, constraint, [{atom, _, What}, Types]}]) ->
- atom_to_list(What) ++ "(" ++
- sequence([erl_types:t_form_to_string(T) || T <- Types], ",") ++ ")";
constraints_to_string([{type, _, constraint, [{atom, _, What}, Types]}|Rest]) ->
- atom_to_list(What) ++ "(" ++
- sequence([erl_types:t_form_to_string(T) || T <- Types], ",")
- ++ "), " ++ constraints_to_string(Rest).
+ S = constraint_to_string(What, Types),
+ case Rest of
+ [] -> S;
+ _ -> S ++ ", " ++ constraints_to_string(Rest)
+ end.
+
+constraint_to_string(is_subtype, [{var, _, Var}, T]) ->
+ atom_to_list(Var) ++ " :: " ++ erl_types:t_form_to_string(T);
+constraint_to_string(What, Types) ->
+ atom_to_list(What) ++ "("
+ ++ sequence([erl_types:t_form_to_string(T) || T <- Types], ",")
+ ++ ")".
sequence([], _Delimiter) -> "";
sequence([H], _Delimiter) -> H;
@@ -146,31 +155,48 @@ process_contract_remote_types(CodeServer) ->
ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer),
RecordDict = dialyzer_codeserver:get_records(CodeServer),
ContractFun =
- fun({_M, _F, _A}, {File, #tmp_contract{contract_funs = CFuns, forms = Forms}}) ->
- NewCs = [CFun(ExpTypes, RecordDict) || CFun <- CFuns],
- Args = general_domain(NewCs),
- {File, #contract{contracts = NewCs, args = Args, forms = Forms}}
+ fun({{_M, _F, _A}=MFA, {File, TmpContract, Xtra}}, C0) ->
+ #tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract,
+ {NewCs, C2} = lists:mapfoldl(fun(CFun, C1) ->
+ CFun(ExpTypes, RecordDict, C1)
+ end, C0, CFuns),
+ Args = general_domain(NewCs),
+ Contract = #contract{contracts = NewCs, args = Args, forms = Forms},
+ {{MFA, {File, Contract, Xtra}}, C2}
end,
ModuleFun =
- fun(_ModuleName, ContractDict) ->
- dict:map(ContractFun, ContractDict)
+ fun({ModuleName, ContractDict}, C3) ->
+ {NewContractList, C4} =
+ lists:mapfoldl(ContractFun, C3, dict:to_list(ContractDict)),
+ {{ModuleName, dict:from_list(NewContractList)}, C4}
end,
- NewContractDict = dict:map(ModuleFun, TmpContractDict),
- NewCallbackDict = dict:map(ModuleFun, TmpCallbackDict),
+ Cache = erl_types:cache__new(),
+ {NewContractList, C5} =
+ lists:mapfoldl(ModuleFun, Cache, dict:to_list(TmpContractDict)),
+ {NewCallbackList, _C6} =
+ lists:mapfoldl(ModuleFun, C5, dict:to_list(TmpCallbackDict)),
+ NewContractDict = dict:from_list(NewContractList),
+ NewCallbackDict = dict:from_list(NewCallbackList),
dialyzer_codeserver:finalize_contracts(NewContractDict, NewCallbackDict,
- CodeServer).
+ CodeServer).
+
+-type opaques_fun() :: fun((module()) -> [erl_types:erl_type()]).
+
+-type fun_types() :: dict:dict(label(), erl_types:type_table()).
-spec check_contracts([{mfa(), file_contract()}],
- dialyzer_callgraph:callgraph(), dict()) -> plt_contracts().
+ dialyzer_callgraph:callgraph(), fun_types(),
+ opaques_fun()) -> plt_contracts().
-check_contracts(Contracts, Callgraph, FunTypes) ->
+check_contracts(Contracts, Callgraph, FunTypes, FindOpaques) ->
FoldFun =
fun(Label, Type, NewContracts) ->
case dialyzer_callgraph:lookup_name(Label, Callgraph) of
{ok, {M,F,A} = MFA} ->
case orddict:find(MFA, Contracts) of
- {ok, {_FileLine, Contract}} ->
- case check_contract(Contract, Type) of
+ {ok, {_FileLine, Contract, _Xtra}} ->
+ Opaques = FindOpaques(M),
+ case check_contract(Contract, Type, Opaques) of
ok ->
case erl_bif_types:is_known(M, F, A) of
true ->
@@ -192,20 +218,23 @@ check_contracts(Contracts, Callgraph, FunTypes) ->
%% Checks all components of a contract
-spec check_contract(#contract{}, erl_types:erl_type()) -> 'ok' | {'error', term()}.
-check_contract(#contract{contracts = Contracts}, SuccType) ->
+check_contract(Contract, SuccType) ->
+ check_contract(Contract, SuccType, 'universe').
+
+check_contract(#contract{contracts = Contracts}, SuccType, Opaques) ->
try
- Contracts1 = [{Contract, insert_constraints(Constraints, dict:new())}
+ Contracts1 = [{Contract, insert_constraints(Constraints)}
|| {Contract, Constraints} <- Contracts],
- Contracts2 = [erl_types:t_subst(Contract, Dict)
- || {Contract, Dict} <- Contracts1],
+ Contracts2 = [erl_types:t_subst(Contract, Map)
+ || {Contract, Map} <- Contracts1],
GenDomains = [erl_types:t_fun_args(C) || C <- Contracts2],
case check_domains(GenDomains) of
error ->
{error, {overlapping_contract, []}};
ok ->
- InfList = [erl_types:t_inf(Contract, SuccType, opaque)
+ InfList = [erl_types:t_inf(Contract, SuccType, Opaques)
|| Contract <- Contracts2],
- case check_contract_inf_list(InfList, SuccType) of
+ case check_contract_inf_list(InfList, SuccType, Opaques) of
{error, _} = Invalid -> Invalid;
ok -> check_extraneous(Contracts2, SuccType)
end
@@ -217,7 +246,7 @@ check_contract(#contract{contracts = Contracts}, SuccType) ->
check_domains([_]) -> ok;
check_domains([Dom|Doms]) ->
Fun = fun(D) ->
- erl_types:any_none_or_unit(erl_types:t_inf_lists(Dom, D, opaque))
+ erl_types:any_none_or_unit(erl_types:t_inf_lists(Dom, D))
end,
case lists:all(Fun, Doms) of
true -> check_domains(Doms);
@@ -227,23 +256,23 @@ check_domains([Dom|Doms]) ->
%% Allow a contract if one of the overloaded contracts is possible.
%% We used to be more strict, e.g., all overloaded contracts had to be
%% possible.
-check_contract_inf_list([FunType|Left], SuccType) ->
+check_contract_inf_list([FunType|Left], SuccType, Opaques) ->
FunArgs = erl_types:t_fun_args(FunType),
case lists:any(fun erl_types:t_is_none_or_unit/1, FunArgs) of
- true -> check_contract_inf_list(Left, SuccType);
+ true -> check_contract_inf_list(Left, SuccType, Opaques);
false ->
STRange = erl_types:t_fun_range(SuccType),
case erl_types:t_is_none_or_unit(STRange) of
true -> ok;
false ->
Range = erl_types:t_fun_range(FunType),
- case erl_types:t_is_none(erl_types:t_inf(STRange, Range, opaque)) of
- true -> check_contract_inf_list(Left, SuccType);
+ case erl_types:t_is_none(erl_types:t_inf(STRange, Range)) of
+ true -> check_contract_inf_list(Left, SuccType, Opaques);
false -> ok
end
end
end;
-check_contract_inf_list([], _SuccType) ->
+check_contract_inf_list([], _SuccType, _Opaques) ->
{error, invalid_contract}.
check_extraneous([], _SuccType) -> ok;
@@ -259,32 +288,50 @@ check_extraneous_1(Contract, SuccType) ->
STRng = erl_types:t_fun_range(SuccType),
?debug("CR = ~p\nSR = ~p\n", [CRngs, STRng]),
case [CR || CR <- CRngs,
- erl_types:t_is_none(erl_types:t_inf(CR, STRng, opaque))] of
+ erl_types:t_is_none(erl_types:t_inf(CR, STRng))] of
[] ->
- CRngList = list_part(CRng),
- STRngList = list_part(STRng),
- case is_not_nil_list(CRngList) andalso is_not_nil_list(STRngList) of
- false -> ok;
- true ->
- CRngElements = erl_types:t_list_elements(CRngList),
- STRngElements = erl_types:t_list_elements(STRngList),
- Inf = erl_types:t_inf(CRngElements, STRngElements, opaque),
- case erl_types:t_is_none(Inf) of
- true -> {error, invalid_contract};
- false -> ok
- end
+ case bad_extraneous_list(CRng, STRng)
+ orelse bad_extraneous_map(CRng, STRng)
+ of
+ true -> {error, invalid_contract};
+ false -> ok
end;
CRs -> {error, {extra_range, erl_types:t_sup(CRs), STRng}}
end.
+bad_extraneous_list(CRng, STRng) ->
+ CRngList = list_part(CRng),
+ STRngList = list_part(STRng),
+ case is_not_nil_list(CRngList) andalso is_not_nil_list(STRngList) of
+ false -> false;
+ true ->
+ CRngElements = erl_types:t_list_elements(CRngList),
+ STRngElements = erl_types:t_list_elements(STRngList),
+ Inf = erl_types:t_inf(CRngElements, STRngElements),
+ erl_types:t_is_none(Inf)
+ end.
+
list_part(Type) ->
- erl_types:t_inf(erl_types:t_list(), Type, opaque).
+ erl_types:t_inf(erl_types:t_list(), Type).
is_not_nil_list(Type) ->
erl_types:t_is_list(Type) andalso not erl_types:t_is_nil(Type).
+bad_extraneous_map(CRng, STRng) ->
+ CRngMap = map_part(CRng),
+ STRngMap = map_part(STRng),
+ (not is_empty_map(CRngMap)) andalso (not is_empty_map(STRngMap))
+ andalso is_empty_map(erl_types:t_inf(CRngMap, STRngMap)).
+
+map_part(Type) ->
+ erl_types:t_inf(erl_types:t_map(), Type).
+
+is_empty_map(Type) ->
+ erl_types:t_is_equal(Type, erl_types:t_from_term(#{})).
+
%% This is the heart of the "range function"
--spec process_contracts([contract_pair()], [erl_types:erl_type()]) -> erl_types:erl_type().
+-spec process_contracts([contract_pair()], [erl_types:erl_type()]) ->
+ erl_types:erl_type().
process_contracts(OverContracts, Args) ->
process_contracts(OverContracts, Args, erl_types:t_none()).
@@ -299,7 +346,8 @@ process_contracts([OverContract|Left], Args, AccRange) ->
process_contracts([], _Args, AccRange) ->
AccRange.
--spec process_contract(contract_pair(), [erl_types:erl_type()]) -> 'error' | {'ok', erl_types:erl_type()}.
+-spec process_contract(contract_pair(), [erl_types:erl_type()]) ->
+ 'error' | {'ok', erl_types:erl_type()}.
process_contract({Contract, Constraints}, CallTypes0) ->
CallTypesFun = erl_types:t_fun(CallTypes0, erl_types:t_any()),
@@ -309,15 +357,15 @@ process_contract({Contract, Constraints}, CallTypes0) ->
[erl_types:t_to_string(ContArgsFun),
erl_types:t_to_string(CallTypesFun)]),
case solve_constraints(ContArgsFun, CallTypesFun, Constraints) of
- {ok, VarDict} ->
- {ok, erl_types:t_subst(erl_types:t_fun_range(Contract), VarDict)};
+ {ok, VarMap} ->
+ {ok, erl_types:t_subst(erl_types:t_fun_range(Contract), VarMap)};
error -> error
end.
solve_constraints(Contract, Call, Constraints) ->
%% First make sure the call follows the constraints
- CDict = insert_constraints(Constraints, dict:new()),
- Contract1 = erl_types:t_subst(Contract, CDict),
+ CMap = insert_constraints(Constraints),
+ Contract1 = erl_types:t_subst(Contract, CMap),
%% Just a safe over-approximation.
%% TODO: Find the types for type variables properly
ContrArgs = erl_types:t_fun_args(Contract1),
@@ -325,7 +373,7 @@ solve_constraints(Contract, Call, Constraints) ->
InfList = erl_types:t_inf_lists(ContrArgs, CallArgs),
case erl_types:any_none_or_unit(InfList) of
true -> error;
- false -> {ok, CDict}
+ false -> {ok, CMap}
end.
%%Inf = erl_types:t_inf(Contract1, Call),
%% Then unify with the constrained call type.
@@ -335,8 +383,11 @@ solve_constraints(Contract, Call, Constraints) ->
%% ?debug("Inf: ~s\n", [erl_types:t_to_string(Inf)]),
%% erl_types:t_assign_variables_to_subtype(Contract, Inf).
+-type contracts() :: dict:dict(mfa(),dialyzer_contracts:file_contract()).
+
%% Checks the contracts for functions that are not implemented
--spec contracts_without_fun(dict(), [_], dialyzer_callgraph:callgraph()) -> [dial_warning()].
+-spec contracts_without_fun(contracts(), [_], dialyzer_callgraph:callgraph()) ->
+ [raw_warning()].
contracts_without_fun(Contracts, AllFuns0, Callgraph) ->
AllFuns1 = [{dialyzer_callgraph:lookup_name(Label, Callgraph), Arity}
@@ -347,137 +398,245 @@ contracts_without_fun(Contracts, AllFuns0, Callgraph) ->
[warn_spec_missing_fun(MFA, Contracts) || MFA <- ErrorContractMFAs].
warn_spec_missing_fun({M, F, A} = MFA, Contracts) ->
- {FileLine, _Contract} = dict:fetch(MFA, Contracts),
- {?WARN_CONTRACT_SYNTAX, FileLine, {spec_missing_fun, [M, F, A]}}.
+ {{File, Line}, _Contract, _Xtra} = dict:fetch(MFA, Contracts),
+ WarningInfo = {File, Line, MFA},
+ {?WARN_CONTRACT_SYNTAX, WarningInfo, {spec_missing_fun, [M, F, A]}}.
%% This treats the "when" constraints. It will be extended, we hope.
-insert_constraints([{subtype, Type1, Type2}|Left], Dict) ->
+insert_constraints(Constraints) ->
+ insert_constraints(Constraints, maps:new()).
+
+insert_constraints([{subtype, Type1, Type2}|Left], Map) ->
case erl_types:t_is_var(Type1) of
true ->
Name = erl_types:t_var_name(Type1),
- Dict1 = case dict:find(Name, Dict) of
- error ->
- dict:store(Name, Type2, Dict);
- {ok, VarType} ->
- dict:store(Name, erl_types:t_inf(VarType, Type2), Dict)
- end,
- insert_constraints(Left, Dict1);
+ Map1 = case maps:find(Name, Map) of
+ error ->
+ maps:put(Name, Type2, Map);
+ {ok, VarType} ->
+ maps:put(Name, erl_types:t_inf(VarType, Type2), Map)
+ end,
+ insert_constraints(Left, Map1);
false ->
%% A lot of things should change to add supertypes
throw({error, io_lib:format("First argument of is_subtype constraint "
"must be a type variable: ~p\n", [Type1])})
end;
-insert_constraints([], Dict) -> Dict.
+insert_constraints([], Map) -> Map.
+
+-type types() :: erl_types:type_table().
+
+-type spec_data() :: {TypeSpec :: [_], Xtra:: [_]}.
--spec store_tmp_contract(mfa(), file_line(), [_], dict(), dict()) -> dict().
+-spec store_tmp_contract(mfa(), file_line(), spec_data(), contracts(), types()) ->
+ contracts().
-store_tmp_contract(MFA, FileLine, TypeSpec, SpecDict, RecordsDict) ->
+store_tmp_contract(MFA, FileLine, {TypeSpec, Xtra}, SpecDict, RecordsDict) ->
%% io:format("contract from form: ~p\n", [TypeSpec]),
- TmpContract = contract_from_form(TypeSpec, RecordsDict, FileLine),
- %% io:format("contract: ~p\n", [Contract]),
- dict:store(MFA, {FileLine, TmpContract}, SpecDict).
+ TmpContract = contract_from_form(TypeSpec, MFA, RecordsDict, FileLine),
+ %% io:format("contract: ~p\n", [TmpContract]),
+ dict:store(MFA, {FileLine, TmpContract, Xtra}, SpecDict).
-contract_from_form(Forms, RecDict, FileLine) ->
- {CFuns, Forms1} = contract_from_form(Forms, RecDict, FileLine, [], []),
+contract_from_form(Forms, MFA, RecDict, FileLine) ->
+ {CFuns, Forms1} = contract_from_form(Forms, MFA, RecDict, FileLine, [], []),
#tmp_contract{contract_funs = CFuns, forms = Forms1}.
-contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], RecDict,
+contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], MFA, RecDict,
FileLine, TypeAcc, FormAcc) ->
TypeFun =
- fun(ExpTypes, AllRecords) ->
- Type =
+ fun(ExpTypes, AllRecords, Cache) ->
+ {NewType, NewCache} =
try
- erl_types:t_from_form(Form, RecDict)
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords, Cache)
catch
throw:{error, Msg} ->
{File, Line} = FileLine,
NewMsg = io_lib:format("~s:~p: ~s", [filename:basename(File),
- Line, Msg]),
+ Line, Msg]),
throw({error, NewMsg})
end,
- NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords),
- {NewType, []}
+ NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType),
+ {{NewTypeNoVars, []}, NewCache}
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, []} | FormAcc],
- contract_from_form(Left, RecDict, FileLine, NewTypeAcc, NewFormAcc);
+ contract_from_form(Left, MFA, RecDict, FileLine, NewTypeAcc, NewFormAcc);
contract_from_form([{type, _L1, bounded_fun,
[{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left],
- RecDict, FileLine, TypeAcc, FormAcc) ->
+ MFA, RecDict, FileLine, TypeAcc, FormAcc) ->
TypeFun =
- fun(ExpTypes, AllRecords) ->
- {Constr1, VarDict} =
- process_constraints(Constr, RecDict, ExpTypes, AllRecords),
- Type = erl_types:t_from_form(Form, RecDict, VarDict),
- NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords),
- {NewType, Constr1}
+ fun(ExpTypes, AllRecords, Cache) ->
+ {Constr1, VarTable, Cache1} =
+ process_constraints(Constr, MFA, RecDict, ExpTypes, AllRecords,
+ Cache),
+ {NewType, NewCache} =
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords,
+ VarTable, Cache1),
+ NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType),
+ {{NewTypeNoVars, Constr1}, NewCache}
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, Constr} | FormAcc],
- contract_from_form(Left, RecDict, FileLine, NewTypeAcc, NewFormAcc);
-contract_from_form([], _RecDict, _FileLine, TypeAcc, FormAcc) ->
+ contract_from_form(Left, MFA, RecDict, FileLine, NewTypeAcc, NewFormAcc);
+contract_from_form([], _MFA, _RecDict, _FileLine, TypeAcc, FormAcc) ->
{lists:reverse(TypeAcc), lists:reverse(FormAcc)}.
-process_constraints(Constrs, RecDict, ExpTypes, AllRecords) ->
- Init = initialize_constraints(Constrs, RecDict, ExpTypes, AllRecords),
- constraints_fixpoint(Init, RecDict, ExpTypes, AllRecords).
-
-initialize_constraints(Constrs, RecDict, ExpTypes, AllRecords) ->
- initialize_constraints(Constrs, RecDict, ExpTypes, AllRecords, []).
-
-initialize_constraints([], _RecDict, _ExpTypes, _AllRecords, Acc) ->
- Acc;
-initialize_constraints([Constr|Rest], RecDict, ExpTypes, AllRecords, Acc) ->
+process_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) ->
+ {Init0, NewCache} = initialize_constraints(Constrs, MFA, RecDict, ExpTypes,
+ AllRecords, Cache),
+ Init = remove_cycles(Init0),
+ constraints_fixpoint(Init, MFA, RecDict, ExpTypes, AllRecords, NewCache).
+
+initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) ->
+ initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ Cache, []).
+
+initialize_constraints([], _MFA, _RecDict, _ExpTypes, _AllRecords,
+ Cache, Acc) ->
+ {Acc, Cache};
+initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, AllRecords,
+ Cache, Acc) ->
case Constr of
{type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]} ->
- T1 = final_form(Type1, RecDict, ExpTypes, AllRecords, dict:new()),
+ VarTable = erl_types:var_table__new(),
+ {T1, NewCache} =
+ final_form(Type1, ExpTypes, MFA, AllRecords, VarTable, Cache),
Entry = {T1, Type2},
- initialize_constraints(Rest, RecDict, ExpTypes, AllRecords, [Entry|Acc]);
+ initialize_constraints(Rest, MFA, RecDict, ExpTypes, AllRecords,
+ NewCache, [Entry|Acc]);
{type, _, constraint, [{atom,_,Name}, List]} ->
N = length(List),
throw({error,
io_lib:format("Unsupported type guard ~w/~w\n", [Name, N])})
end.
-constraints_fixpoint(Constrs, RecDict, ExpTypes, AllRecords) ->
- VarDict =
- constraints_to_dict(Constrs, RecDict, ExpTypes, AllRecords, dict:new()),
- constraints_fixpoint(VarDict, Constrs, RecDict, ExpTypes, AllRecords).
-
-constraints_fixpoint(OldVarDict, Constrs, RecDict, ExpTypes, AllRecords) ->
- NewVarDict =
- constraints_to_dict(Constrs, RecDict, ExpTypes, AllRecords, OldVarDict),
- case NewVarDict of
- OldVarDict ->
- DictFold =
+constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) ->
+ VarTable = erl_types:var_table__new(),
+ {VarTab, NewCache} =
+ constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ VarTable, Cache),
+ constraints_fixpoint(VarTab, MFA, Constrs, RecDict, ExpTypes,
+ AllRecords, NewCache).
+
+constraints_fixpoint(OldVarTab, MFA, Constrs, RecDict, ExpTypes,
+ AllRecords, Cache) ->
+ {NewVarTab, NewCache} =
+ constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ OldVarTab, Cache),
+ case NewVarTab of
+ OldVarTab ->
+ Fun =
fun(Key, Value, Acc) ->
[{subtype, erl_types:t_var(Key), Value}|Acc]
end,
- FinalConstrs = dict:fold(DictFold, [], NewVarDict),
- {FinalConstrs, NewVarDict};
+ FinalConstrs = maps:fold(Fun, [], NewVarTab),
+ {FinalConstrs, NewVarTab, NewCache};
_Other ->
- constraints_fixpoint(NewVarDict, Constrs, RecDict, ExpTypes, AllRecords)
+ constraints_fixpoint(NewVarTab, MFA, Constrs, RecDict, ExpTypes,
+ AllRecords, NewCache)
end.
--define(TYPE_LIMIT, 4).
-
-final_form(Form, RecDict, ExpTypes, AllRecords, VarDict) ->
- T1 = erl_types:t_from_form(Form, RecDict, VarDict),
- T2 = erl_types:t_solve_remote(T1, ExpTypes, AllRecords),
- erl_types:t_limit(T2, ?TYPE_LIMIT).
-
-constraints_to_dict(Constrs, RecDict, ExpTypes, AllRecords, VarDict) ->
- Subtypes =
- constraints_to_subs(Constrs, RecDict, ExpTypes, AllRecords, VarDict, []),
- insert_constraints(Subtypes, dict:new()).
-
-constraints_to_subs([], _RecDict, _ExpTypes, _AllRecords, _VarDict, Acc) ->
- Acc;
-constraints_to_subs([C|Rest], RecDict, ExpTypes, AllRecords, VarDict, Acc) ->
- {T1, Form2} = C,
- T2 = final_form(Form2, RecDict, ExpTypes, AllRecords, VarDict),
+final_form(Form, ExpTypes, MFA, AllRecords, VarTable, Cache) ->
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache).
+
+from_form_with_check(Form, ExpTypes, MFA, AllRecords, Cache) ->
+ VarTable = erl_types:var_table__new(),
+ from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache).
+
+from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache) ->
+ Site = {spec, MFA},
+ C1 = erl_types:t_check_record_fields(Form, ExpTypes, Site, AllRecords,
+ VarTable, Cache),
+ erl_types:t_from_form(Form, ExpTypes, Site, AllRecords, VarTable, C1).
+
+constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, Cache) ->
+ {Subtypes, NewCache} =
+ constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, Cache, []),
+ {insert_constraints(Subtypes), NewCache}.
+
+constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _AllRecords,
+ _VarTab, Cache, Acc) ->
+ {Acc, Cache};
+constraints_to_subs([{T1, Form2}|Rest], MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, Cache, Acc) ->
+ {T2, NewCache} =
+ final_form(Form2, ExpTypes, MFA, AllRecords, VarTab, Cache),
NewAcc = [{subtype, T1, T2}|Acc],
- constraints_to_subs(Rest, RecDict, ExpTypes, AllRecords, VarDict, NewAcc).
+ constraints_to_subs(Rest, MFA, RecDict, ExpTypes, AllRecords,
+ VarTab, NewCache, NewAcc).
+
+%% Replaces variables with '_' when necessary to break up cycles among
+%% the constraints.
+
+remove_cycles(Constrs0) ->
+ Uses = find_uses(Constrs0),
+ G = digraph:new(),
+ Vs0 = [V || {V, _} <- Uses] ++ [V || {_, V} <- Uses],
+ Vs = lists:usort(Vs0),
+ lists:foreach(fun(V) -> _ = digraph:add_vertex(G, V) end, Vs),
+ lists:foreach(fun({From, To}) ->
+ _ = digraph:add_edge(G, {From, To}, From, To, [])
+ end, Uses),
+ ok = remove_cycles(G, Vs),
+ ToRemove = ordsets:subtract(ordsets:from_list(Uses),
+ ordsets:from_list(digraph:edges(G))),
+ Constrs = remove_uses(ToRemove, Constrs0),
+ digraph:delete(G),
+ Constrs.
+
+find_uses([{Var, Form}|Constrs]) ->
+ UsedVars = form_vars(Form, []),
+ VarName = erl_types:t_var_name(Var),
+ [{VarName, UsedVar} || UsedVar <- UsedVars] ++ find_uses(Constrs);
+find_uses([]) ->
+ [].
+
+form_vars({var, _, '_'}, Vs) -> Vs;
+form_vars({var, _, V}, Vs) -> [V|Vs];
+form_vars(T, Vs) when is_tuple(T) ->
+ form_vars(tuple_to_list(T), Vs);
+form_vars([E|Es], Vs) ->
+ form_vars(Es, form_vars(E, Vs));
+form_vars(_, Vs) -> Vs.
+
+remove_cycles(G, Vs) ->
+ NumberOfEdges = digraph:no_edges(G),
+ lists:foreach(fun(V) ->
+ case digraph:get_cycle(G, V) of
+ false -> true;
+ [V] -> digraph:del_edge(G, {V, V});
+ [V, V1|_] -> digraph:del_edge(G, {V, V1})
+ end
+ end, Vs),
+ case digraph:no_edges(G) =:= NumberOfEdges of
+ true -> ok;
+ false -> remove_cycles(G, Vs)
+ end.
+
+remove_uses([], Constrs) -> Constrs;
+remove_uses([{Var, Use}|ToRemove], Constrs0) ->
+ Constrs = remove_uses(Var, Use, Constrs0),
+ remove_uses(ToRemove, Constrs).
+
+remove_uses(_Var, _Use, []) -> [];
+remove_uses(Var, Use, [Constr|Constrs]) ->
+ {V, Form} = Constr,
+ NewConstr = case erl_types:t_var_name(V) =:= Var of
+ true ->
+ {V, remove_use(Form, Use)};
+ false ->
+ Constr
+ end,
+ [NewConstr|remove_uses(Var, Use, Constrs)].
+
+remove_use({var, L, V}, V) -> {var, L, '_'};
+remove_use(T, V) when is_tuple(T) ->
+ list_to_tuple(remove_use(tuple_to_list(T), V));
+remove_use([E|Es], V) ->
+ [remove_use(E, V)|remove_use(Es, V)];
+remove_use(T, _V) -> T.
%% Gets the most general domain of a list of domains of all
%% the overloaded contracts
@@ -486,46 +645,54 @@ general_domain(List) ->
general_domain(List, erl_types:t_none()).
general_domain([{Sig, Constraints}|Left], AccSig) ->
- Dict = insert_constraints(Constraints, dict:new()),
- Sig1 = erl_types:t_subst(Sig, Dict),
+ Map = insert_constraints(Constraints),
+ Sig1 = erl_types:t_subst(Sig, Map),
general_domain(Left, erl_types:t_sup(AccSig, Sig1));
general_domain([], AccSig) ->
%% Get rid of all variables in the domain.
AccSig1 = erl_types:subst_all_vars_to_any(AccSig),
erl_types:t_fun_args(AccSig1).
--spec get_invalid_contract_warnings([module()], dialyzer_codeserver:codeserver(), dialyzer_plt:plt()) -> [dial_warning()].
+-spec get_invalid_contract_warnings([module()],
+ dialyzer_codeserver:codeserver(),
+ dialyzer_plt:plt(),
+ opaques_fun()) -> [raw_warning()].
-get_invalid_contract_warnings(Modules, CodeServer, Plt) ->
- get_invalid_contract_warnings_modules(Modules, CodeServer, Plt, []).
+get_invalid_contract_warnings(Modules, CodeServer, Plt, FindOpaques) ->
+ get_invalid_contract_warnings_modules(Modules, CodeServer, Plt, FindOpaques, []).
-get_invalid_contract_warnings_modules([Mod|Mods], CodeServer, Plt, Acc) ->
+get_invalid_contract_warnings_modules([Mod|Mods], CodeServer, Plt, FindOpaques, Acc) ->
Contracts1 = dialyzer_codeserver:lookup_mod_contracts(Mod, CodeServer),
Contracts2 = dict:to_list(Contracts1),
Records = dialyzer_codeserver:lookup_mod_records(Mod, CodeServer),
- NewAcc = get_invalid_contract_warnings_funs(Contracts2, Plt, Records, Acc),
- get_invalid_contract_warnings_modules(Mods, CodeServer, Plt, NewAcc);
-get_invalid_contract_warnings_modules([], _CodeServer, _Plt, Acc) ->
+ NewAcc = get_invalid_contract_warnings_funs(Contracts2, Plt, Records, FindOpaques, Acc),
+ get_invalid_contract_warnings_modules(Mods, CodeServer, Plt, FindOpaques, NewAcc);
+get_invalid_contract_warnings_modules([], _CodeServer, _Plt, _FindOpaques, Acc) ->
Acc.
-get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left],
- Plt, RecDict, Acc) ->
+get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract, _Xtra}}|Left],
+ Plt, RecDict, FindOpaques, Acc) ->
case dialyzer_plt:lookup(Plt, MFA) of
none ->
%% This must be a contract for a non-available function. Just accept it.
- get_invalid_contract_warnings_funs(Left, Plt, RecDict, Acc);
+ get_invalid_contract_warnings_funs(Left, Plt, RecDict, FindOpaques, Acc);
{value, {Ret, Args}} ->
Sig = erl_types:t_fun(Args, Ret),
+ {M, _F, _A} = MFA,
+ %% io:format("MFA ~p~n", [MFA]),
+ Opaques = FindOpaques(M),
+ {File, Line} = FileLine,
+ WarningInfo = {File, Line, MFA},
NewAcc =
- case check_contract(Contract, Sig) of
+ case check_contract(Contract, Sig, Opaques) of
{error, invalid_contract} ->
- [invalid_contract_warning(MFA, FileLine, Sig, RecDict)|Acc];
+ [invalid_contract_warning(MFA, WarningInfo, Sig, RecDict)|Acc];
{error, {overlapping_contract, []}} ->
- [overlapping_contract_warning(MFA, FileLine)|Acc];
+ [overlapping_contract_warning(MFA, WarningInfo)|Acc];
{error, {extra_range, ExtraRanges, STRange}} ->
Warn =
case t_from_forms_without_remote(Contract#contract.forms,
- RecDict) of
+ MFA, RecDict) of
{ok, NoRemoteType} ->
CRet = erl_types:t_fun_range(NoRemoteType),
erl_types:t_is_subtype(ExtraRanges, CRet);
@@ -534,12 +701,12 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left],
end,
case Warn of
true ->
- [extra_range_warning(MFA, FileLine, ExtraRanges, STRange)|Acc];
+ [extra_range_warning(MFA, WarningInfo, ExtraRanges, STRange)|Acc];
false ->
Acc
end;
{error, Msg} ->
- [{?WARN_CONTRACT_SYNTAX, FileLine, Msg}|Acc];
+ [{?WARN_CONTRACT_SYNTAX, WarningInfo, Msg}|Acc];
ok ->
{M, F, A} = MFA,
CSig0 = get_contract_signature(Contract),
@@ -551,38 +718,38 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left],
BifArgs = erl_bif_types:arg_types(M, F, A),
BifRet = erl_bif_types:type(M, F, A),
BifSig = erl_types:t_fun(BifArgs, BifRet),
- case check_contract(Contract, BifSig) of
+ case check_contract(Contract, BifSig, Opaques) of
{error, _} ->
- [invalid_contract_warning(MFA, FileLine, BifSig, RecDict)
+ [invalid_contract_warning(MFA, WarningInfo, BifSig, RecDict)
|Acc];
ok ->
- picky_contract_check(CSig, BifSig, MFA, FileLine,
+ picky_contract_check(CSig, BifSig, MFA, WarningInfo,
Contract, RecDict, Acc)
end;
false ->
- picky_contract_check(CSig, Sig, MFA, FileLine, Contract,
+ picky_contract_check(CSig, Sig, MFA, WarningInfo, Contract,
RecDict, Acc)
end
end,
- get_invalid_contract_warnings_funs(Left, Plt, RecDict, NewAcc)
+ get_invalid_contract_warnings_funs(Left, Plt, RecDict, FindOpaques, NewAcc)
end;
-get_invalid_contract_warnings_funs([], _Plt, _RecDict, Acc) ->
+get_invalid_contract_warnings_funs([], _Plt, _RecDict, _FindOpaques, Acc) ->
Acc.
-invalid_contract_warning({M, F, A}, FileLine, SuccType, RecDict) ->
+invalid_contract_warning({M, F, A}, WarningInfo, SuccType, RecDict) ->
SuccTypeStr = dialyzer_utils:format_sig(SuccType, RecDict),
- {?WARN_CONTRACT_TYPES, FileLine, {invalid_contract, [M, F, A, SuccTypeStr]}}.
+ {?WARN_CONTRACT_TYPES, WarningInfo, {invalid_contract, [M, F, A, SuccTypeStr]}}.
-overlapping_contract_warning({M, F, A}, FileLine) ->
- {?WARN_CONTRACT_TYPES, FileLine, {overlapping_contract, [M, F, A]}}.
+overlapping_contract_warning({M, F, A}, WarningInfo) ->
+ {?WARN_CONTRACT_TYPES, WarningInfo, {overlapping_contract, [M, F, A]}}.
-extra_range_warning({M, F, A}, FileLine, ExtraRanges, STRange) ->
+extra_range_warning({M, F, A}, WarningInfo, ExtraRanges, STRange) ->
ERangesStr = erl_types:t_to_string(ExtraRanges),
STRangeStr = erl_types:t_to_string(STRange),
- {?WARN_CONTRACT_SUPERTYPE, FileLine,
+ {?WARN_CONTRACT_SUPERTYPE, WarningInfo,
{extra_range, [M, F, A, ERangesStr, STRangeStr]}}.
-picky_contract_check(CSig0, Sig0, MFA, FileLine, Contract, RecDict, Acc) ->
+picky_contract_check(CSig0, Sig0, MFA, WarningInfo, Contract, RecDict, Acc) ->
CSig = erl_types:t_abstract_records(CSig0, RecDict),
Sig = erl_types:t_abstract_records(Sig0, RecDict),
case erl_types:t_is_equal(CSig, Sig) of
@@ -592,25 +759,33 @@ picky_contract_check(CSig0, Sig0, MFA, FileLine, Contract, RecDict, Acc) ->
erl_types:t_is_unit(erl_types:t_fun_range(CSig))) of
true -> Acc;
false ->
- case extra_contract_warning(MFA, FileLine, Contract,
- CSig, Sig, RecDict) of
+ case extra_contract_warning(MFA, WarningInfo, Contract,
+ CSig0, Sig0, RecDict) of
no_warning -> Acc;
{warning, Warning} -> [Warning|Acc]
end
end
end.
-extra_contract_warning({M, F, A}, FileLine, Contract, CSig, Sig, RecDict) ->
- SigString = lists:flatten(dialyzer_utils:format_sig(Sig, RecDict)),
- ContractString0 = lists:flatten(dialyzer_utils:format_sig(CSig, RecDict)),
+extra_contract_warning(MFA, WarningInfo, Contract, CSig, Sig, RecDict) ->
+ %% We do not want to depend upon erl_types:t_to_string() possibly
+ %% hiding the contents of opaque types.
+ SigUnopaque = erl_types:t_unopaque(Sig),
+ CSigUnopaque = erl_types:t_unopaque(CSig),
+ SigString0 =
+ lists:flatten(dialyzer_utils:format_sig(SigUnopaque, RecDict)),
+ ContractString0 =
+ lists:flatten(dialyzer_utils:format_sig(CSigUnopaque, RecDict)),
%% The only difference is in record fields containing 'undefined' or not.
- IsUndefRecordFieldsRelated = SigString =:= ContractString0,
+ IsUndefRecordFieldsRelated = SigString0 =:= ContractString0,
{IsRemoteTypesRelated, SubtypeRelation} =
- is_remote_types_related(Contract, CSig, Sig, RecDict),
+ is_remote_types_related(Contract, CSig, Sig, MFA, RecDict),
case IsUndefRecordFieldsRelated orelse IsRemoteTypesRelated of
true ->
no_warning;
false ->
+ {M, F, A} = MFA,
+ SigString = lists:flatten(dialyzer_utils:format_sig(Sig, RecDict)),
ContractString = contract_to_string(Contract),
{Tag, Msg} =
case SubtypeRelation of
@@ -624,17 +799,18 @@ extra_contract_warning({M, F, A}, FileLine, Contract, CSig, Sig, RecDict) ->
{?WARN_CONTRACT_NOT_EQUAL,
{contract_diff, [M, F, A, ContractString, SigString]}}
end,
- {warning, {Tag, FileLine, Msg}}
+ {warning, {Tag, WarningInfo, Msg}}
end.
-is_remote_types_related(Contract, CSig, Sig, RecDict) ->
+is_remote_types_related(Contract, CSig, Sig, MFA, RecDict) ->
case erl_types:t_is_subtype(CSig, Sig) of
true ->
{false, contract_is_subtype};
false ->
case erl_types:t_is_subtype(Sig, CSig) of
true ->
- case t_from_forms_without_remote(Contract#contract.forms, RecDict) of
+ case t_from_forms_without_remote(Contract#contract.forms, MFA,
+ RecDict) of
{ok, NoRemoteTypeSig} ->
case blame_remote(CSig, NoRemoteTypeSig, Sig) of
true ->
@@ -650,20 +826,14 @@ is_remote_types_related(Contract, CSig, Sig, RecDict) ->
end
end.
-t_from_forms_without_remote([{FType, []}], RecDict) ->
- Type0 = erl_types:t_from_form(FType, RecDict),
- Map =
- fun(Type) ->
- case erl_types:t_is_remote(Type) of
- true -> erl_types:t_none();
- false -> Type
- end
- end,
- {ok, erl_types:t_map(Map, Type0)};
-t_from_forms_without_remote([{_FType, _Constrs}], _RecDict) ->
+t_from_forms_without_remote([{FType, []}], MFA, RecDict) ->
+ Site = {spec, MFA},
+ {Type1, _} = erl_types:t_from_form_without_remote(FType, Site, RecDict),
+ {ok, erl_types:subst_all_vars_to_any(Type1)};
+t_from_forms_without_remote([{_FType, _Constrs}], _MFA, _RecDict) ->
%% 'When' constraints
unsupported;
-t_from_forms_without_remote(_Forms, _RecDict) ->
+t_from_forms_without_remote(_Forms, _MFA, _RecDict) ->
%% Lots of forms
unsupported.
diff --git a/lib/dialyzer/src/dialyzer_coordinator.erl b/lib/dialyzer/src/dialyzer_coordinator.erl
index c3b7ce29af..87cbd25b30 100644
--- a/lib/dialyzer/src/dialyzer_coordinator.erl
+++ b/lib/dialyzer/src/dialyzer_coordinator.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -28,8 +29,8 @@
%%% Export for dialyzer main process
-export([parallel_job/4]).
-%%% Exports for all possible workers
--export([wait_activation/0, job_done/3]).
+%%% Export for all possible workers
+-export([job_done/3]).
%%% Exports for the typesig and dataflow analysis workers
-export([sccs_to_pids/2, request_activation/1]).
@@ -37,7 +38,7 @@
%%% Exports for the compilation workers
-export([get_next_label/2]).
--export_type([coordinator/0, mode/0, init_data/0, result/0]).
+-export_type([coordinator/0, mode/0, init_data/0, result/0, job/0]).
%%--------------------------------------------------------------------
@@ -45,16 +46,18 @@
-type regulator() :: pid().
-type scc_to_pid() :: ets:tid() | 'unused'.
--type coordinator() :: {collector(), regulator(), scc_to_pid()}. %%opaque
+-opaque coordinator() :: {collector(), regulator(), scc_to_pid()}.
-type timing() :: dialyzer_timing:timing_server().
-type scc() :: [mfa_or_funlbl()].
-type mode() :: 'typesig' | 'dataflow' | 'compile' | 'warnings'.
--type compile_jobs() :: [file:filename()].
--type typesig_jobs() :: [scc()].
--type dataflow_jobs() :: [module()].
--type warnings_jobs() :: [module()].
+-type compile_job() :: file:filename().
+-type typesig_job() :: scc().
+-type dataflow_job() :: module().
+-type warnings_job() :: module().
+
+-type job() :: compile_job() | typesig_job() | dataflow_job() | warnings_job().
-type compile_init_data() :: dialyzer_analysis_callgraph:compile_init_data().
-type typesig_init_data() :: dialyzer_succ_typings:typesig_init_data().
@@ -72,9 +75,9 @@
-type result() :: compile_result() | typesig_result() |
dataflow_result() | warnings_result().
--type job() :: scc() | module() | file:filename().
--type job_result() :: dialyzer_analysis_callgraph:one_file_result() |
- typesig_result() | dataflow_result() | warnings_result().
+-type job_result() :: dialyzer_analysis_callgraph:one_file_mid_error() |
+ dialyzer_analysis_callgraph:one_file_result_ok() |
+ typesig_result() | dataflow_result() | warnings_result().
-record(state, {mode :: mode(),
active = 0 :: integer(),
@@ -89,13 +92,13 @@
%%--------------------------------------------------------------------
--spec parallel_job('compile', compile_jobs(), compile_init_data(), timing()) ->
+-spec parallel_job('compile', [compile_job()], compile_init_data(), timing()) ->
{compile_result(), integer()};
- ('typesig', typesig_jobs(), typesig_init_data(), timing()) ->
+ ('typesig', [typesig_job()], typesig_init_data(), timing()) ->
typesig_result();
- ('dataflow', dataflow_jobs(), dataflow_init_data(),
+ ('dataflow', [dataflow_job()], dataflow_init_data(),
timing()) -> dataflow_result();
- ('warnings', warnings_jobs(), warnings_init_data(),
+ ('warnings', [warnings_job()], warnings_init_data(),
timing()) -> warnings_result().
parallel_job(Mode, Jobs, InitData, Timing) ->
@@ -117,7 +120,7 @@ spawn_jobs(Mode, Jobs, InitData, Timing) ->
Pid = dialyzer_worker:launch(Mode, Job, InitData, Coordinator),
case TypesigOrDataflow of
true -> true = ets:insert(SCCtoPID, {Job, Pid}), ok;
- false -> request_activation(Regulator, Pid)
+ false -> ok
end,
Count + 1
end,
@@ -216,10 +219,6 @@ request_activation({_Collector, Regulator, _SCCtoPID}) ->
Regulator ! {req, self()},
wait_activation().
-request_activation(Regulator, Pid) ->
- Regulator ! {req, Pid},
- ok.
-
spawn_regulator() ->
InitTickets = dialyzer_utils:parallelism(),
spawn_link(fun() -> regulator_loop(InitTickets, queue:new()) end).
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 6956850f1a..9399789464 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -2,18 +2,19 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -28,40 +29,50 @@
-module(dialyzer_dataflow).
--export([get_fun_types/4, get_warnings/5, format_args/3]).
+-export([get_fun_types/5, get_warnings/5, format_args/3]).
%% Data structure interfaces.
-export([state__add_warning/2, state__cleanup/1,
state__duplicate/1, dispose_state/1,
state__get_callgraph/1, state__get_races/1,
state__get_records/1, state__put_callgraph/2,
- state__put_races/2, state__records_only/1]).
+ state__put_races/2, state__records_only/1,
+ state__find_function/2]).
-export_type([state/0]).
-include("dialyzer.hrl").
-import(erl_types,
- [any_none/1, t_any/0, t_atom/0, t_atom/1, t_atom_vals/1,
+ [t_inf/2, t_inf/3, t_inf_lists/2, t_inf_lists/3,
+ t_inf_lists/3, t_is_equal/2, t_is_subtype/2, t_subtract/2,
+ t_sup/1, t_sup/2]).
+
+-import(erl_types,
+ [any_none/1, t_any/0, t_atom/0, t_atom/1, t_atom_vals/1, t_atom_vals/2,
t_binary/0, t_boolean/0,
t_bitstr/0, t_bitstr/2, t_bitstr_concat/1, t_bitstr_match/2,
- t_cons/0, t_cons/2, t_cons_hd/1, t_cons_tl/1, t_contains_opaque/1,
- t_find_opaque_mismatch/2, t_float/0, t_from_range/2, t_from_term/1,
- t_fun/0, t_fun/2, t_fun_args/1, t_fun_range/1,
- t_inf/2, t_inf/3, t_inf_lists/2, t_inf_lists/3, t_inf_lists_masked/3,
- t_integer/0, t_integers/1,
- t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_boolean/1, t_is_equal/2,
- t_is_integer/1, t_is_nil/1, t_is_none/1, t_is_none_or_unit/1,
- t_is_number/1, t_is_reference/1, t_is_pid/1, t_is_port/1,
- t_is_subtype/2, t_is_unit/1,
- t_limit/2, t_list/0, t_maybe_improper_list/0, t_module/0,
- t_none/0, t_non_neg_integer/0, t_number/0, t_number_vals/1,
- t_opaque_match_atom/2, t_opaque_match_record/2,
- t_opaque_matching_structure/2,
+ t_cons/0, t_cons/2, t_cons_hd/2, t_cons_tl/2,
+ t_contains_opaque/2,
+ t_find_opaque_mismatch/3, t_float/0, t_from_range/2, t_from_term/1,
+ t_fun/0, t_fun/2, t_fun_args/1, t_fun_args/2, t_fun_range/1,
+ t_fun_range/2, t_integer/0, t_integers/1,
+ t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_any_atom/3,
+ t_is_boolean/2,
+ t_is_integer/2, t_is_list/1,
+ t_is_nil/2, t_is_none/1, t_is_none_or_unit/1,
+ t_is_number/2, t_is_reference/2, t_is_pid/2, t_is_port/2,
+ t_is_unit/1,
+ t_limit/2, t_list/0, t_list_elements/2,
+ t_maybe_improper_list/0, t_module/0,
+ t_none/0, t_non_neg_integer/0, t_number/0, t_number_vals/2,
t_pid/0, t_port/0, t_product/1, t_reference/0,
- t_sup/1, t_sup/2, t_subtract/2, t_to_string/2, t_to_tlist/1,
- t_tuple/0, t_tuple/1, t_tuple_args/1, t_tuple_subtypes/1,
- t_unit/0, t_unopaque/1]).
+ t_to_string/2, t_to_tlist/1,
+ t_tuple/0, t_tuple/1, t_tuple_args/1, t_tuple_args/2,
+ t_tuple_subtypes/2,
+ t_unit/0, t_unopaque/2,
+ t_map/0, t_map/1, t_is_singleton/2
+ ]).
%%-define(DEBUG, true).
%%-define(DEBUG_PP, true).
@@ -76,53 +87,85 @@
%%--------------------------------------------------------------------
+-type type() :: erl_types:erl_type().
+-type types() :: erl_types:type_table().
+
+-type curr_fun() :: 'undefined' | 'top' | mfa_or_funlbl().
+
-define(no_arg, no_arg).
-define(TYPE_LIMIT, 3).
--record(state, {callgraph :: dialyzer_callgraph:callgraph(),
- envs :: dict(),
- fun_tab :: dict(),
- plt :: dialyzer_plt:plt(),
- opaques :: [erl_types:erl_type()],
+-define(BITS, 128).
+
+%% Types with comment 'race' are due to dialyzer_races.erl.
+-record(state, {callgraph :: dialyzer_callgraph:callgraph()
+ | 'undefined', % race
+ codeserver :: dialyzer_codeserver:codeserver()
+ | 'undefined', % race
+ envs :: env_tab()
+ | 'undefined', % race
+ fun_tab :: fun_tab()
+ | 'undefined', % race
+ fun_homes :: dict:dict(label(), mfa())
+ | 'undefined', % race
+ plt :: dialyzer_plt:plt()
+ | 'undefined', % race
+ opaques :: [type()]
+ | 'undefined', % race
races = dialyzer_races:new() :: dialyzer_races:races(),
- records = dict:new() :: dict(),
- tree_map :: dict(),
+ records = dict:new() :: types(),
+ tree_map :: dict:dict(label(), cerl:cerl())
+ | 'undefined', % race
warning_mode = false :: boolean(),
- warnings = [] :: [dial_warning()],
- work :: {[_], [_], set()},
- module :: module()
+ warnings = [] :: [raw_warning()],
+ work :: {[_], [_], sets:set()}
+ | 'undefined', % race
+ module :: module(),
+ curr_fun :: curr_fun()
}).
--record(map, {dict = dict:new() :: dict(),
- subst = dict:new() :: dict(),
+-record(map, {map = maps:new() :: type_tab(),
+ subst = maps:new() :: subst_tab(),
modified = [] :: [Key :: term()],
modified_stack = [] :: [{[Key :: term()],reference()}],
ref = undefined :: reference() | undefined}).
+-type env_tab() :: dict:dict(label(), #map{}).
+-type fun_entry() :: {Args :: [type()], RetType :: type()}.
+-type fun_tab() :: dict:dict('top' | label(),
+ {'not_handled', fun_entry()} | fun_entry()).
+-type key() :: label() | cerl:cerl().
+-type type_tab() :: #{key() => type()}.
+-type subst_tab() :: #{key() => cerl:cerl()}.
+
%% Exported Types
-opaque state() :: #state{}.
%%--------------------------------------------------------------------
+-type fun_types() :: dict:dict(label(), type()).
+
-spec get_warnings(cerl:c_module(), dialyzer_plt:plt(),
- dialyzer_callgraph:callgraph(), dict(), set()) ->
- {[dial_warning()], dict()}.
+ dialyzer_callgraph:callgraph(),
+ dialyzer_codeserver:codeserver(),
+ types()) ->
+ {[raw_warning()], fun_types()}.
-get_warnings(Tree, Plt, Callgraph, Records, NoWarnUnused) ->
- State1 = analyze_module(Tree, Plt, Callgraph, Records, true),
- State2 = find_mismatched_record_patterns(Tree, State1),
- State3 =
- state__renew_warnings(state__get_warnings(State2, NoWarnUnused), State2),
- State4 = state__get_race_warnings(State3),
- {State4#state.warnings, state__all_fun_types(State4)}.
+get_warnings(Tree, Plt, Callgraph, Codeserver, Records) ->
+ State1 = analyze_module(Tree, Plt, Callgraph, Codeserver, Records, true),
+ State2 = state__renew_warnings(state__get_warnings(State1), State1),
+ State3 = state__get_race_warnings(State2),
+ {State3#state.warnings, state__all_fun_types(State3)}.
-spec get_fun_types(cerl:c_module(), dialyzer_plt:plt(),
- dialyzer_callgraph:callgraph(), dict()) -> dict().
+ dialyzer_callgraph:callgraph(),
+ dialyzer_codeserver:codeserver(),
+ types()) -> fun_types().
-get_fun_types(Tree, Plt, Callgraph, Records) ->
- State = analyze_module(Tree, Plt, Callgraph, Records, false),
+get_fun_types(Tree, Plt, Callgraph, Codeserver, Records) ->
+ State = analyze_module(Tree, Plt, Callgraph, Codeserver, Records, false),
state__all_fun_types(State).
%%% ===========================================================================
@@ -131,11 +174,11 @@ get_fun_types(Tree, Plt, Callgraph, Records) ->
%%%
%%% ===========================================================================
-analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) ->
+analyze_module(Tree, Plt, Callgraph, Codeserver, Records, GetWarnings) ->
debug_pp(Tree, false),
Module = cerl:atom_val(cerl:module_name(Tree)),
TopFun = cerl:ann_c_fun([{label, top}], [], Tree),
- State = state__new(Callgraph, TopFun, Plt, Module, Records),
+ State = state__new(Callgraph, Codeserver, TopFun, Plt, Module, Records),
State1 = state__race_analysis(not GetWarnings, State),
State2 = analyze_loop(State1),
case GetWarnings of
@@ -149,25 +192,26 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) ->
analyze_loop(State) ->
case state__get_work(State) of
- none -> State;
- {Fun, NewState1} ->
+ none -> state__set_curr_fun(undefined, State);
+ {Fun, NewState0} ->
+ NewState1 = state__set_curr_fun(get_label(Fun), NewState0),
{ArgTypes, IsCalled} = state__get_args_and_status(Fun, NewState1),
case not IsCalled of
true ->
?debug("Not handling (not called) ~w: ~s\n",
- [state__lookup_name(get_label(Fun), State),
+ [NewState1#state.curr_fun,
t_to_string(t_product(ArgTypes))]),
analyze_loop(NewState1);
false ->
case state__fun_env(Fun, NewState1) of
none ->
?debug("Not handling (no env) ~w: ~s\n",
- [state__lookup_name(get_label(Fun), State),
+ [NewState1#state.curr_fun,
t_to_string(t_product(ArgTypes))]),
analyze_loop(NewState1);
Map ->
?debug("Handling fun ~p: ~s\n",
- [state__lookup_name(get_label(Fun), State),
+ [NewState1#state.curr_fun,
t_to_string(state__fun_type(Fun, NewState1))]),
Vars = cerl:fun_vars(Fun),
Map1 = enter_type_lists(Vars, ArgTypes, Map),
@@ -186,7 +230,7 @@ analyze_loop(State) ->
{NewState4, _Map2, BodyType} =
traverse(Body, Map1, NewState3),
?debug("Done analyzing: ~w:~s\n",
- [state__lookup_name(get_label(Fun), State),
+ [NewState1#state.curr_fun,
t_to_string(t_fun(ArgTypes, BodyType))]),
NewState5 =
case IsRaceAnalysisEnabled of
@@ -204,7 +248,7 @@ analyze_loop(State) ->
traverse(Tree, Map, State) ->
?debug("Handling ~p\n", [cerl:type(Tree)]),
- %%debug_pp_map(Map),
+ %% debug_pp_map(Map),
case cerl:type(Tree) of
alias ->
%% This only happens when checking for illegal record patterns
@@ -252,18 +296,8 @@ traverse(Tree, Map, State) ->
{State1, Map1} = lists:foldl(FoldFun, {State, Map}, Defs),
traverse(Body, Map1, State1);
literal ->
- %% This is needed for finding records
- case cerl:unfold_literal(Tree) of
- Tree ->
- Type = literal_type(Tree),
- NewType =
- case erl_types:t_opaque_match_atom(Type, State#state.opaques) of
- [Opaque] -> Opaque;
- _ -> Type
- end,
- {State, Map, NewType};
- NewTree -> traverse(NewTree, Map, State)
- end;
+ Type = literal_type(Tree),
+ {State, Map, Type};
module ->
handle_module(Tree, Map, State);
primop ->
@@ -286,8 +320,12 @@ traverse(Tree, Map, State) ->
SMA;
false ->
State2 =
- case (t_is_any(ArgType) orelse t_is_simple(ArgType)
- orelse is_call_to_send(Arg)) of
+ case
+ t_is_any(ArgType)
+ orelse t_is_simple(ArgType, State)
+ orelse is_call_to_send(Arg)
+ orelse is_lc_simple_list(Arg, ArgType, State)
+ of
true -> % do not warn in these cases
State1;
false ->
@@ -301,6 +339,8 @@ traverse(Tree, Map, State) ->
handle_try(Tree, Map, State);
tuple ->
handle_tuple(Tree, Map, State);
+ map ->
+ handle_map(Tree, Map, State);
values ->
Elements = cerl:values_es(Tree),
{State1, Map1, EsType} = traverse_list(Elements, Map, State),
@@ -308,18 +348,10 @@ traverse(Tree, Map, State) ->
{State1, Map1, Type};
var ->
?debug("Looking up unknown variable: ~p\n", [Tree]),
- case state__lookup_type_for_rec_var(Tree, State) of
+ case state__lookup_type_for_letrec(Tree, State) of
error ->
LType = lookup_type(Tree, Map),
- Opaques = State#state.opaques,
- case t_opaque_match_record(LType, Opaques) of
- [Opaque] -> {State, Map, Opaque};
- _ ->
- case t_opaque_match_atom(LType, Opaques) of
- [Opaque] -> {State, Map, Opaque};
- _ -> {State, Map, LType}
- end
- end;
+ {State, Map, LType};
{ok, Type} -> {State, Map, Type}
end;
Other ->
@@ -343,20 +375,24 @@ traverse_list([], Map, State, Acc) ->
handle_apply(Tree, Map, State) ->
Args = cerl:apply_args(Tree),
Op = cerl:apply_op(Tree),
- {State1, Map1, ArgTypes} = traverse_list(Args, Map, State),
- {State2, Map2, OpType} = traverse(Op, Map1, State1),
+ {State0, Map1, ArgTypes} = traverse_list(Args, Map, State),
+ {State1, Map2, OpType} = traverse(Op, Map1, State0),
case any_none(ArgTypes) of
true ->
- {State2, Map2, t_none()};
+ {State1, Map2, t_none()};
false ->
- {CallSitesKnown, FunList} =
- case state__lookup_call_site(Tree, State2) of
- error -> {false, []};
- {ok, [external]} -> {false, []};
- {ok, List} -> {true, List}
+ FunList =
+ case state__lookup_call_site(Tree, State) of
+ error -> [external]; %% so that we go directly in the fallback
+ {ok, List} -> List
end,
- case CallSitesKnown of
- false ->
+ FunInfoList = [{local, state__fun_info(Fun, State)} || Fun <- FunList],
+ case
+ handle_apply_or_call(FunInfoList, Args, ArgTypes, Map2, Tree, State1)
+ of
+ {had_external, State2} ->
+ %% Fallback: use whatever info we collected from traversing the op
+ %% instead of the result that has been generalized to t_any().
Arity = length(Args),
OpType1 = t_inf(OpType, t_fun(Arity, t_any())),
case t_is_none(OpType1) of
@@ -367,7 +403,8 @@ handle_apply(Tree, Map, State) ->
Tree, Msg),
{State3, Map2, t_none()};
false ->
- NewArgs = t_inf_lists(ArgTypes, t_fun_args(OpType1)),
+ NewArgs = t_inf_lists(ArgTypes,
+ t_fun_args(OpType1, 'universe')),
case any_none(NewArgs) of
true ->
Msg = {fun_app_args,
@@ -378,7 +415,7 @@ handle_apply(Tree, Map, State) ->
{State3, enter_type(Op, OpType1, Map2), t_none()};
false ->
Map3 = enter_type_lists(Args, NewArgs, Map2),
- Range0 = t_fun_range(OpType1),
+ Range0 = t_fun_range(OpType1, 'universe'),
Range =
case t_is_unit(Range0) of
true -> t_none();
@@ -387,25 +424,41 @@ handle_apply(Tree, Map, State) ->
{State2, enter_type(Op, OpType1, Map3), Range}
end
end;
- true ->
- FunInfoList = [{local, state__fun_info(Fun, State)}
- || Fun <- FunList],
- handle_apply_or_call(FunInfoList, Args, ArgTypes, Map2, Tree, State1)
+ Normal -> Normal
end
end.
handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State) ->
None = t_none(),
+ %% Call-site analysis may be inaccurate and consider more funs than those that
+ %% are actually possible. If all of them are incorrect, then warnings can be
+ %% emitted. If at least one fun is ok, however, then no warning is emitted,
+ %% just in case the bad ones are not really possible. The last argument is
+ %% used for this, with the following encoding:
+ %% Initial value: {none, []}
+ %% First fun checked: {one, <List of warns>}
+ %% More funs checked: {many, <List of warns>}
+ %% A '{one, []}' can only become '{many, []}'.
+ %% If at any point an fun does not add warnings, then the list is also
+ %% replaced with an empty list.
handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State,
- [None || _ <- ArgTypes], None).
+ [None || _ <- ArgTypes], None, false, {none, []}).
handle_apply_or_call([{local, external}|Left], Args, ArgTypes, Map, Tree, State,
- _AccArgTypes, _AccRet) ->
+ _AccArgTypes, _AccRet, _HadExternal, Warns) ->
+ {HowMany, _} = Warns,
+ NewHowMany =
+ case HowMany of
+ none -> one;
+ _ -> many
+ end,
+ NewWarns = {NewHowMany, []},
handle_apply_or_call(Left, Args, ArgTypes, Map, Tree, State,
- ArgTypes, t_any());
+ ArgTypes, t_any(), true, NewWarns);
handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
Args, ArgTypes, Map, Tree,
- #state{opaques = Opaques} = State, AccArgTypes, AccRet) ->
+ #state{opaques = Opaques} = State,
+ AccArgTypes, AccRet, HadExternal, Warns) ->
Any = t_any(),
AnyArgs = [Any || _ <- Args],
GenSig = {AnyArgs, fun(_) -> t_any() end},
@@ -423,83 +476,55 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
{M, F, A} = Fun,
case erl_bif_types:is_known(M, F, A) of
true ->
- IsBIF = true,
BArgs = erl_bif_types:arg_types(M, F, A),
BRange =
fun(FunArgs) ->
- ArgPos = erl_bif_types:structure_inspecting_args(M, F, A),
- NewFunArgs =
- case ArgPos =:= [] of
- true -> FunArgs;
- false -> % some positions need to be un-opaqued
- N = length(FunArgs),
- PFs = lists:zip(lists:seq(1, N), FunArgs),
- [case ordsets:is_element(P, ArgPos) of
- true -> erl_types:t_unopaque(FArg, Opaques);
- false -> FArg
- end || {P, FArg} <- PFs]
- end,
- erl_bif_types:type(M, F, A, NewFunArgs)
+ erl_bif_types:type(M, F, A, FunArgs, Opaques)
end,
{BArgs, BRange};
- false -> IsBIF = false, GenSig
+ false ->
+ GenSig
end;
- local -> IsBIF = false, GenSig
+ local -> GenSig
end,
{SigArgs, SigRange} =
- %% if there is hard-coded or contract information with opaque types,
- %% the checking for possible type violations needs to take place w.r.t.
- %% this information and not w.r.t. the structure-based success typing.
- case prefer_opaque_types(CArgs, BifArgs) of
- true -> {AnyArgs, t_any()}; % effectively forgets the success typing
- false ->
- case Sig of
- {value, {SR, SA}} -> {SA, SR};
- none -> {AnyArgs, t_any()}
- end
- end,
- ArgModeMask = [case lists:member(Arg, Opaques) of
- true -> opaque;
- false -> structured
- end || Arg <- ArgTypes],
- NewArgsSig = t_inf_lists_masked(SigArgs, ArgTypes, ArgModeMask),
- NewArgsContract = t_inf_lists_masked(CArgs, ArgTypes, ArgModeMask),
- NewArgsBif = t_inf_lists_masked(BifArgs, ArgTypes, ArgModeMask),
- NewArgTypes0 = t_inf_lists_masked(NewArgsSig, NewArgsContract, ArgModeMask),
- NewArgTypes = t_inf_lists_masked(NewArgTypes0, NewArgsBif, ArgModeMask),
- BifRet = BifRange(NewArgTypes),
- {TmpArgTypes, TmpArgsContract} =
- case (TypeOfApply =:= remote) andalso (not IsBIF) of
- true ->
- List1 = lists:zip(CArgs, NewArgTypes),
- List2 = lists:zip(CArgs, NewArgsContract),
- {[erl_types:t_unopaque_on_mismatch(T1, T2, Opaques)
- || {T1, T2} <- List1],
- [erl_types:t_unopaque_on_mismatch(T1, T2, Opaques)
- || {T1, T2} <- List2]};
- false -> {NewArgTypes, NewArgsContract}
- end,
- ContrRet = CRange(TmpArgTypes),
- RetMode =
- case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of
- true -> opaque;
- false -> structured
+ case Sig of
+ {value, {SR, SA}} -> {SA, SR};
+ none -> {AnyArgs, t_any()}
end,
- RetWithoutContr = t_inf(SigRange, BifRet, RetMode),
- RetWithoutLocal = t_inf(ContrRet, RetWithoutContr, RetMode),
+
?debug("--------------------------------------------------------\n", []),
- ?debug("Fun: ~p\n", [Fun]),
- ?debug("Args: ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]),
+ ?debug("Fun: ~p\n", [state__lookup_name(Fun, State)]),
+ ?debug("Module ~p\n", [State#state.module]),
+ ?debug("CArgs ~s\n", [erl_types:t_to_string(t_product(CArgs))]),
+ ?debug("ArgTypes ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]),
+ ?debug("BifArgs ~p\n", [erl_types:t_to_string(t_product(BifArgs))]),
+
+ NewArgsSig = t_inf_lists(SigArgs, ArgTypes, Opaques),
+ ?debug("SigArgs ~s\n", [erl_types:t_to_string(t_product(SigArgs))]),
?debug("NewArgsSig: ~s\n", [erl_types:t_to_string(t_product(NewArgsSig))]),
+ NewArgsContract = t_inf_lists(CArgs, ArgTypes, Opaques),
?debug("NewArgsContract: ~s\n",
[erl_types:t_to_string(t_product(NewArgsContract))]),
+ NewArgsBif = t_inf_lists(BifArgs, ArgTypes, Opaques),
?debug("NewArgsBif: ~s\n", [erl_types:t_to_string(t_product(NewArgsBif))]),
- ?debug("NewArgTypes: ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]),
+ NewArgTypes0 = t_inf_lists(NewArgsSig, NewArgsContract),
+ NewArgTypes = t_inf_lists(NewArgTypes0, NewArgsBif, Opaques),
+ ?debug("NewArgTypes ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]),
+ ?debug("\n", []),
+
+ BifRet = BifRange(NewArgTypes),
+ ContrRet = CRange(NewArgTypes),
+ RetWithoutContr = t_inf(SigRange, BifRet),
+ RetWithoutLocal = t_inf(ContrRet, RetWithoutContr),
+
?debug("RetWithoutContr: ~s\n",[erl_types:t_to_string(RetWithoutContr)]),
?debug("RetWithoutLocal: ~s\n", [erl_types:t_to_string(RetWithoutLocal)]),
?debug("BifRet: ~s\n", [erl_types:t_to_string(BifRange(NewArgTypes))]),
- ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(TmpArgTypes))]),
- ?debug("SigRet: ~s\n", [erl_types:t_to_string(SigRange)]),
+ ?debug("SigRange: ~s\n", [erl_types:t_to_string(SigRange)]),
+ ?debug("ContrRet: ~s\n", [erl_types:t_to_string(ContrRet)]),
+ ?debug("LocalRet: ~s\n", [erl_types:t_to_string(LocalRet)]),
+
State1 =
case is_race_analysis_enabled(State) of
true ->
@@ -513,6 +538,9 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
FailedConj = any_none([RetWithoutLocal|NewArgTypes]),
IsFailBif = t_is_none(BifRange(BifArgs)),
IsFailSig = t_is_none(SigRange),
+ ?debug("FailedConj: ~p~n", [FailedConj]),
+ ?debug("IsFailBif: ~p~n", [IsFailBif]),
+ ?debug("IsFailSig: ~p~n", [IsFailSig]),
State2 =
case FailedConj andalso not (IsFailBif orelse IsFailSig) of
true ->
@@ -532,14 +560,14 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
false ->
FailedSig = any_none(NewArgsSig),
FailedContract =
- any_none([CRange(TmpArgsContract)|NewArgsContract]),
+ any_none([CRange(NewArgsContract)|NewArgsContract]),
FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]),
InfSig = t_inf(t_fun(SigArgs, SigRange),
- t_fun(BifArgs, BifRange(BifArgs))),
+ t_fun(BifArgs, BifRange(BifArgs))),
FailReason =
apply_fail_reason(FailedSig, FailedBif, FailedContract),
Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig,
- Contr, CArgs, State1, FailReason),
+ Contr, CArgs, State1, FailReason, Opaques),
WarnType = case Msg of
{call, _} -> ?WARN_FAILING_CALL;
{apply, _} -> ?WARN_FAILING_CALL;
@@ -547,7 +575,8 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
{call_without_opaque, _} -> ?WARN_OPAQUE;
{opaque_type_test, _} -> ?WARN_OPAQUE
end,
- state__add_warning(State1, WarnType, Tree, Msg)
+ Frc = {erlang, is_record, 3} =:= state__lookup_name(Fun, State),
+ state__add_warning(State1, WarnType, Tree, Msg, Frc)
end;
false -> State1
end,
@@ -571,16 +600,37 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
TotalRet =
case t_is_none(LocalRet) andalso t_is_unit(RetWithoutLocal) of
true -> RetWithoutLocal;
- false -> t_inf(RetWithoutLocal, LocalRet, opaque)
+ false -> t_inf(RetWithoutLocal, LocalRet)
end,
NewAccRet = t_sup(AccRet, TotalRet),
?debug("NewAccRet: ~s\n", [t_to_string(NewAccRet)]),
+ {NewWarnings, State4} = state__remove_added_warnings(State, State3),
+ {HowMany, OldWarnings} = Warns,
+ NewWarns =
+ case HowMany of
+ none -> {one, NewWarnings};
+ _ ->
+ case OldWarnings =:= [] of
+ true -> {many, []};
+ false ->
+ case NewWarnings =:= [] of
+ true -> {many, []};
+ false -> {many, NewWarnings ++ OldWarnings}
+ end
+ end
+ end,
handle_apply_or_call(Left, Args, ArgTypes, Map, Tree,
- State3, NewAccArgTypes, NewAccRet);
+ State4, NewAccArgTypes, NewAccRet, HadExternal, NewWarns);
handle_apply_or_call([], Args, _ArgTypes, Map, _Tree, State,
- AccArgTypes, AccRet) ->
- NewMap = enter_type_lists(Args, AccArgTypes, Map),
- {State, NewMap, AccRet}.
+ AccArgTypes, AccRet, HadExternal, {_, Warnings}) ->
+ State1 = state__add_warnings(Warnings, State),
+ case HadExternal of
+ false ->
+ NewMap = enter_type_lists(Args, AccArgTypes, Map),
+ {State1, NewMap, AccRet};
+ true ->
+ {had_external, State1}
+ end.
apply_fail_reason(FailedSig, FailedBif, FailedContract) ->
if
@@ -590,7 +640,7 @@ apply_fail_reason(FailedSig, FailedBif, FailedContract) ->
end.
get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
- Sig, Contract, ContrArgs, State, FailReason) ->
+ Sig, Contract, ContrArgs, State, FailReason, Opaques) ->
ArgStrings = format_args(Args, ArgTypes, State),
ContractInfo =
case Contract of
@@ -599,44 +649,52 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
dialyzer_contracts:contract_to_string(C)};
none -> {false, none}
end,
- EnumArgTypes =
- case NewArgTypes of
- [] -> [];
- _ -> lists:zip(lists:seq(1, length(NewArgTypes)), NewArgTypes)
- end,
+ EnumArgTypes = lists:zip(lists:seq(1, length(NewArgTypes)), NewArgTypes),
ArgNs = [Arg || {Arg, Type} <- EnumArgTypes, t_is_none(Type)],
case state__lookup_name(Fun, State) of
- {M, F, _A} ->
- case is_opaque_type_test_problem(Fun, NewArgTypes, State) of
- true ->
- [Opaque] = NewArgTypes,
- {opaque_type_test, [atom_to_list(F), erl_types:t_to_string(Opaque)]};
- false ->
+ {M, F, A} ->
+ case is_opaque_type_test_problem(Fun, Args, NewArgTypes, State) of
+ {yes, Arg, ArgType} ->
+ {opaque_type_test, [atom_to_list(F), ArgStrings,
+ format_arg(Arg), format_type(ArgType, State)]};
+ no ->
SigArgs = t_fun_args(Sig),
- case is_opaque_related_problem(ArgNs, ArgTypes) of
- true -> %% an opaque term is used where a structured term is expected
- ExpectedArgs =
- case FailReason of
- only_sig -> SigArgs;
- _ -> ContrArgs
- end,
- {call_with_opaque, [M, F, ArgStrings, ArgNs, ExpectedArgs]};
- false ->
- case is_opaque_related_problem(ArgNs, SigArgs) orelse
- is_opaque_related_problem(ArgNs, ContrArgs) of
- true -> %% a structured term is used where an opaque is expected
- ExpectedTriples =
- case FailReason of
- only_sig -> expected_arg_triples(ArgNs, SigArgs, State);
- _ -> expected_arg_triples(ArgNs, ContrArgs, State)
- end,
- {call_without_opaque, [M, F, ArgStrings, ExpectedTriples]};
- false -> %% there is a structured term clash in some argument
- {call, [M, F, ArgStrings,
- ArgNs, FailReason,
- format_sig_args(Sig, State),
- format_type(t_fun_range(Sig), State),
- ContractInfo]}
+ BadOpaque =
+ opaque_problems([SigArgs, ContrArgs], ArgTypes, Opaques, ArgNs),
+ %% In fact *both* 'call_with_opaque' and
+ %% 'call_without_opaque' are possible.
+ case lists:keyfind(decl, 1, BadOpaque) of
+ {decl, BadArgs} ->
+ %% a structured term is used where an opaque is expected
+ ExpectedTriples =
+ case FailReason of
+ only_sig -> expected_arg_triples(BadArgs, SigArgs, State);
+ _ -> expected_arg_triples(BadArgs, ContrArgs, State)
+ end,
+ {call_without_opaque, [M, F, ArgStrings, ExpectedTriples]};
+ false ->
+ case lists:keyfind(use, 1, BadOpaque) of
+ {use, BadArgs} ->
+ %% an opaque term is used where a structured term is expected
+ ExpectedArgs =
+ case FailReason of
+ only_sig -> SigArgs;
+ _ -> ContrArgs
+ end,
+ {call_with_opaque, [M, F, ArgStrings, BadArgs, ExpectedArgs]};
+ false ->
+ case
+ erl_bif_types:opaque_args(M, F, A, ArgTypes, Opaques)
+ of
+ [] -> %% there is a structured term clash in some argument
+ {call, [M, F, ArgStrings,
+ ArgNs, FailReason,
+ format_sig_args(Sig, State),
+ format_type(t_fun_range(Sig), State),
+ ContractInfo]};
+ Ns ->
+ {call_with_opaque, [M, F, ArgStrings, Ns, ContrArgs]}
+ end
end
end
end;
@@ -648,31 +706,48 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
ContractInfo]}
end.
-%% returns 'true' if we are running with opaque on (not checked yet),
-%% and there is either a contract or hard-coded type information with
-%% opaque types
-%% TODO: check that we are running with opaque types
-%% TODO: check the return type also
-prefer_opaque_types(CArgs, BifArgs) ->
- t_contains_opaque(t_product(CArgs))
- orelse t_contains_opaque(t_product(BifArgs)).
-
-is_opaque_related_problem(ArgNs, ArgTypes) ->
- Fun = fun (N) -> erl_types:t_contains_opaque(lists:nth(N, ArgTypes)) end,
- ArgNs =/= [] andalso lists:all(Fun, ArgNs).
-
-is_opaque_type_test_problem(Fun, ArgTypes, State) ->
+%% -> [{ElementI, [ArgN]}] where [ArgN] is a non-empty list of
+%% arguments containing unknown opaque types and Element is 1 or 2.
+opaque_problems(ContractOrSigList, ArgTypes, Opaques, ArgNs) ->
+ ArgElementList = find_unknown(ContractOrSigList, ArgTypes, Opaques, ArgNs),
+ F = fun(1) -> decl; (2) -> use end,
+ [{F(ElementI), lists:usort([ArgN || {ArgN, EI} <- ArgElementList,
+ EI =:= ElementI])} ||
+ ElementI <- lists:usort([EI || {_, EI} <- ArgElementList])].
+
+%% -> [{ArgN, ElementI}] where ElementI = 1 means there is an unknown
+%% opaque type in argument ArgN of the the contract/signature,
+%% and ElementI = 2 means that there is an unknown opaque type in
+%% argument ArgN of the the (current) argument types.
+find_unknown(ContractOrSigList, ArgTypes, Opaques, NoneArgNs) ->
+ ArgNs = lists:seq(1, length(ArgTypes)),
+ [{ArgN, ElementI} ||
+ ContractOrSig <- ContractOrSigList,
+ {E1, E2, ArgN} <- lists:zip3(ContractOrSig, ArgTypes, ArgNs),
+ lists:member(ArgN, NoneArgNs),
+ ElementI <- erl_types:t_find_unknown_opaque(E1, E2, Opaques)].
+
+is_opaque_type_test_problem(Fun, Args, ArgTypes, State) ->
case Fun of
{erlang, FN, 1} when FN =:= is_atom; FN =:= is_boolean;
FN =:= is_binary; FN =:= is_bitstring;
FN =:= is_float; FN =:= is_function;
FN =:= is_integer; FN =:= is_list;
FN =:= is_number; FN =:= is_pid; FN =:= is_port;
- FN =:= is_reference; FN =:= is_tuple ->
- [Type] = ArgTypes,
- erl_types:t_is_opaque(Type) andalso
- not lists:member(Type, State#state.opaques);
- _ -> false
+ FN =:= is_reference; FN =:= is_tuple;
+ FN =:= is_map ->
+ type_test_opaque_arg(Args, ArgTypes, State#state.opaques);
+ {erlang, FN, 2} when FN =:= is_function ->
+ type_test_opaque_arg(Args, ArgTypes, State#state.opaques);
+ _ -> no
+ end.
+
+type_test_opaque_arg([], [], _Opaques) ->
+ no;
+type_test_opaque_arg([Arg|Args], [ArgType|ArgTypes], Opaques) ->
+ case erl_types:t_has_opaque_subtype(ArgType, Opaques) of
+ true -> {yes, Arg, ArgType};
+ false -> type_test_opaque_arg(Args, ArgTypes, Opaques)
end.
expected_arg_triples(ArgNs, ArgTypes, State) ->
@@ -683,47 +758,56 @@ expected_arg_triples(ArgNs, ArgTypes, State) ->
add_bif_warnings({erlang, Op, 2}, [T1, T2] = Ts, Tree, State)
when Op =:= '=:='; Op =:= '==' ->
- Type1 = erl_types:t_unopaque(T1, State#state.opaques),
- Type2 = erl_types:t_unopaque(T2, State#state.opaques),
- Inf = t_inf(T1, T2),
- Inf1 = t_inf(Type1, Type2),
- case t_is_none(Inf) andalso t_is_none(Inf1) andalso(not any_none(Ts))
- andalso (not is_int_float_eq_comp(T1, Op, T2)) of
+ Opaques = State#state.opaques,
+ Inf = t_inf(T1, T2, Opaques),
+ case
+ t_is_none(Inf) andalso (not any_none(Ts))
+ andalso (not is_int_float_eq_comp(T1, Op, T2, Opaques))
+ of
true ->
- Args = case erl_types:t_is_opaque(T1) of
- true -> [format_type(T2, State), Op, format_type(T1, State)];
- false -> [format_type(T1, State), Op, format_type(T2, State)]
- end,
- case any_opaque(Ts) of
- true ->
- state__add_warning(State, ?WARN_OPAQUE, Tree, {opaque_eq, Args});
- false ->
- state__add_warning(State, ?WARN_MATCHING, Tree, {exact_eq, Args})
+ %% Give priority to opaque warning (as usual).
+ case erl_types:t_find_unknown_opaque(T1, T2, Opaques) of
+ [] ->
+ Args = comp_format_args([], T1, Op, T2, State),
+ state__add_warning(State, ?WARN_MATCHING, Tree, {exact_eq, Args});
+ Ns ->
+ Args = comp_format_args(Ns, T1, Op, T2, State),
+ state__add_warning(State, ?WARN_OPAQUE, Tree, {opaque_eq, Args})
end;
false ->
State
end;
add_bif_warnings({erlang, Op, 2}, [T1, T2] = Ts, Tree, State)
when Op =:= '=/='; Op =:= '/=' ->
- Inf = t_inf(T1, T2),
- case t_is_none(Inf) andalso (not any_none(Ts))
- andalso (not is_int_float_eq_comp(T1, Op, T2)) andalso any_opaque(Ts) of
+ Opaques = State#state.opaques,
+ case
+ (not any_none(Ts))
+ andalso (not is_int_float_eq_comp(T1, Op, T2, Opaques))
+ of
true ->
- Args = case erl_types:t_is_opaque(T1) of
- true -> [format_type(T2, State), Op, format_type(T1, State)];
- false -> [format_type(T1, State), Op, format_type(T2, State)]
- end,
- state__add_warning(State, ?WARN_OPAQUE, Tree, {opaque_neq, Args});
+ case erl_types:t_find_unknown_opaque(T1, T2, Opaques) of
+ [] -> State;
+ Ns ->
+ Args = comp_format_args(Ns, T1, Op, T2, State),
+ state__add_warning(State, ?WARN_OPAQUE, Tree, {opaque_neq, Args})
+ end;
false ->
State
end;
add_bif_warnings(_, _, _, State) ->
State.
-is_int_float_eq_comp(T1, Op, T2) ->
+is_int_float_eq_comp(T1, Op, T2, Opaques) ->
(Op =:= '==' orelse Op =:= '/=') andalso
- ((erl_types:t_is_float(T1) andalso erl_types:t_is_integer(T2)) orelse
- (erl_types:t_is_integer(T1) andalso erl_types:t_is_float(T2))).
+ ((erl_types:t_is_float(T1, Opaques)
+ andalso t_is_integer(T2, Opaques)) orelse
+ (t_is_integer(T1, Opaques)
+ andalso erl_types:t_is_float(T2, Opaques))).
+
+comp_format_args([1|_], T1, Op, T2, State) ->
+ [format_type(T2, State), Op, format_type(T1, State)];
+comp_format_args(_, T1, Op, T2, State) ->
+ [format_type(T1, State), Op, format_type(T2, State)].
%%----------------------------------------
@@ -784,16 +868,27 @@ handle_bitstr(Tree, Map, State) ->
{State3, Map2, t_none()};
false ->
UnitVal = cerl:concrete(cerl:bitstr_unit(Tree)),
- Type =
- case t_number_vals(SizeType) of
- [OneSize] -> t_bitstr(0, OneSize * UnitVal);
- _ ->
- MinSize = erl_types:number_min(SizeType),
- t_bitstr(UnitVal, UnitVal * MinSize)
- end,
+ Opaques = State2#state.opaques,
+ NumberVals = t_number_vals(SizeType, Opaques),
+ {State3, Type} =
+ case t_contains_opaque(SizeType, Opaques) of
+ true ->
+ Msg = {opaque_size, [format_type(SizeType, State2),
+ format_cerl(Size)]},
+ {state__add_warning(State2, ?WARN_OPAQUE, Size, Msg),
+ t_none()};
+ false ->
+ case NumberVals of
+ [OneSize] -> {State2, t_bitstr(0, OneSize * UnitVal)};
+ unknown -> {State2, t_bitstr()};
+ _ ->
+ MinSize = erl_types:number_min(SizeType, Opaques),
+ {State2, t_bitstr(UnitVal, UnitVal * MinSize)}
+ end
+ end,
Map3 = enter_type_lists([Val, Size, Tree],
[ValType, SizeType, Type], Map2),
- {State2, Map3, Type}
+ {State3, Map3, Type}
end
end.
@@ -805,34 +900,47 @@ handle_call(Tree, Map, State) ->
Args = cerl:call_args(Tree),
MFAList = [M, F|Args],
{State1, Map1, [MType0, FType0|As]} = traverse_list(MFAList, Map, State),
- %% Module and function names should be treated as *structured terms*
- %% even if they happen to be identical to an atom (or tuple) which
- %% is also involved in the definition of an opaque data type.
- MType = t_inf(t_module(), t_unopaque(MType0)),
- FType = t_inf(t_atom(), t_unopaque(FType0)),
+ Opaques = State#state.opaques,
+ MType = t_inf(t_module(), MType0, Opaques),
+ FType = t_inf(t_atom(), FType0, Opaques),
Map2 = enter_type_lists([M, F], [MType, FType], Map1),
+ MOpaque = t_is_none(MType) andalso (not t_is_none(MType0)),
+ FOpaque = t_is_none(FType) andalso (not t_is_none(FType0)),
case any_none([MType, FType|As]) of
true ->
State2 =
- case t_is_none(MType) andalso (not t_is_none(MType0)) of
- true -> % This is a problem we just detected; not a known one
- MS = format_cerl(M),
- Msg = {app_call, [MS, format_cerl(F),
- format_args(Args, As, State1),
- MS, format_type(t_module(), State1),
- format_type(MType0, State1)]},
- state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg);
- false ->
- case t_is_none(FType) andalso (not t_is_none(FType0)) of
- true ->
- FS = format_cerl(F),
- Msg = {app_call, [format_cerl(M), FS,
- format_args(Args, As, State1),
- FS, format_type(t_atom(), State1),
- format_type(FType0, State1)]},
- state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg);
- false -> State1
- end
+ if
+ MOpaque -> % This is a problem we just detected; not a known one
+ MS = format_cerl(M),
+ case t_is_none(t_inf(t_module(), MType0)) of
+ true ->
+ Msg = {app_call, [MS, format_cerl(F),
+ format_args(Args, As, State1),
+ MS, format_type(t_module(), State1),
+ format_type(MType0, State1)]},
+ state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg);
+ false ->
+ Msg = {opaque_call, [MS, format_cerl(F),
+ format_args(Args, As, State1),
+ MS, format_type(MType0, State1)]},
+ state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg)
+ end;
+ FOpaque ->
+ FS = format_cerl(F),
+ case t_is_none(t_inf(t_atom(), FType0)) of
+ true ->
+ Msg = {app_call, [format_cerl(M), FS,
+ format_args(Args, As, State1),
+ FS, format_type(t_atom(), State1),
+ format_type(FType0, State1)]},
+ state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg);
+ false ->
+ Msg = {opaque_call, [format_cerl(M), FS,
+ format_args(Args, As, State1),
+ FS, format_type(FType0, State1)]},
+ state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg)
+ end;
+ true -> State1
end,
{State2, Map2, t_none()};
false ->
@@ -874,7 +982,7 @@ handle_case(Tree, Map, State) ->
handle_clauses(Clauses, Arg, ArgType, ArgType, State2,
[], Map2, [], []),
Map3 = join_maps_end(MapList, Map2),
- debug_pp_map(Map2),
+ debug_pp_map(Map3),
{State3, Map3, Type}
end.
@@ -886,7 +994,7 @@ handle_cons(Tree, Map, State) ->
{State1, Map1, HdType} = traverse(Hd, Map, State),
{State2, Map2, TlType} = traverse(Tl, Map1, State1),
State3 =
- case t_is_none(t_inf(TlType, t_list())) of
+ case t_is_none(t_inf(TlType, t_list(), State2#state.opaques)) of
true ->
Msg = {improper_list_constr, [format_type(TlType, State2)]},
state__add_warning(State2, ?WARN_NON_PROPER_LIST, Tree, Msg);
@@ -979,8 +1087,9 @@ handle_receive(Tree, Map, State) ->
[], []),
Map1 = join_maps(MapList, Map),
{State3, Map2, TimeoutType} = traverse(Timeout, Map1, State2),
- case (t_is_atom(TimeoutType) andalso
- (t_atom_vals(TimeoutType) =:= ['infinity'])) of
+ Opaques = State3#state.opaques,
+ case (t_is_atom(TimeoutType, Opaques) andalso
+ (t_atom_vals(TimeoutType, Opaques) =:= ['infinity'])) of
true ->
{State3, Map2, ReceiveType};
false ->
@@ -1023,6 +1132,58 @@ handle_try(Tree, Map, State) ->
%%----------------------------------------
+handle_map(Tree,Map,State) ->
+ Pairs = cerl:map_es(Tree),
+ Arg = cerl:map_arg(Tree),
+ {State1, Map1, ArgType} = traverse(Arg, Map, State),
+ ArgType1 = t_inf(t_map(), ArgType),
+ case t_is_none_or_unit(ArgType1) of
+ true ->
+ {State1, Map1, ArgType1};
+ false ->
+ {State2, Map2, TypePairs, ExactKeys} =
+ traverse_map_pairs(Pairs, Map1, State1, t_none(), [], []),
+ InsertPair = fun({KV,assoc,_},Acc) -> erl_types:t_map_put(KV,Acc);
+ ({KV,exact,KVTree},Acc) ->
+ case t_is_none(T=erl_types:t_map_update(KV,Acc)) of
+ true -> throw({none, Acc, KV, KVTree});
+ false -> T
+ end
+ end,
+ try lists:foldl(InsertPair, ArgType1, TypePairs)
+ of ResT ->
+ BindT = t_map([{K, t_any()} || K <- ExactKeys]),
+ case bind_pat_vars_reverse([Arg], [BindT], [], Map2, State2) of
+ {error, _, _, _, _} -> {State2, Map2, ResT};
+ {Map3, _} -> {State2, Map3, ResT}
+ end
+ catch {none, MapType, {K,_}, KVTree} ->
+ Msg2 = {map_update, [format_type(MapType, State2),
+ format_type(K, State2)]},
+ {state__add_warning(State2, ?WARN_MAP_CONSTRUCTION, KVTree, Msg2),
+ Map2, t_none()}
+ end
+ end.
+
+traverse_map_pairs([], Map, State, _ShadowKeys, PairAcc, KeyAcc) ->
+ {State, Map, lists:reverse(PairAcc), KeyAcc};
+traverse_map_pairs([Pair|Pairs], Map, State, ShadowKeys, PairAcc, KeyAcc) ->
+ Key = cerl:map_pair_key(Pair),
+ Val = cerl:map_pair_val(Pair),
+ Op = cerl:map_pair_op(Pair),
+ {State1, Map1, [K,V]} = traverse_list([Key,Val],Map,State),
+ KeyAcc1 =
+ case cerl:is_literal(Op) andalso cerl:concrete(Op) =:= exact andalso
+ t_is_singleton(K, State#state.opaques) andalso
+ t_is_none(t_inf(ShadowKeys, K)) of
+ true -> [K|KeyAcc];
+ false -> KeyAcc
+ end,
+ traverse_map_pairs(Pairs, Map1, State1, t_sup(K, ShadowKeys),
+ [{{K,V},cerl:concrete(Op),Pair}|PairAcc], KeyAcc1).
+
+%%----------------------------------------
+
handle_tuple(Tree, Map, State) ->
Elements = cerl:tuple_es(Tree),
{State1, Map1, EsType} = traverse_list(Elements, Map, State),
@@ -1031,55 +1192,46 @@ handle_tuple(Tree, Map, State) ->
true ->
{State1, Map1, t_none()};
false ->
- %% Let's find out if this is a record or opaque construction.
+ %% Let's find out if this is a record
case Elements of
[Tag|Left] ->
- case cerl:is_c_atom(Tag) of
+ case cerl:is_c_atom(Tag) andalso is_literal_record(Tree) of
true ->
TagVal = cerl:atom_val(Tag),
- case t_opaque_match_record(TupleType, State1#state.opaques) of
- [Opaque] ->
- RecStruct = t_opaque_matching_structure(TupleType, Opaque),
- RecFields = t_tuple_args(RecStruct),
- case bind_pat_vars(Elements, RecFields, [], Map1, State1) of
- {error, _, ErrorPat, ErrorType, _} ->
- Msg = {record_constr,
- [TagVal, format_patterns(ErrorPat),
- format_type(ErrorType, State1)]},
- State2 = state__add_warning(State1, ?WARN_MATCHING,
- Tree, Msg),
- {State2, Map1, t_none()};
- {Map2, _ETypes} ->
- {State1, Map2, Opaque}
- end;
- _ ->
- case state__lookup_record(TagVal, length(Left), State1) of
- error -> {State1, Map1, TupleType};
- {ok, RecType} ->
- InfTupleType = t_inf(RecType, TupleType),
- case t_is_none(InfTupleType) of
- true ->
- RecC = format_type(TupleType, State1),
- FieldDiffs = format_field_diffs(TupleType, State1),
- Msg = {record_constr, [RecC, FieldDiffs]},
- State2 = state__add_warning(State1, ?WARN_MATCHING,
- Tree, Msg),
- {State2, Map1, t_none()};
- false ->
- case bind_pat_vars(Elements, t_tuple_args(RecType),
- [], Map1, State1) of
- {error, bind, ErrorPat, ErrorType, _} ->
- Msg = {record_constr,
- [TagVal, format_patterns(ErrorPat),
- format_type(ErrorType, State1)]},
- State2 = state__add_warning(State1, ?WARN_MATCHING,
- Tree, Msg),
- {State2, Map1, t_none()};
- {Map2, ETypes} ->
- {State1, Map2, t_tuple(ETypes)}
- end
- end
- end
+ case state__lookup_record(TagVal, length(Left), State1) of
+ error -> {State1, Map1, TupleType};
+ {ok, RecType} ->
+ InfTupleType = t_inf(RecType, TupleType),
+ case t_is_none(InfTupleType) of
+ true ->
+ RecC = format_type(TupleType, State1),
+ FieldDiffs = format_field_diffs(TupleType, State1),
+ Msg = {record_constr, [RecC, FieldDiffs]},
+ State2 = state__add_warning(State1, ?WARN_MATCHING,
+ Tree, Msg),
+ {State2, Map1, t_none()};
+ false ->
+ case bind_pat_vars(Elements, t_tuple_args(RecType),
+ [], Map1, State1) of
+ {error, bind, ErrorPat, ErrorType, _} ->
+ Msg = {record_constr,
+ [TagVal, format_patterns(ErrorPat),
+ format_type(ErrorType, State1)]},
+ State2 = state__add_warning(State1, ?WARN_MATCHING,
+ Tree, Msg),
+ {State2, Map1, t_none()};
+ {error, opaque, ErrorPat, ErrorType, OpaqueType} ->
+ Msg = {opaque_match,
+ [format_patterns(ErrorPat),
+ format_type(ErrorType, State1),
+ format_type(OpaqueType, State1)]},
+ State2 = state__add_warning(State1, ?WARN_OPAQUE,
+ Tree, Msg),
+ {State2, Map1, t_none()};
+ {Map2, ETypes} ->
+ {State1, Map2, t_tuple(ETypes)}
+ end
+ end
end;
false ->
{State1, Map1, t_tuple(EsType)}
@@ -1173,15 +1325,10 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) ->
false ->
{State1, Map, t_none(), ArgType0};
true ->
- PatString =
- case ErrorType of
- bind -> format_patterns(Pats);
- record -> format_patterns(Pats);
- opaque -> format_patterns(NewPats)
- end,
{Msg, Force} =
case t_is_none(ArgType0) of
true ->
+ PatString = format_patterns(Pats),
PatTypes = [PatString, format_type(OrigArgType, State1)],
%% See if this is covered by an earlier clause or if it
%% simply cannot match
@@ -1231,6 +1378,12 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) ->
false ->
true
end,
+ PatString =
+ case ErrorType of
+ bind -> format_patterns(Pats);
+ record -> format_patterns(NewPats);
+ opaque -> format_patterns(NewPats)
+ end,
PatTypes = case ErrorType of
bind -> [PatString, format_type(ArgType0, State1)];
record -> [PatString, format_type(Type, State1)];
@@ -1356,11 +1509,15 @@ bind_pat_vars_reverse(Pats, Types, Acc, Map, State) ->
end.
bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
- ?debug("Binding pat: ~w to ~s\n", [cerl:type(Pat), format_type(Type, State)]),
+ ?debug("Binding pat: ~w to ~s\n", [cerl:type(Pat), format_type(Type, State)]
+),
+ Opaques = State#state.opaques,
{NewMap, TypeOut} =
case cerl:type(Pat) of
alias ->
- AliasPat = cerl:alias_pat(Pat),
+ %% Map patterns are more allowing than the type of their literal. We
+ %% must unfold AliasPat if it is a literal.
+ AliasPat = dialyzer_utils:refold_pattern(cerl:alias_pat(Pat)),
Var = cerl:alias_var(Pat),
Map1 = enter_subst(Var, AliasPat, Map),
{Map2, [PatType]} = bind_pat_vars([AliasPat], [Type], [],
@@ -1372,9 +1529,15 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
case Rev of
true -> {Map, t_bitstr()};
false ->
- BinType = t_inf(t_bitstr(), Type),
+ BinType = t_inf(t_bitstr(), Type, Opaques),
case t_is_none(BinType) of
- true -> bind_error([Pat], Type, t_none(), bind);
+ true ->
+ case t_find_opaque_mismatch(t_bitstr(), Type, Opaques) of
+ {ok, T1, T2} ->
+ bind_error([Pat], T1, T2, opaque);
+ error ->
+ bind_error([Pat], Type, t_none(), bind)
+ end;
false ->
Segs = cerl:binary_segments(Pat),
{Map1, SegTypes} = bind_bin_segs(Segs, BinType, Map, State),
@@ -1382,28 +1545,71 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
end
end;
cons ->
- Cons = t_inf(Type, t_cons()),
+ Cons = t_inf(Type, t_cons(), Opaques),
case t_is_none(Cons) of
true ->
- bind_opaque_pats(t_cons(), Type, Pat, Map, State, Rev);
+ bind_opaque_pats(t_cons(), Type, Pat, State);
false ->
{Map1, [HdType, TlType]} =
bind_pat_vars([cerl:cons_hd(Pat), cerl:cons_tl(Pat)],
- [t_cons_hd(Cons), t_cons_tl(Cons)],
+ [t_cons_hd(Cons, Opaques),
+ t_cons_tl(Cons, Opaques)],
[], Map, State, Rev),
{Map1, t_cons(HdType, TlType)}
end;
literal ->
- Literal = literal_type(Pat),
- LiteralOrOpaque =
- case t_opaque_match_atom(Literal, State#state.opaques) of
- [Opaque] -> Opaque;
- _ -> Literal
- end,
- case t_is_none(t_inf(LiteralOrOpaque, Type)) of
+ Pat0 = dialyzer_utils:refold_pattern(Pat),
+ case cerl:is_literal(Pat0) of
true ->
- bind_opaque_pats(Literal, Type, Pat, Map, State, Rev);
- false -> {Map, LiteralOrOpaque}
+ Literal = literal_type(Pat),
+ case t_is_none(t_inf(Literal, Type, Opaques)) of
+ true ->
+ bind_opaque_pats(Literal, Type, Pat, State);
+ false -> {Map, Literal}
+ end;
+ false ->
+ %% Retry with the unfolded pattern
+ {Map1, [PatType]}
+ = bind_pat_vars([Pat0], [Type], [], Map, State, Rev),
+ {Map1, PatType}
+ end;
+ map ->
+ MapT = t_inf(Type, t_map(), Opaques),
+ case t_is_none(MapT) of
+ true ->
+ bind_opaque_pats(t_map(), Type, Pat, State);
+ false ->
+ case Rev of
+ %% TODO: Reverse matching (propagating a matched subset back to a value)
+ true -> {Map, MapT};
+ false ->
+ FoldFun =
+ fun(Pair, {MapAcc, ListAcc}) ->
+ %% Only exact (:=) can appear in patterns
+ exact = cerl:concrete(cerl:map_pair_op(Pair)),
+ Key = cerl:map_pair_key(Pair),
+ KeyType =
+ case cerl:type(Key) of
+ var ->
+ case state__lookup_type_for_letrec(Key, State) of
+ error -> lookup_type(Key, MapAcc);
+ {ok, RecType} -> RecType
+ end;
+ literal ->
+ literal_type(Key)
+ end,
+ Bind = erl_types:t_map_get(KeyType, MapT),
+ {MapAcc1, [ValType]} =
+ bind_pat_vars([cerl:map_pair_val(Pair)],
+ [Bind], [], MapAcc, State, Rev),
+ case t_is_singleton(KeyType, Opaques) of
+ true -> {MapAcc1, [{KeyType, ValType}|ListAcc]};
+ false -> {MapAcc1, ListAcc}
+ end
+ end,
+ {Map1, Pairs} = lists:foldl(FoldFun, {Map, []}, cerl:map_es(Pat)),
+ {Map1, t_inf(MapT, t_map(Pairs))}
+ end
end;
tuple ->
Es = cerl:tuple_es(Pat),
@@ -1411,7 +1617,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
case Es of
[] -> {false, t_tuple([])};
[Tag|Left] ->
- case cerl:is_c_atom(Tag) of
+ case cerl:is_c_atom(Tag) andalso is_literal_record(Pat) of
true ->
TagAtom = cerl:atom_val(Tag),
case state__lookup_record(TagAtom, length(Left), State) of
@@ -1419,27 +1625,28 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
{ok, Record} ->
[_Head|AnyTail] = [t_any() || _ <- Es],
UntypedRecord = t_tuple([t_atom(TagAtom)|AnyTail]),
- {not erl_types:t_is_equal(Record, UntypedRecord), Record}
+ {not t_is_equal(Record, UntypedRecord), Record}
end;
false -> {false, t_tuple(length(Es))}
end
end,
- Tuple = t_inf(Prototype, Type),
+ Tuple = t_inf(Prototype, Type, Opaques),
case t_is_none(Tuple) of
true ->
- bind_opaque_pats(Prototype, Type, Pat, Map, State, Rev);
+ bind_opaque_pats(Prototype, Type, Pat, State);
false ->
- SubTuples = t_tuple_subtypes(Tuple),
+ SubTuples = t_tuple_subtypes(Tuple, Opaques),
%% Need to call the top function to get the try-catch wrapper
MapJ = join_maps_begin(Map),
Results =
case Rev of
true ->
- [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple), [],
- MapJ, State)
+ [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple, Opaques),
+ [], MapJ, State)
|| SubTuple <- SubTuples];
false ->
- [bind_pat_vars(Es, t_tuple_args(SubTuple), [], MapJ, State)
+ [bind_pat_vars(Es, t_tuple_args(SubTuple, Opaques), [],
+ MapJ, State)
|| SubTuple <- SubTuples]
end,
case lists:keyfind(opaque, 2, Results) of
@@ -1466,47 +1673,24 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
bind_pat_vars(Es, t_to_tlist(Type), [], Map, State, Rev),
{Map1, t_product(EsTypes)};
var ->
- Opaques = State#state.opaques,
VarType1 =
- case state__lookup_type_for_rec_var(Pat, State) of
- error ->
- LType = lookup_type(Pat, Map),
- case t_opaque_match_record(LType, Opaques) of
- [Opaque] -> Opaque;
- _ ->
- case t_opaque_match_atom(LType, Opaques) of
- [Opaque] -> Opaque;
- _ -> LType
- end
- end;
+ case state__lookup_type_for_letrec(Pat, State) of
+ error -> lookup_type(Pat, Map);
{ok, RecType} -> RecType
end,
%% Must do inf when binding args to pats. Vars in pats are fresh.
- VarType2 = t_inf(VarType1, Type),
- VarType3 =
- case Opaques =/= [] of
- true ->
- case t_opaque_match_record(VarType2, Opaques) of
- [OpaqueRec] -> OpaqueRec;
- _ ->
- case t_opaque_match_atom(VarType2, Opaques) of
- [OpaqueAtom] -> OpaqueAtom;
- _ -> VarType2
- end
- end;
- false -> VarType2
- end,
- case t_is_none(VarType3) of
+ VarType2 = t_inf(VarType1, Type, Opaques),
+ case t_is_none(VarType2) of
true ->
- case t_find_opaque_mismatch(VarType1, Type) of
+ case t_find_opaque_mismatch(VarType1, Type, Opaques) of
{ok, T1, T2} ->
bind_error([Pat], T1, T2, opaque);
error ->
bind_error([Pat], Type, t_none(), bind)
end;
false ->
- Map1 = enter_type(Pat, VarType3, Map),
- {Map1, VarType3}
+ Map1 = enter_type(Pat, VarType2, Map),
+ {Map1, VarType2}
end;
_Other ->
%% Catch all is needed when binding args to pats
@@ -1529,7 +1713,8 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) ->
binary = SegType, [] = Segs, %% just an assert
T = t_inf(t_bitstr(UnitVal, 0), BinType),
{Map1, [Type]} = bind_pat_vars([Val], [T], [], Map, State, false),
- bind_bin_segs(Segs, t_bitstr(0, 0), [Type|Acc], Map1, State);
+ Type1 = remove_local_opaque_types(Type, State#state.opaques),
+ bind_bin_segs(Segs, t_bitstr(0, 0), [Type1|Acc], Map1, State);
utf -> % XXX: possibly can be strengthened
true = lists:member(SegType, [utf8, utf16, utf32]),
{Map1, [_]} = bind_pat_vars([Val], [t_integer()], [], Map, State, false),
@@ -1539,11 +1724,17 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) ->
Size = cerl:bitstr_size(Seg),
{Map1, [SizeType]} =
bind_pat_vars([Size], [t_non_neg_integer()], [], Map, State, false),
+ Opaques = State#state.opaques,
+ NumberVals = t_number_vals(SizeType, Opaques),
+ case t_contains_opaque(SizeType, Opaques) of
+ true -> bind_error([Seg], SizeType, t_none(), opaque);
+ false -> ok
+ end,
Type =
- case t_number_vals(SizeType) of
+ case NumberVals of
[OneSize] -> t_bitstr(0, UnitVal * OneSize);
- _ ->
- MinSize = erl_types:number_min(SizeType),
+ _ -> % 'unknown' too
+ MinSize = erl_types:number_min(SizeType, Opaques),
t_bitstr(UnitVal, UnitVal * MinSize)
end,
ValConstr =
@@ -1551,16 +1742,24 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) ->
binary -> Type; %% The same constraints as for the whole bitstr
float -> t_float();
integer ->
- case t_number_vals(SizeType) of
+ case NumberVals of
unknown -> t_integer();
List ->
SizeVal = lists:max(List),
Flags = cerl:concrete(cerl:bitstr_flags(Seg)),
N = SizeVal * UnitVal,
- case lists:member(signed, Flags) of
- true -> t_from_range(-(1 bsl (N - 1)), 1 bsl (N - 1) - 1);
- false -> t_from_range(0, 1 bsl N - 1)
- end
+ case N >= ?BITS of
+ true ->
+ case lists:member(signed, Flags) of
+ true -> t_from_range(neg_inf, pos_inf);
+ false -> t_from_range(0, pos_inf)
+ end;
+ false ->
+ case lists:member(signed, Flags) of
+ true -> t_from_range(-(1 bsl (N - 1)), 1 bsl (N - 1) - 1);
+ false -> t_from_range(0, 1 bsl N - 1)
+ end
+ end
end
end,
{Map2, [_]} = bind_pat_vars([Val], [ValConstr], [], Map1, State, false),
@@ -1573,21 +1772,26 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) ->
bind_bin_segs([], _BinType, Acc, Map, _State) ->
{Map, lists:reverse(Acc)}.
-bind_error(Pats, Type, OpaqueType, Error) ->
+bind_error(Pats, Type, OpaqueType, Error0) ->
+ Error = case {Error0, Pats} of
+ {bind, [Pat]} ->
+ case is_literal_record(Pat) of
+ true -> record;
+ false -> Error0
+ end;
+ _ -> Error0
+ end,
throw({error, Error, Pats, Type, OpaqueType}).
-bind_opaque_pats(GenType, Type, Pat, Map, State, Rev) ->
- case t_find_opaque_mismatch(GenType, Type) of
+-spec bind_opaque_pats(type(), type(), cerl:c_literal(), state()) ->
+ no_return().
+
+bind_opaque_pats(GenType, Type, Pat, State) ->
+ case t_find_opaque_mismatch(GenType, Type, State#state.opaques) of
{ok, T1, T2} ->
- case lists:member(T2, State#state.opaques) of
- true ->
- NewType = erl_types:t_struct_from_opaque(Type, [T2]),
- {Map1, _} =
- bind_pat_vars([Pat], [NewType], [], Map, State, Rev),
- {Map1, T2};
- false -> bind_error([Pat], T1, T2, opaque)
- end;
- error -> bind_error([Pat], Type, t_none(), bind)
+ bind_error([Pat], T1, T2, opaque);
+ error ->
+ bind_error([Pat], Type, t_none(), bind)
end.
%%----------------------------------------
@@ -1595,7 +1799,7 @@ bind_opaque_pats(GenType, Type, Pat, Map, State, Rev) ->
%%
bind_guard(Guard, Map, State) ->
- try bind_guard(Guard, Map, dict:new(), pos, State) of
+ try bind_guard(Guard, Map, maps:new(), pos, State) of
{Map1, _Type} -> Map1
catch
throw:{fail, Warning} -> {error, Warning};
@@ -1623,18 +1827,63 @@ bind_guard(Guard, Map, Env, Eval, State) ->
'try' ->
Arg = cerl:try_arg(Guard),
[Var] = cerl:try_vars(Guard),
+ EVars = cerl:try_evars(Guard),
%%?debug("Storing: ~w\n", [Var]),
- NewEnv = dict:store(get_label(Var), Arg, Env),
- bind_guard(cerl:try_body(Guard), Map, NewEnv, Eval, State);
+ Map1 = join_maps_begin(Map),
+ Map2 = mark_as_fresh(EVars, Map1),
+ %% Visit handler first so we know if it should be ignored
+ {{HandlerMap, HandlerType}, HandlerE} =
+ try {bind_guard(cerl:try_handler(Guard), Map2, Env, Eval, State), none}
+ catch throw:HE ->
+ {{Map2, t_none()}, HE}
+ end,
+ BodyEnv = maps:put(get_label(Var), Arg, Env),
+ Wanted = case Eval of pos -> t_atom(true); neg -> t_atom(false);
+ dont_know -> t_any() end,
+ case t_is_none(t_inf(HandlerType, Wanted)) of
+ %% Handler won't save us; pretend it does not exist
+ true -> bind_guard(cerl:try_body(Guard), Map, BodyEnv, Eval, State);
+ false ->
+ {{BodyMap, BodyType}, BodyE} =
+ try {bind_guard(cerl:try_body(Guard), Map1, BodyEnv,
+ Eval, State), none}
+ catch throw:BE ->
+ {{Map1, t_none()}, BE}
+ end,
+ Map3 = join_maps_end([BodyMap, HandlerMap], Map1),
+ case t_is_none(Sup = t_sup(BodyType, HandlerType)) of
+ true ->
+ %% Pick a reason. N.B. We assume that the handler is always
+ %% compiler-generated if the body is; that way, we won't need to
+ %% check.
+ Fatality = case {BodyE, HandlerE} of
+ {{fatal_fail, _}, _} -> fatal_fail;
+ {_, {fatal_fail, _}} -> fatal_fail;
+ _ -> fail
+ end,
+ throw({Fatality,
+ case {BodyE, HandlerE} of
+ {{_, Rsn}, _} when Rsn =/= none -> Rsn;
+ {_, {_,Rsn}} -> Rsn;
+ _ -> none
+ end});
+ false -> {Map3, Sup}
+ end
+ end;
tuple ->
Es0 = cerl:tuple_es(Guard),
{Map1, Es} = bind_guard_list(Es0, Map, Env, dont_know, State),
{Map1, t_tuple(Es)};
+ map ->
+ case Eval of
+ dont_know -> handle_guard_map(Guard, Map, Env, State);
+ _PosOrNeg -> {Map, t_none()} %% Map exprs do not produce bools
+ end;
'let' ->
Arg = cerl:let_arg(Guard),
[Var] = cerl:let_vars(Guard),
%%?debug("Storing: ~w\n", [Var]),
- NewEnv = dict:store(get_label(Var), Arg, Env),
+ NewEnv = maps:put(get_label(Var), Arg, Env),
bind_guard(cerl:let_body(Guard), Map, NewEnv, Eval, State);
values ->
Es = cerl:values_es(Guard),
@@ -1643,7 +1892,7 @@ bind_guard(Guard, Map, Env, Eval, State) ->
{Map, Type};
var ->
?debug("Looking for var(~w)...", [cerl_trees:get_label(Guard)]),
- case dict:find(get_label(Guard), Env) of
+ case maps:find(get_label(Guard), Env) of
error ->
?debug("Did not find it\n", []),
Type = lookup_type(Guard, Map),
@@ -1672,7 +1921,7 @@ handle_guard_call(Guard, Map, Env, Eval, State) ->
{erlang, F, 1} when F =:= is_atom; F =:= is_boolean;
F =:= is_binary; F =:= is_bitstring;
F =:= is_float; F =:= is_function;
- F =:= is_integer; F =:= is_list;
+ F =:= is_integer; F =:= is_list; F =:= is_map;
F =:= is_number; F =:= is_pid; F =:= is_port;
F =:= is_reference; F =:= is_tuple ->
handle_guard_type_test(Guard, F, Map, Env, Eval, State);
@@ -1700,19 +1949,9 @@ handle_guard_call(Guard, Map, Env, Eval, State) ->
handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) ->
Args = cerl:call_args(Guard),
- {Map1, As0} = bind_guard_list(Args, Map, Env, dont_know, State),
- MapFun = fun(Type) ->
- case lists:member(Type, State#state.opaques) of
- true -> erl_types:t_opaque_structure(Type);
- false -> Type
- end
- end,
- As = lists:map(MapFun, As0),
- Mode = case As =:= As0 of
- true -> structured;
- false -> opaque
- end,
- BifRet = erl_bif_types:type(M, F, A, As),
+ {Map1, As} = bind_guard_list(Args, Map, Env, dont_know, State),
+ Opaques = State#state.opaques,
+ BifRet = erl_bif_types:type(M, F, A, As, Opaques),
case t_is_none(BifRet) of
true ->
%% Is this an error-bif?
@@ -1721,11 +1960,8 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) ->
false -> signal_guard_fatal_fail(Eval, Guard, As, State)
end;
false ->
- BifArgs = case erl_bif_types:arg_types(M, F, A) of
- unknown -> lists:duplicate(A, t_any());
- List -> List
- end,
- Map2 = enter_type_lists(Args, t_inf_lists(BifArgs, As0, Mode), Map1),
+ BifArgs = bif_args(M, F, A),
+ Map2 = enter_type_lists(Args, t_inf_lists(BifArgs, As, Opaques), Map1),
Ret =
case Eval of
pos -> t_inf(t_atom(true), BifRet);
@@ -1765,35 +2001,26 @@ bind_type_test(Eval, TypeTest, ArgType, State) ->
is_function -> t_fun();
is_integer -> t_integer();
is_list -> t_maybe_improper_list();
+ is_map -> t_map();
is_number -> t_number();
is_pid -> t_pid();
is_port -> t_port();
is_reference -> t_reference();
is_tuple -> t_tuple()
end,
- Mode = determine_mode(ArgType, State#state.opaques),
case Eval of
pos ->
- Inf = t_inf(Type, ArgType, Mode),
+ Inf = t_inf(Type, ArgType, State#state.opaques),
case t_is_none(Inf) of
true -> error;
false -> {ok, Inf, t_atom(true)}
end;
neg ->
- case Mode of
- opaque ->
- Struct = erl_types:t_opaque_structure(ArgType),
- case t_is_none(t_subtract(Struct, Type)) of
- true -> error;
- false -> {ok, ArgType, t_atom(false)}
- end;
- structured ->
- Sub = t_subtract(ArgType, Type),
- case t_is_none(Sub) of
- true -> error;
- false -> {ok, Sub, t_atom(false)}
- end
- end;
+ Sub = t_subtract(ArgType, Type),
+ case t_is_none(Sub) of
+ true -> error;
+ false -> {ok, Sub, t_atom(false)}
+ end;
dont_know ->
{ok, ArgType, t_boolean()}
end.
@@ -1802,12 +2029,13 @@ handle_guard_comp(Guard, Comp, Map, Env, Eval, State) ->
Args = cerl:call_args(Guard),
[Arg1, Arg2] = Args,
{Map1, ArgTypes} = bind_guard_list(Args, Map, Env, dont_know, State),
+ Opaques = State#state.opaques,
[Type1, Type2] = ArgTypes,
- IsInt1 = t_is_integer(Type1),
- IsInt2 = t_is_integer(Type2),
- case {cerl:type(Arg1), cerl:type(Arg2)} of
- {literal, literal} ->
- case erlang:Comp(cerl:concrete(Arg1), cerl:concrete(Arg2)) of
+ IsInt1 = t_is_integer(Type1, Opaques),
+ IsInt2 = t_is_integer(Type2, Opaques),
+ case {type(Arg1), type(Arg2)} of
+ {{literal, Lit1}, {literal, Lit2}} ->
+ case erlang:Comp(cerl:concrete(Lit1), cerl:concrete(Lit2)) of
true when Eval =:= pos -> {Map, t_atom(true)};
true when Eval =:= dont_know -> {Map, t_atom(true)};
true when Eval =:= neg -> {Map, t_atom(true)};
@@ -1816,13 +2044,14 @@ handle_guard_comp(Guard, Comp, Map, Env, Eval, State) ->
false when Eval =:= dont_know -> {Map, t_atom(false)};
false when Eval =:= neg -> {Map, t_atom(false)}
end;
- {literal, var} when IsInt1 andalso IsInt2 andalso (Eval =:= pos) ->
- case bind_comp_literal_var(Arg1, Arg2, Type2, Comp, Map1) of
+ {{literal, Lit1}, var} when IsInt1 andalso IsInt2 andalso (Eval =:= pos) ->
+ case bind_comp_literal_var(Lit1, Arg2, Type2, Comp, Map1, Opaques) of
error -> signal_guard_fail(Eval, Guard, ArgTypes, State);
{ok, NewMap} -> {NewMap, t_atom(true)}
end;
- {var, literal} when IsInt1 andalso IsInt2 andalso (Eval =:= pos) ->
- case bind_comp_literal_var(Arg2, Arg1, Type1, invert_comp(Comp), Map1) of
+ {var, {literal, Lit2}} when IsInt1 andalso IsInt2 andalso (Eval =:= pos) ->
+ case bind_comp_literal_var(Lit2, Arg1, Type1, invert_comp(Comp),
+ Map1, Opaques) of
error -> signal_guard_fail(Eval, Guard, ArgTypes, State);
{ok, NewMap} -> {NewMap, t_atom(true)}
end;
@@ -1835,10 +2064,10 @@ invert_comp('<') -> '>';
invert_comp('>=') -> '=<';
invert_comp('>') -> '<'.
-bind_comp_literal_var(Lit, Var, VarType, CompOp, Map) ->
+bind_comp_literal_var(Lit, Var, VarType, CompOp, Map, Opaques) ->
LitVal = cerl:concrete(Lit),
NewVarType =
- case t_number_vals(VarType) of
+ case t_number_vals(VarType, Opaques) of
unknown ->
Range =
case CompOp of
@@ -1847,7 +2076,7 @@ bind_comp_literal_var(Lit, Var, VarType, CompOp, Map) ->
'>=' -> t_from_range(neg_inf, LitVal);
'>' -> t_from_range(neg_inf, LitVal - 1)
end,
- t_inf(Range, VarType);
+ t_inf(Range, VarType, Opaques);
NumberVals ->
NewNumberVals = [X || X <- NumberVals, erlang:CompOp(LitVal, X)],
t_integers(NewNumberVals)
@@ -1861,17 +2090,18 @@ handle_guard_is_function(Guard, Map, Env, Eval, State) ->
Args = cerl:call_args(Guard),
{Map1, ArgTypes0} = bind_guard_list(Args, Map, Env, dont_know, State),
[FunType0, ArityType0] = ArgTypes0,
- ArityType = t_inf(ArityType0, t_integer()),
+ Opaques = State#state.opaques,
+ ArityType = t_inf(ArityType0, t_integer(), Opaques),
case t_is_none(ArityType) of
true -> signal_guard_fail(Eval, Guard, ArgTypes0, State);
false ->
FunTypeConstr =
- case t_number_vals(ArityType) of
+ case t_number_vals(ArityType, State#state.opaques) of
unknown -> t_fun();
Vals ->
t_sup([t_fun(lists:duplicate(X, t_any()), t_any()) || X <- Vals])
end,
- FunType = t_inf(FunType0, FunTypeConstr),
+ FunType = t_inf(FunType0, FunTypeConstr, Opaques),
case t_is_none(FunType) of
true ->
case Eval of
@@ -1896,47 +2126,59 @@ handle_guard_is_record(Guard, Map, Env, Eval, State) ->
Arity = cerl:int_val(Arity0),
{Map1, RecType} = bind_guard(Rec, Map, Env, dont_know, State),
ArityMin1 = Arity - 1,
- TupleType =
- case state__lookup_record(Tag, ArityMin1, State) of
- error -> t_tuple([t_atom(Tag)|lists:duplicate(ArityMin1, t_any())]);
- {ok, Prototype} -> Prototype
- end,
- Mode = determine_mode(RecType, State#state.opaques),
- NewTupleType =
- case t_opaque_match_record(TupleType, State#state.opaques) of
- [Opaque] -> Opaque;
- _ -> TupleType
- end,
- Type = t_inf(NewTupleType, RecType, Mode),
- case t_is_none(Type) of
+ Opaques = State#state.opaques,
+ Tuple = t_tuple([t_atom(Tag)|lists:duplicate(ArityMin1, t_any())]),
+ case t_is_none(t_inf(Tuple, RecType, Opaques)) of
true ->
- case Eval of
- pos -> signal_guard_fail(Eval, Guard,
- [RecType, t_from_term(Tag),
- t_from_term(Arity)],
- State);
- neg -> {Map1, t_atom(false)};
- dont_know -> {Map1, t_atom(false)}
+ case erl_types:t_has_opaque_subtype(RecType, Opaques) of
+ true ->
+ signal_guard_fail(Eval, Guard,
+ [RecType, t_from_term(Tag),
+ t_from_term(Arity)],
+ State);
+ false ->
+ case Eval of
+ pos -> signal_guard_fail(Eval, Guard,
+ [RecType, t_from_term(Tag),
+ t_from_term(Arity)],
+ State);
+ neg -> {Map1, t_atom(false)};
+ dont_know -> {Map1, t_atom(false)}
+ end
end;
false ->
- case Eval of
- pos -> {enter_type(Rec, Type, Map1), t_atom(true)};
- neg -> {Map1, t_atom(false)};
- dont_know -> {Map1, t_boolean()}
+ TupleType =
+ case state__lookup_record(Tag, ArityMin1, State) of
+ error -> Tuple;
+ {ok, Prototype} -> Prototype
+ end,
+ Type = t_inf(TupleType, RecType, State#state.opaques),
+ case t_is_none(Type) of
+ true ->
+ %% No special handling of opaque errors.
+ FArgs = "record " ++ format_type(RecType, State),
+ Msg = {record_matching, [FArgs, Tag]},
+ throw({fail, {Guard, Msg}});
+ false ->
+ case Eval of
+ pos -> {enter_type(Rec, Type, Map1), t_atom(true)};
+ neg -> {Map1, t_atom(false)};
+ dont_know -> {Map1, t_boolean()}
+ end
end
end.
handle_guard_eq(Guard, Map, Env, Eval, State) ->
[Arg1, Arg2] = cerl:call_args(Guard),
- case {cerl:type(Arg1), cerl:type(Arg2)} of
- {literal, literal} ->
- case cerl:concrete(Arg1) =:= cerl:concrete(Arg2) of
+ case {type(Arg1), type(Arg2)} of
+ {{literal, Lit1}, {literal, Lit2}} ->
+ case cerl:concrete(Lit1) =:= cerl:concrete(Lit2) of
true ->
if
Eval =:= pos -> {Map, t_atom(true)};
Eval =:= neg ->
- ArgTypes = [t_from_term(cerl:concrete(Arg1)),
- t_from_term(cerl:concrete(Arg2))],
+ ArgTypes = [t_from_term(cerl:concrete(Lit1)),
+ t_from_term(cerl:concrete(Lit2))],
signal_guard_fail(Eval, Guard, ArgTypes, State);
Eval =:= dont_know -> {Map, t_atom(true)}
end;
@@ -1945,28 +2187,28 @@ handle_guard_eq(Guard, Map, Env, Eval, State) ->
Eval =:= neg -> {Map, t_atom(false)};
Eval =:= dont_know -> {Map, t_atom(false)};
Eval =:= pos ->
- ArgTypes = [t_from_term(cerl:concrete(Arg1)),
- t_from_term(cerl:concrete(Arg2))],
+ ArgTypes = [t_from_term(cerl:concrete(Lit1)),
+ t_from_term(cerl:concrete(Lit2))],
signal_guard_fail(Eval, Guard, ArgTypes, State)
end
end;
- {literal, _} when Eval =:= pos ->
- case cerl:concrete(Arg1) of
+ {{literal, Lit1}, _} when Eval =:= pos ->
+ case cerl:concrete(Lit1) of
Atom when is_atom(Atom) ->
- bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State);
+ bind_eqeq_guard_lit_other(Guard, Lit1, Arg2, Map, Env, State);
[] ->
- bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State);
+ bind_eqeq_guard_lit_other(Guard, Lit1, Arg2, Map, Env, State);
_ ->
- bind_eq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State)
+ bind_eq_guard(Guard, Lit1, Arg2, Map, Env, Eval, State)
end;
- {_, literal} when Eval =:= pos ->
- case cerl:concrete(Arg2) of
+ {_, {literal, Lit2}} when Eval =:= pos ->
+ case cerl:concrete(Lit2) of
Atom when is_atom(Atom) ->
- bind_eqeq_guard_lit_other(Guard, Arg2, Arg1, Map, Env, State);
+ bind_eqeq_guard_lit_other(Guard, Lit2, Arg1, Map, Env, State);
[] ->
- bind_eqeq_guard_lit_other(Guard, Arg2, Arg1, Map, Env, State);
+ bind_eqeq_guard_lit_other(Guard, Lit2, Arg1, Map, Env, State);
_ ->
- bind_eq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State)
+ bind_eq_guard(Guard, Arg1, Lit2, Map, Env, Eval, State)
end;
{_, _} ->
bind_eq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State)
@@ -1975,26 +2217,37 @@ handle_guard_eq(Guard, Map, Env, Eval, State) ->
bind_eq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) ->
{Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State),
{Map2, Type2} = bind_guard(Arg2, Map1, Env, dont_know, State),
- case (t_is_nil(Type1) orelse t_is_nil(Type2) orelse
- t_is_atom(Type1) orelse t_is_atom(Type2)) of
+ Opaques = State#state.opaques,
+ case
+ t_is_nil(Type1, Opaques) orelse t_is_nil(Type2, Opaques)
+ orelse t_is_atom(Type1, Opaques) orelse t_is_atom(Type2, Opaques)
+ of
true -> bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State);
false ->
- case Eval of
- pos -> {Map2, t_atom(true)};
- neg -> {Map2, t_atom(false)};
- dont_know -> {Map2, t_boolean()}
+ %% XXX. Is this test OK?
+ OpArgs = erl_types:t_find_unknown_opaque(Type1, Type2, Opaques),
+ case OpArgs =:= [] of
+ true ->
+ case Eval of
+ pos -> {Map2, t_atom(true)};
+ neg -> {Map2, t_atom(false)};
+ dont_know -> {Map2, t_boolean()}
+ end;
+ false ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State)
end
end.
handle_guard_eqeq(Guard, Map, Env, Eval, State) ->
[Arg1, Arg2] = cerl:call_args(Guard),
- case {cerl:type(Arg1), cerl:type(Arg2)} of
- {literal, literal} ->
- case cerl:concrete(Arg1) =:= cerl:concrete(Arg2) of
+ case {type(Arg1), type(Arg2)} of
+ {{literal, Lit1}, {literal, Lit2}} ->
+
+ case cerl:concrete(Lit1) =:= cerl:concrete(Lit2) of
true ->
if Eval =:= neg ->
- ArgTypes = [t_from_term(cerl:concrete(Arg1)),
- t_from_term(cerl:concrete(Arg2))],
+ ArgTypes = [t_from_term(cerl:concrete(Lit1)),
+ t_from_term(cerl:concrete(Lit2))],
signal_guard_fail(Eval, Guard, ArgTypes, State);
Eval =:= pos -> {Map, t_atom(true)};
Eval =:= dont_know -> {Map, t_atom(true)}
@@ -2003,15 +2256,15 @@ handle_guard_eqeq(Guard, Map, Env, Eval, State) ->
if Eval =:= neg -> {Map, t_atom(false)};
Eval =:= dont_know -> {Map, t_atom(false)};
Eval =:= pos ->
- ArgTypes = [t_from_term(cerl:concrete(Arg1)),
- t_from_term(cerl:concrete(Arg2))],
+ ArgTypes = [t_from_term(cerl:concrete(Lit1)),
+ t_from_term(cerl:concrete(Lit2))],
signal_guard_fail(Eval, Guard, ArgTypes, State)
end
end;
- {literal, _} when Eval =:= pos ->
- bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State);
- {_, literal} when Eval =:= pos ->
- bind_eqeq_guard_lit_other(Guard, Arg2, Arg1, Map, Env, State);
+ {{literal, Lit1}, _} when Eval =:= pos ->
+ bind_eqeq_guard_lit_other(Guard, Lit1, Arg2, Map, Env, State);
+ {_, {literal, Lit2}} when Eval =:= pos ->
+ bind_eqeq_guard_lit_other(Guard, Lit2, Arg1, Map, Env, State);
{_, _} ->
bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State)
end.
@@ -2021,44 +2274,52 @@ bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) ->
{Map2, Type2} = bind_guard(Arg2, Map1, Env, dont_know, State),
?debug("Types are:~s =:= ~s\n", [t_to_string(Type1),
t_to_string(Type2)]),
- Inf = t_inf(Type1, Type2),
+ Opaques = State#state.opaques,
+ Inf = t_inf(Type1, Type2, Opaques),
case t_is_none(Inf) of
true ->
- case Eval of
- neg -> {Map2, t_atom(false)};
- dont_know -> {Map2, t_atom(false)};
- pos -> signal_guard_fail(Eval, Guard, [Type1, Type2], State)
+ OpArgs = erl_types:t_find_unknown_opaque(Type1, Type2, Opaques),
+ case OpArgs =:= [] of
+ true ->
+ case Eval of
+ neg -> {Map2, t_atom(false)};
+ dont_know -> {Map2, t_atom(false)};
+ pos -> signal_guard_fail(Eval, Guard, [Type1, Type2], State)
+ end;
+ false ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State)
end;
false ->
case Eval of
- pos ->
- case {cerl:type(Arg1), cerl:type(Arg2)} of
- {var, var} ->
- Map3 = enter_subst(Arg1, Arg2, Map2),
- Map4 = enter_type(Arg2, Inf, Map3),
- {Map4, t_atom(true)};
- {var, _} ->
- Map3 = enter_type(Arg1, Inf, Map2),
- {Map3, t_atom(true)};
- {_, var} ->
- Map3 = enter_type(Arg2, Inf, Map2),
- {Map3, t_atom(true)};
- {_, _} ->
- {Map2, t_atom(true)}
- end;
- neg ->
- {Map2, t_atom(false)};
- dont_know ->
- {Map2, t_boolean()}
+ pos ->
+ case {cerl:type(Arg1), cerl:type(Arg2)} of
+ {var, var} ->
+ Map3 = enter_subst(Arg1, Arg2, Map2),
+ Map4 = enter_type(Arg2, Inf, Map3),
+ {Map4, t_atom(true)};
+ {var, _} ->
+ Map3 = enter_type(Arg1, Inf, Map2),
+ {Map3, t_atom(true)};
+ {_, var} ->
+ Map3 = enter_type(Arg2, Inf, Map2),
+ {Map3, t_atom(true)};
+ {_, _} ->
+ {Map2, t_atom(true)}
+ end;
+ neg ->
+ {Map2, t_atom(false)};
+ dont_know ->
+ {Map2, t_boolean()}
end
end.
bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State) ->
Eval = dont_know,
+ Opaques = State#state.opaques,
case cerl:concrete(Arg1) of
true ->
{_, Type} = MT = bind_guard(Arg2, Map, Env, pos, State),
- case t_is_atom(true, Type) of
+ case t_is_any_atom(true, Type, Opaques) of
true -> MT;
false ->
{_, Type0} = bind_guard(Arg2, Map, Env, Eval, State),
@@ -2066,7 +2327,7 @@ bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State) ->
end;
false ->
{Map1, Type} = bind_guard(Arg2, Map, Env, neg, State),
- case t_is_atom(false, Type) of
+ case t_is_any_atom(false, Type, Opaques) of
true -> {Map1, t_atom(true)};
false ->
{_, Type0} = bind_guard(Arg2, Map, Env, Eval, State),
@@ -2087,14 +2348,15 @@ bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State) ->
handle_guard_and(Guard, Map, Env, Eval, State) ->
[Arg1, Arg2] = cerl:call_args(Guard),
+ Opaques = State#state.opaques,
case Eval of
pos ->
{Map1, Type1} = bind_guard(Arg1, Map, Env, Eval, State),
- case t_is_atom(true, Type1) of
+ case t_is_any_atom(true, Type1, Opaques) of
false -> signal_guard_fail(Eval, Guard, [Type1, t_any()], State);
true ->
{Map2, Type2} = bind_guard(Arg2, Map1, Env, Eval, State),
- case t_is_atom(true, Type2) of
+ case t_is_any_atom(true, Type2, Opaques) of
false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State);
true -> {Map2, t_atom(true)}
end
@@ -2109,7 +2371,10 @@ handle_guard_and(Guard, Map, Env, Eval, State) ->
try bind_guard(Arg2, MapJ, Env, neg, State)
catch throw:{fail, _} -> bind_guard(Arg1, MapJ, Env, pos, State)
end,
- case t_is_atom(false, Type1) orelse t_is_atom(false, Type2) of
+ case
+ t_is_any_atom(false, Type1, Opaques)
+ orelse t_is_any_atom(false, Type2, Opaques)
+ of
true -> {join_maps_end([Map1, Map2], MapJ), t_atom(false)};
false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State)
end;
@@ -2124,11 +2389,16 @@ handle_guard_and(Guard, Map, Env, Eval, State) ->
false ->
NewMap = join_maps_end([Map1, Map2], MapJ),
NewType =
- case {t_atom_vals(Bool1), t_atom_vals(Bool2)} of
+ case {t_atom_vals(Bool1, Opaques), t_atom_vals(Bool2, Opaques)} of
{['true'] , ['true'] } -> t_atom(true);
{['false'], _ } -> t_atom(false);
{_ , ['false']} -> t_atom(false);
+ {unknown , _ } ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State);
+ {_ , unknown } ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State);
{_ , _ } -> t_boolean()
+
end,
{NewMap, NewType}
end
@@ -2136,6 +2406,7 @@ handle_guard_and(Guard, Map, Env, Eval, State) ->
handle_guard_or(Guard, Map, Env, Eval, State) ->
[Arg1, Arg2] = cerl:call_args(Guard),
+ Opaques = State#state.opaques,
case Eval of
pos ->
MapJ = join_maps_begin(Map),
@@ -2149,19 +2420,23 @@ handle_guard_or(Guard, Map, Env, Eval, State) ->
catch
throw:{fail,_} -> bind_guard(Arg2, MapJ, Env, dont_know, State)
end,
- case ((t_is_atom(true, Bool1) andalso t_is_boolean(Bool2))
- orelse
- (t_is_atom(true, Bool2) andalso t_is_boolean(Bool1))) of
+ case
+ ((t_is_any_atom(true, Bool1, Opaques)
+ andalso t_is_boolean(Bool2, Opaques))
+ orelse
+ (t_is_any_atom(true, Bool2, Opaques)
+ andalso t_is_boolean(Bool1, Opaques)))
+ of
true -> {join_maps_end([Map1, Map2], MapJ), t_atom(true)};
false -> signal_guard_fail(Eval, Guard, [Bool1, Bool2], State)
end;
neg ->
{Map1, Type1} = bind_guard(Arg1, Map, Env, neg, State),
- case t_is_atom(false, Type1) of
+ case t_is_any_atom(false, Type1, Opaques) of
false -> signal_guard_fail(Eval, Guard, [Type1, t_any()], State);
true ->
{Map2, Type2} = bind_guard(Arg2, Map1, Env, neg, State),
- case t_is_atom(false, Type2) of
+ case t_is_any_atom(false, Type2, Opaques) of
false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State);
true -> {Map2, t_atom(false)}
end
@@ -2177,10 +2452,14 @@ handle_guard_or(Guard, Map, Env, Eval, State) ->
false ->
NewMap = join_maps_end([Map1, Map2], MapJ),
NewType =
- case {t_atom_vals(Bool1), t_atom_vals(Bool2)} of
+ case {t_atom_vals(Bool1, Opaques), t_atom_vals(Bool2, Opaques)} of
{['false'], ['false']} -> t_atom(false);
{['true'] , _ } -> t_atom(true);
{_ , ['true'] } -> t_atom(true);
+ {unknown , _ } ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State);
+ {_ , unknown } ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State);
{_ , _ } -> t_boolean()
end,
{NewMap, NewType}
@@ -2189,10 +2468,11 @@ handle_guard_or(Guard, Map, Env, Eval, State) ->
handle_guard_not(Guard, Map, Env, Eval, State) ->
[Arg] = cerl:call_args(Guard),
+ Opaques = State#state.opaques,
case Eval of
neg ->
{Map1, Type} = bind_guard(Arg, Map, Env, pos, State),
- case t_is_atom(true, Type) of
+ case t_is_any_atom(true, Type, Opaques) of
true -> {Map1, t_atom(false)};
false ->
{_, Type0} = bind_guard(Arg, Map, Env, Eval, State),
@@ -2200,7 +2480,7 @@ handle_guard_not(Guard, Map, Env, Eval, State) ->
end;
pos ->
{Map1, Type} = bind_guard(Arg, Map, Env, neg, State),
- case t_is_atom(false, Type) of
+ case t_is_any_atom(false, Type, Opaques) of
true -> {Map1, t_atom(true)};
false ->
{_, Type0} = bind_guard(Arg, Map, Env, Eval, State),
@@ -2212,10 +2492,11 @@ handle_guard_not(Guard, Map, Env, Eval, State) ->
case t_is_none(Bool) of
true -> throw({fatal_fail, none});
false ->
- case t_atom_vals(Bool) of
+ case t_atom_vals(Bool, Opaques) of
['true'] -> {Map1, t_atom(false)};
['false'] -> {Map1, t_atom(true)};
- [_, _] -> {Map1, Bool}
+ [_, _] -> {Map1, Bool};
+ unknown -> signal_guard_fail(Eval, Guard, [Type], State)
end
end
end.
@@ -2229,33 +2510,73 @@ bind_guard_list([G|Gs], Map, Env, Eval, State, Acc) ->
bind_guard_list([], Map, _Env, _Eval, _State, Acc) ->
{Map, lists:reverse(Acc)}.
+handle_guard_map(Guard, Map, Env, State) ->
+ Pairs = cerl:map_es(Guard),
+ Arg = cerl:map_arg(Guard),
+ {Map1, ArgType0} = bind_guard(Arg, Map, Env, dont_know, State),
+ ArgType1 = t_inf(t_map(), ArgType0),
+ case t_is_none_or_unit(ArgType1) of
+ true -> {Map1, t_none()};
+ false ->
+ {Map2, TypePairs} = bind_guard_map_pairs(Pairs, Map1, Env, State, []),
+ {Map2, lists:foldl(fun({KV,assoc},Acc) -> erl_types:t_map_put(KV,Acc);
+ ({KV,exact},Acc) -> erl_types:t_map_update(KV,Acc)
+ end, ArgType1, TypePairs)}
+ end.
+
+bind_guard_map_pairs([], Map, _Env, _State, PairAcc) ->
+ {Map, lists:reverse(PairAcc)};
+bind_guard_map_pairs([Pair|Pairs], Map, Env, State, PairAcc) ->
+ Key = cerl:map_pair_key(Pair),
+ Val = cerl:map_pair_val(Pair),
+ Op = cerl:map_pair_op(Pair),
+ {Map1, [K,V]} = bind_guard_list([Key,Val],Map,Env,dont_know,State),
+ bind_guard_map_pairs(Pairs, Map1, Env, State,
+ [{{K,V},cerl:concrete(Op)}|PairAcc]).
+
-type eval() :: 'pos' | 'neg' | 'dont_know'.
--spec signal_guard_fail(eval(), cerl:c_call(), [erl_types:erl_type()],
+-spec signal_guard_fail(eval(), cerl:c_call(), [type()],
state()) -> no_return().
signal_guard_fail(Eval, Guard, ArgTypes, State) ->
+ signal_guard_failure(Eval, Guard, ArgTypes, fail, State).
+
+-spec signal_guard_fatal_fail(eval(), cerl:c_call(), [erl_types:erl_type()],
+ state()) -> no_return().
+
+signal_guard_fatal_fail(Eval, Guard, ArgTypes, State) ->
+ signal_guard_failure(Eval, Guard, ArgTypes, fatal_fail, State).
+
+signal_guard_failure(Eval, Guard, ArgTypes, Tag, State) ->
Args = cerl:call_args(Guard),
F = cerl:atom_val(cerl:call_name(Guard)),
- MFA = {cerl:atom_val(cerl:call_module(Guard)), F, length(Args)},
- Msg =
+ {M, F, A} = MFA = {cerl:atom_val(cerl:call_module(Guard)), F, length(Args)},
+ Opaques = State#state.opaques,
+ {Kind, XInfo} =
+ case erl_bif_types:opaque_args(M, F, A, ArgTypes, Opaques) of
+ [] ->
+ {case Eval of
+ neg -> neg_guard_fail;
+ pos -> guard_fail;
+ dont_know -> guard_fail
+ end,
+ []};
+ Ns -> {opaque_guard, [Ns]}
+ end,
+ FArgs =
case is_infix_op(MFA) of
true ->
[ArgType1, ArgType2] = ArgTypes,
[Arg1, Arg2] = Args,
- Kind =
- case Eval of
- neg -> neg_guard_fail;
- pos -> guard_fail;
- dont_know -> guard_fail
- end,
- {Kind, [format_args_1([Arg1], [ArgType1], State),
- atom_to_list(F),
- format_args_1([Arg2], [ArgType2], State)]};
+ [format_args_1([Arg1], [ArgType1], State),
+ atom_to_list(F),
+ format_args_1([Arg2], [ArgType2], State)] ++ XInfo;
false ->
- mk_guard_msg(Eval, F, Args, ArgTypes, State)
+ [F, format_args(Args, ArgTypes, State)]
end,
- throw({fail, {Guard, Msg}}).
+ Msg = {Kind, FArgs},
+ throw({Tag, {Guard, Msg}}).
is_infix_op({erlang, '=:=', 2}) -> true;
is_infix_op({erlang, '==', 2}) -> true;
@@ -2268,25 +2589,10 @@ is_infix_op({erlang, '>=', 2}) -> true;
is_infix_op({M, F, A}) when is_atom(M), is_atom(F),
is_integer(A), 0 =< A, A =< 255 -> false.
--spec signal_guard_fatal_fail(eval(), cerl:c_call(), [erl_types:erl_type()],
- state()) -> no_return().
-
-signal_guard_fatal_fail(Eval, Guard, ArgTypes, State) ->
- Args = cerl:call_args(Guard),
- F = cerl:atom_val(cerl:call_name(Guard)),
- Msg = mk_guard_msg(Eval, F, Args, ArgTypes, State),
- throw({fatal_fail, {Guard, Msg}}).
-
-mk_guard_msg(Eval, F, Args, ArgTypes, State) ->
- FArgs = [F, format_args(Args, ArgTypes, State)],
- case any_has_opaque_subtype(ArgTypes) of
- true -> {opaque_guard, FArgs};
- false ->
- case Eval of
- neg -> {neg_guard_fail, FArgs};
- pos -> {guard_fail, FArgs};
- dont_know -> {guard_fail, FArgs}
- end
+bif_args(M, F, A) ->
+ case erl_bif_types:arg_types(M, F, A) of
+ unknown -> lists:duplicate(A, t_any());
+ List -> List
end.
bind_guard_case_clauses(Arg, Clauses, Map0, Env, Eval, State) ->
@@ -2300,7 +2606,9 @@ filter_fail_clauses([Clause|Left]) ->
case (cerl:clause_pats(Clause) =:= []) of
true ->
Body = cerl:clause_body(Clause),
- case cerl:is_literal(Body) andalso (cerl:concrete(Body) =:= fail) of
+ case cerl:is_literal(Body) andalso (cerl:concrete(Body) =:= fail) orelse
+ cerl:is_c_primop(Body) andalso
+ (cerl:atom_val(cerl:primop_name(Body)) =:= match_fail) of
true -> filter_fail_clauses(Left);
false -> [Clause|filter_fail_clauses(Left)]
end;
@@ -2366,14 +2674,15 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left],
end,
{NewMap3, CType} = bind_guard(cerl:clause_body(Clause), NewMap2,
Env, Eval, State),
+ Opaques = State#state.opaques,
case Eval of
pos ->
- case t_is_atom(true, CType) of
+ case t_is_any_atom(true, CType, Opaques) of
true -> ok;
false -> throw({fail, none})
end;
neg ->
- case t_is_atom(false, CType) of
+ case t_is_any_atom(false, CType, Opaques) of
true -> ok;
false -> throw({fail, none})
end;
@@ -2413,10 +2722,10 @@ join_maps_end(Maps, MapOut) ->
#map{ref = Ref, modified_stack = [{M1,R1} | S]} = MapOut,
true = lists:all(fun(M) -> M#map.ref =:= Ref end, Maps), % sanity
Keys0 = lists:usort(lists:append([M#map.modified || M <- Maps])),
- #map{dict = Dict, subst = Subst} = MapOut,
+ #map{map = Map, subst = Subst} = MapOut,
Keys = [Key ||
Key <- Keys0,
- dict:is_key(Key, Dict) orelse dict:is_key(Key, Subst)],
+ maps:is_key(Key, Map) orelse maps:is_key(Key, Subst)],
Out = case Maps of
[] -> join_maps(Maps, MapOut);
_ -> join_maps(Keys, Maps, MapOut)
@@ -2427,8 +2736,8 @@ join_maps_end(Maps, MapOut) ->
modified_stack = S}.
join_maps(Maps, MapOut) ->
- #map{dict = Dict, subst = Subst} = MapOut,
- Keys = ordsets:from_list(dict:fetch_keys(Dict) ++ dict:fetch_keys(Subst)),
+ #map{map = Map, subst = Subst} = MapOut,
+ Keys = ordsets:from_list(maps:keys(Map) ++ maps:keys(Subst)),
join_maps(Keys, Maps, MapOut).
join_maps(Keys, Maps, MapOut) ->
@@ -2457,11 +2766,11 @@ join_maps_one_key([], _Key, AccType) ->
-ifdef(DEBUG).
debug_join_check(Maps, MapOut, Out) ->
- #map{dict = Dict, subst = Subst} = Out,
- #map{dict = Dict2, subst = Subst2} = join_maps(Maps, MapOut),
- F = fun(D) -> lists:keysort(1, dict:to_list(D)) end,
+ #map{map = Map, subst = Subst} = Out,
+ #map{map = Map2, subst = Subst2} = join_maps(Maps, MapOut),
+ F = fun(D) -> lists:keysort(1, maps:to_list(D)) end,
[throw({bug, join_maps}) ||
- F(Dict) =/= F(Dict2) orelse F(Subst) =/= F(Subst2)].
+ F(Map) =/= F(Map2) orelse F(Subst) =/= F(Subst2)].
-else.
debug_join_check(_Maps, _MapOut, _Out) -> ok.
-endif.
@@ -2492,30 +2801,34 @@ enter_type(Key, Val, MS) ->
enter_type_lists(Keys, t_to_tlist(Val), MS)
end;
false ->
- #map{dict = Dict, subst = Subst} = MS,
+ #map{map = Map, subst = Subst} = MS,
KeyLabel = get_label(Key),
- case dict:find(KeyLabel, Subst) of
+ case maps:find(KeyLabel, Subst) of
{ok, NewKey} ->
?debug("Binding ~p to ~p\n", [KeyLabel, NewKey]),
enter_type(NewKey, Val, MS);
error ->
?debug("Entering ~p :: ~s\n", [KeyLabel, t_to_string(Val)]),
- case dict:find(KeyLabel, Dict) of
- {ok, Val} -> MS;
- {ok, _OldVal} -> store_map(KeyLabel, Val, MS);
+ case maps:find(KeyLabel, Map) of
+ {ok, Value} ->
+ case erl_types:t_is_equal(Val, Value) of
+ true -> MS;
+ false -> store_map(KeyLabel, Val, MS)
+ end;
error -> store_map(KeyLabel, Val, MS)
end
end
end
end.
-store_map(Key, Val, #map{dict = Dict, ref = undefined} = Map) ->
- Map#map{dict = dict:store(Key, Val, Dict)};
-store_map(Key, Val, #map{dict = Dict, modified = Mod} = Map) ->
- Map#map{dict = dict:store(Key, Val, Dict), modified = [Key | Mod]}.
+store_map(Key, Val, #map{map = Map, ref = undefined} = MapRec) ->
+ MapRec#map{map = maps:put(Key, Val, Map)};
+store_map(Key, Val, #map{map = Map, modified = Mod} = MapRec) ->
+ MapRec#map{map = maps:put(Key, Val, Map), modified = [Key | Mod]}.
-enter_subst(Key, Val, #map{subst = Subst} = MS) ->
+enter_subst(Key, Val0, #map{subst = Subst} = MS) ->
KeyLabel = get_label(Key),
+ Val = dialyzer_utils:refold_pattern(Val0),
case cerl:is_literal(Val) of
true ->
store_map(KeyLabel, literal_type(Val), MS);
@@ -2524,7 +2837,7 @@ enter_subst(Key, Val, #map{subst = Subst} = MS) ->
false -> MS;
true ->
ValLabel = get_label(Val),
- case dict:find(ValLabel, Subst) of
+ case maps:find(ValLabel, Subst) of
{ok, NewVal} ->
enter_subst(Key, NewVal, MS);
error ->
@@ -2538,22 +2851,22 @@ enter_subst(Key, Val, #map{subst = Subst} = MS) ->
end.
store_subst(Key, Val, #map{subst = S, ref = undefined} = Map) ->
- Map#map{subst = dict:store(Key, Val, S)};
+ Map#map{subst = maps:put(Key, Val, S)};
store_subst(Key, Val, #map{subst = S, modified = Mod} = Map) ->
- Map#map{subst = dict:store(Key, Val, S), modified = [Key | Mod]}.
+ Map#map{subst = maps:put(Key, Val, S), modified = [Key | Mod]}.
-lookup_type(Key, #map{dict = Dict, subst = Subst}) ->
- lookup(Key, Dict, Subst, t_none()).
+lookup_type(Key, #map{map = Map, subst = Subst}) ->
+ lookup(Key, Map, Subst, t_none()).
-lookup(Key, Dict, Subst, AnyNone) ->
+lookup(Key, Map, Subst, AnyNone) ->
case cerl:is_literal(Key) of
true -> literal_type(Key);
false ->
Label = get_label(Key),
- case dict:find(Label, Subst) of
- {ok, NewKey} -> lookup(NewKey, Dict, Subst, AnyNone);
+ case maps:find(Label, Subst) of
+ {ok, NewKey} -> lookup(NewKey, Map, Subst, AnyNone);
error ->
- case dict:find(Label, Dict) of
+ case maps:find(Label, Map) of
{ok, Val} -> Val;
error -> AnyNone
end
@@ -2578,6 +2891,9 @@ mark_as_fresh([Tree|Left], Map) ->
bitstr ->
%% The Size field is not fresh.
{SubTrees1 -- [cerl:bitstr_size(Tree)], Map};
+ map_pair ->
+ %% The keys are not fresh
+ {SubTrees1 -- [cerl:map_pair_key(Tree)], Map};
var ->
{SubTrees1, enter_type(Tree, t_any(), Map)};
_ ->
@@ -2588,12 +2904,12 @@ mark_as_fresh([], Map) ->
Map.
-ifdef(DEBUG).
-debug_pp_map(#map{dict = Dict}=Map) ->
- Keys = dict:fetch_keys(Dict),
+debug_pp_map(#map{map = Map}=MapRec) ->
+ Keys = maps:keys(Map),
io:format("Map:\n", []),
lists:foreach(fun (Key) ->
io:format("\t~w :: ~s\n",
- [Key, t_to_string(lookup_type(Key, Map))])
+ [Key, t_to_string(lookup_type(Key, MapRec))])
end, Keys),
ok.
-else.
@@ -2611,10 +2927,15 @@ get_label(L) when is_integer(L) ->
get_label(T) ->
cerl_trees:get_label(T).
-t_is_simple(ArgType) ->
- t_is_atom(ArgType) orelse t_is_number(ArgType) orelse t_is_port(ArgType)
- orelse t_is_pid(ArgType) orelse t_is_reference(ArgType)
- orelse t_is_nil(ArgType).
+t_is_simple(ArgType, State) ->
+ Opaques = State#state.opaques,
+ t_is_atom(ArgType, Opaques) orelse t_is_number(ArgType, Opaques)
+ orelse t_is_port(ArgType, Opaques)
+ orelse t_is_pid(ArgType, Opaques) orelse t_is_reference(ArgType, Opaques)
+ orelse t_is_nil(ArgType, Opaques).
+
+remove_local_opaque_types(Type, Opaques) ->
+ t_unopaque(Type, Opaques).
%% t_is_structured(ArgType) ->
%% case t_is_nil(ArgType) of
@@ -2633,16 +2954,21 @@ is_call_to_send(Tree) ->
Arity = cerl:call_arity(Tree),
cerl:is_c_atom(Mod)
andalso cerl:is_c_atom(Name)
- andalso (cerl:atom_val(Name) =:= '!')
+ andalso is_send(cerl:atom_val(Name))
andalso (cerl:atom_val(Mod) =:= erlang)
andalso (Arity =:= 2)
end.
-any_opaque(Ts) ->
- lists:any(fun erl_types:t_is_opaque/1, Ts).
+is_send('!') -> true;
+is_send(send) -> true;
+is_send(_) -> false.
-any_has_opaque_subtype(Ts) ->
- lists:any(fun erl_types:t_has_opaque_subtype/1, Ts).
+is_lc_simple_list(Tree, TreeType, State) ->
+ Opaques = State#state.opaques,
+ Ann = cerl:get_ann(Tree),
+ lists:member(list_comprehension, Ann)
+ andalso t_is_list(TreeType)
+ andalso t_is_simple(t_list_elements(TreeType, Opaques), State).
filter_match_fail([Clause] = Cls) ->
Body = cerl:clause_body(Clause),
@@ -2662,30 +2988,24 @@ filter_match_fail([]) ->
%% receive after 1 -> ok end
[].
-determine_mode(Type, Opaques) ->
- case lists:member(Type, Opaques) of
- true -> opaque;
- false -> structured
- end.
-
%%% ===========================================================================
%%%
%%% The State.
%%%
%%% ===========================================================================
-state__new(Callgraph, Tree, Plt, Module, Records) ->
- Opaques = erl_types:module_builtin_opaques(Module) ++
- erl_types:t_opaque_from_records(Records),
- TreeMap = build_tree_map(Tree),
+state__new(Callgraph, Codeserver, Tree, Plt, Module, Records) ->
+ Opaques = erl_types:t_opaque_from_records(Records),
+ {TreeMap, FunHomes} = build_tree_map(Tree, Callgraph),
Funs = dict:fetch_keys(TreeMap),
- FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt, Opaques),
+ FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt),
ExportedFuns =
[Fun || Fun <- Funs--[top], dialyzer_callgraph:is_escaping(Fun, Callgraph)],
Work = init_work(ExportedFuns),
Env = lists:foldl(fun(Fun, Env) -> dict:store(Fun, map__new(), Env) end,
dict:new(), Funs),
- #state{callgraph = Callgraph, envs = Env, fun_tab = FunTab, opaques = Opaques,
+ #state{callgraph = Callgraph, codeserver = Codeserver,
+ envs = Env, fun_tab = FunTab, fun_homes = FunHomes, opaques = Opaques,
plt = Plt, races = dialyzer_races:new(), records = Records,
warning_mode = false, warnings = [], work = Work, tree_map = TreeMap,
module = Module}.
@@ -2724,7 +3044,7 @@ state__renew_race_list(RaceList, RaceListSize,
state__renew_warnings(Warnings, State) ->
State#state{warnings = Warnings}.
--spec state__add_warning(dial_warning(), state()) -> state().
+-spec state__add_warning(raw_warning(), state()) -> state().
state__add_warning(Warn, #state{warnings = Warnings} = State) ->
State#state{warnings = [Warn|Warnings]}.
@@ -2739,27 +3059,56 @@ state__add_warning(#state{warnings = Warnings, warning_mode = true} = State,
Ann = cerl:get_ann(Tree),
case Force of
true ->
- Warn = {Tag, {get_file(Ann), abs(get_line(Ann))}, Msg},
+ WarningInfo = {get_file(Ann),
+ abs(get_line(Ann)),
+ State#state.curr_fun},
+ Warn = {Tag, WarningInfo, Msg},
+ ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)]),
State#state{warnings = [Warn|Warnings]};
false ->
case is_compiler_generated(Ann) of
- true -> State;
- false ->
- Warn = {Tag, {get_file(Ann), get_line(Ann)}, Msg},
- State#state{warnings = [Warn|Warnings]}
+ true -> State;
+ false ->
+ WarningInfo = {get_file(Ann), get_line(Ann), State#state.curr_fun},
+ Warn = {Tag, WarningInfo, Msg},
+ case Tag of
+ ?WARN_CONTRACT_RANGE -> ok;
+ _ -> ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)])
+ end,
+ State#state{warnings = [Warn|Warnings]}
end
end.
+state__remove_added_warnings(OldState, NewState) ->
+ #state{warnings = OldWarnings} = OldState,
+ #state{warnings = NewWarnings} = NewState,
+ {NewWarnings -- OldWarnings, NewState#state{warnings = OldWarnings}}.
+
+state__add_warnings(Warns, #state{warnings = Warnings} = State) ->
+ State#state{warnings = Warns ++ Warnings}.
+
+-spec state__set_curr_fun(curr_fun(), state()) -> state().
+
+state__set_curr_fun(undefined, State) ->
+ State#state{curr_fun = undefined};
+state__set_curr_fun(FunLbl, State) ->
+ State#state{curr_fun = find_function(FunLbl, State)}.
+
+-spec state__find_function(mfa_or_funlbl(), state()) -> mfa_or_funlbl().
+
+state__find_function(FunLbl, State) ->
+ find_function(FunLbl, State).
+
state__get_race_warnings(#state{races = Races} = State) ->
{Races1, State1} = dialyzer_races:get_race_warnings(Races, State),
State1#state{races = Races1}.
state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab,
- callgraph = Callgraph, plt = Plt} = State,
- NoWarnUnused) ->
+ callgraph = Callgraph, plt = Plt} = State) ->
FoldFun =
fun({top, _}, AccState) -> AccState;
({FunLbl, Fun}, AccState) ->
+ AccState1 = state__set_curr_fun(FunLbl, AccState),
{NotCalled, Ret} =
case dict:fetch(get_label(Fun), FunTab) of
{not_handled, {_Args0, Ret0}} -> {true, Ret0};
@@ -2767,17 +3116,12 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab,
end,
case NotCalled of
true ->
- {Warn, Msg} =
- case dialyzer_callgraph:lookup_name(FunLbl, Callgraph) of
- error -> {false, {}};
- {ok, {_M, F, A} = MFA} ->
- {not sets:is_element(MFA, NoWarnUnused),
- {unused_fun, [F, A]}}
- end,
- case Warn of
- true -> state__add_warning(AccState, ?WARN_NOT_CALLED, Fun, Msg);
- false -> AccState
- end;
+ case dialyzer_callgraph:lookup_name(FunLbl, Callgraph) of
+ error -> AccState1;
+ {ok, {_M, F, A}} ->
+ Msg = {unused_fun, [F, A]},
+ state__add_warning(AccState1, ?WARN_NOT_CALLED, Fun, Msg)
+ end;
false ->
{Name, Contract} =
case dialyzer_callgraph:lookup_name(FunLbl, Callgraph) of
@@ -2790,7 +3134,7 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab,
%% Check if the function has a contract that allows this.
Warn =
case Contract of
- none -> not parent_allows_this(FunLbl, State);
+ none -> not parent_allows_this(FunLbl, AccState1);
{value, C} ->
GenRet = dialyzer_contracts:get_contract_return(C),
not t_is_unit(GenRet)
@@ -2800,19 +3144,19 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab,
case classify_returns(Fun) of
no_match ->
Msg = {no_return, [no_match|Name]},
- state__add_warning(AccState, ?WARN_RETURN_NO_RETURN,
+ state__add_warning(AccState1, ?WARN_RETURN_NO_RETURN,
Fun, Msg);
only_explicit ->
Msg = {no_return, [only_explicit|Name]},
- state__add_warning(AccState, ?WARN_RETURN_ONLY_EXIT,
+ state__add_warning(AccState1, ?WARN_RETURN_ONLY_EXIT,
Fun, Msg);
only_normal ->
Msg = {no_return, [only_normal|Name]},
- state__add_warning(AccState, ?WARN_RETURN_NO_RETURN,
+ state__add_warning(AccState1, ?WARN_RETURN_NO_RETURN,
Fun, Msg);
both ->
Msg = {no_return, [both|Name]},
- state__add_warning(AccState, ?WARN_RETURN_NO_RETURN,
+ state__add_warning(AccState1, ?WARN_RETURN_NO_RETURN,
Fun, Msg)
end;
false ->
@@ -2829,12 +3173,11 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab,
state__is_escaping(Fun, #state{callgraph = Callgraph}) ->
dialyzer_callgraph:is_escaping(Fun, Callgraph).
-state__lookup_type_for_rec_var(Var, #state{callgraph = Callgraph} = State) ->
+state__lookup_type_for_letrec(Var, #state{callgraph = Callgraph} = State) ->
Label = get_label(Var),
- case dialyzer_callgraph:lookup_rec_var(Label, Callgraph) of
+ case dialyzer_callgraph:lookup_letrec(Label, Callgraph) of
error -> error;
- {ok, MFA} ->
- {ok, FunLabel} = dialyzer_callgraph:lookup_label(MFA, Callgraph),
+ {ok, FunLabel} ->
{ok, state__fun_type(FunLabel, State)}
end.
@@ -2851,8 +3194,10 @@ state__lookup_name(Fun, #state{callgraph = Callgraph}) ->
state__lookup_record(Tag, Arity, #state{records = Records}) ->
case erl_types:lookup_record(Tag, Arity, Records) of
{ok, Fields} ->
- {ok, t_tuple([t_atom(Tag)|
- [FieldType || {_FieldName, FieldType} <- Fields]])};
+ RecType =
+ t_tuple([t_atom(Tag)|
+ [FieldType || {_FieldName, _Abstr, FieldType} <- Fields]]),
+ {ok, RecType};
error ->
error
end.
@@ -2864,22 +3209,36 @@ state__get_args_and_status(Tree, #state{fun_tab = FunTab}) ->
{ok, {ArgTypes, _}} -> {ArgTypes, true}
end.
-build_tree_map(Tree) ->
+build_tree_map(Tree, Callgraph) ->
Fun =
- fun(T, Dict) ->
+ fun(T, {Dict, Homes, FunLbls} = Acc) ->
case cerl:is_c_fun(T) of
true ->
- dict:store(get_label(T), T, Dict);
+ FunLbl = get_label(T),
+ Dict1 = dict:store(FunLbl, T, Dict),
+ case catch dialyzer_callgraph:lookup_name(FunLbl, Callgraph) of
+ {ok, MFA} ->
+ F2 =
+ fun(Lbl, Dict0) ->
+ dict:store(Lbl, MFA, Dict0)
+ end,
+ Homes1 = lists:foldl(F2, Homes, [FunLbl|FunLbls]),
+ {Dict1, Homes1, []};
+ _ ->
+ {Dict1, Homes, [FunLbl|FunLbls]}
+ end;
false ->
- Dict
+ Acc
end
end,
- cerl_trees:fold(Fun, dict:new(), Tree).
+ Dict0 = dict:new(),
+ {Dict, Homes, _} = cerl_trees:fold(Fun, {Dict0, Dict0, []}, Tree),
+ {Dict, Homes}.
-init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt, Opaques) ->
+init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt) ->
NewDict = dict:store(top, {[], t_none()}, Dict),
- init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques);
-init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) ->
+ init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt);
+init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt) ->
Arity = cerl:fun_arity(dict:fetch(Fun, TreeMap)),
FunEntry =
case dialyzer_callgraph:is_escaping(Fun, Callgraph) of
@@ -2896,8 +3255,8 @@ init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) ->
false -> {not_handled, {lists:duplicate(Arity, t_none()), t_unit()}}
end,
NewDict = dict:store(Fun, FunEntry, Dict),
- init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques);
-init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt, _Opaques) ->
+ init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt);
+init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt) ->
?debug("DICT:~p\n",[dict:to_list(Dict)]),
Dict.
@@ -2946,34 +3305,27 @@ state__update_fun_entry(Tree, ArgTypes, Out0,
if Fun =:= top -> Out0;
true ->
case lookup_fun_sig(Fun, CG, Plt) of
- {value, {SigRet, _}} -> t_inf(SigRet, Out0, opaque);
+ {value, {SigRet, _}} -> t_inf(SigRet, Out0);
none -> Out0
end
end,
Out = t_limit(Out1, ?TYPE_LIMIT),
- case dict:find(Fun, FunTab) of
- {ok, {ArgTypes, OldOut}} ->
- case t_is_equal(OldOut, Out) of
- true ->
- ?debug("Fixpoint for ~w: ~s\n",
- [state__lookup_name(Fun, State),
- t_to_string(t_fun(ArgTypes, Out))]),
- State;
- false ->
- NewEntry = {ArgTypes, Out},
- ?debug("New Entry for ~w: ~s\n",
- [state__lookup_name(Fun, State),
- t_to_string(t_fun(ArgTypes, Out))]),
- NewFunTab = dict:store(Fun, NewEntry, FunTab),
- State1 = State#state{fun_tab = NewFunTab},
- state__add_work_from_fun(Tree, State1)
- end;
- {ok, {NewArgTypes, _OldOut}} ->
- %% Can only happen in self-recursive functions. Only update the out type.
- NewEntry = {NewArgTypes, Out},
+ {ok, {OldArgTypes, OldOut}} = dict:find(Fun, FunTab),
+ SameArgs = lists:all(fun({A, B}) -> erl_types:t_is_equal(A, B)
+ end, lists:zip(OldArgTypes, ArgTypes)),
+ SameOut = t_is_equal(OldOut, Out),
+ if
+ SameArgs, SameOut ->
+ ?debug("Fixpoint for ~w: ~s\n",
+ [state__lookup_name(Fun, State),
+ t_to_string(t_fun(ArgTypes, Out))]),
+ State;
+ true ->
+ %% Can only happen in self-recursive functions.
+ NewEntry = {OldArgTypes, Out},
?debug("New Entry for ~w: ~s\n",
[state__lookup_name(Fun, State),
- t_to_string(t_fun(NewArgTypes, Out))]),
+ t_to_string(t_fun(OldArgTypes, Out))]),
NewFunTab = dict:store(Fun, NewEntry, FunTab),
State1 = State#state{fun_tab = NewFunTab},
state__add_work_from_fun(Tree, State1)
@@ -2994,7 +3346,7 @@ state__add_work_from_fun(Tree, #state{callgraph = Callgraph,
%% Must filter the result for results in this module.
FilteredList = [L || {ok, L} <- LabelList, dict:is_key(L, TreeMap)],
?debug("~w: Will try to add:~w\n",
- [state__lookup_name(get_label(Tree), State), MFAList]),
+ [state__lookup_name(Label, State), MFAList]),
lists:foldl(fun(L, AccState) ->
state__add_work(L, AccState)
end, State, FilteredList)
@@ -3055,7 +3407,8 @@ forward_args(Fun, ArgTypes, #state{work = Work, fun_tab = FunTab} = State) ->
case Fixpoint of
true -> State;
false ->
- NewArgTypes = [t_sup(X, Y) || {X, Y} <- lists:zip(ArgTypes, OldArgTypes)],
+ NewArgTypes = [t_sup(X, Y) ||
+ {X, Y} <- lists:zip(ArgTypes, OldArgTypes)],
NewWork = add_work(Fun, Work),
?debug("~w: forwarding args ~s\n",
[state__lookup_name(Fun, State),
@@ -3093,7 +3446,7 @@ state__get_callgraph(#state{callgraph = Callgraph}) ->
state__get_races(#state{races = Races}) ->
Races.
--spec state__get_records(state()) -> dict().
+-spec state__get_records(state()) -> types().
state__get_records(#state{records = Records}) ->
Records.
@@ -3192,12 +3545,17 @@ get_file([_|Tail]) -> get_file(Tail).
is_compiler_generated(Ann) ->
lists:member(compiler_generated, Ann) orelse (get_line(Ann) < 1).
--spec format_args([cerl:cerl()], [erl_types:erl_type()], state()) ->
+is_literal_record(Tree) ->
+ Ann = cerl:get_ann(Tree),
+ lists:member(record, Ann).
+
+-spec format_args([cerl:cerl()], [type()], state()) ->
nonempty_string().
format_args([], [], _State) ->
"()";
-format_args(ArgList, TypeList, State) ->
+format_args(ArgList0, TypeList, State) ->
+ ArgList = fold_literals(ArgList0),
"(" ++ format_args_1(ArgList, TypeList, State) ++ ")".
format_args_1([Arg], [Type], State) ->
@@ -3227,25 +3585,25 @@ format_arg(Arg) ->
Default
end.
--spec format_type(erl_types:erl_type(), state()) -> string().
+-spec format_type(type(), state()) -> string().
format_type(Type, #state{records = R}) ->
t_to_string(Type, R).
--spec format_field_diffs(erl_types:erl_type(), state()) -> string().
+-spec format_field_diffs(type(), state()) -> string().
format_field_diffs(RecConstruction, #state{records = R}) ->
erl_types:record_field_diffs_to_string(RecConstruction, R).
--spec format_sig_args(erl_types:erl_type(), state()) -> string().
+-spec format_sig_args(type(), state()) -> string().
-format_sig_args(Type, #state{records = R}) ->
- SigArgs = t_fun_args(Type),
+format_sig_args(Type, #state{opaques = Opaques} = State) ->
+ SigArgs = t_fun_args(Type, Opaques),
case SigArgs of
[] -> "()";
[SArg|SArgs] ->
- lists:flatten("(" ++ t_to_string(SArg, R)
- ++ ["," ++ t_to_string(T, R) || T <- SArgs] ++ ")")
+ lists:flatten("(" ++ format_type(SArg, State)
+ ++ ["," ++ format_type(T, State) || T <- SArgs] ++ ")")
end.
format_cerl(Tree) ->
@@ -3256,7 +3614,8 @@ format_cerl(Tree) ->
{ribbon, 100000} %% newlines.
]).
-format_patterns(Pats) ->
+format_patterns(Pats0) ->
+ Pats = fold_literals(Pats0),
NewPats = map_pats(cerl:c_values(Pats)),
String = format_cerl(NewPats),
case Pats of
@@ -3288,6 +3647,23 @@ map_pats(Pats) ->
end,
cerl_trees:map(Fun, Pats).
+fold_literals(TreeList) ->
+ [cerl:fold_literal(Tree) || Tree <- TreeList].
+
+type(Tree) ->
+ Folded = cerl:fold_literal(Tree),
+ case cerl:type(Folded) of
+ literal -> {literal, Folded};
+ Type -> Type
+ end.
+
+is_literal(Tree) ->
+ Folded = cerl:fold_literal(Tree),
+ case cerl:is_literal(Folded) of
+ true -> {yes, Folded};
+ false -> no
+ end.
+
parent_allows_this(FunLbl, #state{callgraph = Callgraph, plt = Plt} =State) ->
case state__is_escaping(FunLbl, State) of
false -> false; % if it isn't escaping it can't be a return value
@@ -3315,6 +3691,13 @@ parent_allows_this(FunLbl, #state{callgraph = Callgraph, plt = Plt} =State) ->
end
end.
+find_function({_, _, _} = MFA, _State) ->
+ MFA;
+find_function(top, _State) ->
+ top;
+find_function(FunLbl, #state{fun_homes = Homes}) ->
+ dict:fetch(FunLbl, Homes).
+
classify_returns(Tree) ->
case find_terminals(cerl:fun_body(Tree)) of
{false, false} -> no_match;
@@ -3332,18 +3715,18 @@ find_terminals(Tree) ->
M0 = cerl:call_module(Tree),
F0 = cerl:call_name(Tree),
A = length(cerl:call_args(Tree)),
- case cerl:is_literal(M0) andalso cerl:is_literal(F0) of
- false ->
- %% We cannot make assumptions. Say that both are true.
- {true, true};
- true ->
- M = cerl:concrete(M0),
- F = cerl:concrete(F0),
+ case {is_literal(M0), is_literal(F0)} of
+ {{yes, LitM}, {yes, LitF}} ->
+ M = cerl:concrete(LitM),
+ F = cerl:concrete(LitF),
case (erl_bif_types:is_known(M, F, A)
andalso t_is_none(erl_bif_types:type(M, F, A))) of
true -> {true, false};
false -> {false, true}
- end
+ end;
+ _ ->
+ %% We cannot make assumptions. Say that both are true.
+ {true, true}
end;
'case' -> find_terminals_list(cerl:case_clauses(Tree));
'catch' -> find_terminals(cerl:catch_body(Tree));
@@ -3353,6 +3736,7 @@ find_terminals(Tree) ->
'let' -> find_terminals(cerl:let_body(Tree));
letrec -> find_terminals(cerl:letrec_body(Tree));
literal -> {false, true};
+ map -> {false, true};
primop -> {false, false}; %% match_fail, etc. are not explicit exits.
'receive' ->
Timeout = cerl:receive_timeout(Tree),
@@ -3388,66 +3772,6 @@ find_terminals_list([], Explicit, Normal) ->
%%----------------------------------------------------------------------------
-%% If you write a record pattern in a matching that violates the
-%% definition it will never match. However, the warning is lost in the
-%% regular analysis. This after-pass catches it.
-
-find_mismatched_record_patterns(Tree, State) ->
- cerl_trees:fold(
- fun(SubTree, AccState) ->
- case cerl:is_c_clause(SubTree) of
- true -> lists:foldl(fun(P, AccState1) ->
- find_rec_warnings(P, AccState1)
- end, AccState, cerl:clause_pats(SubTree));
- false -> AccState
- end
- end, State, Tree).
-
-find_rec_warnings(Tree, State) ->
- cerl_trees:fold(
- fun(SubTree, AccState) ->
- case cerl:is_c_tuple(SubTree) of
- true -> find_rec_warnings_tuple(SubTree, AccState);
- false -> AccState
- end
- end, State, Tree).
-
-find_rec_warnings_tuple(Tree, State) ->
- Elements = cerl:tuple_es(Tree),
- {_, _, EsType} = traverse_list(Elements, map__new(), State),
- TupleType = t_tuple(EsType),
- case t_is_none(TupleType) of
- true -> State;
- false ->
- %% Let's find out if this is a record construction.
- case Elements of
- [Tag|Left] ->
- case cerl:is_c_atom(Tag) of
- true ->
- TagVal = cerl:atom_val(Tag),
- case state__lookup_record(TagVal, length(Left), State) of
- error -> State;
- {ok, Prototype} ->
- InfTupleType = t_inf(Prototype, TupleType),
- case t_is_none(InfTupleType) of
- true ->
- Msg = {record_matching,
- [format_patterns([Tree]), TagVal]},
- state__add_warning(State, ?WARN_MATCHING, Tree, Msg);
- false ->
- State
- end
- end;
- false ->
- State
- end;
- _ ->
- State
- end
- end.
-
-%%----------------------------------------------------------------------------
-
-ifdef(DEBUG_PP).
debug_pp(Tree, true) ->
io:put_chars(cerl_prettypr:format(Tree, [{hook, cerl_typean:pp_hook()}])),
diff --git a/lib/dialyzer/src/dialyzer_dep.erl b/lib/dialyzer/src/dialyzer_dep.erl
index febb65b766..273c05c54c 100644
--- a/lib/dialyzer/src/dialyzer_dep.erl
+++ b/lib/dialyzer/src/dialyzer_dep.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -39,7 +40,7 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
-%% analyze(CoreTree) -> {Deps, Esc, Calls}.
+%% analyze(CoreTree) -> {Deps, Esc, Calls, Letrecs}.
%%
%% Deps = a dict mapping labels of functions to an ordset of functions
%% it calls.
@@ -53,18 +54,30 @@
%% which the operation can refer to. If 'external' is part of
%% the set the operation can be externally defined.
%%
+%% Letrecs = a dict mapping var labels to their recursive definition.
+%% top-level letrecs are not included as they are handled
+%% separately.
+%%
+
+-type dep_ordset() :: ordsets:ordset(label() | 'external').
+
+-type deps() :: dict:dict(label() | 'external' | 'top', dep_ordset()).
+-type esc() :: dep_ordset().
+-type calls() :: dict:dict(label(), ordsets:ordset(label())).
+-type letrecs() :: dict:dict(label(), label()).
--spec analyze(cerl:c_module()) -> {dict(), ordset('external' | label()), dict()}.
+-spec analyze(cerl:c_module()) -> {deps(), esc(), calls(), letrecs()}.
analyze(Tree) ->
%% io:format("Handling ~w\n", [cerl:atom_val(cerl:module_name(Tree))]),
{_, State} = traverse(Tree, map__new(), state__new(Tree), top),
Esc = state__esc(State),
- %% Add dependency from 'external' to all escaping function
+ %% Add dependency from 'external' to all escaping functions
State1 = state__add_deps(external, output(Esc), State),
Deps = state__deps(State1),
Calls = state__calls(State1),
- {map__finalize(Deps), set__to_ordsets(Esc), map__finalize(Calls)}.
+ Letrecs = state__letrecs(State1),
+ {map__finalize(Deps), set__to_ordsets(Esc), map__finalize(Calls), Letrecs}.
traverse(Tree, Out, State, CurrentFun) ->
%% io:format("Type: ~w\n", [cerl:type(Tree)]),
@@ -72,22 +85,26 @@ traverse(Tree, Out, State, CurrentFun) ->
apply ->
Op = cerl:apply_op(Tree),
Args = cerl:apply_args(Tree),
- %% Op is always a variable and should not be marked as escaping
- %% based on its use.
case var =:= cerl:type(Op) of
- false -> erlang:error({apply_op_not_a_variable, cerl:type(Op)});
- true -> ok
- end,
- OpFuns = case map__lookup(cerl_trees:get_label(Op), Out) of
- none -> output(none);
- {value, OF} -> OF
- end,
- {ArgFuns, State2} = traverse_list(Args, Out, State, CurrentFun),
- State3 = state__add_esc(merge_outs(ArgFuns), State2),
- State4 = state__add_deps(CurrentFun, OpFuns, State3),
- State5 = state__store_callsite(cerl_trees:get_label(Tree),
- OpFuns, length(Args), State4),
- {output(set__singleton(external)), State5};
+ false ->
+ %% We have discovered an error here, but we ignore it and let
+ %% later passes handle it; we do not modify the dependencies.
+ %% erlang:error({apply_op_not_a_variable, cerl:type(Op)});
+ {output(none), State};
+ true ->
+ %% Op is a variable and should not be marked as escaping
+ %% based on its use.
+ OpFuns = case map__lookup(cerl_trees:get_label(Op), Out) of
+ none -> output(none);
+ {value, OF} -> OF
+ end,
+ {ArgFuns, State2} = traverse_list(Args, Out, State, CurrentFun),
+ State3 = state__add_esc(merge_outs(ArgFuns), State2),
+ State4 = state__add_deps(CurrentFun, OpFuns, State3),
+ State5 = state__store_callsite(cerl_trees:get_label(Tree),
+ OpFuns, length(Args), State4),
+ {output(set__singleton(external)), State5}
+ end;
binary ->
{output(none), State};
'case' ->
@@ -118,8 +135,10 @@ traverse(Tree, Out, State, CurrentFun) ->
TmpState = state__add_deps(Label, O1, State),
state__add_deps(CurrentFun, O2,TmpState)
end,
- {BodyFuns, State2} = traverse(Body, Out, State1,
- cerl_trees:get_label(Tree)),
+ Vars = cerl:fun_vars(Tree),
+ Out1 = bind_single(Vars, output(set__singleton(external)), Out),
+ {BodyFuns, State2} =
+ traverse(Body, Out1, State1, cerl_trees:get_label(Tree)),
{output(set__singleton(Label)), state__add_esc(BodyFuns, State2)};
'let' ->
Vars = cerl:let_vars(Tree),
@@ -131,9 +150,12 @@ traverse(Tree, Out, State, CurrentFun) ->
letrec ->
Defs = cerl:letrec_defs(Tree),
Body = cerl:letrec_body(Tree),
+ State1 = lists:foldl(fun({ Var, Fun }, Acc) ->
+ state__add_letrecs(cerl_trees:get_label(Var), cerl_trees:get_label(Fun), Acc)
+ end, State, Defs),
Out1 = bind_defs(Defs, Out),
- State1 = traverse_defs(Defs, Out1, State, CurrentFun),
- traverse(Body, Out1, State1, CurrentFun);
+ State2 = traverse_defs(Defs, Out1, State1, CurrentFun),
+ traverse(Body, Out1, State2, CurrentFun);
literal ->
{output(none), State};
module ->
@@ -173,6 +195,15 @@ traverse(Tree, Out, State, CurrentFun) ->
Args = cerl:tuple_es(Tree),
{List, State1} = traverse_list(Args, Out, State, CurrentFun),
{merge_outs(List), State1};
+ map ->
+ Args = cerl:map_es(Tree),
+ {List, State1} = traverse_list(Args, Out, State, CurrentFun),
+ {merge_outs(List), State1};
+ map_pair ->
+ Key = cerl:map_pair_key(Tree),
+ Val = cerl:map_pair_val(Tree),
+ {List, State1} = traverse_list([Key,Val], Out, State, CurrentFun),
+ {merge_outs(List), State1};
values ->
traverse_list(cerl:values_es(Tree), Out, State, CurrentFun);
var ->
@@ -291,7 +322,7 @@ primop(Tree, ArgFuns, State) ->
%% Set
%%
--record(set, {set :: set()}).
+-record(set, {set :: sets:set()}).
set__singleton(Val) ->
#set{set = sets:add_element(Val, sets:new())}.
@@ -460,18 +491,30 @@ all_vars(Tree, AccIn) ->
-type local_set() :: 'none' | #set{}.
--record(state, {deps :: dict(),
+-record(state, {deps :: deps(),
esc :: local_set(),
- call :: dict(),
- arities :: dict()}).
+ calls :: calls(),
+ arities :: dict:dict(label() | 'top', arity()),
+ letrecs :: letrecs()}).
state__new(Tree) ->
Exports = set__from_list([X || X <- cerl:module_exports(Tree)]),
- InitEsc = set__from_list([cerl_trees:get_label(Fun)
- || {Var, Fun} <- cerl:module_defs(Tree),
- set__is_element(Var, Exports)]),
+ %% get the labels of all exported functions
+ ExpLs = [cerl_trees:get_label(Fun) || {Var, Fun} <- cerl:module_defs(Tree),
+ set__is_element(Var, Exports)],
+ %% make sure to also initiate an analysis from all functions called
+ %% from on_load attributes; in Core these exist as a list of {F,A} pairs
+ OnLoadFAs = lists:flatten([cerl:atom_val(Args)
+ || {Attr, Args} <- cerl:module_attrs(Tree),
+ cerl:atom_val(Attr) =:= on_load]),
+ OnLoadLs = [cerl_trees:get_label(Fun)
+ || {Var, Fun} <- cerl:module_defs(Tree),
+ lists:member(cerl:var_name(Var), OnLoadFAs)],
+ %% init the escaping function labels to exported + called from on_load
+ InitEsc = set__from_list(OnLoadLs ++ ExpLs),
Arities = cerl_trees:fold(fun find_arities/2, dict:new(), Tree),
- #state{deps = map__new(), esc = InitEsc, call = map__new(), arities = Arities}.
+ #state{deps = map__new(), esc = InitEsc, calls = map__new(),
+ arities = Arities, letrecs = map__new()}.
find_arities(Tree, AccMap) ->
case cerl:is_c_fun(Tree) of
@@ -485,14 +528,20 @@ find_arities(Tree, AccMap) ->
state__add_deps(_From, #output{content = none}, State) ->
State;
-state__add_deps(From, #output{type = single, content=To},
+state__add_deps(From, #output{type = single, content = To},
#state{deps = Map} = State) ->
%% io:format("Adding deps from ~w to ~w\n", [From, set__to_ordsets(To)]),
State#state{deps = map__add(From, To, Map)}.
+state__add_letrecs(Var, Fun, #state{letrecs = Map} = State) ->
+ State#state{letrecs = map__store(Var, Fun, Map)}.
+
state__deps(#state{deps = Deps}) ->
Deps.
+state__letrecs(#state{letrecs = Letrecs}) ->
+ Letrecs.
+
state__add_esc(#output{content = none}, State) ->
State;
state__add_esc(#output{type = single, content = Set},
@@ -505,16 +554,16 @@ state__esc(#state{esc = Esc}) ->
state__store_callsite(_From, #output{content = none}, _CallArity, State) ->
State;
state__store_callsite(From, To, CallArity,
- #state{call = Calls, arities = Arities} = State) ->
+ #state{calls = Calls, arities = Arities} = State) ->
Filter = fun(external) -> true;
(Fun) -> CallArity =:= dict:fetch(Fun, Arities)
end,
case filter_outs(To, Filter) of
#output{content = none} -> State;
- To1 -> State#state{call = map__store(From, To1, Calls)}
+ To1 -> State#state{calls = map__store(From, To1, Calls)}
end.
-state__calls(#state{call = Calls}) ->
+state__calls(#state{calls = Calls}) ->
Calls.
%%------------------------------------------------------------
diff --git a/lib/dialyzer/src/dialyzer_explanation.erl b/lib/dialyzer/src/dialyzer_explanation.erl
index afc2c1965f..968f8df78e 100644
--- a/lib/dialyzer/src/dialyzer_explanation.erl
+++ b/lib/dialyzer/src/dialyzer_explanation.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
diff --git a/lib/dialyzer/src/dialyzer_gui.erl b/lib/dialyzer/src/dialyzer_gui.erl
deleted file mode 100644
index 97e5752577..0000000000
--- a/lib/dialyzer/src/dialyzer_gui.erl
+++ /dev/null
@@ -1,1381 +0,0 @@
-%% -*- erlang-indent-level: 2 -*-
-%%------------------------------------------------------------------------
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%%-----------------------------------------------------------------------
-%%% File : dialyzer_gui.erl
-%%% Authors : Tobias Lindahl <[email protected]>
-%%% Kostis Sagonas <[email protected]>
-%%% Description : The graphical user interface for the Dialyzer tool.
-%%%
-%%% Created : 27 Apr 2004 by Tobias Lindahl <[email protected]>
-%%%-----------------------------------------------------------------------
-
--module(dialyzer_gui).
--compile([{nowarn_deprecated_function,{gs,button,2}},
- {nowarn_deprecated_function,{gs,config,2}},
- {nowarn_deprecated_function,{gs,destroy,1}},
- {nowarn_deprecated_function,{gs,editor,2}},
- {nowarn_deprecated_function,{gs,entry,2}},
- {nowarn_deprecated_function,{gs,frame,2}},
- {nowarn_deprecated_function,{gs,label,2}},
- {nowarn_deprecated_function,{gs,listbox,2}},
- {nowarn_deprecated_function,{gs,menu,2}},
- {nowarn_deprecated_function,{gs,menubar,2}},
- {nowarn_deprecated_function,{gs,menubutton,2}},
- {nowarn_deprecated_function,{gs,menuitem,2}},
- {nowarn_deprecated_function,{gs,radiobutton,2}},
- {nowarn_deprecated_function,{gs,read,2}},
- {nowarn_deprecated_function,{gs,start,0}},
- {nowarn_deprecated_function,{gs,stop,0}},
- {nowarn_deprecated_function,{gs,window,2}}]).
-
--export([start/1]).
-
--include("dialyzer.hrl").
-
-%%------------------------------------------------------------------------
-
--define(DIALYZER_ERROR_TITLE, "Dialyzer Error").
--define(DIALYZER_MESSAGE_TITLE, "Dialyzer Message").
-
-%%------------------------------------------------------------------------
-
--type gs_object() :: any(). %% XXX: should be imported from gs
-
--record(mode, {start_byte_code :: gs_object(),
- start_src_code :: gs_object()}).
-
--record(menu, {file_save_log :: gs_object(),
- file_save_warn :: gs_object(),
- file_quit :: gs_object(),
- help_about :: gs_object(),
- help_manual :: gs_object(),
- help_warnings :: gs_object(),
- opts_macros :: gs_object(),
- opts_includes :: gs_object(),
- plt_empty :: gs_object(),
- plt_search_doc :: gs_object(),
- plt_show_doc :: gs_object(),
- warnings :: gs_object()}).
-
--record(gui_state, {add_all :: gs_object(),
- add_file :: gs_object(),
- add_rec :: gs_object(),
- chosen_box :: gs_object(),
- analysis_pid :: pid(),
- del_file :: gs_object(),
- doc_plt :: dialyzer_plt:plt(),
- clear_chosen :: gs_object(),
- clear_log :: gs_object(),
- clear_warn :: gs_object(),
- init_plt :: dialyzer_plt:plt(),
- dir_entry :: gs_object(),
- file_box :: gs_object(),
- file_wd :: gs_object(),
- gs :: gs_object(),
- log :: gs_object(),
- menu :: #menu{},
- mode :: #mode{},
- options :: #options{},
- packer :: gs_object(),
- run :: gs_object(),
- stop :: gs_object(),
- top :: gs_object(),
- warnings_box :: gs_object(),
- backend_pid :: pid()}).
-
-%%------------------------------------------------------------------------
-
--spec start(#options{}) -> ?RET_NOTHING_SUSPICIOUS.
-
-start(#options{from = From, init_plts = InitPltFiles,
- legal_warnings = LegalWarnings} = DialyzerOptions) ->
- process_flag(trap_exit, true),
-
- GS = gs:start(),
- code:add_pathsa(["."]),
- WH = [{width, 1000}, {height, 550}],
- EmptySpace = {stretch, 1},
-
- {ok, Host} = inet:gethostname(),
- %% --------- Top Window --------------
- TopWin = gs:window(GS, [{title, "Dialyzer " ++ ?VSN ++ " @ " ++ Host},
- {configure, true},
- {default, listbox, {bg, white}},
- {default, editor, {bg, white}},
- {default, entry, {bg, white}},
- {default, button, {font, {helvetica, bold, 12}}},
- {default, label, {font, {helvetica, bold, 12}}}
- |WH]),
- Packer = gs:frame(TopWin, [{packer_x, [{stretch, 3},{fixed, 200},
- {stretch, 7}]},
- {packer_y, [{fixed, 25}, {fixed, 20},
- {stretch, 1, 50},
- {fixed, 25}, {fixed, 20},
- {stretch, 1, 50},
- {fixed, 25}]}]),
-
- %% --------- Chosen box --------------
- gs:label(Packer, [{label, {text, "Directories or modules to analyze"}},
- {height, 20}, {pack_xy, {1, 2}}]),
- ChosenBox = gs:listbox(Packer, [{pack_xy, {1, 3}}, {vscroll, right},
- {selectmode, multiple}]),
-
- %% --------- File box --------------
- gs:label(Packer, [{label, {text, "File"}}, {height, 20}, {pack_xy, {1,5}}]),
- FilePacker = gs:frame(Packer, [{packer_x, [{fixed, 30}, {stretch, 1, 100}]},
- {packer_y, [{fixed, 25}, {stretch, 1, 25}]},
- {pack_xy, {1, 6}}]),
- gs:label(FilePacker, [{label, {text, "Dir:"}}, {pack_xy, {1, 1}}]),
- DirEntry = gs:entry(FilePacker, [{height, 30}, {pack_xy, {2, 1}},
- {keypress, true}]),
- File = gs:listbox(FilePacker, [{pack_x, {1,2}}, {pack_y, 2},
- {selectmode, multiple}, {doubleclick, true},
- {vscroll, right}]),
-
- %% --------- Options --------------
- gs:label(Packer, [{label, {text, "Analysis Options"}},
- {height, 20}, {pack_xy, {2, 2}}]),
- ModePacker = gs:frame(Packer, [{packer_x, [{fixed, 75}, {fixed, 120}]},
- {packer_y, [{fixed, 20}, {fixed, 20},
- {fixed, 20},
- %% EmptySpace,
- {fixed, 20}, {fixed, 20},
- {fixed, 20}, EmptySpace]},
- {bw, 10}, {relief, flat},
- {default, {radiobutton, {align, w}}},
- {default, {label, {align, w}}},
- {pack_xy, {2, 3}}]),
-
- %% Bytecode vs. Source code
- gs:label(ModePacker, [{label, {text, "File Type:"}},
- {height, 20}, {pack_xy, {1,1}}]),
- {ByteSel, SrcSel} = case From of
- byte_code -> {[{select, true}], []};
- src_code -> {[], [{select, true}]}
- end,
- ModeByteCode = gs:radiobutton(ModePacker,
- ByteSel ++ [{group, start_from},
- {label, {text,"BeamFiles"}},
- {pack_xy, {2,1}}]),
- ModeSrcCode = gs:radiobutton(ModePacker,
- SrcSel ++ [{group, start_from},
- {label, {text,"SourceFiles"}},
- {pack_xy, {2,2}}]),
- Mode = #mode{start_byte_code = ModeByteCode,
- start_src_code = ModeSrcCode},
-
- %% --------- Log box --------------
- gs:label(Packer, [{label, {text, "Log"}}, {height, 20}, {pack_xy, {3,2}}]),
- Log = gs:editor(Packer, [{pack_x, 3}, {pack_y, 3}, {enable, false},
- {font, {courier, 12}}, {vscroll, right},
- {wrap, word}]),
-
- %% --------- Warnings box --------------
- gs:label(Packer, [{label, {text, "Warnings"}},{height, 20},{pack_xy, {3,5}}]),
- WarningsBox = gs:editor(Packer, [{pack_x, {2,3}}, {pack_y, 6},
- {enable, false},
- {font, {courier, 12}}, {vscroll, right},
- {wrap, word}]),
-
- %% --------- Buttons --------------
- ButtonPackerHighLeft =
- gs:frame(Packer, [{packer_x, [{fixed, 50}, {fixed, 65}, EmptySpace]},
- {pack_xy, {1,4}}]),
- ButtonPackerHighRight =
- gs:frame(Packer, [{packer_x, [{fixed, 70}, {fixed, 70}, EmptySpace]},
- {pack_xy, {3,4}}]),
- ButtonPackerLowLeft =
- gs:frame(Packer, [{packer_x, [{fixed, 50},
- {fixed, 60},
- {fixed, 110},
- EmptySpace]},
- {pack_xy, {1,7}}]),
- ButtonPackerLowRight =
- gs:frame(Packer, [{packer_x, [{fixed, 100},
- {fixed, 70},
- EmptySpace,
- {fixed, 70},
- {fixed, 70}]},
- {pack_x, {2,3}}, {pack_y, 7}]),
-
- WHButton = [{width, 60}, {height, 20}],
- AddFile = gs:button(ButtonPackerLowLeft, [{pack_xy, {1, 1}},
- {label, {text,"Add"}}|WHButton]),
- AddAll = gs:button(ButtonPackerLowLeft, [{pack_xy, {2, 1}},
- {label, {text,"Add All"}}|WHButton]),
- AddRec = gs:button(ButtonPackerLowLeft, [{pack_xy, {3, 1}},
- {label, {text,"Add Recursively"}}
- |WHButton]),
- DelFile = gs:button(ButtonPackerHighLeft, [{pack_xy, {1, 1}},
- {label, {text,"Delete"}}|WHButton]),
- ClearChosen = gs:button(ButtonPackerHighLeft, [{pack_xy, {2, 1}},
- {label, {text,"Delete All"}}
- |WHButton]),
- ClearLog = gs:button(ButtonPackerHighRight, [{pack_xy, {1, 1}},
- {label, {text,"Clear Log"}}
- |WHButton]),
- ClearWarn = gs:button(ButtonPackerLowRight, [{pack_xy, {1, 1}},
- {label, {text,"Clear Warnings"}}
- |WHButton]),
-
- Run = gs:button(ButtonPackerLowRight, [{pack_xy, {4, 1}},
- {label, {text,"Run"}}|WHButton]),
- Stop = gs:button(ButtonPackerLowRight, [{pack_xy, {5, 1}}, {enable, false},
- {label, {text,"Stop"}}|WHButton]),
-
- %% --------- Menu --------------
- MenuBar = gs:menubar(TopWin, []),
-
- %% File Menu
- MenuBarFile = gs:menubutton(MenuBar, [{label, {text, "File"}}]),
- MenuFile = gs:menu(MenuBarFile, []),
- MenuFileSaveWarn = gs:menuitem(MenuFile, [{label, {text, "Save Warnings"}}]),
- MenuFileSaveLog = gs:menuitem(MenuFile, [{label, {text, "Save Log"}}]),
- MenuFileQuit = gs:menuitem(MenuFile, [{label, {text, "Quit"}}]),
-
- %% Warnings Menu
- MenuBarWarn = gs:menubutton(MenuBar, [{label, {text, "Warnings"}}]),
- MenuWarn = gs:menu(MenuBarWarn, []),
- MenuWarnMatch = gs:menuitem(MenuWarn, [{label, {text, "Match failures"}},
- {itemtype, check}, {select, true}]),
- MenuWarnFailingCall = gs:menuitem(MenuWarn,
- [{label, {text, "Failing function calls"}},
- {itemtype, check}, {select, true}]),
- MenuWarnFunApp = gs:menuitem(MenuWarn, [{label,
- {text, "Bad fun applications"}},
- {itemtype, check}, {select, true}]),
- MenuWarnOpaque = gs:menuitem(MenuWarn, [{label,
- {text, "Opaqueness violations"}},
- {itemtype, check}, {select, true}]),
- MenuWarnLists = gs:menuitem(MenuWarn,
- [{label, {text, "Improper list constructions"}},
- {itemtype, check}, {select, true}]),
- MenuWarnNotCalled = gs:menuitem(MenuWarn,
- [{label, {text, "Unused functions"}},
- {itemtype, check}, {select, true}]),
- MenuWarnReturnOnlyExit = gs:menuitem(MenuWarn,
- [{label,
- {text, "Error handling functions"}},
- {itemtype, check}, {select, false}]),
- MenuWarnReturnNoReturn = gs:menuitem(MenuWarn,
- [{label,
- {text, "Functions of no return"}},
- {itemtype, check}, {select, true}]),
- MenuWarnCallNonExported = gs:menuitem(MenuWarn,
- [{label,
- {text, "Call to unexported function"}},
- {itemtype, check}, {select, true}]),
- MenuWarnRaceCondition = gs:menuitem(MenuWarn,
- [{label,
- {text,"Possible race conditions"}},
- {itemtype, check}, {select, false}]),
- MenuWarnContractTypes = gs:menuitem(MenuWarn,
- [{label, {text, "Wrong contracts"}},
- {itemtype, check}, {select, true}]),
- MenuWarnContractSyntax = gs:menuitem(MenuWarn,
- [{label,
- {text, "Wrong contract syntax"}},
- {itemtype, check}, {select, true}]),
-
- %% PLT Menu
- MenuBarPLT = gs:menubutton(MenuBar, [{label, {text,"PLT"}}]),
- MenuPLT = gs:menu(MenuBarPLT, []),
- MenuPLTEmpty = gs:menuitem(MenuPLT, [{label, {text, "Init with empty PLT"}},
- {itemtype, check}, {select, false}]),
- MenuPLTShow = gs:menuitem(MenuPLT, [{label, {text, "Show contents"}}]),
- MenuPLTSearch = gs:menuitem(MenuPLT, [{label, {text, "Search contents"}}]),
-
- %% Options Menu
- MenuBarOpts = gs:menubutton(MenuBar, [{label,{text,"Options"}}]),
- MenuOpts = gs:menu(MenuBarOpts, []),
- MenuOptsMacros = gs:menuitem(MenuOpts,
- [{label, {text, "Manage Macro Definitions"}}]),
- MenuOptsIncludes = gs:menuitem(MenuOpts,
- [{label, {text, "Manage Include Directories"}}]),
-
- %% Help
- MenuBarHelp = gs:menubutton(MenuBar, [{label, {text, "Help"}}, {side, right}]),
- MenuHelp = gs:menu(MenuBarHelp, []),
- MenuHelpManual = gs:menuitem(MenuHelp, [{label, {text, "Manual"}}]),
- MenuHelpWarnings = gs:menuitem(MenuHelp, [{label, {text, "Warning Options"}}]),
- MenuHelpAbout = gs:menuitem(MenuHelp, [{label, {text, "About"}}]),
-
- Warnings = [{?WARN_RETURN_NO_RETURN, MenuWarnReturnNoReturn},
- {?WARN_RETURN_ONLY_EXIT, MenuWarnReturnOnlyExit},
- {?WARN_NOT_CALLED, MenuWarnNotCalled},
- {?WARN_NON_PROPER_LIST, MenuWarnLists},
- {?WARN_FUN_APP, MenuWarnFunApp},
- {?WARN_MATCHING, MenuWarnMatch},
- {?WARN_OPAQUE, MenuWarnOpaque},
- {?WARN_FAILING_CALL, MenuWarnFailingCall},
- {?WARN_CALLGRAPH, MenuWarnCallNonExported},
- {?WARN_RACE_CONDITION, MenuWarnRaceCondition},
- %% For contracts.
- {?WARN_CONTRACT_TYPES, MenuWarnContractTypes},
- {?WARN_CONTRACT_SYNTAX, MenuWarnContractSyntax}
- ],
-
- init_warnings(Warnings, LegalWarnings),
-
- Menu = #menu{file_quit = MenuFileQuit,
- plt_empty = MenuPLTEmpty,
- help_manual = MenuHelpManual,
- help_about = MenuHelpAbout,
- help_warnings = MenuHelpWarnings,
- opts_macros = MenuOptsMacros,
- opts_includes = MenuOptsIncludes,
- plt_search_doc = MenuPLTSearch,
- plt_show_doc = MenuPLTShow,
- file_save_log = MenuFileSaveLog,
- file_save_warn = MenuFileSaveWarn,
- warnings = Warnings},
-
- %% --------- Init --------------
- gs:config(TopWin, [{map, true}]),
- gs:config(Packer, WH),
- {ok, CWD} = file:get_cwd(),
-
- InitPlt =
- case InitPltFiles of
- [] -> dialyzer_plt:new();
- _ ->
- Plts = [dialyzer_plt:from_file(F) || F <- InitPltFiles],
- dialyzer_plt:merge_plts_or_report_conflicts(InitPltFiles, Plts)
- end,
-
- State = #gui_state{add_all = AddAll,
- add_file = AddFile,
- add_rec = AddRec,
- chosen_box = ChosenBox,
- clear_chosen = ClearChosen,
- clear_log = ClearLog,
- clear_warn = ClearWarn,
- del_file = DelFile,
- doc_plt = dialyzer_plt:new(),
- dir_entry = DirEntry,
- file_box = File,
- file_wd = CWD,
- gs = GS,
- init_plt = InitPlt,
- log = Log,
- menu = Menu,
- mode = Mode,
- options = DialyzerOptions,
- packer = Packer,
- run = Run,
- stop = Stop,
- top = TopWin,
- warnings_box = WarningsBox},
- NewState = change_dir_or_add_file(State, "."),
- gui_loop(NewState).
-
-%% ----------------------------------------------------------------
-%%
-%% Main GUI Loop
-%%
-
--spec gui_loop(#gui_state{}) -> ?RET_NOTHING_SUSPICIOUS.
-
-gui_loop(#gui_state{add_all = AddAll, add_file = AddFile, add_rec = AddRec,
- backend_pid = BackendPid, chosen_box = ChosenBox,
- clear_chosen = ClearChosen, clear_log = ClearLog,
- clear_warn = ClearWarn, del_file = DelFile,
- dir_entry = DirEntry, file_box = File, log = Log,
- menu = Menu, packer = Packer, run = Run, stop = Stop,
- top = TopWin, warnings_box = Warn} = State) ->
- %% --- Menu ---
- Quit = Menu#menu.file_quit,
- Manual = Menu#menu.help_manual,
- Warnings = Menu#menu.help_warnings,
- About = Menu#menu.help_about,
- SaveLog = Menu#menu.file_save_log,
- SaveWarn = Menu#menu.file_save_warn,
- SearchPlt = Menu#menu.plt_search_doc,
- ShowPlt = Menu#menu.plt_show_doc,
- Macros = Menu#menu.opts_macros,
- Includes = Menu#menu.opts_includes,
-
- receive
- {gs, TopWin, configure, _Data, [W, H|_]} ->
- gs:config(Packer, [{width, W}, {height, H}]),
- gui_loop(State);
- {gs, TopWin, destroy, _Data, _Args} ->
- ?RET_NOTHING_SUSPICIOUS;
- {gs, File, doubleclick, _, [_Id, Text|_]} ->
- NewState = change_dir_or_add_file(State, Text),
- gui_loop(NewState);
- {gs, DirEntry, keypress, _, ['Return'|_]} ->
- gs:config(TopWin, [{setfocus, true}]),
- NewState = change_dir_absolute(State, gs:read(DirEntry, text)),
- gui_loop(NewState);
- {gs, DirEntry, keypress, _, _} ->
- gui_loop(State);
- %% ----- Buttons -----
- {gs, AddFile, click, _, _} ->
- handle_add_files(State),
- gui_loop(State);
- {gs, AddAll, click, _, _} ->
- handle_add_all_click(State),
- gui_loop(State);
- {gs, AddRec, click, _, _} ->
- handle_add_rec_click(State),
- gui_loop(State);
- {gs, DelFile, click, _, _} ->
- handle_file_delete(State),
- gui_loop(State);
- {gs, ClearChosen, click, _, _} ->
- gs:config(ChosenBox, [clear]),
- gui_loop(State);
- {gs, ClearLog, click, _, _} ->
- Log = State#gui_state.log,
- gs:config(Log, [{enable, true}]),
- gs:config(Log, [clear]),
- gs:config(Log, [{enable, false}]),
- gui_loop(State);
- {gs, ClearWarn, click, _, _} ->
- Warn = State#gui_state.warnings_box,
- gs:config(Warn, [{enable, true}]),
- gs:config(Warn, [clear]),
- gs:config(Warn, [{enable, false}]),
- gui_loop(State);
- {gs, Run, click, _, _} ->
- NewState = start_analysis(State),
- gui_loop(NewState);
- {gs, Stop, click, _, _} ->
- config_gui_stop(State),
- BackendPid ! {self(), stop},
- update_editor(Log, "\n***** Analysis stopped ****\n"),
- gui_loop(State);
- %% ----- Menu -----
- {gs, Quit, click, _, _} ->
- case maybe_quit(State) of
- true -> ?RET_NOTHING_SUSPICIOUS;
- false -> gui_loop(State)
- end;
- {gs, Manual, click, _, _} ->
- spawn_link(fun() -> manual(State) end),
- gui_loop(State);
- {gs, Warnings, click, _, _} ->
- spawn_link(fun() -> warnings(State) end),
- gui_loop(State);
- {gs, About, click, _, _} ->
- spawn_link(fun() -> about(State) end),
- gui_loop(State);
- {gs, SaveLog, click, _, _} ->
- save_log(State),
- gui_loop(State);
- {gs, SaveWarn, click, _, _} ->
- save_warn(State),
- gui_loop(State);
- {gs, SearchPlt, click, _, _} ->
- spawn_link(fun() -> search_doc_plt(State) end),
- gui_loop(State);
- {gs, ShowPlt, click, _, _} ->
- spawn_link(fun() -> show_doc_plt(State) end),
- gui_loop(State);
- {gs, Macros, click, _, _} ->
- Self = self(),
- spawn_link(fun() -> macro_dialog(State, Self) end),
- gui_loop(State);
- {gs, Includes, click, _, _} ->
- Self = self(),
- spawn_link(fun() -> include_dialog(State, Self) end),
- gui_loop(State);
- {new_options, NewOptions} ->
- NewState = State#gui_state{options = NewOptions},
- gui_loop(NewState);
- %% ----- Analysis -----
- {BackendPid, ext_calls, ExtCalls} ->
- Msg = io_lib:format("The following functions are called "
- "but type information about them is not available.\n"
- "The analysis might get more precise by including "
- "the modules containing these functions:\n\n\t~p\n",
- [ExtCalls]),
- free_editor(State, "Analysis done", Msg),
- gui_loop(State);
- {BackendPid, ext_types, ExtTypes} ->
- Map = fun({M,F,A}) -> io_lib:format("~p:~p/~p",[M,F,A]) end,
- ExtTypeString = string:join(lists:map(Map, ExtTypes), "\n"),
- Msg = io_lib:format("The following remote types are being used "
- "but information about them is not available.\n"
- "The analysis might get more precise by including "
- "the modules containing these types and making sure "
- "that they are exported:\n~s\n", [ExtTypeString]),
- free_editor(State, "Analysis done", Msg),
- gui_loop(State);
- {BackendPid, log, LogMsg} ->
- update_editor(Log, LogMsg),
- gui_loop(State);
- {BackendPid, warnings, Warns} ->
- SortedWarns = lists:keysort(2, Warns), %% Sort on file/line
- WarnList = [dialyzer:format_warning(W) || W <- SortedWarns],
- update_editor(Warn, lists:flatten(WarnList)),
- gui_loop(State);
- {BackendPid, done, _NewPlt, NewDocPlt} ->
- message(State, "Analysis done"),
- config_gui_stop(State),
- gui_loop(State#gui_state{doc_plt = NewDocPlt});
- {'EXIT', BackendPid, {error, Reason}} ->
- free_editor(State, ?DIALYZER_ERROR_TITLE, Reason),
- config_gui_stop(State),
- gui_loop(State);
- {'EXIT', BackendPid, Reason} when Reason =/= 'normal' ->
- free_editor(State, ?DIALYZER_ERROR_TITLE, io_lib:format("~p", [Reason])),
- config_gui_stop(State),
- gui_loop(State);
- _Other ->
- %% io:format("Received ~p\n", [Other]),
- gui_loop(State)
- end.
-
-%% ----------------------------------------------------------------
-%%
-%% Main window actions
-%%
-
-%% ---- Adding and deleting files ----
-
-handle_add_all_click(#gui_state{chosen_box = ChosenBox, file_box = File,
- file_wd = FWD, mode = Mode}) ->
- case gs:read(File, items) of
- [] ->
- ok;
- Add0 ->
- gs:config(File, [{selection, clear}]),
- Add1 = ordsets:subtract(Add0, [".."]),
- Add = ordsets:from_list([filename:join(FWD, X) || X <- Add1]),
- case gs:read(Mode#mode.start_byte_code, select) of
- true ->
- add_files(filter_mods(Add, ".beam"), ChosenBox, byte_code);
- false ->
- add_files(filter_mods(Add, ".erl"), ChosenBox, src_code)
- end
- end.
-
-all_subdirs(Dirs) ->
- all_subdirs(Dirs, []).
-
-all_subdirs([Dir|T], Acc) ->
- {ok, Files} = file:list_dir(Dir),
- SubDirs = lists:zf(fun(F) ->
- SubDir = filename:join(Dir, F),
- case filelib:is_dir(SubDir) of
- true -> {true, SubDir};
- false -> false
- end
- end, Files),
- NewAcc = ordsets:union(ordsets:from_list(SubDirs), Acc),
- all_subdirs(T ++ SubDirs, NewAcc);
-all_subdirs([], Acc) ->
- Acc.
-
-handle_add_rec_click(#gui_state{chosen_box = ChosenBox, file_box = File,
- file_wd = FWD, mode = Mode}) ->
- case gs:read(File, selection) of
- [] ->
- ok;
- List ->
- gs:config(File, [{selection, clear}]),
- Dirs1 = [gs:read(File, {get, X}) || X <- List],
- Dirs2 = ordsets:from_list([filename:join(FWD, X) || X <- Dirs1]),
- Dirs3 = ordsets:filter(fun(X) -> filelib:is_dir(X) end, Dirs2),
- TargetDirs = ordsets:union(Dirs3, all_subdirs(Dirs3)),
- {Code, Ext} = case gs:read(Mode#mode.start_byte_code, select) of
- true -> {byte_code, ".beam"};
- false -> {src_code, ".erl"}
- end,
- add_files(filter_mods(TargetDirs, Ext), ChosenBox, Code)
- end.
-
-handle_add_files(#gui_state{chosen_box = ChosenBox, file_box = File,
- file_wd = FWD, mode = Mode}) ->
- case gs:read(File, selection) of
- [] ->
- ok;
- List ->
- gs:config(File, [{selection, clear}]),
- Add0 = [gs:read(File, {get, X}) || X <- List],
- Add = ordsets:from_list([filename:join(FWD, X) || X <- Add0]),
- case gs:read(Mode#mode.start_byte_code, select) of
- true ->
- add_files(filter_mods(Add, ".beam"), ChosenBox, byte_code);
- false ->
- add_files(filter_mods(Add, ".erl"), ChosenBox, src_code)
- end
- end.
-
-filter_mods(Mods, Extension) ->
- Fun = fun(X) ->
- filename:extension(X) =:= Extension
- orelse
- (filelib:is_dir(X) andalso
- contains_files(X, Extension))
- end,
- ordsets:filter(Fun, Mods).
-
-contains_files(Dir, Extension) ->
- {ok, Files} = file:list_dir(Dir),
- lists:any(fun(X) -> filename:extension(X) =:= Extension end, Files).
-
-add_files(Add, ChosenBox, Type) ->
- Set = gs:read(ChosenBox, items),
- Set1 =
- case Type of
- byte_code -> filter_mods(Set, ".beam");
- src_code -> filter_mods(Set, ".erl")
- end,
- Files = ordsets:union(Add, Set1),
- gs:config(ChosenBox, [{items, Files}]),
- ok.
-
-handle_file_delete(#gui_state{chosen_box = ChosenBox}) ->
- List = gs:read(ChosenBox, selection),
- lists:foreach(fun(X) -> gs:config(ChosenBox, [{del, X}]) end,
- lists:reverse(lists:sort(List))).
-
-%% ---- Other ----
-
-change_dir_or_add_file(#gui_state{file_wd = FWD, mode = Mode, dir_entry = Dir,
- chosen_box = CBox, file_box = File} = State,
- Text) ->
- NewWDorFile =
- case Text of
- ".." -> filename:join(butlast(filename:split(FWD)));
- "." -> FWD;
- _ -> filename:join(FWD, Text)
- end,
- case filelib:is_dir(NewWDorFile) of
- true ->
- gs:config(Dir, [{text, NewWDorFile}]),
- {ok, List} = file:list_dir(NewWDorFile),
- gs:config(File, [{items, [".."|lists:sort(List)]}]),
- State#gui_state{file_wd = NewWDorFile};
- false ->
- case gs:read(Mode#mode.start_byte_code, select) of
- true ->
- case filter_mods([NewWDorFile], ".beam") of
- [] -> ok;
- RealFiles -> add_files(RealFiles, CBox, byte_code)
- end;
- false ->
- case filter_mods([NewWDorFile], ".erl") of
- [] -> ok;
- RealFiles -> add_files(RealFiles, CBox, src_code)
- end
- end,
- State
- end.
-
-butlast([H1, H2 | T]) ->
- [H1 | butlast([H2|T])];
-butlast([_]) ->
- [];
-butlast([]) ->
- ["/"].
-
-change_dir_absolute(#gui_state{file_wd = FWD, dir_entry = Dir,
- file_box = File} = State,
- Text) ->
- case filelib:is_dir(Text) of
- true ->
- WD = filename:join(FWD, Text),
- gs:config(Dir, [{text, WD}]),
- {ok, List} = file:list_dir(WD),
- gs:config(File, [{items, [".."|lists:sort(List)]}]),
- State#gui_state{file_wd = WD};
- false ->
- State
- end.
-
-init_warnings([{Tag, GSItem}|Left], LegalWarnings) ->
- Select = ordsets:is_element(Tag, LegalWarnings),
- gs:config(GSItem, [{select, Select}]),
- init_warnings(Left, LegalWarnings);
-init_warnings([], _LegalWarnings) ->
- ok.
-
-config_gui_start(State) ->
- Enabled = [{enable, true}],
- Disabled = [{enable, false}],
- gs:config(State#gui_state.stop, Enabled),
- gs:config(State#gui_state.run, Disabled),
- gs:config(State#gui_state.del_file, Disabled),
- gs:config(State#gui_state.clear_chosen, Disabled),
- gs:config(State#gui_state.add_file, Disabled),
- gs:config(State#gui_state.add_all, Disabled),
- gs:config(State#gui_state.add_rec, Disabled),
- gs:config(State#gui_state.clear_warn, Disabled),
- gs:config(State#gui_state.clear_log, Disabled),
- Menu = State#gui_state.menu,
- gs:config(Menu#menu.file_save_warn, Disabled),
- gs:config(Menu#menu.file_save_log, Disabled),
- gs:config(Menu#menu.opts_macros, Disabled),
- gs:config(Menu#menu.opts_includes, Disabled),
- gs:config(Menu#menu.plt_empty, Disabled),
- gs:config(Menu#menu.plt_search_doc, Disabled),
- gs:config(Menu#menu.plt_show_doc, Disabled),
- Mode = State#gui_state.mode,
- gs:config(Mode#mode.start_byte_code, Disabled),
- gs:config(Mode#mode.start_src_code, Disabled).
-
-config_gui_stop(State) ->
- Enabled = [{enable, true}],
- Disabled = [{enable, false}],
- gs:config(State#gui_state.stop, Disabled),
- gs:config(State#gui_state.run, Enabled),
- gs:config(State#gui_state.del_file, Enabled),
- gs:config(State#gui_state.clear_chosen, Enabled),
- gs:config(State#gui_state.add_file, Enabled),
- gs:config(State#gui_state.add_all, Enabled),
- gs:config(State#gui_state.add_rec, Enabled),
- gs:config(State#gui_state.clear_warn, Enabled),
- gs:config(State#gui_state.clear_log, Enabled),
- Menu = State#gui_state.menu,
- gs:config(Menu#menu.file_save_warn, Enabled),
- gs:config(Menu#menu.file_save_log, Enabled),
- gs:config(Menu#menu.opts_macros, Enabled),
- gs:config(Menu#menu.opts_includes, Enabled),
- gs:config(Menu#menu.plt_empty, Enabled),
- gs:config(Menu#menu.plt_search_doc, Enabled),
- gs:config(Menu#menu.plt_show_doc, Enabled),
- Mode = State#gui_state.mode,
- gs:config(Mode#mode.start_byte_code, Enabled),
- gs:config(Mode#mode.start_src_code, Enabled).
-
-%% ----------------------------------------------------------------
-%%
-%% Messages
-%%
-
-message(State, Message) ->
- output_sms(State, ?DIALYZER_MESSAGE_TITLE, Message).
-
-error_sms(State, Message) ->
- output_sms(State, ?DIALYZER_ERROR_TITLE, Message).
-
-%%
-%% This function is to be used *only* for small messages because lines
-%% are not wrapped and the created window has a limited area for text.
-%% For bigger messages, the function free_editor/3 is to be used.
-%%
-output_sms(#gui_state{gs = GS, top = TopWin}, Title, Message) ->
- %% Lines = string:words(Message, $\n),
- %% io:format("The message has ~w lines\n", [Lines]),
- WH = [{width, 400}, {height, 100}],
- MessageWin = gs:window(GS, [{title, Title},
- {default, button, {font, {helvetica, bold, 12}}}
- |WH]),
- MessagePacker = gs:frame(MessageWin, [{packer_y, [{fixed, 75}, {fixed, 25}]},
- {packer_x, [{fixed, 175},{fixed, 50},
- {fixed, 175}]}]),
- gs:label(MessagePacker, [{pack_x, {1, 3}}, {pack_y, 1},
- {label, {text, Message}}]),
- OK = gs:button(MessagePacker, [{label, {text, "OK"}}, {pack_xy, {2, 2}}]),
- gs:config(MessageWin, [{map, true}]),
- gs:config(MessagePacker, WH),
- message_loop(OK, MessageWin, TopWin).
-
-message_loop(Ok, Win, TopWin) ->
- receive
- {gs, Ok, click, _, _} ->
- gs:destroy(Win);
- {gs, Win, destroy, _, _} ->
- ok;
- {gs, TopWin, destroy, _, _} ->
- exit(normal);
- {gs, _, _, _, _} ->
- message_loop(Ok, Win, TopWin)
- end.
-
-dialog(#gui_state{gs = GS, top = TopWin}, Message, OkLabel, CancelLabel) ->
- WH = [{width, 400}, {height, 100}],
- WHButton = [{width, 70}, {height, 20}],
- DialogWin = gs:window(GS, [{title, "Dialyzer Message"},
- {default, button, {font, {helvetica, bold, 12}}}
- |WH]),
- DialogPacker = gs:frame(DialogWin, [{packer_y, [{fixed, 75}, {fixed, 25}]},
- {packer_x, [{fixed, 150}, {fixed, 50},
- {fixed, 50}, {fixed, 150}]}]),
- gs:label(DialogPacker, [{pack_x, {1,4}}, {pack_y, 1},
- {label, {text, Message}}]),
- Ok = gs:button(DialogPacker, [{label, {text, OkLabel}},
- {pack_xy, {2,2}}|WHButton]),
- Cancel = gs:button(DialogPacker, [{label, {text, CancelLabel}},
- {pack_xy, {3,2}}|WHButton]),
- gs:config(DialogWin, [{map, true}]),
- gs:config(DialogPacker, WH),
- dialog_loop(Ok, Cancel, DialogWin, TopWin).
-
-dialog_loop(Ok, Cancel, Win, TopWin) ->
- receive
- {gs, Ok, click, _, _} ->
- gs:destroy(Win),
- true;
- {gs, Cancel, click, _, _} ->
- gs:destroy(Win),
- false;
- {gs, Win, destroy, _, _} ->
- false;
- {gs, TopWin, destroy, _, _} ->
- exit(normal);
- {gs, _, _, _, _} ->
- dialog_loop(Ok, Cancel, Win, TopWin)
- end.
-
-maybe_quit(#gui_state{top = TopWin} = State) ->
- case dialog(State, "Do you really want to quit?", "Yes", "No") of
- true ->
- flush(),
- gs:destroy(TopWin),
- gs:stop(),
- true;
- false ->
- false
- end.
-
-
-%% ----------------------------------------------------------------
-%%
-%% Menu actions
-%%
-
-%% ---- Help Menu ----
-
-manual(State) ->
- help_menu_common(State, "Dialyzer Manual", 500, "manual.txt", white).
-
-warnings(State) ->
- help_menu_common(State, "Dialyzer Warnings", 500, "warnings.txt", white).
-
-about(State) ->
- help_menu_common(State, "About Dialyzer", 160, "about.txt", yellow).
-
-help_menu_common(#gui_state{gs = GS, top = TopWin} = State,
- Title, Height, TxtFileName, BackGroundColor) ->
- WH = [{width, 600}, {height, Height}],
- Win = gs:window(GS, [{title, Title}, {configure, true},
- {default, editor, {bg, BackGroundColor}} | WH]),
- EmptySpace = {stretch, 1},
- Frame = gs:frame(Win, [{packer_x, [EmptySpace, {fixed, 60}, EmptySpace]},
- {packer_y, [EmptySpace, {fixed, 30}]} | WH]),
- Editor = gs:editor(Frame, [{pack_x, {1, 3}}, {pack_y, 1},
- {font, {courier, 12}}, {vscroll, right},
- {wrap, word}]),
- Button = gs:button(Frame, [{label, {text, "Ok"}}, {pack_xy, {2, 2}}]),
- gs:config(Win, [{map, true}]),
- gs:config(Frame, WH),
- AboutFile = filename:join([code:lib_dir(dialyzer), "doc", TxtFileName]),
- case gs:config(Editor, {load, AboutFile}) of
- {error, Reason} ->
- gs:destroy(Win),
- error_sms(State,
- io_lib:format("Could not find doc/~s file!\n\n ~p",
- [TxtFileName, Reason]));
- ok ->
- gs:config(Editor, [{enable, false}]),
- show_info_loop(TopWin, Win, Frame, Button)
- end.
-
-%% ---- File Menu ----
-
-save_log(#gui_state{file_wd = CWD, log = Log} = State) ->
- {Win, Entry, OkButton, CancelButton} = file_box(State, "Save Log", CWD),
- save_loop(State, OkButton, CancelButton, Entry, Win, Log).
-
-save_warn(#gui_state{file_wd = CWD, warnings_box = WBox} = State) ->
- {Win, Entry, OkButton, CancelButton} = file_box(State, "Save Warnings", CWD),
- save_loop(State, OkButton, CancelButton, Entry, Win, WBox).
-
-file_box(#gui_state{gs = GS}, Title, Default) ->
- WH = [{width, 400}, {height, 75}],
- Win = gs:window(GS, [{title, Title}|WH]),
- Fix25 = {fixed, 27}, Fix75 = {fixed, 75},
- WinPacker = gs:frame(Win, [{packer_y, [Fix25, Fix25, Fix25]},
- {packer_x, [Fix75, Fix75, Fix75, {fixed, 175}]}]),
- gs:label(WinPacker, [{pack_xy, {1,2}}, {label, {text, "Enter file:"}}]),
- Entry = gs:entry(WinPacker, [{pack_x, {2,4}}, {pack_y, 2}, {keypress, true}]),
- OkButton = gs:button(WinPacker, [{label, {text, "Ok"}}, {pack_xy, {2,3}}]),
- CancelButton = gs:button(WinPacker, [{label, {text, "Cancel"}},
- {pack_xy, {3,3}}]),
- gs:config(Entry, [{text, Default}]),
- gs:config(Win, [{map, true}]),
- gs:config(WinPacker, WH),
- {Win, Entry, OkButton, CancelButton}.
-
-save_loop(#gui_state{top = TopWin} = State,
- OkButton, CancelButton, Entry, Save, Editor) ->
- receive
- {gs, OkButton, click, _, _} ->
- File = gs:read(Entry, text),
- case gs:config(Editor, [{save, File}]) of
- {error, _} ->
- error_sms(State, "Could not write to file:\n" ++ File),
- save_loop(State, OkButton, CancelButton, Entry, Save, Editor);
- _ ->
- gs:destroy(Save)
- end;
- {gs, Entry, keypress, _, ['Return'|_]} ->
- File = gs:read(Entry, text),
- case gs:config(Editor, [{save, File}]) of
- {error, _} ->
- error_sms(State, "Could not write to file:\n" ++ File),
- save_loop(State, OkButton, CancelButton, Entry, Save, Editor);
- _ ->
- gs:destroy(Save)
- end;
- {gs, Entry, keypress, _, _} ->
- save_loop(State, OkButton, CancelButton, Entry, Save, Editor);
- {gs, CancelButton, click, _, _} ->
- gs:destroy(Save);
- {gs, TopWin, destroy, _, _} ->
- exit(normal);
- {gs, Save, destroy, _, _} ->
- ok;
- {gs, _, _, _, _} ->
- save_loop(State, OkButton, CancelButton, Entry, Save, Editor)
- end.
-
-%% ---- Plt Menu ----
-
-search_doc_plt(#gui_state{gs = GS, top = TopWin} = State) ->
- WH = [{width, 400}, {height, 100}],
- WHB = [{width, 120}, {height, 30}],
- Title = io_lib:format("Search the PLT", []),
- Win = gs:window(GS, [{title, Title}, {configure, true},
- {default, editor, {bg, white}} | WH]),
- EmptySpace = {stretch, 1},
- Frame = gs:frame(Win, [{packer_x, [EmptySpace, EmptySpace, EmptySpace]},
- {packer_y, [{fixed, 30}, {fixed, 30},
- EmptySpace, {fixed, 30}]} | WH]),
- gs:label(Frame, [{pack_xy, {1,1}}, {label, {text, "Module"}}]),
- ModEntry = gs:entry(Frame, [{pack_xy, {1,2}}]),
- gs:label(Frame, [{pack_xy, {2,1}}, {label, {text, "Function"}}]),
- FunEntry = gs:entry(Frame, [{pack_xy, {2,2}}]),
- gs:label(Frame, [{pack_xy, {3,1}}, {label, {text, "Arity"}}]),
- ArityEntry = gs:entry(Frame, [{pack_xy, {3,2}}]),
- ButtonPacker = gs:frame(Frame, [{pack_xy, {2,4}},
- {packer_x, [{fixed, 60}, {fixed, 60}]},
- {packer_y, {fixed, 30}}]),
- SearchButton = gs:button(ButtonPacker, [{label, {text, "Search"}},
- {pack_xy, {1,1}}]),
- CancelButton = gs:button(ButtonPacker, [{label, {text, "Cancel"}},
- {pack_xy, {2,1}}]),
- gs:config(Win, [{map, true}]),
- gs:config(Frame, WH),
- gs:config(ButtonPacker, WHB),
- search_doc_plt_loop(State, CancelButton, SearchButton, ModEntry,
- FunEntry, ArityEntry, Win, TopWin).
-
-search_doc_plt_loop(State, CancelButton, SearchButton, ModEntry,
- FunEntry, ArityEntry, Win, TopWin) ->
- receive
- {gs, CancelButton, click, _, _} ->
- gs:destroy(Win),
- ok;
- {gs, TopWin, destroy, _, _} ->
- exit(normal);
- {gs, SearchButton, click, _, _} ->
- M = format_search(gs:read(ModEntry, text)),
- F = format_search(gs:read(FunEntry, text)),
- A = format_search(gs:read(ArityEntry, text)),
- case dialyzer_plt:get_specs(State#gui_state.doc_plt, M, F, A) of
- "" ->
- error_sms(State, "No such function"),
- search_doc_plt_loop(State, CancelButton, SearchButton, ModEntry,
- FunEntry, ArityEntry, Win, TopWin);
- NonEmptyString ->
- gs:destroy(Win),
- free_editor(State, "Content of PLT", NonEmptyString)
- end
- end.
-
-format_search([]) ->
- '_';
-format_search(String) ->
- try list_to_integer(String)
- catch error:_ -> list_to_atom(String)
- end.
-
-show_doc_plt(#gui_state{doc_plt = DocPLT} = State) ->
- case dialyzer_plt:get_specs(DocPLT) of
- "" -> error_sms(State, "No analysis has been made yet!\n");
- NonEmptyString -> free_editor(State, "Content of PLT", NonEmptyString)
- end.
-
-free_editor(#gui_state{gs = GS, top = TopWin}, Title, Contents0) ->
- Contents = lists:flatten(Contents0),
- Tokens = string:tokens(Contents, "\n"),
- NofLines = length(Tokens),
- LongestLine = lists:max([length(X) || X <- Tokens]),
- Height0 = NofLines * 25 + 80,
- Height = if Height0 > 500 -> 500; true -> Height0 end,
- Width0 = LongestLine * 7 + 60,
- Width = if Width0 > 800 -> 800; true -> Width0 end,
- WH = [{width, Width}, {height, Height}],
- Win = gs:window(GS, [{title, Title}, {configure, true},
- {default, editor, {bg, white}} | WH]),
- EmptySpace = {stretch, 1},
- Frame = gs:frame(Win, [{packer_x, [EmptySpace, {fixed, 60}, EmptySpace]},
- {packer_y, [EmptySpace, {fixed, 30}]}
- | WH]),
- Editor = gs:editor(Frame, [{pack_x, {1,3}}, {pack_y, 1},
- {font, {courier, 12}}, {vscroll, right},
- {wrap, word}, {enable, true}]),
- Button = gs:button(Frame, [{label, {text, "Ok"}}, {pack_xy, {2,2}}]),
- gs:config(Editor, [{insert, {insert, Contents}}]),
- gs:config(Editor, [{enable, false}]),
- gs:config(Win, [{map, true}]),
- gs:config(Frame, WH),
- show_info_loop(TopWin, Win, Frame, Button).
-
-%% ---- Common ----
-
-show_info_loop(TopWin, Win, Frame, Button) ->
- receive
- {gs, Button, click, _, _} ->
- gs:destroy(Win);
- {gs, TopWin, destroy, _, _} ->
- exit(normal);
- {gs, Win, destroy, _, _} ->
- ok;
- {gs, Win, configure, _Data, [W, H|_]} ->
- gs:config(Frame, [{width, W}, {height, H}]),
- show_info_loop(TopWin, Win, Frame, Button)
- end.
-
-include_dialog(#gui_state{gs = GS, options = Options}, Parent) ->
- WH = [{width, 300}, {height, 400}],
- Title = io_lib:format("Include Directories", []),
- Win = gs:window(GS, [{title, Title}, {configure, true},
- {default, entry, {bg, white}}| WH]),
- EmptySpace = {stretch, 1},
- Frame = gs:frame(Win, [{packer_x, [EmptySpace]},
- {packer_y, [{fixed, 30}, {fixed, 30}, {fixed, 30},
- EmptySpace, {fixed, 30}, {fixed, 30}]}
- | WH]),
- gs:label(Frame, [{pack_xy, {1,1}}, {label, {text, "Directory"}}]),
- DirEntry = gs:entry(Frame, [{pack_xy, {1,2}}]),
- ButtonPacker1 = gs:frame(Frame, [{pack_xy, {1,3}},
- {packer_x, [{fixed, 70}, {fixed, 70},
- EmptySpace]},
- {packer_y, {fixed, 30}}]),
- AddButton = gs:button(ButtonPacker1, [{label, {text, "Add"}},
- {pack_xy, {1,1}}]),
- Dirs = [io_lib:format("~s", [X]) || X <- Options#options.include_dirs],
- DirBox = gs:listbox(Frame, [{pack_xy, {1,4}}, {vscroll, right},
- {bg, white}, {configure, true},
- {selectmode, multiple}, {items, Dirs}]),
- ButtonPacker2 = gs:frame(Frame, [{pack_xy, {1,5}},
- {packer_x, [{fixed, 60}, {fixed, 70},
- EmptySpace]},
- {packer_y, {fixed, 30}}]),
- DeleteButton = gs:button(ButtonPacker2, [{label, {text, "Delete"}},
- {pack_xy, {1,1}}]),
- DeleteAllButton = gs:button(ButtonPacker2, [{label, {text, "Delete All"}},
- {pack_xy, {2,1}}]),
- ButtonPacker3 = gs:frame(Frame, [{pack_xy, {1,6}},
- {packer_x, [EmptySpace,
- {fixed, 60}, {fixed, 60}]},
- {packer_y, {fixed, 30}}]),
- OkButton = gs:button(ButtonPacker3, [{label, {text, "Ok"}},
- {pack_xy, {2,1}}]),
- CancelButton = gs:button(ButtonPacker3, [{label, {text, "Cancel"}},
- {pack_xy, {3,1}}]),
- gs:config(Win, [{map, true}]),
- gs:config(Frame, WH),
- include_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton,
- DirBox, DirEntry, OkButton, CancelButton, Win).
-
-include_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton,
- DirBox, DirEntry, OkButton, CancelButton, Win) ->
- receive
- {gs, CancelButton, click, _, _} ->
- gs:destroy(Win),
- ok;
- {gs, OkButton, click, _, _} ->
- gs:destroy(Win),
- Parent ! {new_options, Options},
- ok;
- {gs, Win, configure, _Data, [W, H|_]} ->
- gs:config(Frame, [{width, W}, {height, H}]),
- include_loop(Parent, Options, Frame, AddButton, DeleteAllButton,
- DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win);
- {gs, AddButton, click, _, _} ->
- Dirs = Options#options.include_dirs,
- NewDirs =
- case gs:read(DirEntry, text) of
- [] -> Dirs;
- Add -> [Add|Dirs]
- end,
- NewOptions = Options#options{include_dirs = NewDirs},
- gs:config(DirBox, [{items, NewDirs}]),
- include_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win);
- {gs, DeleteAllButton, click, _, _} ->
- gs:config(DirBox, [clear]),
- NewOptions = Options#options{include_dirs = []},
- include_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win);
- {gs, DeleteButton, click, _, _} ->
- NewOptions =
- case gs:read(DirBox, selection) of
- [] ->
- Options;
- List ->
- lists:foreach(fun(X) -> gs:config(DirBox, [{del, X}]) end,
- lists:sort(List)),
- NewDirs = gs:read(DirBox, items),
- Options#options{include_dirs = NewDirs}
- end,
- include_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win);
- {gs, Win, destroy, _, _} ->
- ok
- end.
-
-macro_dialog(#gui_state{gs = GS, options = Options}, Parent) ->
- WH = [{width, 300}, {height, 400}],
- Title = io_lib:format("Macro Definitions", []),
- Win = gs:window(GS, [{title, Title}, {configure, true},
- {default, entry, {bg, white}}| WH]),
- EmptySpace = {stretch, 1},
- Frame = gs:frame(Win, [{packer_x, [EmptySpace, EmptySpace]},
- {packer_y, [{fixed, 30}, {fixed, 30}, {fixed, 30},
- EmptySpace, {fixed, 30}, {fixed, 30}]}
- | WH]),
- gs:label(Frame, [{pack_xy, {1,1}}, {label, {text, "Macro"}}]),
- MacroEntry = gs:entry(Frame, [{pack_xy, {1,2}}]),
- gs:label(Frame, [{pack_xy, {2,1}}, {label, {text, "Term"}}]),
- TermEntry = gs:entry(Frame, [{pack_xy, {2,2}}]),
- ButtonPacker1 = gs:frame(Frame, [{pack_x, {1,2}}, {pack_y, 3},
- {packer_x, [{fixed, 70},{fixed, 70},
- EmptySpace]},
- {packer_y, {fixed, 30}}]),
- AddButton = gs:button(ButtonPacker1, [{label, {text, "Add"}},
- {pack_xy, {1,1}}]),
- Macros = [io_lib:format("~p = ~p",[X,Y]) || {X,Y} <- Options#options.defines],
- MacroBox = gs:listbox(Frame, [{pack_x, {1,2}}, {pack_y, 4}, {vscroll, right},
- {bg, white}, {configure, true},
- {selectmode, multiple},
- {items, Macros}]),
- ButtonPacker2 = gs:frame(Frame, [{pack_x, {1,2}}, {pack_y, 5},
- {packer_x, [{fixed, 60}, {fixed, 70},
- EmptySpace]},
- {packer_y, {fixed, 30}}]),
- DeleteButton = gs:button(ButtonPacker2, [{label, {text, "Delete"}},
- {pack_xy, {1,1}}]),
- DeleteAllButton = gs:button(ButtonPacker2, [{label, {text, "Delete All"}},
- {pack_xy, {2,1}}]),
- ButtonPacker3 = gs:frame(Frame, [{pack_x, {1,2}}, {pack_y, 6},
- {packer_x, [EmptySpace,
- {fixed, 60}, {fixed, 60}]},
- {packer_y, {fixed, 30}}]),
- OkButton = gs:button(ButtonPacker3, [{label, {text, "Ok"}},
- {pack_xy, {2,1}}]),
- CancelButton = gs:button(ButtonPacker3, [{label, {text, "Cancel"}},
- {pack_xy, {3,1}}]),
- gs:config(Win, [{map, true}]),
- gs:config(Frame, WH),
- macro_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton,
- MacroBox, MacroEntry, TermEntry, OkButton, CancelButton, Win).
-
-macro_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton,
- MacroBox, MacroEntry, TermEntry, OkButton, CancelButton, Win) ->
- receive
- {gs, CancelButton, click, _, _} ->
- gs:destroy(Win),
- ok;
- {gs, OkButton, click, _, _} ->
- gs:destroy(Win),
- Parent ! {new_options, Options},
- ok;
- {gs, Win, configure, _Data, [W, H|_]} ->
- gs:config(Frame, [{width, W}, {height, H}]),
- macro_loop(Parent, Options, Frame, AddButton, DeleteAllButton,
- DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton,
- CancelButton, Win);
- {gs, AddButton, click, _, _} ->
- Defines = Options#options.defines,
- NewDefines =
- case gs:read(MacroEntry, text) of
- "" -> Defines;
- Macro ->
- Empty = [{text, ""}],
- case gs:read(TermEntry, text) of
- "" ->
- gs:config(MacroEntry, Empty),
- orddict:store(list_to_atom(Macro), true, Defines);
- String ->
- case parse(String) of
- {ok, Term} ->
- gs:config(MacroEntry, Empty),
- gs:config(TermEntry, Empty),
- orddict:store(list_to_atom(Macro), Term, Defines);
- {error, _Reason} ->
- Defines
- end
- end
- end,
- NewOptions = Options#options{defines = NewDefines},
- NewEntries = [io_lib:format("~p = ~p", [X, Y]) || {X, Y} <- NewDefines],
- gs:config(MacroBox, [{items, NewEntries}]),
- macro_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton,
- CancelButton, Win);
- {gs, DeleteAllButton, click, _, _} ->
- gs:config(MacroBox, [clear]),
- NewOptions = Options#options{defines = []},
- macro_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton,
- CancelButton, Win);
- {gs, DeleteButton, click, _, _} ->
- NewOptions =
- case gs:read(MacroBox, selection) of
- [] ->
- Options;
- List ->
- gs:config(MacroBox, [{selection, clear}]),
- Fun =
- fun(X) ->
- Val = gs:read(MacroBox, {get, X}),
- [MacroName|_] = re:split(Val, " ", [{return, list}]),
- list_to_atom(MacroName)
- end,
- Delete = [Fun(X) || X <- List],
- lists:foreach(fun(X) -> gs:config(MacroBox, [{del, X}]) end,
- lists:reverse(lists:sort(List))),
- Defines = Options#options.defines,
- NewDefines = lists:foldl(fun(X, Acc) ->
- orddict:erase(X, Acc)
- end,
- Defines, Delete),
- Options#options{defines = NewDefines}
- end,
- macro_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton,
- CancelButton, Win);
- {gs, Win, destroy, _, _} ->
- ok
- end.
-
-parse(String) ->
- case erl_scan:string(String ++ ".", 1) of
- {ok, Ts, _} ->
- case erl_parse:parse_exprs(Ts) of
- {ok, [Expr]} ->
- try erl_parse:normalise(Expr)
- catch error:Reason -> {error, Reason}
- end;
- {error, E} ->
- parse_error(E)
- end;
- {error, E, _} ->
- parse_error(E)
- end.
-
-parse_error(E) ->
- S = io_lib:fwrite("Error parsing expression: ~P.", [E,15]),
- {error, S}.
-
-%% ----------------------------------------------------------------
-%%
-%% Run the analysis
-%%
-
-start_analysis(State) ->
- Analysis = build_analysis_record(State),
- case get_anal_files(State, Analysis#analysis.start_from) of
- error ->
- Msg = "You must choose one or more files or dirs\n"
- "before starting the analysis!",
- error_sms(State, Msg),
- config_gui_stop(State),
- State;
- {ok, Files} ->
- Msg = "\n========== Starting Analysis ==========\n\n",
- update_editor(State#gui_state.log, Msg),
- NewAnalysis = Analysis#analysis{files = Files},
- run_analysis(State, NewAnalysis)
- end.
-
-build_analysis_record(#gui_state{mode = Mode, menu = Menu, options = Options,
- init_plt = InitPlt0}) ->
- StartFrom =
- case gs:read(Mode#mode.start_byte_code, select) of
- true -> byte_code;
- false -> src_code
- end,
- InitPlt =
- case gs:read(Menu#menu.plt_empty, select) of
- true -> dialyzer_plt:new();
- false -> InitPlt0
- end,
- #analysis{defines = Options#options.defines,
- include_dirs = Options#options.include_dirs,
- plt = InitPlt,
- start_from = StartFrom,
- solvers = Options#options.solvers}.
-
-get_anal_files(#gui_state{chosen_box = ChosenBox}, StartFrom) ->
- Files = gs:read(ChosenBox, items),
- FilteredMods =
- case StartFrom of
- src_code -> filter_mods(Files, ".erl");
- byte_code -> filter_mods(Files, ".beam")
- end,
- FilteredDirs = [X || X <- Files, filelib:is_dir(X)],
- case ordsets:union(FilteredMods, FilteredDirs) of
- [] -> error;
- Set -> {ok, Set}
- end.
-
-run_analysis(State, Analysis) ->
- config_gui_start(State),
- Self = self(),
- NewAnalysis = Analysis#analysis{doc_plt = dialyzer_plt:new()},
- LegalWarnings = find_legal_warnings(State),
- Fun =
- fun() ->
- dialyzer_analysis_callgraph:start(Self, LegalWarnings, NewAnalysis)
- end,
- BackendPid = spawn_link(Fun),
- State#gui_state{backend_pid = BackendPid}.
-
-find_legal_warnings(#gui_state{menu = #menu{warnings = Warnings}}) ->
- ordsets:from_list([Tag || {Tag, GSItem} <- Warnings,
- gs:read(GSItem, select) =:= true]).
-
-flush() ->
- receive
- _ -> flush()
- after
- 0 -> ok
- end.
-
-update_editor(Editor, Msg) ->
- gs:config(Editor, [{enable, true}]),
- NofRows = gs:read(Editor, size),
- gs:config(Editor, [{insertpos, 'end'}]),
- gs:config(Editor, [{insert, {insert, Msg}}]),
- NewNofRows = gs:read(Editor, size),
- ScrollPos = gs:read(Editor, vscrollpos),
- gs:config(Editor, [{vscrollpos, ScrollPos + NewNofRows - NofRows}]),
- gs:config(Editor, [{enable, false}]).
diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl
index 08f31c1e13..30d2bdeca4 100644
--- a/lib/dialyzer/src/dialyzer_gui_wx.erl
+++ b/lib/dialyzer/src/dialyzer_gui_wx.erl
@@ -2,18 +2,19 @@
%%------------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -51,7 +52,6 @@
add_dir :: wx:wx_object(),
add_rec :: wx:wx_object(),
chosen_box :: wx:wx_object(),
- analysis_pid :: pid(),
del_file :: wx:wx_object(),
doc_plt :: dialyzer_plt:plt(),
clear_chosen :: wx:wx_object(),
@@ -61,7 +61,7 @@
init_plt :: dialyzer_plt:plt(),
dir_entry :: wx:wx_object(),
file_box :: wx:wx_object(),
- files_to_analyze :: ordset(string()),
+ files_to_analyze :: ordsets:ordset(string()),
gui :: wx:wx_object(),
log :: wx:wx_object(),
menu :: menu(),
@@ -71,11 +71,11 @@
stop :: wx:wx_object(),
frame :: wx:wx_object(),
warnings_box :: wx:wx_object(),
- explanation_box :: wx:wx_object(),
+ explanation_box :: wx:wx_object() | 'undefined',
wantedWarnings :: list(),
rawWarnings :: list(),
- backend_pid :: pid(),
- expl_pid :: pid()}).
+ backend_pid :: pid() | 'undefined',
+ expl_pid :: pid() | 'undefined'}).
%%------------------------------------------------------------------------
@@ -422,7 +422,7 @@ gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt,
#wx{id=?menuID_HELP_ABOUT, obj=Frame,
event=#wxCommand{type=command_menu_selected}} ->
Message = " This is DIALYZER version " ++ ?VSN ++ " \n"++
- "DIALYZER is a DIscrepany AnaLYZer for ERlang programs.\n\n"++
+ "DIALYZER is a DIscrepancy AnaLYZer for ERlang programs.\n\n"++
" Copyright (C) Tobias Lindahl <[email protected]>\n"++
" Kostis Sagonas <[email protected]>\n\n",
output_sms(State, "About Dialyzer", Message, info),
@@ -699,8 +699,7 @@ handle_add_files(#gui_state{chosen_box = ChosenBox, file_box = FileBox,
end.
handle_add_dir(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox,
- files_to_analyze = FileList,
- mode = Mode} = State) ->
+ files_to_analyze = FileList, mode = Mode} = State) ->
case wxDirPickerCtrl:getPath(DirBox) of
"" ->
State;
@@ -714,8 +713,8 @@ handle_add_dir(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox,
State#gui_state{files_to_analyze = add_files(filter_mods(NewDir1,Ext), FileList, ChosenBox, Ext)}
end.
-handle_add_rec(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox, files_to_analyze = FileList,
- mode = Mode} = State) ->
+handle_add_rec(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox,
+ files_to_analyze = FileList, mode = Mode} = State) ->
case wxDirPickerCtrl:getPath(DirBox) of
"" ->
State;
@@ -723,11 +722,11 @@ handle_add_rec(#gui_state{chosen_box = ChosenBox, dir_entry = DirBox, files_to_a
NewDir = ordsets:new(),
NewDir1 = ordsets:add_element(Dir,NewDir),
TargetDirs = ordsets:union(NewDir1, all_subdirs(NewDir1)),
- case wxRadioBox:getSelection(Mode) of
- 0 -> Ext = ".beam";
- 1-> Ext = ".erl"
- end,
- State#gui_state{files_to_analyze = add_files(filter_mods(TargetDirs,Ext), FileList, ChosenBox, Ext)}
+ Ext = case wxRadioBox:getSelection(Mode) of
+ 0 -> ".beam";
+ 1 -> ".erl"
+ end,
+ State#gui_state{files_to_analyze = add_files(filter_mods(TargetDirs, Ext), FileList, ChosenBox, Ext)}
end.
handle_file_delete(#gui_state{chosen_box = ChosenBox,
@@ -886,13 +885,10 @@ config_gui_start(State) ->
wxRadioBox:disable(State#gui_state.mode).
save_file(#gui_state{frame = Frame, warnings_box = WBox, log = Log} = State, Type) ->
- case Type of
- warnings ->
- Message = "Save Warnings",
- Box = WBox;
- log -> Message = "Save Log",
- Box = Log
- end,
+ {Message, Box} = case Type of
+ warnings -> {"Save Warnings", WBox};
+ log -> {"Save Log", Log}
+ end,
case wxTextCtrl:getValue(Box) of
"" -> error_sms(State,"There is nothing to save...\n");
_ ->
@@ -936,8 +932,7 @@ include_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) ->
wxButton:connect(DeleteAllButton, command_button_clicked),
wxButton:connect(Ok, command_button_clicked),
wxButton:connect(Cancel, command_button_clicked),
- Dirs = [io_lib:format("~s", [X])
- || X <- Options#options.include_dirs],
+ Dirs = [io_lib:format("~s", [X]) || X <- Options#options.include_dirs],
wxListBox:set(Box, Dirs),
Layout = wxBoxSizer:new(?wxVERTICAL),
Buttons = wxBoxSizer:new(?wxHORIZONTAL),
diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl
index 06672e595f..add660eae9 100644
--- a/lib/dialyzer/src/dialyzer_options.erl
+++ b/lib/dialyzer/src/dialyzer_options.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -28,7 +29,7 @@
-module(dialyzer_options).
--export([build/1]).
+-export([build/1, build_warnings/2]).
-include("dialyzer.hrl").
@@ -46,7 +47,7 @@ build(Opts) ->
?WARN_CALLGRAPH,
?WARN_FAILING_CALL,
?WARN_BIN_CONSTRUCTION,
- ?WARN_CALLGRAPH,
+ ?WARN_MAP_CONSTRUCTION,
?WARN_CONTRACT_RANGE,
?WARN_CONTRACT_TYPES,
?WARN_CONTRACT_SYNTAX,
@@ -72,9 +73,15 @@ preprocess_opts([Opt|Opts]) ->
[Opt|preprocess_opts(Opts)].
postprocess_opts(Opts = #options{}) ->
+ check_file_existence(Opts),
Opts1 = check_output_plt(Opts),
adapt_get_warnings(Opts1).
+check_file_existence(#options{analysis_type = plt_remove}) -> ok;
+check_file_existence(#options{files = Files, files_rec = FilesRec}) ->
+ assert_filenames_exist(Files),
+ assert_filenames_exist(FilesRec).
+
check_output_plt(Opts = #options{analysis_type = Mode, from = From,
output_plt = OutPLT}) ->
case is_plt_mode(Mode) of
@@ -126,14 +133,14 @@ build_options([{OptionName, Value} = Term|Rest], Options) ->
apps ->
OldValues = Options#options.files_rec,
AppDirs = get_app_dirs(Value),
- assert_filenames(Term, AppDirs),
+ assert_filenames_form(Term, AppDirs),
build_options(Rest, Options#options{files_rec = AppDirs ++ OldValues});
files ->
- assert_filenames(Term, Value),
+ assert_filenames_form(Term, Value),
build_options(Rest, Options#options{files = Value});
files_rec ->
OldValues = Options#options.files_rec,
- assert_filenames(Term, Value),
+ assert_filenames_form(Term, Value),
build_options(Rest, Options#options{files_rec = Value ++ OldValues});
analysis_type ->
NewOptions =
@@ -210,16 +217,26 @@ get_app_dirs(Apps) when is_list(Apps) ->
get_app_dirs(Apps) ->
bad_option("Use a list of otp applications", Apps).
-assert_filenames(Term, [FileName|Left]) when length(FileName) >= 0 ->
+assert_filenames(Term, Files) ->
+ assert_filenames_form(Term, Files),
+ assert_filenames_exist(Files).
+
+assert_filenames_form(Term, [FileName|Left]) when length(FileName) >= 0 ->
+ assert_filenames_form(Term, Left);
+assert_filenames_form(_Term, []) ->
+ ok;
+assert_filenames_form(Term, [_|_]) ->
+ bad_option("Malformed or non-existing filename", Term).
+
+assert_filenames_exist([FileName|Left]) ->
case filelib:is_file(FileName) orelse filelib:is_dir(FileName) of
true -> ok;
- false -> bad_option("No such file, directory or application", FileName)
+ false ->
+ bad_option("No such file, directory or application", FileName)
end,
- assert_filenames(Term, Left);
-assert_filenames(_Term, []) ->
- ok;
-assert_filenames(Term, [_|_]) ->
- bad_option("Malformed or non-existing filename", Term).
+ assert_filenames_exist(Left);
+assert_filenames_exist([]) ->
+ ok.
assert_filename(FileName) when length(FileName) >= 0 ->
ok;
@@ -269,7 +286,7 @@ assert_solvers([v2|Terms]) ->
assert_solvers([Term|_]) ->
bad_option("Illegal value for solver", Term).
--spec build_warnings([atom()], [dial_warning()]) -> [dial_warning()].
+-spec build_warnings([atom()], dial_warn_tags()) -> dial_warn_tags().
build_warnings([Opt|Opts], Warnings) ->
NewWarnings =
@@ -301,6 +318,8 @@ build_warnings([Opt|Opts], Warnings) ->
ordsets:add_element(?WARN_RETURN_ONLY_EXIT, Warnings);
race_conditions ->
ordsets:add_element(?WARN_RACE_CONDITION, Warnings);
+ no_missing_calls ->
+ ordsets:del_element(?WARN_CALLGRAPH, Warnings);
specdiffs ->
S = ordsets:from_list([?WARN_CONTRACT_SUBTYPE,
?WARN_CONTRACT_SUPERTYPE,
@@ -310,6 +329,8 @@ build_warnings([Opt|Opts], Warnings) ->
ordsets:add_element(?WARN_CONTRACT_SUBTYPE, Warnings);
underspecs ->
ordsets:add_element(?WARN_CONTRACT_SUPERTYPE, Warnings);
+ unknown ->
+ ordsets:add_element(?WARN_UNKNOWN, Warnings);
OtherAtom ->
bad_option("Unknown dialyzer warning option", OtherAtom)
end,
diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl
index 5f64099210..cf2f0e919e 100644
--- a/lib/dialyzer/src/dialyzer_plt.erl
+++ b/lib/dialyzer/src/dialyzer_plt.erl
@@ -2,18 +2,19 @@
%%----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -67,7 +68,7 @@
%%----------------------------------------------------------------------
--type mod_deps() :: dict().
+-type mod_deps() :: dialyzer_callgraph:mod_deps().
-type deep_string() :: string() | [deep_string()].
@@ -80,11 +81,11 @@
%%----------------------------------------------------------------------
--record(plt, {info = table_new() :: dict(),
- types = table_new() :: dict(),
- contracts = table_new() :: dict(),
- callbacks = table_new() :: dict(),
- exported_types = sets:new() :: set()}).
+-record(plt, {info = table_new() :: dict:dict(),
+ types = table_new() :: dict:dict(),
+ contracts = table_new() :: dict:dict(),
+ callbacks = table_new() :: dict:dict(),
+ exported_types = sets:new() :: sets:set()}).
-record(mini_plt, {info :: ets:tid(),
contracts :: ets:tid(),
@@ -96,15 +97,15 @@
-include("dialyzer.hrl").
-type file_md5() :: {file:filename(), binary()}.
--type plt_info() :: {[file_md5()], dict()}.
+-type plt_info() :: {[file_md5()], dict:dict()}.
-record(file_plt, {version = "" :: string(),
file_md5_list = [] :: [file_md5()],
- info = dict:new() :: dict(),
- contracts = dict:new() :: dict(),
- callbacks = dict:new() :: dict(),
- types = dict:new() :: dict(),
- exported_types = sets:new() :: set(),
+ info = dict:new() :: dict:dict(),
+ contracts = dict:new() :: dict:dict(),
+ callbacks = dict:new() :: dict:dict(),
+ types = dict:new() :: dict:dict(),
+ exported_types = sets:new() :: sets:set(),
mod_deps :: mod_deps(),
implementation_md5 = [] :: [file_md5()]}).
@@ -136,7 +137,7 @@ delete_list(#plt{info = Info, types = Types,
#plt{info = table_delete_list(Info, List),
types = Types,
contracts = table_delete_list(Contracts, List),
- callbacks = table_delete_list(Callbacks, List),
+ callbacks = Callbacks,
exported_types = ExpTypes}.
-spec insert_contract_list(plt(), dialyzer_contracts:plt_contracts()) -> plt().
@@ -158,9 +159,7 @@ lookup_contract(#mini_plt{contracts = ETSContracts},
ets_table_lookup(ETSContracts, MFA).
-spec lookup_callbacks(plt(), module()) ->
- 'none' | {'value', [{mfa(), {{Filename::string(),
- Line::pos_integer()},
- #contract{}}}]}.
+ 'none' | {'value', [{mfa(), dialyzer_contracts:file_contract()}]}.
lookup_callbacks(#mini_plt{callbacks = ETSCallbacks}, Mod) when is_atom(Mod) ->
ets_table_lookup(ETSCallbacks, Mod).
@@ -184,22 +183,22 @@ lookup(Plt, Label) when is_integer(Label) ->
lookup_1(#mini_plt{info = Info}, MFAorLabel) ->
ets_table_lookup(Info, MFAorLabel).
--spec insert_types(plt(), dict()) -> plt().
+-spec insert_types(plt(), dict:dict()) -> plt().
insert_types(PLT, Rec) ->
PLT#plt{types = Rec}.
--spec insert_exported_types(plt(), set()) -> plt().
+-spec insert_exported_types(plt(), sets:set()) -> plt().
insert_exported_types(PLT, Set) ->
PLT#plt{exported_types = Set}.
--spec get_types(plt()) -> dict().
+-spec get_types(plt()) -> dict:dict().
get_types(#plt{types = Types}) ->
Types.
--spec get_exported_types(plt()) -> set().
+-spec get_exported_types(plt()) -> sets:set().
get_exported_types(#plt{exported_types = ExpTypes}) ->
ExpTypes.
@@ -211,7 +210,7 @@ get_exported_types(#plt{exported_types = ExpTypes}) ->
lookup_module(#plt{info = Info}, M) when is_atom(M) ->
table_lookup_module(Info, M).
--spec all_modules(plt()) -> set().
+-spec all_modules(plt()) -> sets:set().
all_modules(#plt{info = Info, contracts = Cs}) ->
sets:union(table_all_modules(Info), table_all_modules(Cs)).
@@ -618,9 +617,7 @@ table_insert_list(Plt, [{Key, Val}|Left]) ->
table_insert_list(Plt, []) ->
Plt.
-table_insert(Plt, Key, {_Ret, _Arg} = Obj) ->
- dict:store(Key, Obj, Plt);
-table_insert(Plt, Key, #contract{} = C) ->
+table_insert(Plt, Key, {_File, #contract{}, _Xtra} = C) ->
dict:store(Key, C, Plt).
table_lookup(Plt, Obj) ->
diff --git a/lib/dialyzer/src/dialyzer_race_data_server.erl b/lib/dialyzer/src/dialyzer_race_data_server.erl
new file mode 100644
index 0000000000..3600491809
--- /dev/null
+++ b/lib/dialyzer/src/dialyzer_race_data_server.erl
@@ -0,0 +1,134 @@
+%% -*- erlang-indent-level: 2 -*-
+%%-----------------------------------------------------------------------
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2016. 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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File : dialyzer_race_data_server.erl
+%%% Author : Tobias Lindahl <[email protected]>
+%%% Description :
+%%%
+%%% Created : 18 Sep 2015 by Luca Favatella <[email protected]>
+%%%-------------------------------------------------------------------
+-module(dialyzer_race_data_server).
+
+-export([new/0,
+ duplicate/1,
+ stop/1,
+ call/2,
+ cast/2]).
+
+-include("dialyzer.hrl").
+
+%%----------------------------------------------------------------------
+
+-record(state, {race_code = dict:new() :: dict:dict(),
+ public_tables = [] :: [label()],
+ named_tables = [] :: [string()],
+ beh_api_calls = [] :: [{mfa(), mfa()}]}).
+
+%%----------------------------------------------------------------------
+
+-spec new() -> pid().
+
+new() ->
+ spawn_link(fun() -> loop(#state{}) end).
+
+-spec duplicate(pid()) -> pid().
+
+duplicate(Server) ->
+ call(dup, Server).
+
+-spec stop(pid()) -> ok.
+
+stop(Server) ->
+ cast(stop, Server).
+
+-spec call(atom(), pid()) -> term().
+
+call(Query, Server) ->
+ Ref = make_ref(),
+ Server ! {call, self(), Ref, Query},
+ receive
+ {Ref, Reply} -> Reply
+ end.
+
+-spec cast(atom() | {atom(), term()}, pid()) -> ok.
+
+cast(Message, Server) ->
+ Server ! {cast, Message},
+ ok.
+
+%%----------------------------------------------------------------------
+
+loop(State) ->
+ receive
+ {call, From, Ref, Query} ->
+ Reply = handle_call(Query, State),
+ From ! {Ref, Reply},
+ loop(State);
+ {cast, stop} ->
+ ok;
+ {cast, Message} ->
+ NewState = handle_cast(Message, State),
+ loop(NewState)
+ end.
+
+handle_cast(race_code_new, State) ->
+ State#state{race_code = dict:new()};
+handle_cast({Tag, Data}, State) ->
+ case Tag of
+ renew_race_info -> renew_race_info_handler(Data, State);
+ renew_race_code -> renew_race_code_handler(Data, State);
+ renew_race_public_tables -> renew_race_public_tables_handler(Data, State);
+ put_race_code -> State#state{race_code = Data};
+ put_public_tables -> State#state{public_tables = Data};
+ put_named_tables -> State#state{named_tables = Data};
+ put_behaviour_api_calls -> State#state{beh_api_calls = Data}
+ end.
+
+handle_call(Query,
+ #state{race_code = RaceCode,
+ public_tables = PublicTables,
+ named_tables = NamedTables,
+ beh_api_calls = BehApiCalls}
+ = State) ->
+ case Query of
+ dup -> spawn_link(fun() -> loop(State) end);
+ get_race_code -> RaceCode;
+ get_public_tables -> PublicTables;
+ get_named_tables -> NamedTables;
+ get_behaviour_api_calls -> BehApiCalls
+ end.
+
+%%----------------------------------------------------------------------
+
+renew_race_info_handler({RaceCode, PublicTables, NamedTables},
+ #state{} = State) ->
+ State#state{race_code = RaceCode,
+ public_tables = PublicTables,
+ named_tables = NamedTables}.
+
+renew_race_code_handler({Fun, FunArgs, Code},
+ #state{race_code = RaceCode} = State) ->
+ State#state{race_code = dict:store(Fun, [FunArgs, Code], RaceCode)}.
+
+renew_race_public_tables_handler(VarLabel,
+ #state{public_tables = PT} = State) ->
+ State#state{public_tables = ordsets:add_element(VarLabel, PT)}.
diff --git a/lib/dialyzer/src/dialyzer_races.erl b/lib/dialyzer/src/dialyzer_races.erl
index 2aa8343bce..bb43d1dcb8 100644
--- a/lib/dialyzer/src/dialyzer_races.erl
+++ b/lib/dialyzer/src/dialyzer_races.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -85,27 +86,34 @@
-type race_tag() :: 'whereis_register' | 'whereis_unregister'
| 'ets_lookup_insert' | 'mnesia_dirty_read_write'.
--record(beg_clause, {arg :: var_to_map1(),
- pats :: var_to_map1(),
- guard :: cerl:cerl()}).
--record(end_clause, {arg :: var_to_map1(),
- pats :: var_to_map1(),
- guard :: cerl:cerl()}).
+%% The following type is similar to the raw_warning() type but has a
+%% tag which is local to this module and is not propagated to outside
+-type dial_race_warning() :: {race_warn_tag(), warning_info(), {atom(), [term()]}}.
+-type race_warn_tag() :: ?WARN_WHEREIS_REGISTER | ?WARN_WHEREIS_UNREGISTER
+ | ?WARN_ETS_LOOKUP_INSERT | ?WARN_MNESIA_DIRTY_READ_WRITE.
+
+-record(beg_clause, {arg :: var_to_map1() | 'undefined',
+ pats :: var_to_map1() | 'undefined',
+ guard :: cerl:cerl() | 'undefined'}).
+-record(end_clause, {arg :: var_to_map1() | 'undefined',
+ pats :: var_to_map1() | 'undefined',
+ guard :: cerl:cerl() | 'undefined'}).
-record(end_case, {clauses :: [#end_clause{}]}).
--record(curr_fun, {status :: 'in' | 'out',
- mfa :: dialyzer_callgraph:mfa_or_funlbl(),
- label :: label(),
- def_vars :: [core_vars()],
- arg_types :: [erl_types:erl_type()],
- call_vars :: [core_vars()],
- var_map :: dict()}).
+-record(curr_fun, {status :: 'in' | 'out' | 'undefined',
+ mfa :: dialyzer_callgraph:mfa_or_funlbl()
+ | 'undefined',
+ label :: label() | 'undefined',
+ def_vars :: [core_vars()] | 'undefined',
+ arg_types :: [erl_types:erl_type()] | 'undefined',
+ call_vars :: [core_vars()] | 'undefined',
+ var_map :: dict:dict() | 'undefined'}).
-record(dep_call, {call_name :: dep_calls(),
- args :: args(),
+ args :: args() | 'undefined',
arg_types :: [erl_types:erl_type()],
vars :: [core_vars()],
- state :: _, %% XXX: recursive
+ state :: dialyzer_dataflow:state(),
file_line :: file_line(),
- var_map :: dict()}).
+ var_map :: dict:dict() | 'undefined'}).
-record(fun_call, {caller :: dialyzer_callgraph:mfa_or_funlbl(),
callee :: dialyzer_callgraph:mfa_or_funlbl(),
arg_types :: [erl_types:erl_type()],
@@ -114,7 +122,7 @@
arg :: var_to_map1()}).
-record(warn_call, {call_name :: warn_calls(),
args :: args(),
- var_map :: dict()}).
+ var_map :: dict:dict() | 'undefined'}).
-type case_tags() :: 'beg_case' | #beg_clause{} | #end_clause{} | #end_case{}.
-type code() :: [#dep_call{} | #fun_call{} | #warn_call{} |
@@ -132,8 +140,9 @@
fun_mfa :: dialyzer_callgraph:mfa_or_funlbl(),
fun_label :: label()}).
--record(races, {curr_fun :: dialyzer_callgraph:mfa_or_funlbl(),
- curr_fun_label :: label(),
+-record(races, {curr_fun :: dialyzer_callgraph:mfa_or_funlbl()
+ | 'undefined',
+ curr_fun_label :: label() | 'undefined',
curr_fun_args = 'empty' :: core_args(),
new_table = 'no_t' :: table(),
race_list = [] :: code(),
@@ -141,7 +150,7 @@
race_tags = [] :: [#race_fun{}],
%% true for fun types and warning mode
race_analysis = false :: boolean(),
- race_warnings = [] :: [dial_warning()]}).
+ race_warnings = [] :: [dial_race_warning()]}).
%%% ===========================================================================
%%%
@@ -306,10 +315,13 @@ race(State) ->
DepList = fixup_race_list(RaceWarnTag, VarArgs, State1),
{State2, RaceWarn} =
get_race_warn(Fun, Args, ArgTypes, DepList, State),
+ {File, Line} = FileLine,
+ CurrMFA = dialyzer_dataflow:state__find_function(CurrFun, State),
+ WarningInfo = {File, Line, CurrMFA},
race(
state__add_race_warning(
state__renew_race_tags(T, State2), RaceWarn, RaceWarnTag,
- FileLine))
+ WarningInfo))
end,
state__renew_race_tags([], RetState).
@@ -984,8 +996,7 @@ fixup_race_forward_helper(CurrFun, CurrFunLabel, Fun, FunLabel,
NewRaceVarMap, Args, NewFunArgs, NewFunTypes, NestingLevel};
{CurrFun, Fun} ->
NewCallsToAnalyze = lists:delete(Head, CallsToAnalyze),
- NewRaceVarMap =
- race_var_map(Args, NewFunArgs, RaceVarMap, bind),
+ NewRaceVarMap = race_var_map(Args, NewFunArgs, RaceVarMap, bind),
RetC =
case Fun of
InitFun ->
@@ -1012,8 +1023,7 @@ fixup_race_forward_helper(CurrFun, CurrFunLabel, Fun, FunLabel,
label = FunLabel, var_map = NewRaceVarMap,
def_vars = Args, call_vars = NewFunArgs,
arg_types = NewFunTypes}|
- lists:reverse(StateRaceList)] ++
- RetC;
+ lists:reverse(StateRaceList)] ++ RetC;
_ ->
[#curr_fun{status = in, mfa = Fun,
label = FunLabel, var_map = NewRaceVarMap,
@@ -1048,13 +1058,9 @@ fixup_race_backward(CurrFun, Calls, CallsToAnalyze, Parents, Height) ->
false -> [CurrFun|Parents]
end;
[Head|Tail] ->
- MorePaths =
- case Head of
- {Parent, CurrFun} -> true;
- {Parent, _TupleB} -> false
- end,
- case MorePaths of
- true ->
+ {Parent, TupleB} = Head,
+ case TupleB =:= CurrFun of
+ true -> % more paths are needed
NewCallsToAnalyze = lists:delete(Head, CallsToAnalyze),
NewParents =
fixup_race_backward(Parent, NewCallsToAnalyze,
@@ -1565,7 +1571,7 @@ any_args(StrList) ->
end
end.
--spec bind_dict_vars(label(), label(), dict()) -> dict().
+-spec bind_dict_vars(label(), label(), dict:dict()) -> dict:dict().
bind_dict_vars(Key, Label, RaceVarMap) ->
case Key =:= Label of
@@ -1751,7 +1757,7 @@ compare_vars(Var1, Var2, RaceVarMap) when is_integer(Var1), is_integer(Var2) ->
compare_vars(_Var1, _Var2, _RaceVarMap) ->
false.
--spec compare_var_list(label_type(), [label_type()], dict()) -> boolean().
+-spec compare_var_list(label_type(), [label_type()], dict:dict()) -> boolean().
compare_var_list(Var, VarList, RaceVarMap) ->
lists:any(fun (V) -> compare_vars(Var, V, RaceVarMap) end, VarList).
@@ -1763,7 +1769,7 @@ ets_list_args(MaybeList) ->
catch _:_ -> [?no_label]
end;
false -> [ets_tuple_args(MaybeList)]
- end.
+ end.
ets_list_argtypes(ListStr) ->
ListStr1 = string:strip(ListStr, left, $[),
@@ -1848,7 +1854,8 @@ ets_tuple_argtypes1(Str, Tuple, TupleList, NestingLevel) ->
end.
format_arg(?bypassed) -> ?no_label;
-format_arg(Arg) ->
+format_arg(Arg0) ->
+ Arg = cerl:fold_literal(Arg0),
case cerl:type(Arg) of
var -> cerl_trees:get_label(Arg);
tuple -> list_to_tuple([format_arg(A) || A <- cerl:tuple_es(Arg)]);
@@ -1878,7 +1885,7 @@ format_args_1([Arg|Args], [Type|Types], CleanState) ->
case Arg =:= ?bypassed of
true -> [?no_label, format_type(Type, CleanState)];
false ->
- case cerl:is_literal(Arg) of
+ case cerl:is_literal(cerl:fold_literal(Arg)) of
true -> [?no_label, format_cerl(Arg)];
false -> [format_arg(Arg), format_type(Type, CleanState)]
end
@@ -1956,7 +1963,8 @@ mnesia_tuple_argtypes(TupleStr) ->
[TupleStr2|_T] = string:tokens(TupleStr1, " ,"),
lists:flatten(string:tokens(TupleStr2, " |")).
--spec race_var_map(var_to_map1(), var_to_map2(), dict(), op()) -> dict().
+-spec race_var_map(var_to_map1(), var_to_map2(), dict:dict(), op()) ->
+ dict:dict().
race_var_map(Vars1, Vars2, RaceVarMap, Op) ->
case Vars1 =:= ?no_arg orelse Vars1 =:= ?bypassed
@@ -2147,7 +2155,8 @@ race_var_map_guard_helper1(Arg, Pats, RaceVarMap, Op) ->
end
end.
-race_var_map_guard_helper2(Arg, Pat, Bool, RaceVarMap, Op) ->
+race_var_map_guard_helper2(Arg, Pat0, Bool, RaceVarMap, Op) ->
+ Pat = cerl:fold_literal(Pat0),
case cerl:type(Pat) of
literal ->
[Arg1, Arg2] = cerl:call_args(Arg),
@@ -2321,7 +2330,7 @@ get_race_warnings_helper(Warnings, State) ->
[] ->
{dialyzer_dataflow:state__get_races(State), State};
[H|T] ->
- {RaceWarnTag, FileLine, {race_condition, [M, F, A, AT, S, DepList]}} = H,
+ {RaceWarnTag, WarningInfo, {race_condition, [M, F, A, AT, S, DepList]}} = H,
Reason =
case RaceWarnTag of
?WARN_WHEREIS_REGISTER ->
@@ -2344,7 +2353,7 @@ get_race_warnings_helper(Warnings, State) ->
"caused by its combination with ")
end,
W =
- {?WARN_RACE_CONDITION, FileLine,
+ {?WARN_RACE_CONDITION, WarningInfo,
{race_condition,
[M, F, dialyzer_dataflow:format_args(A, AT, S), Reason]}},
get_race_warnings_helper(T,
@@ -2374,12 +2383,12 @@ get_reason(DependencyList, Reason) ->
end
end.
-state__add_race_warning(State, RaceWarn, RaceWarnTag, FileLine) ->
+state__add_race_warning(State, RaceWarn, RaceWarnTag, WarningInfo) ->
case RaceWarn of
no_race -> State;
_Else ->
Races = dialyzer_dataflow:state__get_races(State),
- Warn = {RaceWarnTag, FileLine, RaceWarn},
+ Warn = {RaceWarnTag, WarningInfo, RaceWarn},
dialyzer_dataflow:state__put_races(add_race_warning(Warn, Races), State)
end.
diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl
index 84379642bf..987da3aecf 100644
--- a/lib/dialyzer/src/dialyzer_succ_typings.erl
+++ b/lib/dialyzer/src/dialyzer_succ_typings.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2015. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -29,7 +30,7 @@
-export([analyze_callgraph/3,
analyze_callgraph/6,
- get_warnings/8
+ get_warnings/7
]).
-export([
@@ -69,10 +70,8 @@
-type scc() :: [mfa_or_funlbl()] | [module()].
-
-record(st, {callgraph :: dialyzer_callgraph:callgraph(),
codeserver :: dialyzer_codeserver:codeserver(),
- no_warn_unused :: set(),
parent = none :: parent(),
timing_server :: dialyzer_timing:timing_server(),
solvers :: [solver()],
@@ -135,50 +134,49 @@ get_refined_success_typings(SCCs, #st{callgraph = Callgraph,
end
end.
--type doc_plt() :: 'undefined' | dialyzer_plt:plt().
-spec get_warnings(dialyzer_callgraph:callgraph(), dialyzer_plt:plt(),
- doc_plt(), dialyzer_codeserver:codeserver(), set(),
+ doc_plt(), dialyzer_codeserver:codeserver(),
dialyzer_timing:timing_server(), [solver()], pid()) ->
- {[dial_warning()], dialyzer_plt:plt(), doc_plt()}.
+ {[raw_warning()], dialyzer_plt:plt(), doc_plt()}.
get_warnings(Callgraph, Plt, DocPlt, Codeserver,
- NoWarnUnused, TimingServer, Solvers, Parent) ->
+ TimingServer, Solvers, Parent) ->
InitState =
init_state_and_get_success_typings(Callgraph, Plt, Codeserver,
TimingServer, Solvers, Parent),
- NewState = InitState#st{no_warn_unused = NoWarnUnused},
- Mods = dialyzer_callgraph:modules(NewState#st.callgraph),
- MiniPlt = NewState#st.plt,
+ Mods = dialyzer_callgraph:modules(InitState#st.callgraph),
+ MiniPlt = InitState#st.plt,
+ FindOpaques = lookup_and_find_opaques_fun(Codeserver),
CWarns =
- dialyzer_contracts:get_invalid_contract_warnings(Mods, Codeserver, MiniPlt),
+ dialyzer_contracts:get_invalid_contract_warnings(Mods, Codeserver,
+ MiniPlt, FindOpaques),
MiniDocPlt = dialyzer_plt:get_mini_plt(DocPlt),
ModWarns =
?timing(TimingServer, "warning",
- get_warnings_from_modules(Mods, NewState, MiniDocPlt)),
+ get_warnings_from_modules(Mods, InitState, MiniDocPlt)),
{postprocess_warnings(CWarns ++ ModWarns, Codeserver),
dialyzer_plt:restore_full_plt(MiniPlt, Plt),
dialyzer_plt:restore_full_plt(MiniDocPlt, DocPlt)}.
get_warnings_from_modules(Mods, State, DocPlt) ->
#st{callgraph = Callgraph, codeserver = Codeserver,
- no_warn_unused = NoWarnUnused, plt = Plt,
- timing_server = TimingServer} = State,
- Init = {Codeserver, Callgraph, NoWarnUnused, Plt, DocPlt},
+ plt = Plt, timing_server = TimingServer} = State,
+ Init = {Codeserver, Callgraph, Plt, DocPlt},
dialyzer_coordinator:parallel_job(warnings, Mods, Init, TimingServer).
--spec collect_warnings(module(), warnings_init_data()) -> [dial_warning()].
+-spec collect_warnings(module(), warnings_init_data()) -> [raw_warning()].
-collect_warnings(M, {Codeserver, Callgraph, NoWarnUnused, Plt, DocPlt}) ->
+collect_warnings(M, {Codeserver, Callgraph, Plt, DocPlt}) ->
ModCode = dialyzer_codeserver:lookup_mod_code(M, Codeserver),
Records = dialyzer_codeserver:lookup_mod_records(M, Codeserver),
Contracts = dialyzer_codeserver:lookup_mod_contracts(M, Codeserver),
AllFuns = collect_fun_info([ModCode]),
%% Check if there are contracts for functions that do not exist
- Warnings1 =
+ Warnings1 =
dialyzer_contracts:contracts_without_fun(Contracts, AllFuns, Callgraph),
{Warnings2, FunTypes} =
- dialyzer_dataflow:get_warnings(ModCode, Plt, Callgraph,
- Records, NoWarnUnused),
+ dialyzer_dataflow:get_warnings(ModCode, Plt, Callgraph, Codeserver,
+ Records),
Attrs = cerl:module_attrs(ModCode),
Warnings3 =
dialyzer_behaviours:check_callbacks(M, Attrs, Records, Plt, Codeserver),
@@ -195,17 +193,19 @@ postprocess_warnings(RawWarnings, Codeserver) ->
postprocess_dataflow_warns([], _Callgraph, WAcc, Acc) ->
lists:reverse(Acc, WAcc);
-postprocess_dataflow_warns([{?WARN_CONTRACT_RANGE, {CallF, CallL}, Msg}|Rest],
+postprocess_dataflow_warns([{?WARN_CONTRACT_RANGE, WarningInfo, Msg}|Rest],
Codeserver, WAcc, Acc) ->
+ {CallF, CallL, _CallMFA} = WarningInfo,
{contract_range, [Contract, M, F, A, ArgStrings, CRet]} = Msg,
case dialyzer_codeserver:lookup_mfa_contract({M,F,A}, Codeserver) of
- {ok, {{ContrF, _ContrL} = FileLine, _C}} ->
+ {ok, {{ContrF, ContrL}, _C, _X}} ->
case CallF =:= ContrF of
true ->
NewMsg = {contract_range, [Contract, M, F, ArgStrings, CallL, CRet]},
- W = {?WARN_CONTRACT_RANGE, FileLine, NewMsg},
+ WarningInfo2 = {ContrF, ContrL, {M, F, A}},
+ W = {?WARN_CONTRACT_RANGE, WarningInfo2, NewMsg},
Filter =
- fun({?WARN_CONTRACT_TYPES, FL, _}) when FL =:= FileLine -> false;
+ fun({?WARN_CONTRACT_TYPES, WI, _}) when WI =:= WarningInfo2 -> false;
(_) -> true
end,
FilterWAcc = lists:filter(Filter, WAcc),
@@ -217,7 +217,7 @@ postprocess_dataflow_warns([{?WARN_CONTRACT_RANGE, {CallF, CallL}, Msg}|Rest],
%% The contract is not in a module that is currently under analysis.
%% We display the warning in the file/line of the call.
NewMsg = {contract_range, [Contract, M, F, ArgStrings, CallL, CRet]},
- W = {?WARN_CONTRACT_RANGE, {CallF, CallL}, NewMsg},
+ W = {?WARN_CONTRACT_RANGE, WarningInfo, NewMsg},
postprocess_dataflow_warns(Rest, Codeserver, WAcc, [W|Acc])
end.
@@ -260,8 +260,17 @@ refine_one_module(M, {CodeServer, Callgraph, Plt, _Solvers}) ->
Records = dialyzer_codeserver:lookup_mod_records(M, CodeServer),
FunTypes = get_fun_types_from_plt(AllFuns, Callgraph, Plt),
NewFunTypes =
- dialyzer_dataflow:get_fun_types(ModCode, Plt, Callgraph, Records),
- case reached_fixpoint(FunTypes, NewFunTypes) of
+ dialyzer_dataflow:get_fun_types(ModCode, Plt, Callgraph, CodeServer, Records),
+ Contracts1 = dialyzer_codeserver:lookup_mod_contracts(M, CodeServer),
+ Contracts = orddict:from_list(dict:to_list(Contracts1)),
+ FindOpaques = find_opaques_fun(Records),
+ DecoratedFunTypes =
+ decorate_succ_typings(Contracts, Callgraph, NewFunTypes, FindOpaques),
+ %% ?debug("NewFunTypes ~p\n ~n", [dict:to_list(NewFunTypes)]),
+ %% ?debug("refine DecoratedFunTypes ~p\n ~n", [dict:to_list(DecoratedFunTypes)]),
+ debug_pp_functions("Refine", NewFunTypes, DecoratedFunTypes, Callgraph),
+
+ case reached_fixpoint(FunTypes, DecoratedFunTypes) of
true -> [];
{false, NotFixpoint} ->
?debug("Not fixpoint\n", []),
@@ -357,9 +366,16 @@ find_succ_types_for_scc(SCC, {Codeserver, Callgraph, Plt, Solvers}) ->
AllFunSet = sets:from_list([X || {X, _} <- AllFuns]),
FilteredFunTypes =
dict:filter(fun(X, _) -> sets:is_element(X, AllFunSet) end, FunTypes),
+ FindOpaques = lookup_and_find_opaques_fun(Codeserver),
+ DecoratedFunTypes =
+ decorate_succ_typings(Contracts3, Callgraph, FilteredFunTypes, FindOpaques),
%% Check contracts
PltContracts =
- dialyzer_contracts:check_contracts(Contracts3, Callgraph, FilteredFunTypes),
+ dialyzer_contracts:check_contracts(Contracts3, Callgraph,
+ DecoratedFunTypes, FindOpaques),
+ %% ?debug("FilteredFunTypes ~p\n ~n", [dict:to_list(FilteredFunTypes)]),
+ %% ?debug("SCC DecoratedFunTypes ~p\n ~n", [dict:to_list(DecoratedFunTypes)]),
+ debug_pp_functions("SCC", FilteredFunTypes, DecoratedFunTypes, Callgraph),
ContractFixpoint =
lists:all(fun({MFA, _C}) ->
%% Check the non-deleted PLT
@@ -368,16 +384,44 @@ find_succ_types_for_scc(SCC, {Codeserver, Callgraph, Plt, Solvers}) ->
{value, _} -> true
end
end, PltContracts),
- Plt = insert_into_plt(FilteredFunTypes, Callgraph, Plt),
+ Plt = insert_into_plt(DecoratedFunTypes, Callgraph, Plt),
Plt = dialyzer_plt:insert_contract_list(Plt, PltContracts),
case (ContractFixpoint andalso
- reached_fixpoint_strict(PropTypes, FilteredFunTypes)) of
+ reached_fixpoint_strict(PropTypes, DecoratedFunTypes)) of
true -> [];
false ->
?debug("Not fixpoint for: ~w\n", [AllFuns]),
[Fun || {Fun, _Arity} <- AllFuns]
end.
+decorate_succ_typings(Contracts, Callgraph, FunTypes, FindOpaques) ->
+ F = fun(Label, Type) ->
+ case dialyzer_callgraph:lookup_name(Label, Callgraph) of
+ {ok, MFA} ->
+ case orddict:find(MFA, Contracts) of
+ {ok, {_FileLine, Contract, _Xtra}} ->
+ Args = dialyzer_contracts:get_contract_args(Contract),
+ Ret = dialyzer_contracts:get_contract_return(Contract),
+ C = erl_types:t_fun(Args, Ret),
+ {M, _, _} = MFA,
+ Opaques = FindOpaques(M),
+ erl_types:t_decorate_with_opaque(Type, C, Opaques);
+ error -> Type
+ end;
+ error -> Type
+ end
+ end,
+ dict:map(F, FunTypes).
+
+lookup_and_find_opaques_fun(Codeserver) ->
+ fun(Module) ->
+ Records = dialyzer_codeserver:lookup_mod_records(Module, Codeserver),
+ (find_opaques_fun(Records))(Module)
+ end.
+
+find_opaques_fun(Records) ->
+ fun(_Module) -> erl_types:t_opaque_from_records(Records) end.
+
get_fun_types_from_plt(FunList, Callgraph, Plt) ->
get_fun_types_from_plt(FunList, Callgraph, Plt, dict:new()).
@@ -443,9 +487,30 @@ debug_pp_succ_typings(SuccTypes) ->
|| {MFA, {contract, RetFun, ArgT}} <- SuccTypes],
?debug("\n", []),
ok.
+
+debug_pp_functions(Header, FunTypes, DecoratedFunTypes, Callgraph) ->
+ ?debug("FunTypes (~s)\n", [Header]),
+ FTypes = lists:keysort(1, dict:to_list(FunTypes)),
+ DTypes = lists:keysort(1, dict:to_list(DecoratedFunTypes)),
+ Fun = fun({{Label, Type},{Label, DecoratedType}}) ->
+ Name = lookup_name(Label, Callgraph),
+ ?debug("~w (~w): ~s\n",
+ [Name, Label, erl_types:t_to_string(Type)]),
+ case erl_types:t_is_equal(Type, DecoratedType) of
+ true -> ok;
+ false ->
+ ?debug(" With opaque types: ~s\n",
+ [erl_types:t_to_string(DecoratedType)])
+ end
+ end,
+ lists:foreach(Fun, lists:zip(FTypes, DTypes)),
+ ?debug("\n", []).
-else.
debug_pp_succ_typings(_) ->
ok.
+
+debug_pp_functions(_, _, _, _) ->
+ ok.
-endif.
lookup_name(F, CG) ->
diff --git a/lib/dialyzer/src/dialyzer_timing.erl b/lib/dialyzer/src/dialyzer_timing.erl
index b1a4bdc07c..aa71318d8e 100644
--- a/lib/dialyzer/src/dialyzer_timing.erl
+++ b/lib/dialyzer/src/dialyzer_timing.erl
@@ -2,18 +2,19 @@
%%-------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -38,7 +39,7 @@ init(Active) ->
case Active of
true ->
io:format("\n"),
- spawn_link(fun() -> loop(now(), 0, "") end);
+ spawn_link(fun() -> loop(erlang:monotonic_time(), 0, "") end);
debug ->
io:format("\n"),
spawn_link(fun() -> debug_loop("") end);
@@ -105,14 +106,14 @@ debug_loop(Phase) ->
start_stamp(none, _) -> ok;
start_stamp(Pid, Msg) ->
- Pid ! {stamp, Msg, now()},
+ Pid ! {stamp, Msg, erlang:monotonic_time()},
ok.
-spec end_stamp(timing_server()) -> ok.
end_stamp(none) -> ok;
end_stamp(Pid) ->
- Pid ! {stamp, now()},
+ Pid ! {stamp, erlang:monotonic_time()},
ok.
-spec send_size_info(timing_server(), integer(), string()) -> ok.
@@ -126,8 +127,8 @@ send_size_info(Pid, Size, Unit) ->
stop(none) -> ok;
stop(Pid) ->
- Pid ! {self(), stop, now()},
+ Pid ! {self(), stop, erlang:monotonic_time()},
receive ok -> ok end.
diff(T2, T1) ->
- timer:now_diff(T2,T1) / 1000000.
+ (T2-T1) / erlang:convert_time_unit(1, seconds, native).
diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl
index a418a11e65..1787b66192 100644
--- a/lib/dialyzer/src/dialyzer_typesig.erl
+++ b/lib/dialyzer/src/dialyzer_typesig.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -31,39 +32,46 @@
-export([analyze_scc/6]).
-export([get_safe_underapprox/2]).
+%%-import(helper, %% 'helper' could be any module doing sanity checks...
+-import(erl_types,
+ [t_has_var/1, t_inf/2, t_is_equal/2, t_is_subtype/2,
+ t_subtract/2, t_subtract_list/2, t_sup/1, t_sup/2,t_unify/2]).
+
-import(erl_types,
[t_any/0, t_atom/0, t_atom_vals/1,
t_binary/0, t_bitstr/0, t_bitstr/2, t_bitstr_concat/1, t_boolean/0,
t_collect_vars/1, t_cons/2, t_cons_hd/1, t_cons_tl/1,
t_float/0, t_from_range/2, t_from_term/1,
t_fun/0, t_fun/2, t_fun_args/1, t_fun_range/1,
- t_has_var/1,
- t_inf/2, t_inf/3, t_integer/0,
- t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_cons/1, t_is_equal/2,
+ t_integer/0,
+ t_is_any/1, t_is_atom/1, t_is_any_atom/2, t_is_cons/1,
t_is_float/1, t_is_fun/1,
t_is_integer/1, t_non_neg_integer/0,
t_is_list/1, t_is_nil/1, t_is_none/1, t_is_number/1,
+ t_is_singleton/1,
- t_is_subtype/2, t_limit/2, t_list/0, t_list/1,
+ t_limit/2, t_list/0, t_list/1,
t_list_elements/1, t_nonempty_list/1, t_maybe_improper_list/0,
t_module/0, t_number/0, t_number_vals/1,
- t_opaque_match_record/2, t_opaque_matching_structure/2,
- t_opaque_from_records/1,
t_pid/0, t_port/0, t_product/1, t_reference/0,
- t_subst/2, t_subtract/2, t_subtract_list/2, t_sup/1, t_sup/2,
+ t_subst/2,
t_timeout/0, t_tuple/0, t_tuple/1,
- t_unify/3, t_var/1, t_var_name/1,
- t_none/0, t_unit/0]).
+ t_var/1, t_var_name/1,
+ t_none/0, t_unit/0,
+ t_map/0, t_map/1, t_map_get/2, t_map_put/2
+ ]).
-include("dialyzer.hrl").
%%-----------------------------------------------------------------------------
-type dep() :: integer(). %% type variable names used as constraint ids
+-type deps() :: ordsets:ordset(dep()).
+
-type type_var() :: erl_types:erl_type(). %% actually: {'c','var',_,_}
--record(fun_var, {'fun' :: fun((_) -> erl_types:erl_type()), deps :: [dep()],
- origin :: integer()}).
+-record(fun_var, {'fun' :: fun((_) -> erl_types:erl_type()), deps :: deps(),
+ origin :: integer() | 'undefined'}).
-type constr_op() :: 'eq' | 'sub'.
-type fvar_or_type() :: #fun_var{} | erl_types:erl_type().
@@ -71,50 +79,56 @@
-record(constraint, {lhs :: erl_types:erl_type(),
op :: constr_op(),
rhs :: fvar_or_type(),
- deps :: [dep()]}).
+ deps :: deps()}).
-type constraint() :: #constraint{}.
+-type mask() :: ordsets:ordset(non_neg_integer()).
+
-record(constraint_list, {type :: 'conj' | 'disj',
list :: [constr()],
- deps :: [dep()],
- masks :: [{dep(),[non_neg_integer()]}] |
- {'d',dict()},
- id :: {'list', dep()}}).
+ deps :: deps(),
+ masks = maps:new() :: #{dep() => mask()},
+ id :: {'list', dep()} | 'undefined'}).
-type constraint_list() :: #constraint_list{}.
--record(constraint_ref, {id :: type_var(), deps :: [dep()]}).
+-record(constraint_ref, {id :: type_var(), deps :: deps()}).
-type constraint_ref() :: #constraint_ref{}.
-type constr() :: constraint() | constraint_list() | constraint_ref().
--type typesig_scc() :: [{mfa(), {cerl:c_var(), cerl:c_fun()}, dict()}].
--type typesig_funmap() :: [{type_var(), type_var()}]. %% Orddict
-
--type dict_or_ets() :: {'d', dict()} | {'e', ets:tid()}.
-
--record(state, {callgraph :: dialyzer_callgraph:callgraph(),
- cs = [] :: [constr()],
- cmap = {'d', dict:new()} :: dict_or_ets(),
- fun_map = [] :: typesig_funmap(),
- fun_arities = dict:new() :: dict(),
- in_match = false :: boolean(),
- in_guard = false :: boolean(),
- module :: module(),
- name_map = dict:new() :: dict(),
- next_label = 0 :: label(),
- self_rec :: erl_types:erl_type(),
- plt :: dialyzer_plt:plt(),
- prop_types = {'d', dict:new()} :: dict_or_ets(),
- records = dict:new() :: dict(),
- opaques = [] :: [erl_types:erl_type()],
- scc = [] :: [type_var()],
- mfas :: [tuple()],
- solvers = [] :: [solver()]
+-type types() :: erl_types:type_table().
+
+-type typesig_scc() :: [{mfa(), {cerl:c_var(), cerl:c_fun()}, types()}].
+-type typesig_funmap() :: #{type_var() => type_var()}.
+
+-type prop_types() :: dict:dict(label(), types()).
+
+-record(state, {callgraph :: dialyzer_callgraph:callgraph()
+ | 'undefined',
+ cs = [] :: [constr()],
+ cmap = maps:new() :: #{type_var() => constr()},
+ fun_map = maps:new() :: typesig_funmap(),
+ fun_arities = maps:new() :: #{type_var() => arity()},
+ in_match = false :: boolean(),
+ in_guard = false :: boolean(),
+ module :: module(),
+ name_map = maps:new() :: #{mfa() => cerl:c_fun()},
+ next_label = 0 :: label(),
+ self_rec :: 'false' | erl_types:erl_type(),
+ plt :: dialyzer_plt:plt()
+ | 'undefined',
+ prop_types = dict:new() :: prop_types(),
+ records = dict:new() :: types(),
+ scc = [] :: ordsets:ordset(type_var()),
+ mfas :: [mfa()],
+ solvers = [] :: [solver()]
}).
+-type state() :: #state{}.
+
%%-----------------------------------------------------------------------------
-define(TYPE_LIMIT, 4).
@@ -164,7 +178,7 @@
-spec analyze_scc(typesig_scc(), label(),
dialyzer_callgraph:callgraph(),
- dialyzer_plt:plt(), dict(), [solver()]) -> dict().
+ dialyzer_plt:plt(), prop_types(), [solver()]) -> prop_types().
analyze_scc(SCC, NextLabel, CallGraph, Plt, PropTypes, Solvers0) ->
Solvers = solvers(Solvers0),
@@ -176,7 +190,8 @@ analyze_scc(SCC, NextLabel, CallGraph, Plt, PropTypes, Solvers0) ->
Funs = state__scc(State3),
pp_constrs_scc(Funs, State3),
constraints_to_dot_scc(Funs, State3),
- solve(Funs, State3).
+ T = solve(Funs, State3),
+ dict:from_list(maps:to_list(T)).
assert_format_of_scc([{_MFA, {_Var, _Fun}, _Records}|Left]) ->
assert_format_of_scc(Left);
@@ -192,11 +207,10 @@ solvers(Solvers) -> Solvers.
%%
%% ============================================================================
-traverse_scc([{MFA, Def, Rec}|Left], DefSet, AccState) ->
+traverse_scc([{_MFA, Def, Rec}|Left], DefSet, AccState) ->
TmpState1 = state__set_rec_dict(AccState, Rec),
- TmpState2 = state__set_opaques(TmpState1, MFA),
DummyLetrec = cerl:c_letrec([Def], cerl:c_atom(foo)),
- {NewAccState, _} = traverse(DummyLetrec, DefSet, TmpState2),
+ {NewAccState, _} = traverse(DummyLetrec, DefSet, TmpState1),
traverse_scc(Left, DefSet, NewAccState);
traverse_scc([], _DefSet, AccState) ->
AccState.
@@ -301,7 +315,7 @@ traverse(Tree, DefinedVars, State) ->
Hd = cerl:cons_hd(Tree),
Tl = cerl:cons_tl(Tree),
{State1, [HdVar, TlVar]} = traverse_list([Hd, Tl], DefinedVars, State),
- case cerl:is_literal(cerl:fold_literal(Tree)) of
+ case cerl:is_literal(fold_literal_maybe_match(Tree, State)) of
true ->
%% We do not need to do anything more here.
{State, t_cons(HdVar, TlVar)};
@@ -382,17 +396,17 @@ traverse(Tree, DefinedVars, State) ->
{State2, _} = traverse_list(Funs, DefinedVars1, State1),
traverse(Body, DefinedVars1, State2);
literal ->
- %% This is needed for finding records
- case cerl:unfold_literal(Tree) of
- Tree ->
- Type = t_from_term(cerl:concrete(Tree)),
- NewType =
- case erl_types:t_opaque_match_atom(Type, State#state.opaques) of
- [Opaque] -> Opaque;
- _ -> Type
- end,
- {State, NewType};
- NewTree -> traverse(NewTree, DefinedVars, State)
+ %% Maps are special; a literal pattern matches more than just the value
+ %% constructed by the literal. For example #{} constructs the empty map,
+ %% but matches every map.
+ case state__is_in_match(State) of
+ true ->
+ Tree1 = dialyzer_utils:refold_pattern(Tree),
+ case cerl:is_literal(Tree1) of
+ false -> traverse(Tree1, DefinedVars, State);
+ true -> {State, t_from_term(cerl:concrete(Tree))}
+ end;
+ _ -> {State, t_from_term(cerl:concrete(Tree))}
end;
module ->
Defs = cerl:module_defs(Tree),
@@ -437,7 +451,7 @@ traverse(Tree, DefinedVars, State) ->
Elements = cerl:tuple_es(Tree),
{State1, EVars} = traverse_list(Elements, DefinedVars, State),
{State2, TupleType} =
- case cerl:is_literal(cerl:fold_literal(Tree)) of
+ case cerl:is_literal(fold_literal_maybe_match(Tree, State1)) of
true ->
%% We do not need to do anything more here.
{State, t_tuple(EVars)};
@@ -460,29 +474,127 @@ traverse(Tree, DefinedVars, State) ->
end,
case Elements of
[Tag|Fields] ->
- case cerl:is_c_atom(Tag) of
+ case cerl:is_c_atom(Tag) andalso is_literal_record(Tree) of
true ->
- %% Check if an opaque term is constructed.
- case t_opaque_match_record(TupleType, State#state.opaques) of
- [Opaque] ->
- OpStruct = t_opaque_matching_structure(TupleType, Opaque),
- State3 = state__store_conj(TupleType, sub, OpStruct, State2),
- {State3, Opaque};
- %% Check if a record is constructed.
- _ ->
- Arity = length(Fields),
- Records = State2#state.records,
- case lookup_record(Records, cerl:atom_val(Tag), Arity) of
- error -> {State2, TupleType};
- {ok, RecType} ->
- State3 = state__store_conj(TupleType, sub, RecType, State2),
- {State3, TupleType}
- end
- end;
+ %% Check if a record is constructed.
+ Arity = length(Fields),
+ Records = State2#state.records,
+ case lookup_record(Records, cerl:atom_val(Tag), Arity) of
+ error -> {State2, TupleType};
+ {ok, RecType} ->
+ State3 = state__store_conj(TupleType, sub, RecType, State2),
+ {State3, TupleType}
+ end;
false -> {State2, TupleType}
- end;
+ end;
[] -> {State2, TupleType}
end;
+ map ->
+ Entries = cerl:map_es(Tree),
+ MapFoldFun = fun(Entry, AccState) ->
+ AccState1 = state__set_in_match(AccState, false),
+ {AccState2, KeyVar} = traverse(cerl:map_pair_key(Entry),
+ DefinedVars, AccState1),
+ AccState3 = state__set_in_match(
+ AccState2, state__is_in_match(AccState)),
+ {AccState4, ValVar} = traverse(cerl:map_pair_val(Entry),
+ DefinedVars, AccState3),
+ {{KeyVar, ValVar}, AccState4}
+ end,
+ {Pairs, State1} = lists:mapfoldl(MapFoldFun, State, Entries),
+ %% We mustn't recurse into map arguments to matches. Not only are they
+ %% syntactically only allowed to be the literal #{}, but that would also
+ %% cause an infinite recursion, since traverse/3 unfolds literals with
+ %% maps in them using dialyzer_utils:reflow_pattern/1.
+ {State2, ArgVar} =
+ case state__is_in_match(State) of
+ false -> traverse(cerl:map_arg(Tree), DefinedVars, State1);
+ true -> {State1, t_map()}
+ end,
+ MapVar = mk_var(Tree),
+ MapType = ?mk_fun_var(
+ fun(Map) ->
+ lists:foldl(
+ fun({K,V}, TypeAcc) ->
+ t_map_put({lookup_type(K, Map),
+ lookup_type(V, Map)},
+ TypeAcc)
+ end, t_inf(t_map(), lookup_type(ArgVar, Map)),
+ Pairs)
+ end, [ArgVar | lists:append([[K,V] || {K,V} <- Pairs])]),
+ %% TODO: does the "same element appearing several times" problem apply
+ %% here too?
+ Fun =
+ fun({KeyVar, ValVar}, {AccState, ShadowKeys}) ->
+ %% If Val is known to be the last association of Key (i.e. Key
+ %% is not in ShadowKeys), Val must be a subtype of what is
+ %% associated to Key in Tree
+ TypeFun =
+ fun(Map) ->
+ KeyType = lookup_type(KeyVar, Map),
+ case t_is_singleton(KeyType) of
+ false -> t_any();
+ true ->
+ MT = t_inf(lookup_type(MapVar, Map), t_map()),
+ case t_is_none(MT) of
+ true -> t_none();
+ false ->
+ DisjointFromKeyType =
+ fun(ShadowKey) ->
+ t_is_none(t_inf(lookup_type(ShadowKey, Map),
+ KeyType))
+ end,
+ case lists:all(DisjointFromKeyType, ShadowKeys) of
+ true -> t_map_get(KeyType, MT);
+ %% A later association might shadow this one
+ false -> t_any()
+ end
+ end
+ end
+ end,
+ ValType = ?mk_fun_var(TypeFun, [KeyVar, MapVar | ShadowKeys]),
+ {state__store_conj(ValVar, sub, ValType, AccState),
+ [KeyVar | ShadowKeys]}
+ end,
+ %% Accumulate shadowing keys right-to-left
+ {State3, _} = lists:foldr(Fun, {State2, []}, Pairs),
+ %% In a map expression, Arg must contain all keys that are inserted with
+ %% the exact (:=) operator, and are known (i.e. are not in ShadowedKeys)
+ %% to not have been introduced by a previous association
+ State4 =
+ case state__is_in_match(State) of
+ true -> State3;
+ false ->
+ ArgFun =
+ fun(Map) ->
+ FoldFun =
+ fun({{KeyVar, _}, Entry}, {AccType, ShadowedKeys}) ->
+ OpTree = cerl:map_pair_op(Entry),
+ KeyType = lookup_type(KeyVar, Map),
+ AccType1 =
+ case cerl:is_literal(OpTree) andalso
+ cerl:concrete(OpTree) =:= exact of
+ true ->
+ case t_is_none(t_inf(ShadowedKeys, KeyType)) of
+ true ->
+ t_map_put({KeyType, t_any()}, AccType);
+ false ->
+ AccType
+ end;
+ false ->
+ AccType
+ end,
+ {AccType1, t_sup(KeyType, ShadowedKeys)}
+ end,
+ %% Accumulate shadowed keys left-to-right
+ {ResType, _} = lists:foldl(FoldFun, {t_map(), t_none()},
+ lists:zip(Pairs, Entries)),
+ ResType
+ end,
+ ArgType = ?mk_fun_var(ArgFun, [KeyVar || {KeyVar, _} <- Pairs]),
+ state__store_conj(ArgVar, sub, ArgType, State3)
+ end,
+ {state__store_conj(MapVar, sub, MapType, State4), MapVar};
values ->
%% We can get into trouble when unifying products that have the
%% same element appearing several times. Handle these cases by
@@ -591,9 +703,13 @@ handle_try(Tree, DefinedVars, State) ->
case state__is_in_guard(State) of
true ->
Conj1 = mk_conj_constraint_list([ArgBodyCs,
- mk_constraint(BodyVar, eq, TreeVar)]),
+ mk_constraint(BodyVar,
+ eq,
+ TreeVar)]),
Disj = mk_disj_constraint_list([Conj1,
- mk_constraint(HandlerVar, eq, TreeVar)]),
+ mk_constraint(HandlerVar,
+ eq,
+ TreeVar)]),
NewState1 = state__new_constraint_context(HandlerState),
Conj2 = mk_conj_constraint_list([OldCs, Disj]),
NewState2 = state__store_conj(Conj2, NewState1),
@@ -604,19 +720,27 @@ handle_try(Tree, DefinedVars, State) ->
{false, false} ->
Conj1 =
mk_conj_constraint_list([ArgBodyCs,
- mk_constraint(TreeVar, eq, BodyVar)]),
+ mk_constraint(TreeVar,
+ eq,
+ BodyVar)]),
Conj2 =
mk_conj_constraint_list([HandlerCs,
- mk_constraint(TreeVar, eq, HandlerVar)]),
+ mk_constraint(TreeVar,
+ eq,
+ HandlerVar)]),
Disj = mk_disj_constraint_list([Conj1, Conj2]),
{Disj, TreeVar};
{false, true} ->
{mk_conj_constraint_list([ArgBodyCs,
- mk_constraint(TreeVar, eq, BodyVar)]),
+ mk_constraint(TreeVar,
+ eq,
+ BodyVar)]),
BodyVar};
{true, false} ->
{mk_conj_constraint_list([HandlerCs,
- mk_constraint(TreeVar, eq, HandlerVar)]),
+ mk_constraint(TreeVar,
+ eq,
+ HandlerVar)]),
HandlerVar};
{true, true} ->
?debug("Throw failed\n", []),
@@ -668,10 +792,7 @@ handle_call(Call, DefinedVars, State) ->
get_plt_constr(MFA, Dst, ArgVars, State) ->
Plt = state__plt(State),
PltRes = dialyzer_plt:lookup(Plt, MFA),
- Opaques = State#state.opaques,
- Module = State#state.module,
SCCMFAs = State#state.mfas,
- {FunModule, _, _} = MFA,
Contract =
case lists:member(MFA, SCCMFAs) of
true -> none;
@@ -691,28 +812,24 @@ get_plt_constr(MFA, Dst, ArgVars, State) ->
none ->
{?mk_fun_var(fun(Map) ->
ArgTypes = lookup_type_list(ArgVars, Map),
- dialyzer_contracts:get_contract_return(C, ArgTypes)
+ get_contract_return(C, ArgTypes)
end, ArgVars), GenArgs};
{value, {PltRetType, PltArgTypes}} ->
%% Need to combine the contract with the success typing.
{?mk_fun_var(
fun(Map) ->
- ArgTypes0 = lookup_type_list(ArgVars, Map),
- ArgTypes = case FunModule =:= Module of
- false ->
- List = lists:zip(PltArgTypes, ArgTypes0),
- [erl_types:t_unopaque_on_mismatch(T1, T2, Opaques)
- || {T1, T2} <- List];
- true -> ArgTypes0
- end,
- CRet = dialyzer_contracts:get_contract_return(C, ArgTypes),
- t_inf(CRet, PltRetType, opaque)
+ ArgTypes = lookup_type_list(ArgVars, Map),
+ CRet = get_contract_return(C, ArgTypes),
+ t_inf(CRet, PltRetType)
end, ArgVars),
- [t_inf(X, Y, opaque) || {X, Y} <- lists:zip(GenArgs, PltArgTypes)]}
+ [t_inf(X, Y) || {X, Y} <- lists:zip(GenArgs, PltArgTypes)]}
end,
state__store_conj_lists([Dst|ArgVars], sub, [RetType|ArgCs], State)
end.
+get_contract_return(C, ArgTypes) ->
+ dialyzer_contracts:get_contract_return(C, ArgTypes).
+
filter_match_fail([Clause] = Cls) ->
Body = cerl:clause_body(Clause),
case cerl:type(Body) of
@@ -828,11 +945,11 @@ get_safe_underapprox(Pats, Guard) ->
Map1 = cerl_trees:fold(fun(X, Acc) ->
case cerl:is_c_var(X) of
true ->
- dict:store(cerl_trees:get_label(X), t_any(),
- Acc);
+ maps:put(cerl_trees:get_label(X), t_any(),
+ Acc);
false -> Acc
end
- end, dict:new(), cerl:c_values(Pats)),
+ end, maps:new(), cerl:c_values(Pats)),
{Type, Map2} = get_underapprox_from_guard(Guard, Map1),
Map3 = case t_is_none(t_inf(t_from_term(true), Type)) of
true -> throw(dont_know);
@@ -840,8 +957,8 @@ get_safe_underapprox(Pats, Guard) ->
case cerl:is_c_var(Guard) of
false -> Map2;
true ->
- dict:store(cerl_trees:get_label(Guard),
- t_from_term(true), Map2)
+ maps:put(cerl_trees:get_label(Guard),
+ t_from_term(true), Map2)
end
end,
{Ts, _Map4} = get_safe_underapprox_1(Pats, [], Map3),
@@ -867,13 +984,14 @@ get_underapprox_from_guard(Tree, Map) ->
case t_is_none(Inf) of
true -> throw(dont_know);
false ->
- {True, dict:store(cerl_trees:get_label(Fun), Inf, Map1)}
+ {True, maps:put(cerl_trees:get_label(Fun), Inf, Map1)}
end
end;
MFA ->
case get_type_test(MFA) of
{ok, Type} ->
- [Arg] = cerl:call_args(Tree),
+ [Arg0] = cerl:call_args(Tree),
+ Arg = cerl:fold_literal(Arg0),
{ArgType, Map1} = get_underapprox_from_guard(Arg, Map),
Inf = t_inf(Type, ArgType),
case t_is_none(Inf) of
@@ -882,7 +1000,7 @@ get_underapprox_from_guard(Tree, Map) ->
case cerl:is_literal(Arg) of
true -> {True, Map1};
false ->
- {True, dict:store(cerl_trees:get_label(Arg), Inf, Map1)}
+ {True, maps:put(cerl_trees:get_label(Arg), Inf, Map1)}
end
end;
error ->
@@ -890,7 +1008,9 @@ get_underapprox_from_guard(Tree, Map) ->
{erlang, '=:=', 2} -> throw(dont_know);
{erlang, '==', 2} -> throw(dont_know);
{erlang, 'and', 2} ->
- [Arg1, Arg2] = cerl:call_args(Tree),
+ [Arg1_0, Arg2_0] = cerl:call_args(Tree),
+ Arg1 = cerl:fold_literal(Arg1_0),
+ Arg2 = cerl:fold_literal(Arg2_0),
case ((cerl:is_c_var(Arg1) orelse cerl:is_literal(Arg1))
andalso
(cerl:is_c_var(Arg2) orelse cerl:is_literal(Arg2))) of
@@ -912,7 +1032,7 @@ get_underapprox_from_guard(Tree, Map) ->
end;
var ->
Type =
- case dict:find(cerl_trees:get_label(Tree), Map) of
+ case maps:find(cerl_trees:get_label(Tree), Map) of
error -> throw(dont_know);
{ok, T} -> T
end,
@@ -946,6 +1066,7 @@ get_type_test({erlang, is_float, 1}) -> {ok, t_float()};
get_type_test({erlang, is_function, 1}) -> {ok, t_fun()};
get_type_test({erlang, is_integer, 1}) -> {ok, t_integer()};
get_type_test({erlang, is_list, 1}) -> {ok, t_list()};
+get_type_test({erlang, is_map, 1}) -> {ok, t_map()};
get_type_test({erlang, is_number, 1}) -> {ok, t_number()};
get_type_test({erlang, is_pid, 1}) -> {ok, t_pid()};
get_type_test({erlang, is_port, 1}) -> {ok, t_port()};
@@ -1002,7 +1123,9 @@ bitstr_val_constr(SizeType, UnitVal, Flags) ->
end
end.
-get_safe_underapprox_1([Pat|Left], Acc, Map) ->
+get_safe_underapprox_1([Pat0|Left], Acc, Map) ->
+ %% Maps should be treated as patterns, not as literals
+ Pat = dialyzer_utils:refold_pattern(Pat0),
case cerl:type(Pat) of
alias ->
APat = cerl:alias_pat(Pat),
@@ -1013,7 +1136,7 @@ get_safe_underapprox_1([Pat|Left], Acc, Map) ->
case t_is_none(Inf) of
true -> throw(dont_know);
false ->
- Map3 = dict:store(cerl_trees:get_label(AVar), Inf, Map2),
+ Map3 = maps:put(cerl_trees:get_label(AVar), Inf, Map2),
get_safe_underapprox_1(Left, [Inf|Acc], Map3)
end;
binary ->
@@ -1045,13 +1168,43 @@ get_safe_underapprox_1([Pat|Left], Acc, Map) ->
{Ts, Map1} = get_safe_underapprox_1(Es, [], Map),
Type = t_tuple(Ts),
get_safe_underapprox_1(Left, [Type|Acc], Map1);
+ map ->
+ %% Some assertions in case the syntax gets more premissive in the future
+ true = #{} =:= cerl:concrete(cerl:map_arg(Pat)),
+ true = lists:all(fun(P) ->
+ cerl:is_literal(Op = cerl:map_pair_op(P)) andalso
+ exact =:= cerl:concrete(Op)
+ end, cerl:map_es(Pat)),
+ KeyTrees = lists:map(fun cerl:map_pair_key/1, cerl:map_es(Pat)),
+ ValTrees = lists:map(fun cerl:map_pair_val/1, cerl:map_es(Pat)),
+ %% Keys must not be underapproximated. Overapproximations are safe.
+ Keys = get_safe_overapprox(KeyTrees),
+ {Vals, Map1} = get_safe_underapprox_1(ValTrees, [], Map),
+ case lists:all(fun erl_types:t_is_singleton/1, Keys) of
+ false -> throw(dont_know);
+ true -> ok
+ end,
+ SortedPairs = lists:sort(lists:zip(Keys, Vals)),
+ %% We need to deal with duplicates ourselves
+ SquashDuplicates =
+ fun SquashDuplicates([{K,First},{K,Second}|List]) ->
+ case t_is_none(Inf = t_inf(First, Second)) of
+ true -> throw(dont_know);
+ false -> [{K, Inf}|SquashDuplicates(List)]
+ end;
+ SquashDuplicates([Good|Rest]) ->
+ [Good|SquashDuplicates(Rest)];
+ SquashDuplicates([]) -> []
+ end,
+ Type = t_map(SquashDuplicates(SortedPairs)),
+ get_safe_underapprox_1(Left, [Type|Acc], Map1);
values ->
Es = cerl:values_es(Pat),
{Ts, Map1} = get_safe_underapprox_1(Es, [], Map),
Type = t_product(Ts),
get_safe_underapprox_1(Left, [Type|Acc], Map1);
var ->
- case dict:find(cerl_trees:get_label(Pat), Map) of
+ case maps:find(cerl_trees:get_label(Pat), Map) of
error -> throw(dont_know);
{ok, VarType} -> get_safe_underapprox_1(Left, [VarType|Acc], Map)
end
@@ -1059,6 +1212,15 @@ get_safe_underapprox_1([Pat|Left], Acc, Map) ->
get_safe_underapprox_1([], Acc, Map) ->
{lists:reverse(Acc), Map}.
+get_safe_overapprox(Pats) ->
+ lists:map(fun get_safe_overapprox_1/1, Pats).
+
+get_safe_overapprox_1(Pat) ->
+ case cerl:is_literal(Lit = cerl:fold_literal(Pat)) of
+ true -> t_from_term(cerl:concrete(Lit));
+ false -> t_any()
+ end.
+
%%----------------------------------------
%% Guards
%%
@@ -1086,7 +1248,7 @@ get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State)
when Op =:= '+'; Op =:= '-'; Op =:= '*' ->
ReturnType = ?mk_fun_var(fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
- erl_bif_types:type(erlang, Op, 2, TmpArgTypes)
+ bif_return(erlang, Op, 2, TmpArgTypes)
end, Args),
ArgFun =
fun(A, Pos) ->
@@ -1128,8 +1290,8 @@ get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State)
fun(LocalArg1, LocalArg2, LocalOp) ->
fun(Map) ->
DstType = lookup_type(Dst, Map),
- IsTrue = t_is_atom(true, DstType),
- IsFalse = t_is_atom(false, DstType),
+ IsTrue = t_is_any_atom(true, DstType),
+ IsFalse = t_is_any_atom(false, DstType),
case IsTrue orelse IsFalse of
true ->
Arg1Type = lookup_type(LocalArg1, Map),
@@ -1176,7 +1338,7 @@ get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State)
Arg2Var = ?mk_fun_var(Arg2Fun, DstArgs),
DstVar = ?mk_fun_var(fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
- erl_bif_types:type(erlang, Op, 2, TmpArgTypes)
+ bif_return(erlang, Op, 2, TmpArgTypes)
end, Args),
mk_conj_constraint_list([mk_constraint(Dst, sub, DstVar),
mk_constraint(Arg1, sub, Arg1Var),
@@ -1218,7 +1380,7 @@ get_bif_constr({erlang, '++', 2}, Dst, [Hd, Tl] = Args, _State) ->
ArgTypes = erl_bif_types:arg_types(erlang, '++', 2),
ReturnType = ?mk_fun_var(fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
- erl_bif_types:type(erlang, '++', 2, TmpArgTypes)
+ bif_return(erlang, '++', 2, TmpArgTypes)
end, Args),
Cs = mk_constraints(Args, sub, ArgTypes),
mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType),
@@ -1240,7 +1402,7 @@ get_bif_constr({erlang, is_function, 1}, Dst, [Arg], State) ->
get_bif_constr({erlang, is_function, 2}, Dst, [Fun, Arity], _State) ->
ArgFun = fun(Map) ->
DstType = lookup_type(Dst, Map),
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true ->
ArityType = lookup_type(Arity, Map),
case t_number_vals(ArityType) of
@@ -1258,6 +1420,8 @@ get_bif_constr({erlang, is_integer, 1}, Dst, [Arg], State) ->
get_bif_test_constr(Dst, Arg, t_integer(), State);
get_bif_constr({erlang, is_list, 1}, Dst, [Arg], State) ->
get_bif_test_constr(Dst, Arg, t_maybe_improper_list(), State);
+get_bif_constr({erlang, is_map, 1}, Dst, [Arg], State) ->
+ get_bif_test_constr(Dst, Arg, t_map(), State);
get_bif_constr({erlang, is_number, 1}, Dst, [Arg], State) ->
get_bif_test_constr(Dst, Arg, t_number(), State);
get_bif_constr({erlang, is_pid, 1}, Dst, [Arg], State) ->
@@ -1268,7 +1432,7 @@ get_bif_constr({erlang, is_reference, 1}, Dst, [Arg], State) ->
get_bif_test_constr(Dst, Arg, t_reference(), State);
get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) ->
ArgFun = fun(Map) ->
- case t_is_atom(true, lookup_type(Dst, Map)) of
+ case t_is_any_atom(true, lookup_type(Dst, Map)) of
true -> t_tuple();
false -> t_any()
end
@@ -1276,7 +1440,7 @@ get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) ->
ArgV = ?mk_fun_var(ArgFun, [Dst]),
DstFun = fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
- erl_bif_types:type(erlang, is_record, 2, TmpArgTypes)
+ bif_return(erlang, is_record, 2, TmpArgTypes)
end,
DstV = ?mk_fun_var(DstFun, Args),
mk_conj_constraint_list([mk_constraint(Dst, sub, DstV),
@@ -1285,10 +1449,9 @@ get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) ->
get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
%% TODO: Revise this to make it precise for Tag and Arity.
Records = State#state.records,
- AllOpaques = State#state.opaques,
ArgFun =
fun(Map) ->
- case t_is_atom(true, lookup_type(Dst, Map)) of
+ case t_is_any_atom(true, lookup_type(Dst, Map)) of
true ->
ArityType = lookup_type(Arity, Map),
case t_is_integer(ArityType) of
@@ -1304,10 +1467,7 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
[TagVal] ->
case lookup_record(Records, TagVal, ArityVal - 1) of
{ok, Type} ->
- case t_opaque_match_record(Type, AllOpaques) of
- [Opaque] -> Opaque;
- _ -> Type
- end;
+ Type;
error -> GenRecord
end;
_ -> GenRecord
@@ -1323,38 +1483,9 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
end,
ArgV = ?mk_fun_var(ArgFun, [Tag, Arity, Dst]),
DstFun = fun(Map) ->
- [TmpVar, TmpTag, TmpArity] = TmpArgTypes = lookup_type_list(Args, Map),
- TmpArgTypes2 =
- case lists:member(TmpVar, AllOpaques) of
- true ->
- case t_is_integer(TmpArity) of
- true ->
- case t_number_vals(TmpArity) of
- [TmpArityVal] ->
- case t_is_atom(TmpTag) of
- true ->
- case t_atom_vals(TmpTag) of
- [TmpTagVal] ->
- case lookup_record(Records, TmpTagVal,
- TmpArityVal - 1) of
- {ok, TmpType} ->
- case t_is_none(t_inf(TmpType, TmpVar, opaque)) of
- true -> TmpArgTypes;
- false -> [TmpType, TmpTag, TmpArity]
- end;
- error -> TmpArgTypes
- end;
- _ -> TmpArgTypes
- end;
- false -> TmpArgTypes
- end;
- _ -> TmpArgTypes
- end;
- false -> TmpArgTypes
- end;
- false -> TmpArgTypes
- end,
- erl_bif_types:type(erlang, is_record, 3, TmpArgTypes2)
+ [TmpVar, TmpTag, TmpArity] = lookup_type_list(Args, Map),
+ TmpArgTypes = [TmpVar,TmpTag,TmpArity],
+ bif_return(erlang, is_record, 3, TmpArgTypes)
end,
DstV = ?mk_fun_var(DstFun, Args),
mk_conj_constraint_list([mk_constraint(Dst, sub, DstV),
@@ -1369,12 +1500,14 @@ get_bif_constr({erlang, 'and', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
ArgFun = fun(Var) ->
fun(Map) ->
DstType = lookup_type(Dst, Map),
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true -> True;
false ->
- case t_is_atom(false, DstType) of
+ case t_is_any_atom(false, DstType) of
true ->
- case t_is_atom(true, lookup_type(Var, Map)) of
+ case
+ t_is_any_atom(true, lookup_type(Var, Map))
+ of
true -> False;
false -> t_boolean()
end;
@@ -1386,15 +1519,15 @@ get_bif_constr({erlang, 'and', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
end,
DstFun = fun(Map) ->
Arg1Type = lookup_type(Arg1, Map),
- case t_is_atom(false, Arg1Type) of
+ case t_is_any_atom(false, Arg1Type) of
true -> False;
false ->
Arg2Type = lookup_type(Arg2, Map),
- case t_is_atom(false, Arg2Type) of
+ case t_is_any_atom(false, Arg2Type) of
true -> False;
false ->
- case (t_is_atom(true, Arg1Type)
- andalso t_is_atom(true, Arg2Type)) of
+ case (t_is_any_atom(true, Arg1Type)
+ andalso t_is_any_atom(true, Arg2Type)) of
true -> True;
false -> t_boolean()
end
@@ -1413,12 +1546,14 @@ get_bif_constr({erlang, 'or', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
ArgFun = fun(Var) ->
fun(Map) ->
DstType = lookup_type(Dst, Map),
- case t_is_atom(false, DstType) of
+ case t_is_any_atom(false, DstType) of
true -> False;
false ->
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true ->
- case t_is_atom(false, lookup_type(Var, Map)) of
+ case
+ t_is_any_atom(false, lookup_type(Var, Map))
+ of
true -> True;
false -> t_boolean()
end;
@@ -1430,15 +1565,15 @@ get_bif_constr({erlang, 'or', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
end,
DstFun = fun(Map) ->
Arg1Type = lookup_type(Arg1, Map),
- case t_is_atom(true, Arg1Type) of
+ case t_is_any_atom(true, Arg1Type) of
true -> True;
false ->
Arg2Type = lookup_type(Arg2, Map),
- case t_is_atom(true, Arg2Type) of
+ case t_is_any_atom(true, Arg2Type) of
true -> True;
false ->
- case (t_is_atom(false, Arg1Type)
- andalso t_is_atom(false, Arg2Type)) of
+ case (t_is_any_atom(false, Arg1Type)
+ andalso t_is_any_atom(false, Arg2Type)) of
true -> False;
false -> t_boolean()
end
@@ -1465,10 +1600,10 @@ get_bif_constr({erlang, 'not', 1}, Dst, [Arg] = Args, _State) ->
Fun = fun(Var) ->
fun(Map) ->
Type = lookup_type(Var, Map),
- case t_is_atom(true, Type) of
+ case t_is_any_atom(true, Type) of
true -> False;
false ->
- case t_is_atom(false, Type) of
+ case t_is_any_atom(false, Type) of
true -> True;
false -> t_boolean()
end
@@ -1485,10 +1620,10 @@ get_bif_constr({erlang, '=:=', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
fun(Map) ->
DstType = lookup_type(Dst, Map),
OtherVarType = lookup_type(OtherVar, Map),
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true -> OtherVarType;
false ->
- case t_is_atom(false, DstType) of
+ case t_is_any_atom(false, DstType) of
true ->
case is_singleton_type(OtherVarType) of
true -> t_subtract(lookup_type(Self, Map), OtherVarType);
@@ -1518,7 +1653,7 @@ get_bif_constr({erlang, '=:=', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
get_bif_constr({erlang, '==', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
DstFun = fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
- erl_bif_types:type(erlang, '==', 2, TmpArgTypes)
+ bif_return(erlang, '==', 2, TmpArgTypes)
end,
ArgFun =
fun(Var, Self) ->
@@ -1527,16 +1662,16 @@ get_bif_constr({erlang, '==', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
DstType = lookup_type(Dst, Map),
case is_singleton_non_number_type(VarType) of
true ->
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true -> VarType;
false ->
- case t_is_atom(false, DstType) of
+ case t_is_any_atom(false, DstType) of
true -> t_subtract(lookup_type(Self, Map), VarType);
false -> t_any()
end
end;
false ->
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true ->
case t_is_number(VarType) of
true -> t_number();
@@ -1560,18 +1695,14 @@ get_bif_constr({erlang, '==', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
mk_constraint(Arg1, sub, ArgV1),
mk_constraint(Arg2, sub, ArgV2)]);
get_bif_constr({erlang, element, 2} = _BIF, Dst, Args,
- #state{cs = Constrs, opaques = Opaques}) ->
+ #state{cs = Constrs}) ->
GenType = erl_bif_types:type(erlang, element, 2),
case t_is_none(GenType) of
true -> ?debug("Bif: ~w failed\n", [_BIF]), throw(error);
false ->
Fun = fun(Map) ->
- [I, T] = ATs = lookup_type_list(Args, Map),
- ATs2 = case lists:member(T, Opaques) of
- true -> [I, erl_types:t_opaque_structure(T)];
- false -> ATs
- end,
- erl_bif_types:type(erlang, element, 2, ATs2)
+ ATs2 = lookup_type_list(Args, Map),
+ bif_return(erlang, element, 2, ATs2)
end,
ReturnType = ?mk_fun_var(Fun, Args),
ArgTypes = erl_bif_types:arg_types(erlang, element, 2),
@@ -1583,22 +1714,14 @@ get_bif_constr({erlang, element, 2} = _BIF, Dst, Args,
end,
mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType)|NewCs])
end;
-get_bif_constr({M, F, A} = _BIF, Dst, Args, State) ->
+get_bif_constr({M, F, A} = _BIF, Dst, Args, _State) ->
GenType = erl_bif_types:type(M, F, A),
- Opaques = State#state.opaques,
case t_is_none(GenType) of
true -> ?debug("Bif: ~w failed\n", [_BIF]), throw(error);
false ->
- UnopaqueFun =
- fun(T) -> case lists:member(T, Opaques) of
- true -> erl_types:t_unopaque(T, [T]);
- false -> T
- end
- end,
ReturnType = ?mk_fun_var(fun(Map) ->
- TmpArgTypes0 = lookup_type_list(Args, Map),
- TmpArgTypes = [UnopaqueFun(T) || T<- TmpArgTypes0],
- erl_bif_types:type(M, F, A, TmpArgTypes)
+ TmpArgTypes = lookup_type_list(Args, Map),
+ bif_return(M, F, A, TmpArgTypes)
end, Args),
case erl_bif_types:is_known(M, F, A) of
false ->
@@ -1616,13 +1739,13 @@ get_bif_constr({M, F, A} = _BIF, Dst, Args, State) ->
end.
eval_inv_arith('+', _Pos, Dst, Arg) ->
- erl_bif_types:type(erlang, '-', 2, [Dst, Arg]);
+ bif_return(erlang, '-', 2, [Dst, Arg]);
eval_inv_arith('*', _Pos, Dst, Arg) ->
- case t_number_vals(Arg) of
- [0] -> t_integer();
- _ ->
- TmpRet = erl_bif_types:type(erlang, 'div', 2, [Dst, Arg]),
- Zero = t_from_term(0),
+ Zero = t_from_term(0),
+ case t_is_none(t_inf(Arg, Zero)) of
+ false -> t_integer();
+ true ->
+ TmpRet = bif_return(erlang, 'div', 2, [Dst, Arg]),
%% If 0 is not part of the result, it cannot be part of the argument.
case t_is_subtype(Zero, Dst) of
false -> t_subtract(TmpRet, Zero);
@@ -1630,9 +1753,9 @@ eval_inv_arith('*', _Pos, Dst, Arg) ->
end
end;
eval_inv_arith('-', 1, Dst, Arg) ->
- erl_bif_types:type(erlang, '-', 2, [Arg, Dst]);
+ bif_return(erlang, '-', 2, [Arg, Dst]);
eval_inv_arith('-', 2, Dst, Arg) ->
- erl_bif_types:type(erlang, '+', 2, [Arg, Dst]).
+ bif_return(erlang, '+', 2, [Arg, Dst]).
range_inc(neg_inf) -> neg_inf;
range_inc(pos_inf) -> pos_inf;
@@ -1642,33 +1765,20 @@ range_dec(neg_inf) -> neg_inf;
range_dec(pos_inf) -> pos_inf;
range_dec(Int) when is_integer(Int) -> Int - 1.
-get_bif_test_constr(Dst, Arg, Type, State) ->
+get_bif_test_constr(Dst, Arg, Type, _State) ->
ArgFun = fun(Map) ->
DstType = lookup_type(Dst, Map),
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true -> Type;
false -> t_any()
end
end,
ArgV = ?mk_fun_var(ArgFun, [Dst]),
- Opaques = State#state.opaques,
DstFun = fun(Map) ->
ArgType = lookup_type(Arg, Map),
case t_is_none(t_inf(ArgType, Type)) of
true ->
- case lists:member(ArgType, Opaques) of
- true ->
- OpaqueStruct = erl_types:t_opaque_structure(ArgType),
- case t_is_none(t_inf(OpaqueStruct, Type)) of
- true -> t_from_term(false);
- false ->
- case t_is_subtype(ArgType, Type) of
- true -> t_from_term(true);
- false -> t_boolean()
- end
- end;
- false -> t_from_term(false)
- end;
+ t_from_term(false);
false ->
case t_is_subtype(ArgType, Type) of
true -> t_from_term(true);
@@ -1693,12 +1803,16 @@ solve([Fun], State) ->
solve([_|_] = SCC, State) ->
?debug("============ Analyzing SCC: ~w ===========\n",
[[debug_lookup_name(F) || F <- SCC]]),
- {Parallel, NewState} =
- case parallel_split(SCC) of
- false -> {false, State};
- SplitSCC -> {SplitSCC, minimize_state(State)}
- end,
- solve_scc(SCC, Parallel, map_new(), NewState, false).
+ Users = comp_users(SCC, State),
+ solve_scc(SCC, map_new(), State, Users, _ToSolve=SCC, false).
+
+comp_users(SCC, State) ->
+ Vars0 = [{Fun, state__get_rec_var(Fun, State)} || Fun <- SCC],
+ Vars = lists:sort([t_var_name(Var) || {_, {ok, Var}} <- Vars0]),
+ family([{t_var(V), F} ||
+ F <- SCC,
+ V <- ordsets:intersection(get_deps(state__get_cs(F, State)),
+ Vars)]).
solve_fun(Fun, FunMap, State) ->
Cs = state__get_cs(Fun, State),
@@ -1713,7 +1827,7 @@ solve_fun(Fun, FunMap, State) ->
end,
enter_type(Fun, NewType, NewFunMap1).
-solve_scc(SCC, Parallel, Map, State, TryingUnit) ->
+solve_scc(SCC, Map, State, Users, ToSolve, TryingUnit) ->
Vars0 = [{Fun, state__get_rec_var(Fun, State)} || Fun <- SCC],
Vars = [Var || {_, {ok, Var}} <- Vars0],
Funs = [Fun || {Fun, {ok, _}} <- Vars0],
@@ -1721,16 +1835,13 @@ solve_scc(SCC, Parallel, Map, State, TryingUnit) ->
RecTypes = [t_limit(Type, ?TYPE_LIMIT) || Type <- Types],
CleanMap = lists:foldl(fun(Fun, AccFunMap) ->
erase_type(t_var_name(Fun), AccFunMap)
- end, Map, SCC),
+ end, Map, ToSolve),
Map1 = enter_type_lists(Vars, RecTypes, CleanMap),
?debug("Checking SCC: ~w\n", [[debug_lookup_name(F) || F <- SCC]]),
- FunSet = ordsets:from_list([t_var_name(F) || F <- SCC]),
- Map2 =
- case Parallel of
- false -> solve_whole_scc(SCC, Map1, State);
- SplitSCC -> solve_whole_scc_parallel(SplitSCC, Map1, State)
- end,
- case maps_are_equal(Map2, Map, FunSet) of
+ SolveFun = fun(X, Y) -> scc_fold_fun(X, Y, State) end,
+ Map2 = lists:foldl(SolveFun, Map1, ToSolve),
+ Updated = updated_vars_only(Vars, Map, Map2),
+ case Updated =:= [] of
true ->
?debug("SCC ~w reached fixpoint\n", [SCC]),
NewTypes = unsafe_lookup_type_list(Funs, Map2),
@@ -1743,129 +1854,21 @@ solve_scc(SCC, Parallel, Map, State, TryingUnit) ->
true -> t_fun(t_fun_args(T), t_unit())
end || T <- NewTypes],
Map3 = enter_type_lists(Funs, UnitTypes, Map2),
- solve_scc(SCC, Parallel, Map3, State, true);
+ solve_scc(SCC, Map3, State, Users, SCC, true);
false ->
- case Parallel of
- false -> true;
- _ -> dispose_state(State)
- end,
Map2
end;
false ->
?debug("SCC ~w did not reach fixpoint\n", [SCC]),
- solve_scc(SCC, Parallel, Map2, State, TryingUnit)
+ ToSolve1 = affected(Updated, Users),
+ solve_scc(SCC, Map2, State, Users, ToSolve1, TryingUnit)
end.
-solve_whole_scc(SCC, Map, State) ->
- SolveFun = fun(X, Y) -> scc_fold_fun(X, Y, State) end,
- lists:foldl(SolveFun, Map, SCC).
-
-%%------------------------------------------------------------------------------
-
--define(worth_it, 42).
-
-parallel_split(SCC) ->
- Length = length(SCC),
- case Length > 2*?worth_it of
- false -> false;
- true ->
- case min(dialyzer_utils:parallelism(), 8) of
- 1 -> false;
- CPUs ->
- FullShare = Length div CPUs + 1,
- Unit = max(FullShare, ?worth_it),
- split(SCC, Unit, [])
- end
- end.
-
-minimize_state(#state{
- cmap = {d, CMap},
- fun_map = FunMap,
- fun_arities = FunArities,
- self_rec = SelfRec,
- prop_types = {d, PropTypes},
- opaques = Opaques,
- solvers = Solvers
- }) ->
- Opts = [{read_concurrency, true}],
- ETSCMap = ets:new(cmap, Opts),
- ETSPropTypes = ets:new(prop_types, Opts),
- true = ets:insert(ETSCMap, dict:to_list(CMap)),
- true = ets:insert(ETSPropTypes, dict:to_list(PropTypes)),
- #state
- {cmap = {e, ETSCMap},
- fun_map = FunMap,
- fun_arities = FunArities,
- self_rec = SelfRec,
- prop_types = {e, ETSPropTypes},
- opaques = Opaques,
- solvers = Solvers
- }.
-
-dispose_state(#state{cmap = {e, ETSCMap},
- prop_types = {e, ETSPropTypes}}) ->
- true = ets:delete(ETSCMap),
- true = ets:delete(ETSPropTypes).
-
-solve_whole_scc_parallel(SplitSCC, Map, State) ->
- Workers = spawn_workers(SplitSCC, Map, State),
- wait_results(Workers, Map, fold_res_fun(State)).
-
-spawn_workers(SplitSCC, Map, State) ->
- Spawner = solve_scc_spawner(self(), Map, State),
- lists:foreach(Spawner, SplitSCC),
- length(SplitSCC).
-
-wait_results(0, Map, _FoldResFun) ->
- Map;
-wait_results(Pending, Map, FoldResFun) ->
- Res = receive_scc_result(),
- NewMap = lists:foldl(FoldResFun, Map, Res),
- wait_results(Pending-1, NewMap, FoldResFun).
-
-solve_scc_spawner(Parent, Map, State) ->
- fun(SCCPart) ->
- spawn_link(fun() -> solve_scc_worker(Parent, SCCPart, Map, State) end)
- end.
-
-split([], _Unit, Acc) ->
- Acc;
-split(List, Unit, Acc) ->
- {Taken, Rest} =
- try
- lists:split(Unit, List)
- catch
- _:_ -> {List, []}
- end,
- split(Rest, Unit, [Taken|Acc]).
-
-solve_scc_worker(Parent, SCCPart, Map, State) ->
- SolveFun = fun(X, Y) -> scc_fold_fun(X, Y, State) end,
- FinalMap = lists:foldl(SolveFun, Map, SCCPart),
- Res =
- [{F, t_limit(unsafe_lookup_type(F, FinalMap), ?TYPE_LIMIT)} ||
- F <- SCCPart],
- send_scc_result(Parent, Res).
-
-fold_res_fun(State) ->
- fun({F, Type}, Map) ->
- case state__get_rec_var(F, State) of
- {ok, R} ->
- enter_type(R, Type, enter_type(F, Type, Map));
- error ->
- enter_type(F, Type, Map)
- end
- end.
-
-receive_scc_result() ->
- receive
- {scc_fun, Res} -> Res
- end.
-
-send_scc_result(Parent, Res) ->
- Parent ! {scc_fun, Res}.
-
-%%------------------------------------------------------------------------------
+affected(Updated, Users) ->
+ lists:umerge([case lists:keyfind(V, 1, Users) of
+ {V, Vs} -> Vs;
+ false -> []
+ end || V <- Updated]).
scc_fold_fun(F, FunMap, State) ->
Deps = get_deps(state__get_cs(F, State)),
@@ -1907,7 +1910,7 @@ solver(Solver, SolveFun) ->
solve_fun(v1, _Fun, Cs, FunMap, State) ->
fun() ->
- {ok, _MapDict, NewMap} = solve_ref_or_list(Cs, FunMap, dict:new(), State),
+ {ok, _MapDict, NewMap} = solve_ref_or_list(Cs, FunMap, map_new(), State),
{ok, NewMap}
end;
solve_fun(v2, Fun, _Cs, FunMap, State) ->
@@ -1947,8 +1950,8 @@ sane_maps(Map1, Map2, Keys, _S1, _S2) ->
%% Solver v2
--record(v2_state, {constr_data = dict:new() :: dict(),
- state :: #state{}}).
+-record(v2_state, {constr_data = maps:new() :: map(),
+ state :: state()}).
v2_solve_ref(Fun, Map, State) ->
V2State = #v2_state{state = State},
@@ -1956,8 +1959,7 @@ v2_solve_ref(Fun, Map, State) ->
{ok, NewMap}.
v2_solve(#constraint{}=C, Map, V2State) ->
- State = V2State#v2_state.state,
- case solve_one_c(C, Map, State#state.opaques) of
+ case solve_one_c(C, Map) of
error ->
report_failed_constraint(C, Map),
{error, V2State};
@@ -2031,7 +2033,7 @@ v2_solve_self_recursive(Cs, Map, Id, RecType0, V2State0) ->
{ok, NewMap, V2State, U} ->
pp_map("recursive finished", NewMap),
NewRecType = unsafe_lookup_type(Id, NewMap),
- case t_is_equal(NewRecType, RecType0) of
+ case is_equal(NewRecType, RecType0) of
true ->
{NewMap2, U1} = enter_var_type(RecVar, NewRecType, NewMap),
{ok, NewMap2, V2State, lists:umerge(U, U1)};
@@ -2110,30 +2112,30 @@ v2_solve_disj(Is, [C|Cs], I, Map, V2State, UL, MapL, Eval, Uneval0, Failed) ->
v2_solve_disj(Is, Cs, I+1, Map, V2State, UL, MapL, Eval, Uneval, Failed).
save_local_map(#v2_state{constr_data = ConData}=V2State, Id, U, Map) ->
- Part0 = [{V,dict:fetch(V, Map)} || V <- U],
+ Part0 = [{V,maps:get(V, Map)} || V <- U],
Part1 =
- case dict:find(Id, ConData) of
+ case maps:find(Id, ConData) of
error -> []; % cannot happen
{ok, {Part2,[]}} -> Part2
end,
?debug("save local map Id=~w:\n", [Id]),
Part = lists:ukeymerge(1, lists:keysort(1, Part0), Part1),
- pp_map("New Part", dict:from_list(Part0)),
- pp_map("Old Part", dict:from_list(Part1)),
- pp_map(" => Part", dict:from_list(Part)),
- V2State#v2_state{constr_data = dict:store(Id, {Part,[]}, ConData)}.
+ pp_map("New Part", maps:from_list(Part0)),
+ pp_map("Old Part", maps:from_list(Part1)),
+ pp_map(" => Part", maps:from_list(Part)),
+ V2State#v2_state{constr_data = maps:put(Id, {Part,[]}, ConData)}.
restore_local_map(#v2_state{constr_data = ConData}, Id, Map0) ->
- case dict:find(Id, ConData) of
+ case maps:find(Id, ConData) of
error -> Map0;
{ok, failed} -> Map0;
{ok, {[],_}} -> Map0;
{ok, {Part0,U}} ->
Part = [KV || {K,_V} = KV <- Part0, not lists:member(K, U)],
?debug("restore local map Id=~w U=~w\n", [Id, U]),
- pp_map("Part", dict:from_list(Part)),
+ pp_map("Part", maps:from_list(Part)),
pp_map("Map0", Map0),
- Map = lists:foldl(fun({K,V}, D) -> dict:store(K, V, D) end, Map0, Part),
+ Map = lists:foldl(fun({K,V}, D) -> maps:put(K, V, D) end, Map0, Part),
pp_map("Map", Map),
Map
end.
@@ -2213,31 +2215,26 @@ report_detected_loop(_) ->
add_mask_to_flags(Flags, [Im|M], I, L) when I > Im ->
add_mask_to_flags(Flags, M, I, [Im|L]);
add_mask_to_flags(Flags, [_|M], _I, L) ->
- {lists:umerge(Flags, M), lists:reverse(L)}.
+ {lists:umerge(M, Flags), lists:reverse(L)}.
-get_mask(V, {d, Masks}) ->
- case dict:find(V, Masks) of
+get_mask(V, Masks) ->
+ case maps:find(V, Masks) of
error -> [];
{ok, M} -> M
- end;
-get_mask(V, Masks) ->
- case lists:keyfind(V, 1, Masks) of
- false -> [];
- {V, M} -> M
end.
get_flags(#v2_state{constr_data = ConData}=V2State0, C) ->
#constraint_list{id = Id, list = Cs, masks = Masks} = C,
- case dict:find(Id, ConData) of
+ case maps:find(Id, ConData) of
error ->
?debug("get_flags Id=~w Flags=all ~w\n", [Id, length(Cs)]),
- V2State = V2State0#v2_state{constr_data = dict:store(Id, {[],[]}, ConData)},
+ V2State = V2State0#v2_state{constr_data = maps:put(Id, {[],[]}, ConData)},
{V2State, lists:seq(1, length(Cs))};
{ok, failed} ->
{V2State0, failed_list};
{ok, {Part,U}} when U =/= [] ->
?debug("get_flags Id=~w U=~w\n", [Id, U]),
- V2State = V2State0#v2_state{constr_data = dict:store(Id, {Part,[]}, ConData)},
+ V2State = V2State0#v2_state{constr_data = maps:put(Id, {Part,[]}, ConData)},
save_updated_vars_list(Cs, vars_per_child(U, Masks), V2State)
end.
@@ -2266,13 +2263,13 @@ save_updated_vars(#constraint_ref{id = Id}, U, V2State) ->
save_updated_vars1(V2State, C, U) ->
#v2_state{constr_data = ConData} = V2State,
#constraint_list{id = Id} = C,
- case dict:find(Id, ConData) of
+ case maps:find(Id, ConData) of
error -> V2State; % error means everything is flagged
{ok, failed} -> V2State;
{ok, {Part,U0}} ->
%% Duplicates are not so common; let masks/2 remove them.
U1 = U ++ U0,
- V2State#v2_state{constr_data = dict:store(Id, {Part,U1}, ConData)}
+ V2State#v2_state{constr_data = maps:put(Id, {Part,U1}, ConData)}
end.
-ifdef(DEBUG).
@@ -2282,12 +2279,12 @@ pp_constr_data(_Tag, #v2_state{constr_data = D}) ->
case _PartU of
{_Part, _U} ->
io:format("Id: ~w Vars: ~w\n", [_Id, _U]),
- [pp_map("Part", dict:from_list(_Part)) || _Part =/= []];
+ [pp_map("Part", maps:from_list(_Part)) || _Part =/= []];
failed ->
io:format("Id: ~w failed list\n", [_Id])
end
end ||
- {_Id, _PartU} <- lists:keysort(1, dict:to_list(D))],
+ {_Id, _PartU} <- lists:keysort(1, maps:to_list(D))],
ok.
-else.
@@ -2297,17 +2294,17 @@ pp_constr_data(_Tag, _V2State) ->
failed_list(#constraint_list{id = Id}, #v2_state{constr_data = D}=V2State) ->
?debug("error list ~w~n", [Id]),
- V2State#v2_state{constr_data = dict:store(Id, failed, D)}.
+ V2State#v2_state{constr_data = maps:put(Id, failed, D)}.
is_failed_list(#constraint_list{id = Id}, #v2_state{constr_data = D}) ->
- dict:find(Id, D) =:= {ok, failed}.
+ maps:find(Id, D) =:= {ok, failed}.
%% Solver v1
solve_ref_or_list(#constraint_ref{id = Id, deps = Deps},
Map, MapDict, State) ->
{OldLocalMap, Check} =
- case dict:find(Id, MapDict) of
+ case maps:find(Id, MapDict) of
error -> {map_new(), false};
{ok, M} -> {M, true}
end,
@@ -2353,12 +2350,12 @@ solve_ref_or_list(#constraint_ref{id = Id, deps = Deps},
{ok, Var} -> enter_type(Var, FunType, NewMap1);
error -> NewMap1
end,
- {ok, dict:store(Id, NewMap2, NewMapDict), NewMap2}
+ {ok, maps:put(Id, NewMap2, NewMapDict), NewMap2}
end;
solve_ref_or_list(#constraint_list{type=Type, list = Cs, deps = Deps, id = Id},
Map, MapDict, State) ->
{OldLocalMap, Check} =
- case dict:find(Id, MapDict) of
+ case maps:find(Id, MapDict) of
error -> {map_new(), false};
{ok, M} -> {M, true}
end,
@@ -2397,7 +2394,7 @@ solve_self_recursive(Cs, Map, MapDict, Id, RecType0, State) ->
{ok, NewMapDict, NewMap} ->
pp_map("NewMap", NewMap),
NewRecType = unsafe_lookup_type(Id, NewMap),
- case t_is_equal(NewRecType, RecType0) of
+ case is_equal(NewRecType, RecType0) of
true ->
{ok, NewMapDict, enter_type(RecVar, NewRecType, NewMap)};
false ->
@@ -2408,7 +2405,7 @@ solve_self_recursive(Cs, Map, MapDict, Id, RecType0, State) ->
solve_clist(Cs, conj, Id, Deps, MapDict, Map, State) ->
case solve_cs(Cs, Map, MapDict, State) of
{error, NewMapDict} ->
- {error, dict:store(Id, error, NewMapDict)};
+ {error, maps:put(Id, error, NewMapDict)};
{ok, NewMapDict, NewMap} = Ret ->
case Cs of
[_] ->
@@ -2416,7 +2413,7 @@ solve_clist(Cs, conj, Id, Deps, MapDict, Map, State) ->
Ret;
_ ->
case maps_are_equal(Map, NewMap, Deps) of
- true -> {ok, dict:store(Id, NewMap, NewMapDict), NewMap};
+ true -> {ok, maps:put(Id, NewMap, NewMapDict), NewMap};
false -> solve_clist(Cs, conj, Id, Deps, NewMapDict, NewMap, State)
end
end
@@ -2430,10 +2427,10 @@ solve_clist(Cs, disj, Id, _Deps, MapDict, Map, State) ->
end,
{Maps, NewMapDict} = lists:mapfoldl(Fun, MapDict, Cs),
case [X || {ok, X} <- Maps] of
- [] -> {error, dict:store(Id, error, NewMapDict)};
+ [] -> {error, maps:put(Id, error, NewMapDict)};
MapList ->
NewMap = join_maps(MapList),
- {ok, dict:store(Id, NewMap, NewMapDict), NewMap}
+ {ok, maps:put(Id, NewMap, NewMapDict), NewMap}
end.
solve_cs([#constraint_ref{} = C|Tail], Map, MapDict, State) ->
@@ -2447,7 +2444,7 @@ solve_cs([#constraint_list{} = C|Tail], Map, MapDict, State) ->
{error, _NewMapDict} = Error -> Error
end;
solve_cs([#constraint{} = C|Tail], Map, MapDict, State) ->
- case solve_one_c(C, Map, State#state.opaques) of
+ case solve_one_c(C, Map) of
error ->
report_failed_constraint(C, Map),
{error, MapDict};
@@ -2457,10 +2454,10 @@ solve_cs([#constraint{} = C|Tail], Map, MapDict, State) ->
solve_cs([], Map, MapDict, _State) ->
{ok, MapDict, Map}.
-solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map, Opaques) ->
+solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map) ->
LhsType = lookup_type(Lhs, Map),
RhsType = lookup_type(Rhs, Map),
- Inf = t_inf(LhsType, RhsType, opaque),
+ Inf = t_inf(LhsType, RhsType),
?debug("Solving: ~s :: ~s ~w ~s :: ~s\n\tInf: ~s\n",
[format_type(Lhs), format_type(LhsType), Op,
format_type(Rhs), format_type(RhsType), format_type(Inf)]),
@@ -2468,12 +2465,12 @@ solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map, Opaques) ->
true -> error;
false ->
case Op of
- sub -> solve_subtype(Lhs, Inf, Map, Opaques);
+ sub -> solve_subtype(Lhs, Inf, Map);
eq ->
- case solve_subtype(Lhs, Inf, Map, Opaques) of
+ case solve_subtype(Lhs, Inf, Map) of
error -> error;
{ok, {Map1, U1}} ->
- case solve_subtype(Rhs, Inf, Map1, Opaques) of
+ case solve_subtype(Rhs, Inf, Map1) of
error -> error;
{ok, {Map2, U2}} -> {ok, {Map2, lists:umerge(U1, U2)}}
end
@@ -2481,7 +2478,7 @@ solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map, Opaques) ->
end
end.
-solve_subtype(Type, Inf, Map, Opaques) ->
+solve_subtype(Type, Inf, Map) ->
%% case cerl:is_literal(Type) of
%% true ->
%% case t_is_subtype(t_from_term(cerl:concrete(Type)), Inf) of
@@ -2489,7 +2486,7 @@ solve_subtype(Type, Inf, Map, Opaques) ->
%% false -> error
%% end;
%% false ->
- try t_unify(Type, Inf, Opaques) of
+ try t_unify(Type, Inf) of
{_, List} -> {ok, enter_type_list(List, Map)}
catch
throw:{mismatch, _T1, _T2} ->
@@ -2514,7 +2511,7 @@ report_failed_constraint(_C, _Map) ->
%% ============================================================================
map_new() ->
- dict:new().
+ maps:new().
join_maps([Map]) ->
Map;
@@ -2524,9 +2521,9 @@ join_maps(Maps) ->
constrained_keys(Maps) ->
lists:foldl(fun(TmpMap, AccKeys) ->
- [Key || Key <- AccKeys, dict:is_key(Key, TmpMap)]
+ [Key || Key <- AccKeys, maps:is_key(Key, TmpMap)]
end,
- dict:fetch_keys(hd(Maps)), tl(Maps)).
+ maps:keys(hd(Maps)), tl(Maps)).
join_maps([Key|Left], Maps = [Map|MapsLeft], AccMap) ->
NewType = join_one_key(Key, MapsLeft, lookup_type(Key, Map)),
@@ -2540,7 +2537,7 @@ join_one_key(Key, [Map|Maps], Type) ->
true -> Type;
false ->
NewType = lookup_type(Key, Map),
- case t_is_equal(NewType, Type) of
+ case is_equal(NewType, Type) of
true -> join_one_key(Key, Maps, Type);
false -> join_one_key(Key, Maps, t_sup(NewType, Type))
end
@@ -2555,7 +2552,7 @@ maps_are_equal(Map1, Map2, Deps) ->
maps_are_equal_1(Map1, Map2, [H|Tail]) ->
T1 = lookup_type(H, Map1),
T2 = lookup_type(H, Map2),
- case t_is_equal(T1, T2) of
+ case is_equal(T1, T2) of
true -> maps_are_equal_1(Map1, Map2, Tail);
false ->
?debug("~w: ~s =/= ~s\n", [H, format_type(T1), format_type(T2)]),
@@ -2572,11 +2569,11 @@ prune_keys(Map1, Map2, Deps) ->
NofDeps = length(Deps),
case NofDeps > ?PRUNE_LIMIT of
true ->
- Keys1 = dict:fetch_keys(Map1),
+ Keys1 = maps:keys(Map1),
case length(Keys1) > NofDeps of
true ->
Set1 = lists:sort(Keys1),
- Set2 = lists:sort(dict:fetch_keys(Map2)),
+ Set2 = lists:sort(maps:keys(Map2)),
ordsets:intersection(ordsets:union(Set1, Set2), Deps);
false ->
Deps
@@ -2587,14 +2584,22 @@ prune_keys(Map1, Map2, Deps) ->
enter_type(Key, Val, Map) when is_integer(Key) ->
?debug("Entering ~s :: ~s\n", [format_type(t_var(Key)), format_type(Val)]),
- case t_is_any(Val) of
+ %% Keep any() in the map if it is opaque:
+ case is_equal(Val, t_any()) of
true ->
erase_type(Key, Map);
false ->
LimitedVal = t_limit(Val, ?INTERNAL_TYPE_LIMIT),
- case dict:find(Key, Map) of
- {ok, LimitedVal} -> Map;
- {ok, _} -> map_store(Key, LimitedVal, Map);
+ case is_equal(LimitedVal, Val) of
+ true -> ok;
+ false -> ?debug("LimitedVal ~s\n", [format_type(LimitedVal)])
+ end,
+ case maps:find(Key, Map) of
+ {ok, Value} ->
+ case is_equal(Value, LimitedVal) of
+ true -> Map;
+ false -> map_store(Key, LimitedVal, Map)
+ end;
error -> map_store(Key, LimitedVal, Map)
end
end;
@@ -2623,16 +2628,16 @@ enter_type2(Key, Val, Map) ->
map_store(Key, Val, Map) ->
?debug("Storing ~w :: ~s\n", [Key, format_type(Val)]),
- dict:store(Key, Val, Map).
+ maps:put(Key, Val, Map).
erase_type(Key, Map) ->
- dict:erase(Key, Map).
+ maps:remove(Key, Map).
lookup_type_list(List, Map) ->
[lookup_type(X, Map) || X <- List].
unsafe_lookup_type(Key, Map) ->
- case dict:find(t_var_name(Key), Map) of
+ case maps:find(t_var_name(Key), Map) of
{ok, Type} -> Type;
error -> t_none()
end.
@@ -2641,7 +2646,7 @@ unsafe_lookup_type_list(List, Map) ->
[unsafe_lookup_type(X, Map) || X <- List].
lookup_type(Key, Map) when is_integer(Key) ->
- case dict:find(Key, Map) of
+ case maps:find(Key, Map) of
error -> t_any();
{ok, Val} -> Val
end;
@@ -2681,12 +2686,15 @@ updated_vars_only(U, OldMap, NewMap) ->
[V || V <- U, not is_same(V, OldMap, NewMap)].
is_same(Key, Map1, Map2) ->
- t_is_equal(lookup_type(Key, Map1), lookup_type(Key, Map2)).
+ is_equal(lookup_type(Key, Map1), lookup_type(Key, Map2)).
+
+is_equal(Type1, Type2) ->
+ t_is_equal(Type1, Type2).
pp_map(_S, _Map) ->
?debug("\t~s: ~p\n",
[_S, [{X, lists:flatten(format_type(Y))} ||
- {X, Y} <- lists:keysort(1, dict:to_list(_Map))]]).
+ {X, Y} <- lists:keysort(1, maps:to_list(_Map))]]).
%% ============================================================================
%%
@@ -2696,7 +2704,7 @@ pp_map(_S, _Map) ->
new_state(SCC0, NextLabel, CallGraph, Plt, PropTypes, Solvers) ->
List = [{MFA, Var} || {MFA, {Var, _Fun}, _Rec} <- SCC0],
- NameMap = dict:from_list(List),
+ NameMap = maps:from_list(List),
MFAs = [MFA || {MFA, _Var} <- List],
SCC = [mk_var(Fun) || {_MFA, {_Var, Fun}, _Rec} <- SCC0],
SelfRec =
@@ -2710,17 +2718,12 @@ new_state(SCC0, NextLabel, CallGraph, Plt, PropTypes, Solvers) ->
_Many -> false
end,
#state{callgraph = CallGraph, name_map = NameMap, next_label = NextLabel,
- prop_types = {d, PropTypes}, plt = Plt, scc = ordsets:from_list(SCC),
+ prop_types = PropTypes, plt = Plt, scc = ordsets:from_list(SCC),
mfas = MFAs, self_rec = SelfRec, solvers = Solvers}.
state__set_rec_dict(State, RecDict) ->
State#state{records = RecDict}.
-state__set_opaques(#state{records = RecDict} = State, {M, _F, _A}) ->
- Opaques =
- erl_types:module_builtin_opaques(M) ++ t_opaque_from_records(RecDict),
- State#state{opaques = Opaques, module = M}.
-
state__set_in_match(State, Bool) ->
State#state{in_match = Bool}.
@@ -2743,15 +2746,15 @@ state__get_fun_prototype(Op, Arity, State) ->
end.
state__lookup_rec_var_in_scope(MFA, #state{name_map = NameMap}) ->
- dict:find(MFA, NameMap).
+ maps:find(MFA, NameMap).
state__store_fun_arity(Tree, #state{fun_arities = Map} = State) ->
Arity = length(cerl:fun_vars(Tree)),
Id = mk_var(Tree),
- State#state{fun_arities = dict:store(Id, Arity, Map)}.
+ State#state{fun_arities = maps:put(Id, Arity, Map)}.
state__fun_arity(Id, #state{fun_arities = Map}) ->
- dict:fetch(Id, Map).
+ maps:get(Id, Map).
state__lookup_undef_var(Tree, #state{callgraph = CG, plt = Plt}) ->
Label = cerl_trees:get_label(Tree),
@@ -2760,7 +2763,8 @@ state__lookup_undef_var(Tree, #state{callgraph = CG, plt = Plt}) ->
{ok, MFA} ->
case dialyzer_plt:lookup(Plt, MFA) of
none -> error;
- {value, {RetType, ArgTypes}} -> {ok, t_fun(ArgTypes, RetType)}
+ {value, {RetType, ArgTypes}} ->
+ {ok, t_fun(ArgTypes, RetType)}
end
end.
@@ -2810,21 +2814,14 @@ state__plt(#state{plt = PLT}) ->
state__new_constraint_context(State) ->
State#state{cs = []}.
-state__prop_domain(FunLabel, #state{prop_types = {e, ETSPropTypes}}) ->
- try ets:lookup_element(ETSPropTypes, FunLabel, 2) of
- {_Range_Fun, Dom} -> {ok, Dom};
- FunType -> {ok, t_fun_args(FunType)}
- catch
- _:_ -> error
- end;
-state__prop_domain(FunLabel, #state{prop_types = {d, PropTypes}}) ->
+state__prop_domain(FunLabel, #state{prop_types = PropTypes}) ->
case dict:find(FunLabel, PropTypes) of
error -> error;
{ok, {_Range_Fun, Dom}} -> {ok, Dom};
{ok, FunType} -> {ok, t_fun_args(FunType)}
end.
-state__add_prop_constrs(Tree, #state{prop_types = {d, PropTypes}} = State) ->
+state__add_prop_constrs(Tree, #state{prop_types = PropTypes} = State) ->
Label = cerl_trees:get_label(Tree),
case dict:find(Label, PropTypes) of
error -> State;
@@ -2887,31 +2884,26 @@ state__mk_vars(N, #state{next_label = NL} = State) ->
Vars = [t_var(X) || X <- lists:seq(NL, NewLabel-1)],
{State#state{next_label = NewLabel}, Vars}.
-state__store_constrs(Id, Cs, #state{cmap = {d, Dict}} = State) ->
- NewDict = dict:store(Id, Cs, Dict),
- State#state{cmap = {d, NewDict}}.
+state__store_constrs(Id, Cs, #state{cmap = Map} = State) ->
+ NewMap = maps:put(Id, Cs, Map),
+ State#state{cmap = NewMap}.
-state__get_cs(Var, #state{cmap = {e, ETSDict}}) ->
- ets:lookup_element(ETSDict, Var, 2);
-state__get_cs(Var, #state{cmap = {d, Dict}}) ->
- dict:fetch(Var, Dict).
+state__get_cs(Var, #state{cmap = Map}) ->
+ maps:get(Var, Map).
state__is_self_rec(Fun, #state{self_rec = SelfRec}) ->
- Fun =:= SelfRec.
+ not (SelfRec =:= 'false') andalso is_equal(Fun, SelfRec).
state__store_funs(Vars0, Funs0, #state{fun_map = Map} = State) ->
debug_make_name_map(Vars0, Funs0),
Vars = mk_var_list(Vars0),
Funs = mk_var_list(Funs0),
- NewMap = lists:foldl(fun({Var, Fun}, MP) -> orddict:store(Var, Fun, MP) end,
+ NewMap = lists:foldl(fun({Var, Fun}, MP) -> maps:put(Fun, Var, MP) end,
Map, lists:zip(Vars, Funs)),
State#state{fun_map = NewMap}.
state__get_rec_var(Fun, #state{fun_map = Map}) ->
- case [V || {V, FV} <- Map, FV =:= Fun] of
- [Var] -> {ok, Var};
- [] -> error
- end.
+ maps:find(Fun, Map).
state__finalize(State) ->
State1 = enumerate_constraints(State),
@@ -2923,20 +2915,21 @@ state__finalize(State) ->
%%
%% ============================================================================
--spec mk_constraint(erl_types:erl_type(), constr_op(), fvar_or_type()) -> #constraint{}.
+-spec mk_constraint(erl_types:erl_type(),
+ constr_op(),
+ fvar_or_type()) -> #constraint{}.
mk_constraint(Lhs, Op, Rhs) ->
case t_is_any(Lhs) orelse constraint_opnd_is_any(Rhs) of
false ->
Deps = find_constraint_deps([Lhs, Rhs]),
- C0 = mk_constraint_1(Lhs, Op, Rhs),
- C = C0#constraint{deps = Deps},
+ C = mk_constraint_1(Lhs, Op, Rhs, Deps),
case Deps =:= [] of
true ->
%% This constraint is constant. Solve it immediately.
- case solve_one_c(C, map_new(), []) of
+ case solve_one_c(C, map_new()) of
error -> throw(error);
- _ ->
+ _R ->
%% This is always true, keep it anyway for logistic reasons
C
end;
@@ -2944,10 +2937,12 @@ mk_constraint(Lhs, Op, Rhs) ->
C
end;
true ->
- C = mk_constraint_1(t_any(), Op, t_any()),
- C#constraint{deps = []}
+ mk_constraint_any(Op)
end.
+mk_constraint_any(Op) ->
+ mk_constraint_1(t_any(), Op, t_any(), []).
+
%% the following function is used so that we do not call
%% erl_types:t_is_any/1 with a term other than an erl_type()
-spec constraint_opnd_is_any(fvar_or_type()) -> boolean().
@@ -2975,13 +2970,13 @@ mk_fun_var(Fun, Types) ->
-endif.
--spec get_deps(constr()) -> [dep()].
+-spec get_deps(constr()) -> deps().
get_deps(#constraint{deps = D}) -> D;
get_deps(#constraint_list{deps = D}) -> D;
get_deps(#constraint_ref{deps = D}) -> D.
--spec find_constraint_deps([fvar_or_type()]) -> [dep()].
+-spec find_constraint_deps([fvar_or_type()]) -> deps().
find_constraint_deps(List) ->
ordsets:from_list(find_constraint_deps(List, [])).
@@ -2994,15 +2989,16 @@ find_constraint_deps([Type|Tail], Acc) ->
find_constraint_deps([], Acc) ->
lists:flatten(Acc).
-mk_constraint_1(Lhs, eq, Rhs) when Lhs < Rhs ->
- #constraint{lhs = Lhs, op = eq, rhs = Rhs};
-mk_constraint_1(Lhs, eq, Rhs) ->
- #constraint{lhs = Rhs, op = eq, rhs = Lhs};
-mk_constraint_1(Lhs, Op, Rhs) ->
- #constraint{lhs = Lhs, op = Op, rhs = Rhs}.
+mk_constraint_1(Lhs, eq, Rhs, Deps) when Lhs < Rhs ->
+ #constraint{lhs = Lhs, op = eq, rhs = Rhs, deps = Deps};
+mk_constraint_1(Lhs, eq, Rhs, Deps) ->
+ #constraint{lhs = Rhs, op = eq, rhs = Lhs, deps = Deps};
+mk_constraint_1(Lhs, Op, Rhs, Deps) ->
+ #constraint{lhs = Lhs, op = Op, rhs = Rhs, deps = Deps}.
mk_constraints([Lhs|LhsTail], Op, [Rhs|RhsTail]) ->
- [mk_constraint(Lhs, Op, Rhs)|mk_constraints(LhsTail, Op, RhsTail)];
+ [mk_constraint(Lhs, Op, Rhs) |
+ mk_constraints(LhsTail, Op, RhsTail)];
mk_constraints([], _Op, []) ->
[].
@@ -3013,13 +3009,24 @@ mk_constraint_ref(Id, Deps) ->
mk_constraint_list(Type, List) ->
List1 = ordsets:from_list(lift_lists(Type, List)),
- List2 = ordsets:filter(fun(X) -> get_deps(X) =/= [] end, List1),
- Deps = calculate_deps(List2),
+ case Type of
+ conj ->
+ List2 = ordsets:filter(fun(X) -> get_deps(X) =/= [] end, List1),
+ mk_constraint_list_cont(Type, List2);
+ disj ->
+ case lists:any(fun(X) -> get_deps(X) =:= [] end, List1) of
+ true -> mk_constraint_list_cont(Type, [mk_constraint_any(eq)]);
+ false -> mk_constraint_list_cont(Type, List1)
+ end
+ end.
+
+mk_constraint_list_cont(Type, List) ->
+ Deps = calculate_deps(List),
case Deps =:= [] of
true -> #constraint_list{type = conj,
- list = [mk_constraint(t_any(), eq, t_any())],
+ list = [mk_constraint_any(eq)],
deps = []};
- false -> #constraint_list{type = Type, list = List2, deps = Deps}
+ false -> #constraint_list{type = Type, list = List, deps = Deps}
end.
lift_lists(Type, List) ->
@@ -3217,18 +3224,11 @@ order_fun_constraints([], Funs, Acc, State) ->
update_masks(C, Masks) ->
C#constraint_list{masks = Masks}.
--define(VARS_LIMIT, 50).
-
calculate_masks([C|Cs], I, L0) ->
calculate_masks(Cs, I+1, [{V, I} || V <- get_deps(C)] ++ L0);
calculate_masks([], _I, L) ->
M = family(L),
- case length(M) > ?VARS_LIMIT of
- true ->
- {d, dict:from_list(M)};
- false ->
- M
- end.
+ maps:from_list(M).
%% ============================================================================
%%
@@ -3236,6 +3236,9 @@ calculate_masks([], _I, L) ->
%%
%% ============================================================================
+bif_return(M, F, A, Xs) ->
+ erl_bif_types:type(M, F, A, Xs).
+
is_singleton_non_number_type(Type) ->
case t_is_number(Type) of
true -> false;
@@ -3265,7 +3268,7 @@ is_singleton_type(Type) ->
find_element(Args, Cs) ->
[Pos, Tuple] = Args,
- case erl_types:t_is_number(Pos) of
+ case t_is_number(Pos) of
true ->
case erl_types:t_number_vals(Pos) of
'unknown' -> 'unknown';
@@ -3298,17 +3301,32 @@ find_constraint(Tuple, [#constraint_list{list = List}|Cs]) ->
find_constraint(Tuple, [_|Cs]) ->
find_constraint(Tuple, Cs).
+-spec fold_literal_maybe_match(cerl:cerl(), state()) -> cerl:cerl().
+
+fold_literal_maybe_match(Tree0, State) ->
+ Tree1 = cerl:fold_literal(Tree0),
+ case state__is_in_match(State) of
+ false -> Tree1;
+ true -> dialyzer_utils:refold_pattern(Tree1)
+ end.
+
lookup_record(Records, Tag, Arity) ->
case erl_types:lookup_record(Tag, Arity, Records) of
{ok, Fields} ->
- {ok, t_tuple([t_from_term(Tag)|
- [FieldType || {_FieldName, FieldType} <- Fields]])};
+ RecType =
+ t_tuple([t_from_term(Tag)|
+ [FieldType || {_FieldName, _Abstr, FieldType} <- Fields]]),
+ {ok, RecType};
error ->
error
end.
+is_literal_record(Tree) ->
+ Ann = cerl:get_ann(Tree),
+ lists:member(record, Ann).
+
family(L) ->
- sofs:to_external(sofs:rel2fam(sofs:relation(L))).
+ dialyzer_utils:family(L).
%% ============================================================================
%%
@@ -3338,7 +3356,7 @@ join_chars([H|T], Sep) ->
[H|[[Sep,X] || X <- T]].
debug_lookup_name(Var) ->
- case dict:find(t_var_name(Var), get(dialyzer_typesig_map)) of
+ case maps:find(t_var_name(Var), get(dialyzer_typesig_map)) of
error -> Var;
{ok, Name} -> Name
end.
@@ -3348,7 +3366,7 @@ debug_lookup_name(Var) ->
debug_make_name_map(Vars, Funs) ->
Map = get(dialyzer_typesig_map),
NewMap =
- if Map =:= undefined -> debug_make_name_map(Vars, Funs, dict:new());
+ if Map =:= undefined -> debug_make_name_map(Vars, Funs, maps:new());
true -> debug_make_name_map(Vars, Funs, Map)
end,
put(dialyzer_typesig_map, NewMap).
@@ -3356,7 +3374,7 @@ debug_make_name_map(Vars, Funs) ->
debug_make_name_map([Var|VarLeft], [Fun|FunLeft], Map) ->
Name = {cerl:fname_id(Var), cerl:fname_arity(Var)},
FunLabel = cerl_trees:get_label(Fun),
- debug_make_name_map(VarLeft, FunLeft, dict:store(FunLabel, Name, Map));
+ debug_make_name_map(VarLeft, FunLeft, maps:put(FunLabel, Name, Map));
debug_make_name_map([], [], Map) ->
Map.
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index a4c4d37a0f..76a5cf3d0b 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -31,6 +32,7 @@
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,
@@ -39,12 +41,17 @@
get_core_from_src/2,
get_record_and_type_info/1,
get_spec_info/3,
+ get_fun_meta_info/3,
+ is_suppressed_fun/2,
+ is_suppressed_tag/3,
merge_records/2,
pp_hook/0,
process_record_remote_types/1,
sets_filter/2,
src_compiler_opts/0,
- parallelism/0
+ refold_pattern/1,
+ parallelism/0,
+ family/1
]).
-include("dialyzer.hrl").
@@ -58,16 +65,16 @@ print_types(RecDict) ->
print_types1([], _) ->
ok;
-print_types1([{type, _Name} = Key|T], RecDict) ->
- {ok, {_Mod, Form, _Args}} = dict:find(Key, RecDict),
- io:format("\n~w: ~w\n", [Key, erl_types:t_from_form(Form, RecDict)]),
+print_types1([{type, _Name, _NArgs} = Key|T], RecDict) ->
+ {ok, {{_Mod, _FileLine, _Form, _Args}, Type}} = dict:find(Key, RecDict),
+ io:format("\n~w: ~w\n", [Key, Type]),
print_types1(T, RecDict);
-print_types1([{opaque, _Name} = Key|T], RecDict) ->
- {ok, {_Mod, Form, _Args}} = dict:find(Key, RecDict),
- io:format("\n~w: ~w\n", [Key, erl_types:t_from_form(Form, RecDict)]),
+print_types1([{opaque, _Name, _NArgs} = Key|T], RecDict) ->
+ {ok, {{_Mod, _FileLine, _Form, _Args}, Type}} = dict:find(Key, RecDict),
+ io:format("\n~w: ~w\n", [Key, Type]),
print_types1(T, RecDict);
print_types1([{record, _Name} = Key|T], RecDict) ->
- {ok, [{_Arity, _Fields} = AF]} = dict:find(Key, RecDict),
+ {ok, {_FileLine, [{_Arity, _Fields} = AF]}} = dict:find(Key, RecDict),
io:format("~w: ~w\n\n", [Key, AF]),
print_types1(T, RecDict).
-define(debug(D_), print_types(D_)).
@@ -77,9 +84,11 @@ print_types1([{record, _Name} = Key|T], RecDict) ->
%% ----------------------------------------------------------------------------
--type abstract_code() :: [tuple()]. %% XXX: import from somewhere
+-type abstract_code() :: [erl_parse:abstract_form()].
-type comp_options() :: [compile:option()].
--type mod_or_fname() :: atom() | file:filename().
+-type mod_or_fname() :: module() | file:filename().
+-type fa() :: {atom(), arity()}.
+-type codeserver() :: dialyzer_codeserver:codeserver().
%% ============================================================================
%%
@@ -97,7 +106,7 @@ get_abstract_code_from_src(File) ->
{'ok', abstract_code()} | {'error', [string()]}.
get_abstract_code_from_src(File, Opts) ->
- case compile:file(File, [to_pp, binary|Opts]) of
+ case compile:noenv_file(File, [to_pp, binary|Opts]) of
error -> {error, []};
{error, Errors, _} -> {error, format_errors(Errors)};
{ok, _, AbstrCode} -> {ok, AbstrCode}
@@ -136,6 +145,26 @@ get_abstract_code_from_beam(File) ->
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
+ end;
+ _ ->
+ %% No or unsuitable compile info.
+ error
+ end.
+
+compile_info_to_options(CompInfo) ->
+ case lists:keyfind(options, 1, CompInfo) of
+ {options, CompOpts} -> {ok, CompOpts};
+ _ -> 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().
@@ -150,7 +179,9 @@ get_core_from_abstract_code(AbstrCode, Opts) ->
%% performed them. In some cases we end up in trouble when
%% performing them again.
AbstrCode1 = cleanup_parse_transforms(AbstrCode),
- try compile:forms(AbstrCode1, Opts ++ src_compiler_opts()) of
+ %% 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
catch
@@ -163,82 +194,88 @@ get_core_from_abstract_code(AbstrCode, Opts) ->
%%
%% ============================================================================
+-type type_table() :: erl_types:type_table().
+-type mod_records() :: dict:dict(module(), type_table()).
+
-spec get_record_and_type_info(abstract_code()) ->
- {'ok', dict()} | {'error', string()}.
+ {'ok', type_table()} | {'error', string()}.
get_record_and_type_info(AbstractCode) ->
Module = get_module(AbstractCode),
get_record_and_type_info(AbstractCode, Module, dict:new()).
--spec get_record_and_type_info(abstract_code(), module(), dict()) ->
- {'ok', dict()} | {'error', string()}.
+-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).
+ get_record_and_type_info(AbstractCode, Module, RecDict, "nofile").
-get_record_and_type_info([{attribute, _, record, {Name, Fields0}}|Left],
- Module, Records, RecDict) ->
+get_record_and_type_info([{attribute, A, record, {Name, Fields0}}|Left],
+ Module, RecDict, File) ->
{ok, Fields} = get_record_fields(Fields0, RecDict),
Arity = length(Fields),
- NewRecDict = dict:store({record, Name}, [{Arity, Fields}], RecDict),
- get_record_and_type_info(Left, Module, [{record, Name}|Records], NewRecDict);
-get_record_and_type_info([{attribute, _, type, {{record, Name}, Fields0, []}}
- |Left], Module, Records, RecDict) ->
+ FN = {File, erl_anno:line(A)},
+ NewRecDict = dict:store({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, []}}
+ |Left], Module, RecDict, File) ->
%% This overrides the original record declaration.
{ok, Fields} = get_record_fields(Fields0, RecDict),
Arity = length(Fields),
- NewRecDict = dict:store({record, Name}, [{Arity, Fields}], RecDict),
- get_record_and_type_info(Left, Module, Records, NewRecDict);
-get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm}}|Left],
- Module, Records, RecDict) when Attr =:= 'type';
- Attr =:= 'opaque' ->
- try
- NewRecDict = add_new_type(Attr, Name, TypeForm, [], Module, RecDict),
- get_record_and_type_info(Left, Module, Records, NewRecDict)
+ FN = {File, erl_anno:line(A)},
+ NewRecDict = dict:store({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],
+ Module, RecDict, File)
+ when Attr =:= 'type'; Attr =:= 'opaque' ->
+ FN = {File, erl_anno:line(A)},
+ 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, _, Attr, {Name, TypeForm, Args}}|Left],
- Module, Records, RecDict) when Attr =:= 'type';
- Attr =:= 'opaque' ->
- try
- NewRecDict = add_new_type(Attr, Name, TypeForm, Args, Module, RecDict),
- get_record_and_type_info(Left, Module, Records, NewRecDict)
+get_record_and_type_info([{attribute, A, Attr, {Name, TypeForm, Args}}|Left],
+ Module, RecDict, File)
+ when Attr =:= 'type'; Attr =:= 'opaque' ->
+ FN = {File, erl_anno:line(A)},
+ 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([_Other|Left], Module, Records, RecDict) ->
- get_record_and_type_info(Left, Module, Records, RecDict);
-get_record_and_type_info([], _Module, Records, RecDict) ->
- case type_record_fields(lists:reverse(Records), RecDict) of
- {ok, _NewRecDict} = Ok ->
- ?debug(_NewRecDict),
- Ok;
- {error, Name, Error} ->
- {error, flat_format(" Error while parsing #~w{}: ~s\n", [Name, Error])}
- end.
-
-add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) ->
+get_record_and_type_info([{attribute, _, 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) ->
+ get_record_and_type_info(Left, Module, RecDict, File);
+get_record_and_type_info([], _Module, RecDict, _File) ->
+ {ok, RecDict}.
+
+add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, FN,
+ RecDict) ->
Arity = length(ArgForms),
case erl_types:type_is_defined(TypeOrOpaque, Name, Arity, RecDict) of
true ->
Msg = flat_format("Type ~s/~w already defined\n", [Name, Arity]),
throw({error, Msg});
false ->
- ArgTypes = [erl_types:t_from_form(X) || X <- ArgForms],
- case lists:all(fun erl_types:t_is_var/1, ArgTypes) of
- true ->
- ArgNames = [erl_types:t_var_name(X) || X <- ArgTypes],
+ try erl_types:t_var_names(ArgForms) of
+ ArgNames ->
dict:store({TypeOrOpaque, Name, Arity},
- {Module, TypeForm, ArgNames}, RecDict);
- false ->
+ {{Module, FN, TypeForm, ArgNames},
+ erl_types:t_any()}, RecDict)
+ catch
+ _:_ ->
throw({error, flat_format("Type declaration for ~w does not "
"have variables as parameters", [Name])})
end
end.
get_record_fields(Fields, RecDict) ->
- get_record_fields(Fields, RecDict, []).
+ Fs = get_record_fields(Fields, RecDict, []),
+ {ok, [{Name, Form, erl_types:t_any()} || {Name, Form} <- Fs]}.
get_record_fields([{typed_record_field, OrdRecField, TypeForm}|Left],
RecDict, Acc) ->
@@ -247,64 +284,147 @@ get_record_fields([{typed_record_field, OrdRecField, TypeForm}|Left],
{record_field, _Line, Name0} -> erl_parse:normalise(Name0);
{record_field, _Line, Name0, _Init} -> erl_parse:normalise(Name0)
end,
- get_record_fields(Left, RecDict, [{Name, TypeForm}|Acc]);
+ get_record_fields(Left, RecDict, [{Name, TypeForm}|Acc]);
get_record_fields([{record_field, _Line, Name}|Left], RecDict, Acc) ->
- NewAcc = [{erl_parse:normalise(Name), {var, -1, '_'}}|Acc],
+ A = erl_anno:set_generated(true, erl_anno:new(1)),
+ NewAcc = [{erl_parse:normalise(Name), {var, A, '_'}}|Acc],
get_record_fields(Left, RecDict, NewAcc);
get_record_fields([{record_field, _Line, Name, _Init}|Left], RecDict, Acc) ->
- NewAcc = [{erl_parse:normalise(Name), {var, -1, '_'}}|Acc],
+ A = erl_anno:set_generated(true, erl_anno:new(1)),
+ NewAcc = [{erl_parse:normalise(Name), {var, A, '_'}}|Acc],
get_record_fields(Left, RecDict, NewAcc);
get_record_fields([], _RecDict, Acc) ->
- {ok, lists:reverse(Acc)}.
-
-type_record_fields([], RecDict) ->
- {ok, RecDict};
-type_record_fields([RecKey|Recs], RecDict) ->
- {ok, [{Arity, Fields}]} = dict:find(RecKey, RecDict),
- try
- TypedFields =
- [{FieldName, erl_types:t_from_form(FieldTypeForm, RecDict)}
- || {FieldName, FieldTypeForm} <- Fields],
- RecDict1 = dict:store(RecKey, [{Arity, TypedFields}], RecDict),
- Fun = fun(OldOrdDict) ->
- orddict:store(Arity, TypedFields, OldOrdDict)
- end,
- RecDict2 = dict:update(RecKey, Fun, RecDict1),
- type_record_fields(Recs, RecDict2)
- catch
- throw:{error, Error} ->
- {record, Name} = RecKey,
- {error, Name, Error}
- end.
+ lists:reverse(Acc).
--spec process_record_remote_types(dialyzer_codeserver:codeserver()) -> dialyzer_codeserver:codeserver().
+-spec process_record_remote_types(codeserver()) -> codeserver().
+%% The field types are cached. Used during analysis when handling records.
process_record_remote_types(CServer) ->
TempRecords = dialyzer_codeserver:get_temp_records(CServer),
- TempExpTypes = dialyzer_codeserver:get_temp_exported_types(CServer),
- RecordFun =
- fun(Key, Value) ->
- case Key of
- {record, _Name} ->
- FieldFun =
- fun(_Arity, Fields) ->
- [{Name, erl_types:t_solve_remote(Field, TempExpTypes,
- TempRecords)}
- || {Name, Field} <- Fields]
- end,
- orddict:map(FieldFun, Value);
- _Other -> Value
- end
+ ExpTypes = dialyzer_codeserver:get_exported_types(CServer),
+ Cache = erl_types:cache__new(),
+ {TempRecords1, Cache1} =
+ process_opaque_types0(TempRecords, ExpTypes, Cache),
+ %% A cache (not the field type cache) is used for speeding things up a bit.
+ VarTable = erl_types:var_table__new(),
+ ModuleFun =
+ fun({Module, Record}, C0) ->
+ RecordFun =
+ fun({Key, Value}, C2) ->
+ case Key of
+ {record, Name} ->
+ FieldFun =
+ fun({Arity, Fields}, C4) ->
+ Site = {record, {Module, Name, Arity}},
+ {Fields1, C7} =
+ lists:mapfoldl(fun({FieldName, Field, _}, C5) ->
+ {FieldT, C6} =
+ erl_types:t_from_form
+ (Field, ExpTypes, Site,
+ TempRecords1, VarTable,
+ C5),
+ {{FieldName, Field, FieldT}, C6}
+ end, C4, Fields),
+ {{Arity, Fields1}, C7}
+ end,
+ {FileLine, Fields} = Value,
+ {FieldsList, C3} =
+ lists:mapfoldl(FieldFun, C2, orddict:to_list(Fields)),
+ {{Key, {FileLine, orddict:from_list(FieldsList)}}, C3};
+ _Other -> {{Key, Value}, C2}
+ end
+ end,
+ {RecordList, C1} =
+ lists:mapfoldl(RecordFun, C0, dict:to_list(Record)),
+ {{Module, dict:from_list(RecordList)}, C1}
end,
+ {NewRecordsList, C1} =
+ lists:mapfoldl(ModuleFun, Cache1, dict:to_list(TempRecords1)),
+ NewRecords = dict:from_list(NewRecordsList),
+ _C8 = check_record_fields(NewRecords, ExpTypes, C1),
+ dialyzer_codeserver:finalize_records(NewRecords, CServer).
+
+%% erl_types:t_from_form() substitutes the declaration of opaque types
+%% for the expanded type in some cases. To make sure the initial type,
+%% any(), is not used, the expansion is done twice.
+%% XXX: Recursive opaque types are not handled well.
+process_opaque_types0(TempRecords0, TempExpTypes, Cache) ->
+ {TempRecords1, NewCache} =
+ process_opaque_types(TempRecords0, TempExpTypes, Cache),
+ process_opaque_types(TempRecords1, TempExpTypes, NewCache).
+
+process_opaque_types(TempRecords, TempExpTypes, Cache) ->
+ VarTable = erl_types:var_table__new(),
ModuleFun =
- fun(_Module, Record) ->
- dict:map(RecordFun, Record)
+ fun({Module, Record}, C0) ->
+ RecordFun =
+ fun({Key, Value}, C2) ->
+ case Key of
+ {opaque, Name, NArgs} ->
+ {{_Module, _FileLine, Form, _ArgNames}=F, _Type} = Value,
+ Site = {type, {Module, Name, NArgs}},
+ {Type, C3} =
+ erl_types:t_from_form(Form, TempExpTypes, Site,
+ TempRecords, VarTable, C2),
+ {{Key, {F, Type}}, C3};
+ _Other -> {{Key, Value}, C2}
+ end
+ end,
+ {RecordList, C1} =
+ lists:mapfoldl(RecordFun, C0, dict:to_list(Record)),
+ {{Module, dict:from_list(RecordList)}, C1}
+ %% dict:map(RecordFun, Record)
end,
- NewRecords = dict:map(ModuleFun, TempRecords),
- CServer1 = dialyzer_codeserver:finalize_records(NewRecords, CServer),
- dialyzer_codeserver:finalize_exported_types(TempExpTypes, CServer1).
+ {TempRecordList, NewCache} =
+ lists:mapfoldl(ModuleFun, Cache, dict:to_list(TempRecords)),
+ {dict:from_list(TempRecordList), NewCache}.
+ %% dict:map(ModuleFun, TempRecords).
+
+check_record_fields(Records, TempExpTypes, Cache) ->
+ VarTable = erl_types:var_table__new(),
+ CheckFun =
+ fun({Module, Element}, C0) ->
+ CheckForm = fun(Form, Site, C1) ->
+ erl_types:t_check_record_fields(Form, TempExpTypes,
+ Site, Records,
+ VarTable, C1)
+ end,
+ ElemFun =
+ fun({Key, Value}, C2) ->
+ case Key of
+ {record, Name} ->
+ FieldFun =
+ fun({Arity, Fields}, C3) ->
+ Site = {record, {Module, Name, Arity}},
+ lists:foldl(fun({_, Field, _}, C4) ->
+ CheckForm(Field, Site, C4)
+ end, C3, Fields)
+ end,
+ {FileLine, Fields} = Value,
+ Fun = fun() -> lists:foldl(FieldFun, C2, Fields) end,
+ msg_with_position(Fun, FileLine);
+ {_OpaqueOrType, Name, NArgs} ->
+ Site = {type, {Module, Name, NArgs}},
+ {{_Module, FileLine, Form, _ArgNames}, _Type} = Value,
+ Fun = fun() -> CheckForm(Form, Site, C2) end,
+ msg_with_position(Fun, FileLine)
+ end
+ end,
+ lists:foldl(ElemFun, C0, dict:to_list(Element))
+ end,
+ lists:foldl(CheckFun, Cache, dict:to_list(Records)).
+
+msg_with_position(Fun, FileLine) ->
+ try Fun()
+ catch
+ throw:{error, Msg} ->
+ {File, Line} = FileLine,
+ BaseName = filename:basename(File),
+ NewMsg = io_lib:format("~s:~p: ~s", [BaseName, Line, Msg]),
+ throw({error, NewMsg})
+ end.
--spec merge_records(dict(), dict()) -> dict().
+-spec merge_records(mod_records(), mod_records()) -> mod_records().
merge_records(NewRecords, OldRecords) ->
dict:merge(fun(_Key, NewVal, _OldVal) -> NewVal end, NewRecords, OldRecords).
@@ -315,15 +435,26 @@ merge_records(NewRecords, OldRecords) ->
%%
%% ============================================================================
--type spec_dict() :: dict().
--type callback_dict() :: dict().
+-type spec_dict() :: dict:dict().
+-type callback_dict() :: dict:dict().
--spec get_spec_info(atom(), abstract_code(), dict()) ->
+-spec get_spec_info(module(), abstract_code(), type_table()) ->
{'ok', spec_dict(), callback_dict()} | {'error', string()}.
get_spec_info(ModName, AbstractCode, RecordsDict) ->
+ OptionalCallbacks0 = get_optional_callbacks(AbstractCode, ModName),
+ OptionalCallbacks = gb_sets:from_list(OptionalCallbacks0),
get_spec_info(AbstractCode, dict:new(), dict:new(),
- RecordsDict, ModName, "nofile").
+ RecordsDict, 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).
%% TypeSpec is a list of conditional contracts for a function.
%% Each contract is of the form {[Argument], Range, [Constraint]} where
@@ -331,14 +462,16 @@ get_spec_info(ModName, AbstractCode, RecordsDict) ->
%% - Constraint is of the form {subtype, T1, T2} where T1 and T2
%% are erl_types:erl_type()
-get_spec_info([{attribute, Ln, Contract, {Id, TypeSpec}}|Left],
- SpecDict, CallbackDict, RecordsDict, ModName, File)
+get_spec_info([{attribute, Anno, Contract, {Id, TypeSpec}}|Left],
+ SpecDict, CallbackDict, RecordsDict, 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}
end,
+ Xtra = [optional_callback || gb_sets:is_member(MFA, OptCb)],
ActiveDict =
case Contract of
spec -> SpecDict;
@@ -346,8 +479,9 @@ get_spec_info([{attribute, Ln, Contract, {Id, TypeSpec}}|Left],
end,
try dict:find(MFA, ActiveDict) of
error ->
+ SpecData = {TypeSpec, Xtra},
NewActiveDict =
- dialyzer_contracts:store_tmp_contract(MFA, {File, Ln}, TypeSpec,
+ dialyzer_contracts:store_tmp_contract(MFA, {File, Ln}, SpecData,
ActiveDict, RecordsDict),
{NewSpecDict, NewCallbackDict} =
case Contract of
@@ -355,8 +489,8 @@ get_spec_info([{attribute, Ln, Contract, {Id, TypeSpec}}|Left],
callback -> {SpecDict, NewActiveDict}
end,
get_spec_info(Left, NewSpecDict, NewCallbackDict,
- RecordsDict, ModName,File);
- {ok, {{OtherFile, L},_C}} ->
+ RecordsDict, ModName, OptCb, File);
+ {ok, {{OtherFile, L}, _D}} ->
{Mod, Fun, Arity} = MFA,
Msg = flat_format(" Contract/callback for function ~w:~w/~w "
"already defined in ~s:~w\n",
@@ -368,22 +502,144 @@ get_spec_info([{attribute, Ln, Contract, {Id, TypeSpec}}|Left],
[Ln, Error])}
end;
get_spec_info([{attribute, _, file, {IncludeFile, _}}|Left],
- SpecDict, CallbackDict, RecordsDict, ModName, _File) ->
+ SpecDict, CallbackDict, RecordsDict, ModName, OptCb, _File) ->
get_spec_info(Left, SpecDict, CallbackDict,
- RecordsDict, ModName, IncludeFile);
+ RecordsDict, ModName, OptCb, IncludeFile);
get_spec_info([_Other|Left], SpecDict, CallbackDict,
- RecordsDict, ModName, File) ->
- get_spec_info(Left, SpecDict, CallbackDict, RecordsDict, ModName, File);
-get_spec_info([], SpecDict, CallbackDict, _RecordsDict, _ModName, _File) ->
+ RecordsDict, ModName, OptCb, File) ->
+ get_spec_info(Left, SpecDict, CallbackDict,
+ RecordsDict, ModName, OptCb, File);
+get_spec_info([], SpecDict, CallbackDict,
+ _RecordsDict, _ModName, _OptCb, _File) ->
{ok, SpecDict, CallbackDict}.
+-spec get_fun_meta_info(module(), abstract_code(), [dial_warn_tag()]) ->
+ dialyzer_codeserver:fun_meta_info().
+
+get_fun_meta_info(M, Abs, LegalWarnings) ->
+ NoWarn = get_nowarn_unused_function(M, Abs),
+ FuncSupp = get_func_suppressions(M, Abs),
+ Warnings0 = get_options(Abs, LegalWarnings),
+ Warnings = ordsets:to_list(Warnings0),
+ ModuleWarnings = [{M, W} || W <- Warnings],
+ RawProps = lists:append([NoWarn, FuncSupp, ModuleWarnings]),
+ process_options(dialyzer_utils:family(RawProps), Warnings0).
+
+process_options([{M, _}=Mod|Left], Warnings) when is_atom(M) ->
+ [Mod|process_options(Left, Warnings)];
+process_options([{{_M, _F, _A}=MFA, Opts}|Left], Warnings) ->
+ WL = case lists:member(nowarn_function, Opts) of
+ true -> [{nowarn_function, func}]; % takes precedence
+ false ->
+ Ws = dialyzer_options:build_warnings(Opts, Warnings),
+ ModOnly = [{W, mod} || W <- ordsets:subtract(Warnings, Ws)],
+ FunOnly = [{W, func} || W <- ordsets:subtract(Ws, Warnings)],
+ ordsets:union(ModOnly, FunOnly)
+ end,
+ case WL of
+ [] -> process_options(Left, Warnings);
+ _ -> [{MFA, WL}|process_options(Left, Warnings)]
+ end;
+process_options([], _Warnings) -> [].
+
+-spec get_nowarn_unused_function(module(), abstract_code()) ->
+ [{mfa(), 'no_unused'}].
+
+get_nowarn_unused_function(M, Abs) ->
+ Opts = get_options_with_tag(compile, Abs),
+ 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),
+ TagsFaList = check_fa_list(AttrFile, nowarn_unused_function, Functions),
+ FAs = case Warn of
+ false -> Functions;
+ true ->
+ [FA || {{nowarn_unused_function,_L,_File}, FA} <- TagsFaList]
+ end,
+ [{{M, F, A}, no_unused} || {F, A} <- FAs].
+
+-spec get_func_suppressions(module(), abstract_code()) ->
+ [{mfa(), 'nowarn_function' | dial_warn_tag()}].
+
+get_func_suppressions(M, Abs) ->
+ Functions = [{F, A} || {function, _, F, A, _} <- Abs],
+ AttrFile = collect_attribute(Abs, dialyzer),
+ TagsFAs = check_fa_list(AttrFile, '*', Functions),
+ %% Check the options:
+ 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].
+
+-spec get_options(abstract_code(), [dial_warn_tag()]) ->
+ ordsets:ordset(dial_warn_tag()).
+
+get_options(Abs, LegalWarnings) ->
+ AttrFile = collect_attribute(Abs, dialyzer),
+ get_options1(AttrFile, LegalWarnings).
+
+get_options1([{Args, L, File}|Left], Warnings) ->
+ Opts = [O ||
+ O <- lists:flatten([Args]),
+ is_atom(O)],
+ try dialyzer_options:build_warnings(Opts, Warnings) of
+ NewWarnings ->
+ get_options1(Left, NewWarnings)
+ catch
+ throw:{dialyzer_options_error, Msg} ->
+ Msg1 = flat_format(" ~s:~w: ~s", [File, L, Msg]),
+ throw({error, Msg1})
+ end;
+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) -> [].
+
+-spec is_suppressed_fun(mfa(), codeserver()) -> boolean().
+
+is_suppressed_fun(MFA, CodeServer) ->
+ lookup_fun_property(MFA, nowarn_function, CodeServer).
+
+-spec is_suppressed_tag(mfa() | module(), dial_warn_tag(), codeserver()) ->
+ boolean().
+
+is_suppressed_tag(MorMFA, Tag, Codeserver) ->
+ not lookup_fun_property(MorMFA, Tag, Codeserver).
+
+lookup_fun_property({M, _F, _A}=MFA, Property, CodeServer) ->
+ MFAPropList = dialyzer_codeserver:lookup_meta_info(MFA, CodeServer),
+ case proplists:get_value(Property, MFAPropList, no) of
+ mod -> false; % suppressed in function
+ func -> true; % requested in function
+ no -> lookup_fun_property(M, Property, CodeServer)
+ end;
+lookup_fun_property(M, Property, CodeServer) when is_atom(M) ->
+ MPropList = dialyzer_codeserver:lookup_meta_info(M, CodeServer),
+ proplists:is_defined(Property, MPropList).
+
%% ============================================================================
%%
%% Exported types
%%
%% ============================================================================
--spec sets_filter([module()], set()) -> set().
+-spec sets_filter([module()], sets:set()) -> sets:set().
sets_filter([], ExpTypes) ->
ExpTypes;
@@ -402,7 +658,7 @@ sets_filter([Mod|Mods], ExpTypes) ->
src_compiler_opts() ->
[no_copt, to_core, binary, return_errors,
no_inline, strict_record_tests, strict_record_updates,
- no_is_record_optimization].
+ dialyzer].
-spec get_module(abstract_code()) -> module().
@@ -419,6 +675,20 @@ cleanup_parse_transforms([Other|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]) ->
@@ -434,7 +704,7 @@ format_errors([]) ->
format_sig(Type) ->
format_sig(Type, dict:new()).
--spec format_sig(erl_types:erl_type(), dict()) -> string().
+-spec format_sig(erl_types:erl_type(), type_table()) -> string().
format_sig(Type, RecDict) ->
"fun(" ++ Sig = lists:flatten(erl_types:t_to_string(Type, RecDict)),
@@ -444,23 +714,80 @@ format_sig(Type, RecDict) ->
flat_format(Fmt, Lst) ->
lists:flatten(io_lib:format(Fmt, Lst)).
+-spec get_options_with_tag(atom(), abstract_code()) -> [term()].
+
+get_options_with_tag(Tag, Abs) ->
+ lists:flatten([O || {attribute, _, Tag0, O} <- Abs, Tag =:= Tag0]).
+
+%% Check F/A, and collect (unchecked) warning tags with line and file.
+-spec check_fa_list([collected_attribute()], atom(), [fa()]) ->
+ [{{atom(), erl_anno:line(), file:filename()},fa()}].
+
+check_fa_list(AttrFile, Tag, Functions) ->
+ FuncTab = gb_sets:from_list(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]),
+ Tag0 <- lists:flatten([Tags]),
+ Tag =:= '*' orelse Tag =:= Tag0,
+ Term <- lists:flatten([Terms0])],
+ case lists:dropwhile(fun({_, T}) -> is_fa(T) end, TermsL) of
+ [] -> ok;
+ [{_, Bad}|_] ->
+ Msg1 = flat_format(" Bad function ~w in line ~s:~w",
+ [Bad, File, L]),
+ throw({error, Msg1})
+ end,
+ case lists:dropwhile(fun({_, FA}) -> is_known(FA, Funcs) end, TermsL) of
+ [] -> ok;
+ [{_, {F, A}}|_] ->
+ Msg2 = flat_format(" Unknown function ~w/~w in line ~s:~w",
+ [F, A, File, L]),
+ throw({error, Msg2})
+ end,
+ TermsL ++ check_fa_list1(Left, Tag, Funcs);
+check_fa_list1([], _Tag, _Funcs) -> [].
+
+is_known(FA, Funcs) ->
+ gb_sets:is_element(FA, Funcs).
+
+-spec is_fa_list(term()) -> boolean().
+
+is_fa_list([E|L]) -> is_fa(E) andalso is_fa_list(L);
+is_fa_list([]) -> true;
+is_fa_list(_) -> false.
+
+-spec is_fa(term()) -> boolean().
+
+is_fa({FuncName, Arity})
+ when is_atom(FuncName), is_integer(Arity), Arity >= 0 -> true;
+is_fa(_) -> false.
+
%%-------------------------------------------------------------------
%% Author : Per Gustafsson <[email protected]>
%% Description : Provides better printing of binaries.
%% Created : 5 March 2007
%%-------------------------------------------------------------------
+-spec pp_hook() -> fun((cerl:cerl(), _, _) -> term()).
pp_hook() ->
fun pp_hook/3.
--spec pp_hook() -> fun((cerl:cerl(), _, _) -> term()).
-
pp_hook(Node, Ctxt, Cont) ->
case cerl:type(Node) of
binary ->
pp_binary(Node, Ctxt, Cont);
bitstr ->
pp_segment(Node, Ctxt, Cont);
+ map ->
+ pp_map(Node, Ctxt, Cont);
+ literal ->
+ case is_map(cerl:concrete(Node)) of
+ true -> pp_map(Node, Ctxt, Cont);
+ false -> Cont(Node, Ctxt)
+ end;
_ ->
Cont(Node, Ctxt)
end.
@@ -541,6 +868,87 @@ pp_atom(Atom) ->
String = atom_to_list(cerl:atom_val(Atom)),
prettypr:text(String).
+pp_map(Node, Ctxt, Cont) ->
+ Arg = cerl:map_arg(Node),
+ Before = case cerl:is_c_map_empty(Arg) of
+ true -> prettypr:floating(prettypr:text("#{"));
+ false ->
+ prettypr:beside(Cont(Arg,Ctxt),
+ prettypr:floating(prettypr:text("#{")))
+ end,
+ prettypr:beside(
+ Before, prettypr:beside(
+ prettypr:par(seq(cerl:map_es(Node),
+ prettypr:floating(prettypr:text(",")),
+ Ctxt, Cont)),
+ prettypr:floating(prettypr:text("}")))).
+
+seq([H | T], Separator, Ctxt, Fun) ->
+ case T of
+ [] -> [Fun(H, Ctxt)];
+ _ -> [prettypr:beside(Fun(H, Ctxt), Separator)
+ | seq(T, Separator, Ctxt, Fun)]
+ end;
+seq([], _, _, _) ->
+ [prettypr:empty()].
+
+%%------------------------------------------------------------------------------
+
+-spec refold_pattern(cerl:cerl()) -> cerl:cerl().
+
+refold_pattern(Pat) ->
+ %% Avoid the churn of unfolding and refolding
+ case cerl:is_literal(Pat) andalso find_map(cerl:concrete(Pat)) of
+ true ->
+ Tree = refold_concrete_pat(cerl:concrete(Pat)),
+ PatAnn = cerl:get_ann(Pat),
+ case proplists:is_defined(label, PatAnn) of
+ %% Literals are not normally annotated with a label, but can be if, for
+ %% example, they were created by cerl:fold_literal/1.
+ true -> cerl:set_ann(Tree, PatAnn);
+ false ->
+ [{label, Label}] = cerl:get_ann(Tree),
+ cerl:set_ann(Tree, [{label, Label}|PatAnn])
+ end;
+ false -> Pat
+ end.
+
+find_map(#{}) -> true;
+find_map(Tuple) when is_tuple(Tuple) -> find_map(tuple_to_list(Tuple));
+find_map([H|T]) -> find_map(H) orelse find_map(T);
+find_map(_) -> false.
+
+refold_concrete_pat(Val) ->
+ case Val of
+ _ when is_tuple(Val) ->
+ Els = lists:map(fun refold_concrete_pat/1, tuple_to_list(Val)),
+ case lists:all(fun cerl:is_literal/1, Els) of
+ true -> cerl:abstract(Val);
+ false -> label(cerl:c_tuple_skel(Els))
+ end;
+ [H|T] ->
+ case cerl:is_literal(HP=refold_concrete_pat(H))
+ and cerl:is_literal(TP=refold_concrete_pat(T))
+ of
+ true -> cerl:abstract(Val);
+ false -> label(cerl:c_cons_skel(HP, TP))
+ end;
+ M when is_map(M) ->
+ %% Map patterns are not generated by the parser(!), but they have a
+ %% property we want, namely that they are never folded into literals.
+ %% N.B.: The key in a map pattern is an expression, *not* a pattern.
+ label(cerl:c_map_pattern([cerl:c_map_pair_exact(cerl:abstract(K),
+ refold_concrete_pat(V))
+ || {K, V} <- maps:to_list(M)]));
+ _ ->
+ cerl:abstract(Val)
+ end.
+
+label(Tree) ->
+ %% Sigh
+ Label = -erlang:unique_integer([positive]),
+ cerl:set_ann(Tree, [{label, Label}]).
+
%%------------------------------------------------------------------------------
-spec parallelism() -> integer().
@@ -549,3 +957,8 @@ parallelism() ->
CPUs = erlang:system_info(logical_processors_available),
Schedulers = erlang:system_info(schedulers),
min(CPUs, Schedulers).
+
+-spec family([{K,V}]) -> [{K,[V]}].
+
+family(L) ->
+ sofs:to_external(sofs:rel2fam(sofs:relation(L))).
diff --git a/lib/dialyzer/src/dialyzer_worker.erl b/lib/dialyzer/src/dialyzer_worker.erl
index 1338997899..b9ab27c11d 100644
--- a/lib/dialyzer/src/dialyzer_worker.erl
+++ b/lib/dialyzer/src/dialyzer_worker.erl
@@ -2,38 +2,39 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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(dialyzer_worker).
--export([launch/4, sequential/4]).
+-export([launch/4]).
-export_type([worker/0]).
--type worker() :: pid(). %%opaque
+-opaque worker() :: pid().
-type mode() :: dialyzer_coordinator:mode().
-type coordinator() :: dialyzer_coordinator:coordinator().
-type init_data() :: dialyzer_coordinator:init_data().
--type result() :: dialyzer_coordinator:result().
+-type job() :: dialyzer_coordinator:job().
-record(state, {
mode :: mode(),
- job :: mfa_or_funlbl() | file:filename(),
+ job :: job(),
coordinator :: coordinator(),
init_data :: init_data(),
depends_on = [] :: list()
@@ -51,23 +52,28 @@
%%--------------------------------------------------------------------
--spec launch(mode(), [mfa_or_funlbl()], init_data(), coordinator()) -> worker().
+-spec launch(mode(), job(), init_data(), coordinator()) -> worker().
launch(Mode, Job, InitData, Coordinator) ->
State = #state{mode = Mode,
job = Job,
init_data = InitData,
coordinator = Coordinator},
- InitState =
- case Mode of
- X when X =:= 'typesig'; X =:= 'dataflow' -> initializing;
- X when X =:= 'compile'; X =:= 'warnings' -> running
- end,
- spawn_link(fun() -> loop(InitState, State) end).
+ spawn_link(fun() -> init(State) end).
%%--------------------------------------------------------------------
-loop(updating, State) ->
+init(#state{job = SCC, mode = Mode, init_data = InitData} = State) when
+ Mode =:= 'typesig'; Mode =:= 'dataflow' ->
+ DependsOn = dialyzer_succ_typings:find_depends_on(SCC, InitData),
+ ?debug("Deps ~p: ~p\n",[SCC, DependsOn]),
+ loop(updating, State#state{depends_on = DependsOn});
+init(#state{mode = Mode} = State) when
+ Mode =:= 'compile'; Mode =:= 'warnings' ->
+ loop(running, State).
+
+loop(updating, #state{mode = Mode} = State) when
+ Mode =:= 'typesig'; Mode =:= 'dataflow' ->
?debug("Update: ~p\n",[State#state.job]),
NextStatus =
case waits_more_success_typings(State) of
@@ -75,16 +81,13 @@ loop(updating, State) ->
false -> running
end,
loop(NextStatus, State);
-loop(initializing, #state{job = SCC, init_data = InitData} = State) ->
- DependsOn = dialyzer_succ_typings:find_depends_on(SCC, InitData),
- ?debug("Deps ~p: ~p\n",[State#state.job, DependsOn]),
- loop(updating, State#state{depends_on = DependsOn});
-loop(waiting, State) ->
+loop(waiting, #state{mode = Mode} = State) when
+ Mode =:= 'typesig'; Mode =:= 'dataflow' ->
?debug("Wait: ~p\n",[State#state.job]),
NewState = wait_for_success_typings(State),
loop(updating, NewState);
loop(running, #state{mode = 'compile'} = State) ->
- dialyzer_coordinator:wait_activation(),
+ dialyzer_coordinator:request_activation(State#state.coordinator),
?debug("Compile: ~s\n",[State#state.job]),
Result =
case start_compilation(State) of
@@ -96,7 +99,7 @@ loop(running, #state{mode = 'compile'} = State) ->
end,
report_to_coordinator(Result, State);
loop(running, #state{mode = 'warnings'} = State) ->
- dialyzer_coordinator:wait_activation(),
+ dialyzer_coordinator:request_activation(State#state.coordinator),
?debug("Warning: ~s\n",[State#state.job]),
Result = collect_warnings(State),
report_to_coordinator(Result, State);
@@ -168,22 +171,3 @@ continue_compilation(Label, Data) ->
collect_warnings(#state{job = Job, init_data = InitData}) ->
dialyzer_succ_typings:collect_warnings(Job, InitData).
-
-%%------------------------------------------------------------------------------
-
--type extra() :: label() | 'unused'.
-
--spec sequential(mode(), [mfa_or_funlbl()], init_data(), extra()) -> result().
-
-sequential('compile', Job, InitData, Extra) ->
- case dialyzer_analysis_callgraph:start_compilation(Job, InitData) of
- {ok, EstimatedSize, Data} ->
- {EstimatedSize, continue_compilation(Extra, Data)};
- {error, _Reason} = Error -> {0, Error}
- end;
-sequential('typesig', Job, InitData, _Extra) ->
- dialyzer_succ_typings:find_succ_types_for_scc(Job, InitData);
-sequential('dataflow', Job, InitData, _Extra) ->
- dialyzer_succ_typings:refine_one_module(Job, InitData);
-sequential('warnings', Job, InitData, _Extra) ->
- dialyzer_succ_typings:collect_warnings(Job, InitData).